"""
The arcgis.tools module is used for consuming the GIS functionality exposed from ArcGIS Online
or Portal web services. It has implementations for Spatial Analysis tools, GeoAnalytics tools,
Raster Analysis tools, Geoprocessing tools, Geocoders and Geometry Utility services.
These tools primarily operate on items and layers from the GIS.
"""
from __future__ import absolute_import, division, print_function, annotations

from arcgis._impl.common._deprecate import deprecated
import json
import logging
import os
import random
import string
import tempfile
import time
from contextlib import contextmanager
import concurrent.futures
import arcgis
import arcgis.gis
from arcgis.gis import Item
from arcgis._impl.common._mixins import PropertyMap
from arcgis._impl.common._utils import _DisableLogger
from arcgis.geocoding import Geocoder
from arcgis.geometry import (
    Point,
    MultiPoint,
    Polygon,
    Envelope,
    Polyline,
    Geometry,
)
from arcgis.features import (
    Feature,
    FeatureSet,
    FeatureCollection,
    FeatureLayer,
)
from urllib.error import HTTPError
from arcgis.geoprocessing import import_toolbox
from ._async.jobs import GeometryJob
from arcgis.raster._util import _set_context as _set_raster_context
from arcgis._impl.common._utils import inspect_function_inputs
from arcgis.geoprocessing._job import RAJob, OMJob
from functools import lru_cache
from arcgis.raster import Raster, ImageryLayer, _ImageServerRaster

_log = logging.getLogger(__name__)

try:
    import pandas as pd

    _FEATURE_INPUTS = (
        Feature,
        FeatureSet,
        FeatureLayer,
        FeatureCollection,
        pd.DataFrame,
    )
except ImportError:
    _FEATURE_INPUTS = (Feature, FeatureSet, FeatureLayer, FeatureCollection)


def _is_dask(df):
    """check if dask DataFrame"""
    try:
        import dask.dataframe as dd

        if (
            isinstance(df, dd.DataFrame)
            and hasattr(df, "spatial")
            and getattr(df.spatial, "name")
        ):
            return True
        else:
            return False
    except Exception as e:
        return False
    return False


__all__ = [
    "_GeoanalyticsTools",
    "_FeatureAnalysisTools",
    "_GeometryService",
    "_RasterAnalysisTools",
]


# --------------------------------------------------------------------------
def _inspect_function_inputs(fn, **params):
    """
    Given any function and a set of key/value pairs, where the ```params``` is a dictionary,
    the code inspects the input method and then returns a list of accepted inputs as
    a new dictionary.  This method is used primarily to validate GP services and ensure
    that the parameters given are supported in the current version of the tool.

    :return: dictionary

    Example:

    >>> def add(x,y):
    >>>    return x+y

    >>> valid_inputs = _inspect_function_inputs(fn=add, **{'x' : 1, 'y': 3, 'cat' : 3})
    >>> print(valid_inputs)
    {'x' : 1, 'y': 3}

    """
    import inspect

    try:
        args = list(inspect.signature(fn).parameters.keys())
    except ValueError:
        args = inspect.getfullargspec(func=fn).args
    valid = {}
    for key in params.keys():
        if key in args:
            valid[key] = params[key]
    return valid


# --------------------------------------------------------------------------
def _id_generator(size=6, chars=string.ascii_uppercase + string.digits):
    return "".join(random.choice(chars) for _ in range(size))


# --------------------------------------------------------------------------
@contextmanager
def _tempinput(data):
    temp = tempfile.NamedTemporaryFile(delete=False)
    temp.write((bytes(data, "UTF-8")))
    temp.close()
    yield temp.name
    os.unlink(temp.name)


###########################################################################
class BaseAnalytics(object):
    @lru_cache(maxsize=255)
    def _validate_token(self, con: "Connection", url: str, token: str) -> bool:
        """validates that a token should be given to the endpoint"""
        resp = con.get(f"{url}?token={token}", try_json=True, return_raw_response=True)
        if resp.text.lower().find("error") == -1:
            return True
        else:
            return False
        return False

    def _feature_input(self, input_layer):
        from arcgis.features.geo._accessor import _is_geoenabled

        point_fs = {
            "layerDefinition": {
                "currentVersion": 10.11,
                "copyrightText": "",
                "defaultVisibility": True,
                "relationships": [],
                "isDataVersioned": False,
                "supportsRollbackOnFailureParameter": True,
                "supportsStatistics": True,
                "supportsAdvancedQueries": True,
                "geometryType": "esriGeometryPoint",
                "minScale": 0,
                "maxScale": 0,
                "objectIdField": "OBJECTID",
                "templates": [],
                "type": "Feature Layer",
                "displayField": "TITLE",
                "visibilityField": "VISIBLE",
                "name": "startDrawPoint",
                "hasAttachments": False,
                "typeIdField": "TYPEID",
                "capabilities": "Query",
                "allowGeometryUpdates": True,
                "htmlPopupType": "",
                "hasM": False,
                "hasZ": False,
                "globalIdField": "",
                "supportedQueryFormats": "JSON",
                "hasStaticData": False,
                "maxRecordCount": -1,
                "indexes": [],
                "types": [],
                "fields": [
                    {
                        "alias": "OBJECTID",
                        "name": "OBJECTID",
                        "type": "esriFieldTypeOID",
                        "editable": False,
                    },
                    {
                        "alias": "Title",
                        "name": "TITLE",
                        "length": 50,
                        "type": "esriFieldTypeString",
                        "editable": True,
                    },
                    {
                        "alias": "Visible",
                        "name": "VISIBLE",
                        "type": "esriFieldTypeInteger",
                        "editable": True,
                    },
                    {
                        "alias": "Description",
                        "name": "DESCRIPTION",
                        "length": 1073741822,
                        "type": "esriFieldTypeString",
                        "editable": True,
                    },
                    {
                        "alias": "Type ID",
                        "name": "TYPEID",
                        "type": "esriFieldTypeInteger",
                        "editable": True,
                    },
                ],
            },
            "featureSet": {
                "features": [
                    {
                        "geometry": {
                            "x": 80.27032792000051,
                            "y": 13.085227147000467,
                            "spatialReference": {
                                "wkid": 4326,
                                "latestWkid": 4326,
                            },
                        },
                        "attributes": {
                            "description": "blayer desc",
                            "title": "blayer",
                            "OBJECTID": 0,
                            "VISIBLE": 1,
                        },
                        "symbol": {
                            "angle": 0,
                            "xoffset": 0,
                            "yoffset": 8.15625,
                            "type": "esriPMS",
                            "url": "https://cdn.arcgis.com/cdn/7674/js/jsapi/esri/dijit/images/Directions/greenPoint.png",
                            "imageData": "iVBORw0KGgoAAAANSUhEUgAAABUAAAAdCAYAAABFRCf7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4OTI1MkU2ODE0QzUxMUUyQURFMUNDNThGMTA3MjkzMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4OTI1MkU2OTE0QzUxMUUyQURFMUNDNThGMTA3MjkzMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjg5MjUyRTY2MTRDNTExRTJBREUxQ0M1OEYxMDcyOTMxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg5MjUyRTY3MTRDNTExRTJBREUxQ0M1OEYxMDcyOTMxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+iVNkdQAABJlJREFUeNp0VltvG0UUnpkdr72261CnCQWEIA9FqOKlqooARUKCtAUhoA+VoBVRhfgFXKSKJ97goRL8ARCIclGgL0VUkBBAoBaVoggEQQVSAhFS06SJje3Y3t25cc7srL3YjddHs3N85pvvfOfMyJRs83n8o+P7POI9yQibooTeBa68ISbSRv+hifpCGHX2s6dnfrrRWjroOPzB0T0+zZ0q8uDRSrniF/MB8X2fADhR8IRRRDphh7Q6rbgtOucU0Sdnj59Z2hb00PtHD+Zp/p2x6uitO4o7iLYP8DMafjVE2wXUboALm50W2ahtXO3q8MTX02fnh0Affu/IkSAXnL55dLzMPU6kURZMIZQhFtRk2VBKcpQTIQVZ21hrdUX4zDcnPv2kBzr59mP3BLnChfGx8YrHPKIAELSzMPhQk+ydzpOvIYwywjFeK7K+vt6IlZw8/+y5RZ4gm9eCUrGCmkUyBkCV0Sd5UlBtTLIhRWQE9ixwsVwe6dY3X4WwJ+j9bx7a7/v5i6O7qlxisFZJAvBF7Rjty56CWlmszilj6BNgXd+syTCO7uNK62nuezyUkWWASTPHDtOjbgOHkJTOsbXAyJhIC+rlODdROM211gcQKBJxoh+EKAs4AGqybHVfBvdICNIU/IDHYbcJiS6le4wwbW1B9UDXJcg9QBxtbglh1BlAJzjoUxIGQZFRwtAypgnjtH0spDG9MWVs34xrN5uBLnEoTKQUgDLgZ6hliLunBaIDhy4LYhyotptZlphGyLUhfyspxxj3AIpaVqikdgyzoGn7p0xNj71rNamweCscWC0qoQ8YRm3K2OgpeFoc+j9FSUYKB+4OgxIK4RcZUJ6RsUgqCrShxWzza9035aw/lzYGY5P4xFSMR5vMcFpm87opL4HjXsr76dLhC2xYhgx3I0BfoS7RCp+3K/e8vn+Ke2zWK+cYofQG9yMlw1eK1aAni9oSWil9eOmFhXkPnbXZ1eXqwVsirfQU9Vynm75lymLbxvpSP4yqI4iR5uWlFxdOI56Xbro5t3qhOrW7ZmL1EOFwp7k6pRXuWaZgBmuwJSIl1fNXXvrxjRTLy2ZTm1v9YeTBXedNbCYZZ1U4pdt+NGiomuKKEvKp5ZM/f5z9zctc1vju1b9cv5q/M/icBd4+KNztlnGWKfYjAMqm+K7zZ/PYP6d+X3TrafbmR8N71QcrOPMLd5RGdj838WFup393orNLWRki6vFv197661i40m6AKwYLneG79BzDPNhNYFWwnfguGyKgPl32bwseoTnKekVpS9n49vorWwv1JsSVwAJHCHcW2Agsk3rBBZXBihhcn11biTfDixpPik1bEZyj34EVXXzJrUccWwrbZo5+B6ztRpvO1kLjjO5qW3YccZ5JeTAecQxqqV0Q6hM5KVIrNL5a/77yQPUyLbK9qiMv49zFhW6MMnPE0dwxlQ48ckXDNHJOq0C2xByreHtxhPk1sK4DEI5dut7+QWCZCyj9MXKLWmD/gl1Xtfhd6F2CI86dv+XiIrdOpeeCDd0VyW7KGbLptn9p/mrgNsIxwzKN0QO3IvlPgAEA3AQhIZtaN54AAAAASUVORK5CYII=",
                            "contentType": "image/png",
                            "width": 15.75,
                            "height": 21.75,
                        },
                    }
                ],
                "geometryType": "esriGeometryPoint",
            },
            "nextObjectId": 1,
        }

        input_layer_url = ""
        if isinstance(input_layer, arcgis.gis.Item):
            if input_layer.type.lower() == "feature service":
                input_param = input_layer.layers[0]._lyr_dict
                try:
                    input_layer_url = input_param.get("url", "")
                    if (
                        isinstance(input_param, dict)
                        and "serviceToken" not in input_param.keys()
                    ):
                        token = input_layer.layers[0]._gis._con._create_token(
                            input_layer_url
                        )
                        if token and self._validate_token(
                            input_layer._gis._con,
                            url=input_layer_url,
                            token=token,
                        ):
                            input_param.update({"serviceToken": token})
                except:
                    pass
            elif input_layer.type.lower() == "feature collection":
                fcdict = input_layer.get_data()
                fc = arcgis.features.FeatureCollection(fcdict["layers"][0])
                input_param = fc.layer
            else:
                raise TypeError(
                    "item type must be feature service or feature collection"
                )

        elif isinstance(input_layer, arcgis.features.FeatureLayerCollection):
            input_param = input_layer.layers[0]._lyr_dict

        elif isinstance(input_layer, arcgis.features.FeatureCollection):
            if "layers" in input_layer.properties:
                input_param = input_layer.properties["layers"][0]
            elif hasattr(input_layer, "_lyr_dict"):
                input_param = input_layer._lyr_dict
            elif hasattr(input_layer, "layer"):
                input_param = input_layer.layer
        elif isinstance(input_layer, arcgis.gis.Layer):
            input_param = input_layer._lyr_dict
            try:
                input_layer_url = input_param.get("url", "")
                if (
                    isinstance(input_param, dict)
                    and "serviceToken" not in input_param.keys()
                ):
                    token = input_layer._gis._con._create_token(input_layer_url)
                    if token and self._validate_token(
                        input_layer._gis._con,
                        url=input_layer_url,
                        token=token,
                    ):
                        input_param.update({"serviceToken": token})
            except:
                pass

        elif isinstance(
            input_layer, tuple
        ):  # geocoding location, convert to point featureset
            input_param = point_fs
            input_param["featureSet"]["features"][0]["geometry"]["x"] = input_layer[1]
            input_param["featureSet"]["features"][0]["geometry"]["y"] = input_layer[0]
        elif _is_dask(input_layer):
            input_param = (
                input_layer.spatial.to_feature_collection().compute()._lyr_dict
            )

        elif isinstance(
            input_layer, dict
        ):  # could add support for geometry one day using geometry -> featureset
            if "location" in input_layer:  # geocoder result
                geom = arcgis.geometry.Geometry(input_layer["location"])
                fset = FeatureSet([Feature(geom)])
                featcoll = {
                    "layerDefinition": {
                        "geometryType": "esriGeometryPoint",
                        "objectIdField": "OBJECTID",
                        "fields": [
                            {
                                "alias": "OBJECTID",
                                "name": "OBJECTID",
                                "type": "esriFieldTypeOID",
                                "editable": False,
                            }
                        ],
                    },
                    "featureSet": fset.to_dict(),
                }
                input_param = featcoll
            else:
                input_param = input_layer
        elif isinstance(input_layer, str):
            input_layer_url = input_layer
            if input_layer_url.endswith("/"):
                input_layer_url = input_layer_url[:-1]
            input_param = {"url": input_layer_url}
        elif isinstance(input_layer, pd.DataFrame) and _is_geoenabled(input_layer):
            return self._feature_input(input_layer.spatial.to_feature_collection())
        else:
            raise Exception(
                "Invalid format of input layer. url string, feature service Item, feature service instance or dict supported"
            )

        return input_param

    def _raster_input(self, input_raster):
        if isinstance(input_raster, arcgis.gis.Item):
            if input_raster.type.lower() == "image service":
                input_param = {"itemId": input_raster.itemid}
            else:
                raise TypeError("item type must be image service")
        elif isinstance(input_raster, str):
            input_param = {"url": input_raster}
        elif isinstance(input_raster, dict):
            input_param = input_raster
        else:
            raise Exception(
                "Invalid format of input raster. image service Item or image service url, cloud raster uri or shared data path supported"
            )

        return input_param


###########################################################################
class _GISService(object):
    """a GIS service"""

    def __init__(self, url, gis=None):
        self._token = None

        self.url = url
        self._url = url

        err = None

        if gis is None:
            gis = arcgis.gis.GIS(set_active=False)
            self._gis = gis
            self._con = gis._con
            self._token = None
        elif hasattr(gis, "token"):  # means it is a connection object
            self._gis = None
            self._con = gis
        else:
            self._gis = gis
            self._con = gis._con

        with _DisableLogger():
            try:
                # try as a federated server
                if (
                    isinstance(self._con, arcgis.gis._impl._con.Connection)
                    and self._con._auth.lower() != "anon"
                ):
                    try:
                        self._token = self._gis._con._create_token(url)
                    except Exception as e:  # GUESSED Auth Wrong, try anonymously
                        if (
                            str(e).find(
                                "'code': 201, 'message': 'Exception in generating token'"
                            )
                            > -1
                        ):
                            self._con._auth = "ANON"
                        elif (
                            str(e)
                            .lower()
                            .find("unable to generate token for this server")
                            > -1
                        ):
                            raise RuntimeError(str(e))
                        elif (
                            str(e).lower().find("exception in generating token") > -1
                            and self._con._auth == "IWA"
                        ):
                            self._con._auth = "ANON"
                        else:
                            from requests.exceptions import RequestException

                            raise RequestException(str(e))
                else:
                    self._token = self._con.token
                self._refresh()
            except RuntimeError as e:
                try:
                    # try as a public server
                    self._token = None
                    self._refresh()
                except HTTPError as httperror:
                    _log.error(httperror)
                    err = httperror
                except RuntimeError as e:
                    if "Token Required" in e.args[0]:
                        # try token in the provided gis
                        self._token = self._con.token
                        self._refresh()

        if err is not None:
            raise RuntimeError(
                "HTTPError: this service url encountered an HTTP Error: " + self.url
            )

    def _refresh(self):
        params = {"f": "json"}
        dictdata = self._con.post(self.url, params, token=self._token)
        self.properties = PropertyMap(dictdata)

    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self.url)

    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self.url)

    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._token)


###########################################################################
class _AsyncService(_GISService):
    def __init__(self, url, gis):
        super(_AsyncService, self).__init__(url, gis)

    def _refresh(self):
        params = {"f": "json"}
        dictdata = self._con.get(path=self.url, params=params, token=self._token)
        self.properties = PropertyMap(dictdata)

    def _analysis_job(self, task, params):
        """Submits an Analysis job and returns the job URL for monitoring the job
        status in addition to the json response data for the submitted job.
        """

        # Unpack the Analysis job parameters as a dictionary and add token and
        # formatting parameters to the dictionary. The dictionary is used in the
        # HTTP POST request. Headers are also added as a dictionary to be included
        # with the POST.
        #
        # print("Submitting analysis job...")

        task_url = "{}/{}".format(self.url, task)
        submit_url = "{}/submitJob".format(task_url)

        params["f"] = "json"

        resp = self._con.post(submit_url, params, token=self._token)
        # print(resp)
        return task_url, resp, resp["jobId"]

    def _analysis_job_status(self, task_url, job_info):
        """Tracks the status of the submitted Analysis job."""

        if "jobId" in job_info:
            # Get the id of the Analysis job to track the status.
            #
            job_id = job_info.get("jobId")
            job_url = "{}/jobs/{}".format(task_url, job_id)
            params = {"f": "json"}
            job_response = self._con.post(job_url, params, token=self._token)

            # Query and report the Analysis job status.
            #
            num_messages = 0

            if "jobStatus" in job_response:
                while not job_response.get("jobStatus") == "esriJobSucceeded":
                    time.sleep(5)

                    job_response = self._con.post(job_url, params, token=self._token)
                    # print(job_response)
                    messages = (
                        job_response["messages"] if "messages" in job_response else []
                    )
                    num = len(messages)
                    if num > num_messages:
                        for index in range(num_messages, num):
                            msg = messages[index]
                            if arcgis.env.verbose:
                                print(msg["description"])
                            if msg["type"] == "esriJobMessageTypeInformative":
                                _log.info(msg["description"])
                            elif msg["type"] == "esriJobMessageTypeWarning":
                                _log.warning(msg["description"])
                            elif msg["type"] == "esriJobMessageTypeError":
                                _log.error(msg["description"])
                                # print(msg['description'], file=sys.stderr)
                            else:
                                _log.warning(msg["description"])
                        num_messages = num

                    if job_response.get("jobStatus") == "esriJobFailed":
                        raise Exception("Job failed.")
                    elif job_response.get("jobStatus") == "esriJobCancelled":
                        raise Exception("Job cancelled.")
                    elif job_response.get("jobStatus") == "esriJobTimedOut":
                        raise Exception("Job timed out.")

                if "results" in job_response:
                    return job_response
            else:
                raise Exception("No job results.")
        else:
            raise Exception("No job url.")

    def _analysis_job_results(self, task_url, job_info, job_id=None):
        """Use the job result json to get information about the feature service
        created from the Analysis job."""

        # Get the paramUrl to get information about the Analysis job results.
        #
        if job_id is None:
            job_id = job_info.get("jobId")

        if "results" in job_info:
            results = job_info.get("results")
            result_values = {}
            for key in list(results.keys()):
                param_value = results[key]
                if "paramUrl" in param_value:
                    param_url = param_value.get("paramUrl")
                    result_url = "{}/jobs/{}/{}".format(task_url, job_id, param_url)

                    params = {"f": "json"}
                    param_result = self._con.post(result_url, params, token=self._token)

                    job_value = param_result.get("value")
                    result_values[key] = job_value
            return result_values
        else:
            raise Exception("Unable to get analysis job results.")

    def _feature_input(self, input_layer):
        point_fs = {
            "layerDefinition": {
                "currentVersion": 10.11,
                "copyrightText": "",
                "defaultVisibility": True,
                "relationships": [],
                "isDataVersioned": False,
                "supportsRollbackOnFailureParameter": True,
                "supportsStatistics": True,
                "supportsAdvancedQueries": True,
                "geometryType": "esriGeometryPoint",
                "minScale": 0,
                "maxScale": 0,
                "objectIdField": "OBJECTID",
                "templates": [],
                "type": "Feature Layer",
                "displayField": "TITLE",
                "visibilityField": "VISIBLE",
                "name": "startDrawPoint",
                "hasAttachments": False,
                "typeIdField": "TYPEID",
                "capabilities": "Query",
                "allowGeometryUpdates": True,
                "htmlPopupType": "",
                "hasM": False,
                "hasZ": False,
                "globalIdField": "",
                "supportedQueryFormats": "JSON",
                "hasStaticData": False,
                "maxRecordCount": -1,
                "indexes": [],
                "types": [],
                "fields": [
                    {
                        "alias": "OBJECTID",
                        "name": "OBJECTID",
                        "type": "esriFieldTypeOID",
                        "editable": False,
                    },
                    {
                        "alias": "Title",
                        "name": "TITLE",
                        "length": 50,
                        "type": "esriFieldTypeString",
                        "editable": True,
                    },
                    {
                        "alias": "Visible",
                        "name": "VISIBLE",
                        "type": "esriFieldTypeInteger",
                        "editable": True,
                    },
                    {
                        "alias": "Description",
                        "name": "DESCRIPTION",
                        "length": 1073741822,
                        "type": "esriFieldTypeString",
                        "editable": True,
                    },
                    {
                        "alias": "Type ID",
                        "name": "TYPEID",
                        "type": "esriFieldTypeInteger",
                        "editable": True,
                    },
                ],
            },
            "featureSet": {
                "features": [
                    {
                        "geometry": {
                            "x": 80.27032792000051,
                            "y": 13.085227147000467,
                            "spatialReference": {
                                "wkid": 4326,
                                "latestWkid": 4326,
                            },
                        },
                        "attributes": {
                            "description": "blayer desc",
                            "title": "blayer",
                            "OBJECTID": 0,
                            "VISIBLE": 1,
                        },
                        "symbol": {
                            "angle": 0,
                            "xoffset": 0,
                            "yoffset": 8.15625,
                            "type": "esriPMS",
                            "url": "https://cdn.arcgis.com/cdn/7674/js/jsapi/esri/dijit/images/Directions/greenPoint.png",
                            "imageData": "iVBORw0KGgoAAAANSUhEUgAAABUAAAAdCAYAAABFRCf7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4OTI1MkU2ODE0QzUxMUUyQURFMUNDNThGMTA3MjkzMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4OTI1MkU2OTE0QzUxMUUyQURFMUNDNThGMTA3MjkzMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjg5MjUyRTY2MTRDNTExRTJBREUxQ0M1OEYxMDcyOTMxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg5MjUyRTY3MTRDNTExRTJBREUxQ0M1OEYxMDcyOTMxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+iVNkdQAABJlJREFUeNp0VltvG0UUnpkdr72261CnCQWEIA9FqOKlqooARUKCtAUhoA+VoBVRhfgFXKSKJ97goRL8ARCIclGgL0VUkBBAoBaVoggEQQVSAhFS06SJje3Y3t25cc7srL3YjddHs3N85pvvfOfMyJRs83n8o+P7POI9yQibooTeBa68ISbSRv+hifpCGHX2s6dnfrrRWjroOPzB0T0+zZ0q8uDRSrniF/MB8X2fADhR8IRRRDphh7Q6rbgtOucU0Sdnj59Z2hb00PtHD+Zp/p2x6uitO4o7iLYP8DMafjVE2wXUboALm50W2ahtXO3q8MTX02fnh0Affu/IkSAXnL55dLzMPU6kURZMIZQhFtRk2VBKcpQTIQVZ21hrdUX4zDcnPv2kBzr59mP3BLnChfGx8YrHPKIAELSzMPhQk+ydzpOvIYwywjFeK7K+vt6IlZw8/+y5RZ4gm9eCUrGCmkUyBkCV0Sd5UlBtTLIhRWQE9ixwsVwe6dY3X4WwJ+j9bx7a7/v5i6O7qlxisFZJAvBF7Rjty56CWlmszilj6BNgXd+syTCO7uNK62nuezyUkWWASTPHDtOjbgOHkJTOsbXAyJhIC+rlODdROM211gcQKBJxoh+EKAs4AGqybHVfBvdICNIU/IDHYbcJiS6le4wwbW1B9UDXJcg9QBxtbglh1BlAJzjoUxIGQZFRwtAypgnjtH0spDG9MWVs34xrN5uBLnEoTKQUgDLgZ6hliLunBaIDhy4LYhyotptZlphGyLUhfyspxxj3AIpaVqikdgyzoGn7p0xNj71rNamweCscWC0qoQ8YRm3K2OgpeFoc+j9FSUYKB+4OgxIK4RcZUJ6RsUgqCrShxWzza9035aw/lzYGY5P4xFSMR5vMcFpm87opL4HjXsr76dLhC2xYhgx3I0BfoS7RCp+3K/e8vn+Ke2zWK+cYofQG9yMlw1eK1aAni9oSWil9eOmFhXkPnbXZ1eXqwVsirfQU9Vynm75lymLbxvpSP4yqI4iR5uWlFxdOI56Xbro5t3qhOrW7ZmL1EOFwp7k6pRXuWaZgBmuwJSIl1fNXXvrxjRTLy2ZTm1v9YeTBXedNbCYZZ1U4pdt+NGiomuKKEvKp5ZM/f5z9zctc1vju1b9cv5q/M/icBd4+KNztlnGWKfYjAMqm+K7zZ/PYP6d+X3TrafbmR8N71QcrOPMLd5RGdj838WFup393orNLWRki6vFv197661i40m6AKwYLneG79BzDPNhNYFWwnfguGyKgPl32bwseoTnKekVpS9n49vorWwv1JsSVwAJHCHcW2Agsk3rBBZXBihhcn11biTfDixpPik1bEZyj34EVXXzJrUccWwrbZo5+B6ztRpvO1kLjjO5qW3YccZ5JeTAecQxqqV0Q6hM5KVIrNL5a/77yQPUyLbK9qiMv49zFhW6MMnPE0dwxlQ48ckXDNHJOq0C2xByreHtxhPk1sK4DEI5dut7+QWCZCyj9MXKLWmD/gl1Xtfhd6F2CI86dv+XiIrdOpeeCDd0VyW7KGbLptn9p/mrgNsIxwzKN0QO3IvlPgAEA3AQhIZtaN54AAAAASUVORK5CYII=",
                            "contentType": "image/png",
                            "width": 15.75,
                            "height": 21.75,
                        },
                    }
                ],
                "geometryType": "esriGeometryPoint",
            },
            "nextObjectId": 1,
        }

        input_layer_url = ""
        if isinstance(input_layer, arcgis.gis.Item):
            if input_layer.type.lower() == "feature service":
                input_param = input_layer.layers[0]._lyr_dict
            elif input_layer.type.lower() == "feature collection":
                fcdict = input_layer.get_data()
                fc = arcgis.features.FeatureCollection(fcdict["layers"][0])
                input_param = fc.layer
            else:
                raise TypeError(
                    "item type must be feature service or feature collection"
                )

        elif isinstance(input_layer, arcgis.features.FeatureLayerCollection):
            input_param = input_layer.layers[0]._lyr_dict

        elif isinstance(input_layer, arcgis.features.FeatureCollection):
            input_param = input_layer.properties

        elif isinstance(input_layer, arcgis.gis.Layer):
            input_param = input_layer._lyr_dict

        elif isinstance(
            input_layer, tuple
        ):  # geocoding location, convert to point featureset
            input_param = point_fs
            input_param["featureSet"]["features"][0]["geometry"]["x"] = input_layer[1]
            input_param["featureSet"]["features"][0]["geometry"]["y"] = input_layer[0]

        elif isinstance(
            input_layer, dict
        ):  # could add support for geometry one day using geometry -> featureset
            if "location" in input_layer:  # geocoder result
                geom = arcgis.geometry.Geometry(input_layer["location"])
                fset = FeatureSet([Feature(geom)])
                featcoll = {
                    "layerDefinition": {
                        "geometryType": "esriGeometryPoint",
                        "objectIdField": "OBJECTID",
                        "fields": [
                            {
                                "alias": "OBJECTID",
                                "name": "OBJECTID",
                                "type": "esriFieldTypeOID",
                                "editable": False,
                            }
                        ],
                    },
                    "featureSet": fset.to_dict(),
                }
                input_param = featcoll
            else:
                input_param = input_layer
        elif isinstance(input_layer, str):
            input_layer_url = input_layer
            input_param = {"url": input_layer_url}
        else:
            raise Exception(
                "Invalid format of input layer. url string, feature service Item, feature service instance or dict supported"
            )

        return input_param

    def _raster_input(self, input_raster):
        if isinstance(input_raster, arcgis.gis.Item):
            if input_raster.type.lower() == "image service":
                input_param = {"itemId": input_raster.itemid}
            else:
                raise TypeError("item type must be image service")
        elif isinstance(input_raster, str):
            input_param = {"url": input_raster}
        elif isinstance(input_raster, dict):
            input_param = input_raster
        else:
            raise Exception(
                "Invalid format of input raster. image service Item or image service url, cloud raster uri or shared data path supported"
            )

        return input_param


###########################################################################
class _FeatureAnalysisTools(BaseAnalytics):
    """FA Tools"""

    _gptbx = None
    _url = None
    _gis = None
    _properties = None

    # ----------------------------------------------------------------------
    def __init__(self, url, gis, verbose=False):
        """initializer"""
        self._url = url
        self._gis = gis
        self._con = gis._con
        self._verbose = verbose

    # ----------------------------------------------------------------------
    def _refresh(self):
        params = {"f": "json"}
        try:
            dictdata = self._con.post(self._url, params)
        except:
            dictdata = self._con.get(self._url, params)
        self._properties = PropertyMap(dictdata)

    # ----------------------------------------------------------------------
    @property
    def properties(self):
        """returns the services properties"""
        if self._properties is None:
            self._refresh()
        return self._properties

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbx is None:
            self._gptbx = import_toolbox(
                url_or_item=self._url, gis=self._gis, verbose=self._verbose
            )
            self._gptbx._is_fa = True
        return self._gptbx

    # ----------------------------------------------------------------------
    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._con.token)

    @property
    def _tools(self):
        return self.properties.tasks

    # ----------------------------------------------------------------------
    def _output_name_dict(self, output_name, overwrite):
        if output_name and isinstance(output_name, str):
            output_name = {
                "serviceProperties": {
                    "name": str(output_name).strip().replace(" ", "_")
                }
            }
        elif output_name and isinstance(output_name, FeatureLayer):
            _lyr_dict = {
                "serviceProperties": {
                    "name": output_name.properties.name,
                    "serviceUrl": output_name.container.url,
                }
            }
            if "serviceItemId" in output_name.properties:
                _lyr_dict["itemProperties"] = {
                    "itemId": output_name.properties.serviceItemId,
                    "overwrite": overwrite,
                }
            output_name = _lyr_dict
        else:
            output_name = None
        return output_name

    # ----------------------------------------------------------------------
    def aggregate_points(
        self,
        point_layer,
        polygon_layer=None,
        keep_boundaries_with_no_points=True,
        summary_fields=None,
        group_by_field=None,
        minority_majority=False,
        percent_points=False,
        output_name=None,
        context=None,
        estimate=False,
        bin_type="SQUARE",
        bin_size=None,
        bin_size_unit=None,
        future=False,
    ):
        """
        The Aggregate Points task works with a layer of point features and a layer of polygon features. It first figures out which points fall within each polygon's area.
        After determining this point-in-polygon spatial relationship, statistics about all points in the polygon are calculated and assigned to the area. The most basic statistic is the count of the number of points within the polygon, but you can get other statistics as well.

        For example, if your points represented coffee shops and each point has a TOTAL_SALES attribute, you can get statistics like the sum of all TOTAL_SALES within the polygon, or the minimum or maximum TOTAL_SALES value, or the standard deviation of all sales within the polygon.

        ====================================    ====================================================================
        **Parameter**                           **Description**
        ------------------------------------    --------------------------------------------------------------------
        point_layer                             Required point layer. The point features that will be aggregated
                                                into the polygons in the polygon_layer. See :ref:`Feature Input<FeatureInput>`.
        ------------------------------------    --------------------------------------------------------------------
        polygon_layer                           Optional polygon layer. The polygon features (areas) into which the input points will be aggregated. See :ref:`Feature Input<FeatureInput>`. The `polygon_layer` is **required** if the `bin_type`, `bin_size` and `bin_size_unit` are not specified.
        ------------------------------------    --------------------------------------------------------------------
        keep_boundaries_with_no_points          Optional boolean. A Boolean value that specifies whether the polygons that have no points within them should be returned in the output. The default is true.
        ------------------------------------    --------------------------------------------------------------------
        summary_fields                          Optional list of strings. A list of field names and statistical summary type that you wish to calculate for all points within each polygon.
                                                Note that the count of points within each polygon is always returned.
                                                summary type is one of the following:

                                                * Sum - Adds the total value of all the points in each polygon
                                                * Mean - Calculates the average of all the points in each polygon.
                                                * Min - Finds the smallest value of all the points in each polygon.
                                                * Max - Finds the largest value of all the points in each polygon.
                                                * Stddev - Finds the standard deviation of all the points in each polygon.

                                                Example [fieldName1 summaryType1,fieldName2 summaryType2].
        ------------------------------------    --------------------------------------------------------------------
        group_by_field                          Optional string. A field name in the point_layer. Points that have
                                                the same value for the group by field will have their own counts and
                                                summary field statistics. You can create statistical groups using an
                                                attribute in the analysis layer.
                                                For example, if you are aggregating crimes to neighborhood boundaries,
                                                you may have an attribute Crime_type with five different crime types.
                                                Each unique crime type forms a group, and the statistics you choose will
                                                be calculated for each unique value of Crime_type. When you choose
                                                a grouping attribute, two results are created: the result layer and a
                                                related table containing the statistics.
        ------------------------------------    --------------------------------------------------------------------
        minority_majority                       Optional boolean. This boolean parameter is applicable only when a
                                                group_by_field is specified. If true, the minority (least dominant) or
                                                the majority (most dominant) attribute values for each group field
                                                within each boundary are calculated. Two new fields are added to the
                                                aggregated_layer prefixed with `Majority_` and `Minority_`.
                                                The default is false.
        ------------------------------------    --------------------------------------------------------------------
        percent_points                          Optional boolean. This boolean parameter is applicable only when a
                                                group_by_field is specified. If set to true, the percentage count of
                                                points for each unique group_by_field value is calculated.
                                                A new field is added to the group summary output table containing the
                                                percentages of each attribute value within each group.

                                                If minority_majority is true, two additional fields are added to the
                                                aggregated_layer containing the percentages of the minority and majority
                                                attribute values within each group.
        ------------------------------------    --------------------------------------------------------------------
        output_name                             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ------------------------------------    --------------------------------------------------------------------
        context                                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For aggregate_points, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        ------------------------------------    --------------------------------------------------------------------
        gis                                     Optional, the GIS on which this tool runs.
                                                If not specified, the active GIS is used.
        ------------------------------------    --------------------------------------------------------------------
        estimate                                Optional Boolean. If True, the number of credits to run the operation
                                                will be returned.
        ------------------------------------    --------------------------------------------------------------------
        future                                  Optional Boolean. When True, the task will be performed asynchronously.
        ------------------------------------    --------------------------------------------------------------------
        bin_type                                Optional String. The type of bin that will be generated and points
                                                will be aggregated into. Bin options are as follows: Hexagon and Square.
                                                Square is the Default. When generating bins, for Square, the number and
                                                units specified determine the height and length of the square.
                                                For Hexagon, the number and units specified determine the distance
                                                between parallel sides. Either `bin_type` or `polygon_layer` must be
                                                specified. If `bin_type` is chosen, then `bin_size` and `bin_size_unit`
                                                specifying the size of the bins must be included.
        ------------------------------------    --------------------------------------------------------------------
        bin_size                                Optional Float. The distance for the bins of type
                                                `bin_type` that the `point_layer` will be aggregated into.
                                                When generating bins for `Square` the number and units specified determine
                                                the height and length of the square. For `Hexagon`, the number and units
                                                specified determine the distance between parallel sides.
        ------------------------------------    --------------------------------------------------------------------
        bin_size_unit                           Optional String. The linear unit to be used with the distance value
                                                specified in `bin_size`.
                                                Values: `Meters, Kilometers, Feet, Miles, NauticalMiles, or Yards`
        ====================================    ====================================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "AggregatePoints"
        if polygon_layer is None and bin_size is None and bin_size_unit is None:
            raise Exception(
                "User must provide either the `polygon_layer` or binning information to use this tool."
            )

        if summary_fields is None:
            summary_fields = []
        point_layer = self._feature_input(point_layer)
        if polygon_layer:
            polygon_layer = self._feature_input(polygon_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params = {}
            params["pointLayer"] = point_layer
            params["polygonLayer"] = polygon_layer
            if bin_type is not None:
                params["binType"] = bin_type
            if bin_size is not None:
                params["binSize"] = bin_size
            if bin_size_unit is not None:
                params["binSizeUnit"] = bin_size_unit
            if keep_boundaries_with_no_points is not None:
                params["keepBoundariesWithNoPoints"] = keep_boundaries_with_no_points
            if summary_fields is not None:
                params["summaryFields"] = summary_fields
            if group_by_field is not None:
                params["groupByField"] = group_by_field
            if minority_majority is not None:
                params["minorityMajority"] = minority_majority
            if percent_points is not None:
                params["percentPoints"] = percent_points
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.aggregate_points(
            point_layer=point_layer,
            polygon_layer=polygon_layer,
            keep_boundaries_with_no_points=keep_boundaries_with_no_points,
            summary_fields=summary_fields,
            group_by_field=group_by_field,
            minority_majority=minority_majority,
            percent_points=percent_points,
            output_name=output_name,
            context=context,
            bin_type=bin_type,
            bin_size=bin_size,
            bin_size_unit=bin_size_unit,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if isinstance(ret, FeatureCollection):
            return ret
        elif "aggregatedLayer" in ret and output_name:
            return ret["aggregatedLayer"]
        return ret

    # ----------------------------------------------------------------------
    def choose_best_facilities(
        self,
        goal="Allocate",
        demand_locations_layer=None,
        demand=1,
        demand_field=None,
        max_travel_range=2147483647,
        max_travel_range_field=None,
        max_travel_range_units="Minutes",
        travel_mode="Driving Time",
        time_of_day=None,
        time_zone_for_time_of_day="GeoLocal",
        travel_direction="FacilityToDemand",
        required_facilities_layer=None,
        required_facilities_capacity=2147483647,
        required_facilities_capacity_field=None,
        candidate_facilities_layer=None,
        candidate_count=1,
        candidate_facilities_capacity=2147483647,
        candidate_facilities_capacity_field=None,
        percent_demand_coverage=100,
        output_name=None,
        context=None,
        estimate=False,
        point_barrier_layer=None,
        line_barrier_layer=None,
        polygon_barrier_layer=None,
        future=False,
    ):
        """
        The ``choose_best_facilities`` method finds the set of facilities that will best serve demand from surrounding areas.

        Facilities might be public institutions that offer a service, such as fire stations, schools, or libraries,
        or they might be commercial ones such as drug stores or distribution centers for a parcel delivery service.
        Demand represents the need for a service that the facilities can meet. Demand is associated with point locations,
        with each location representing a given amount of demand.

        =====================================    =========================================================
        **Parameter**                             **Description**
        -------------------------------------    ---------------------------------------------------------
        goal                                     Required string. Specify the goal that must be satisfied when allocating
                                                demand locations to facilities.

                                                Choice list:['Allocate', 'MinimizeImpedance', 'MaximizeCoverage', 'MaximizeCapacitatedCoverage', 'PercentCoverage']

                                                Default value is 'Allocate'.

        -------------------------------------    ---------------------------------------------------------
        demand_locations_layer                   Required point feature layer. A point layer specifying the locations
                                                that have demand for facilities. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------    ---------------------------------------------------------
        demand                                   Optional float. The amount of demand available at every demand locations.

                                                The default value is 1.0.
        -------------------------------------    ---------------------------------------------------------
        demand_field                             Optional string. A numeric field on the ``demand_locations_layer``
                                                representing the amount of demand available at each demand location.
                                                If specified, the ``demand`` parameter is ignored.
        -------------------------------------    ---------------------------------------------------------
        max_travel_range                         Optional float. Specify the maximum travel time or distance allowed
                                                between a demand location and the facility it is allocated to.

                                                The default is unlimited (2,147,483,647.0).
        -------------------------------------    ---------------------------------------------------------
        max_travel_range_field                   Optional string. A numeric field on the ``demand_locations_layer`` specifying the maximum
                                                travel time or distance allowed between a demand location
                                                and the facility it is allocated to. If specified, the ``max_travel_range`` parameter is ignored.
        -------------------------------------    ---------------------------------------------------------
        max_travel_range_units                   Optional string. The units for the maximum travel time or distance allowed
                                                between a demand location and the facility it is allocated to.

                                                Choice list:['Seconds', 'Minutes', 'Hours', 'Days', 'Meters', 'Kilometers', 'Feet', 'Yards', 'Miles'].

                                                The default is 'Minutes'.
        -------------------------------------    ---------------------------------------------------------
        travel_mode                              Specify the mode of transportation for the analysis.

                                                Choice list: ['Driving Distance', 'Driving Time', 'Rural Driving Distance',
                                                            'Rural Driving Time', 'Trucking Distance', 'Trucking Time',
                                                            'Walking Distance', 'Walking Time']

        -------------------------------------    ---------------------------------------------------------
        time_of_day                              Optional datetime.datetime. Specify whether travel times
                                                should consider traffic conditions. To use traffic in the
                                                analysis, set travel_mode to a travel mode object whose
                                                impedance_attribute_name property is set to travel_time and
                                                assign a value to time_of_day. (A travel mode with other
                                                impedance_attribute_name values don't support traffic.)
                                                The ``time_of_day`` value represents the time at which travel
                                                begins, or departs, from the origin points. The time is
                                                specified as datetime.datetime.

                                                The service supports two kinds of traffic: ty
                                                pical and live.
                                                Typical traffic references travel speeds that are made up of
                                                historical averages for each five-minute interval spanning a week.
                                                Live traffic retrieves speeds from a traffic feed that processes
                                                phone probe records, sensors, and other data sources to record
                                                actual travel speeds and predict speeds for the near future.

                                                The `data coverage <http://www.arcgis.com/home/webmap/viewer.html?webmap=b7a893e8e1e04311bd925ea25cb8d7c7>`_ page shows the countries Esri currently provides traffic data for.

                                                Typical Traffic:

                                                To ensure the task uses typical traffic in locations where it
                                                is available, choose a time and day of the week, and then convert
                                                the day of the week to one of the following dates from 1990:

                                                * Monday - 1/1/1990
                                                * Tuesday - 1/2/1990
                                                * Wednesday - 1/3/1990
                                                * Thursday - 1/4/1990
                                                * Friday - 1/5/1990
                                                * Saturday - 1/6/1990
                                                * Sunday - 1/7/1990

                                                Set the time and date as datetime.datetime.

                                                For example, to solve for 1:03 p.m. on Thursdays, set the
                                                time and date to 1:03 p.m., 4 January 1990; and convert to
                                                datetime eg. datetime.datetime(1990, 1, 4, 1, 3).

                                                Live Traffic:

                                                To use live traffic when and where it is available,
                                                choose a time and date and convert to datetime.

                                                Esri saves live traffic data for 4 hours and references
                                                predictive data extending 4 hours into the future. If the
                                                time and date you specify for this parameter is outside the
                                                24-hour time window, or the travel time in the analysis
                                                continues past the predictive data window, the task falls
                                                back to typical traffic speeds.

                                                Examples:
                                                from datetime import datetime

                                                * "time_of_day": datetime(1990, 1, 4, 1, 3) # 13:03, 4 January 1990. Typical traffic on Thursdays at 1:03 p.m.
                                                * "time_of_day": datetime(1990, 1, 7, 17, 0) # 17:00, 7 January 1990. Typical traffic on Sundays at 5:00 p.m.
                                                * "time_of_day": datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. If the current time is between 8:00 p.m., 21 Oct. 2014 and 8:00 p.m., 22 Oct. 2014, live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
                                                * "time_of_day": datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015. If the current time is between 10:20 p.m., 17 Mar. 2015 and 10:20 p.m., 18 Mar. 2015, live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
        -------------------------------------    ---------------------------------------------------------
        time_zone_for_time_of_day                Optional string. Specify the time zone or zones of the time_of_day parameter.

                                                Choice list: ['GeoLocal', 'UTC']

                                                GeoLocal-refers to the time zone in which the origins_layer points are located.

                                                UTC-refers to Coordinated Universal Time.
        -------------------------------------    ---------------------------------------------------------
        travel_direction                         Optional string. Specify whether to measure travel times or distances
                                                from facilities to demand locations or from demand locations to facilities.

                                                Choice list: ['FacilityToDemand', 'DemandToFacility']

        -------------------------------------    ---------------------------------------------------------
        required_facilities_layer                Optional point feature layer. A point layer specifying one or more locations that act as facilities
                                                by providing some kind of service. Facilities specified by this parameter
                                                are required to be part of the output solution and will be used before any
                                                facilities from the ``candidate_facilities_layer`` when allocating demand locations.
        -------------------------------------    ---------------------------------------------------------
        required_facilities_capacity             Optional float. Specify how much demand every facility in the ``required_facilities_layer``
                                                is capable of supplying.

                                                The default value is unlimited (2,147,483,647).

        -------------------------------------    ---------------------------------------------------------
        required_facilities_capacity_field       Optional string. A field on the required_facilities_layer
                                                representing how much demand each facility in the
                                                ``required_facilities_layer`` is capable of supplying. This
                                                parameter takes precedence when ``required_facilities_capacity``
                                                parameter is also specified.
        -------------------------------------    ---------------------------------------------------------
        candidate_facilities_layer               Optional point layer. A point layer specifying one or more
                                                locations that act as facilities by providing some kind of
                                                service. Facilities specified by this parameter are not
                                                required to be part of the output solution and will be used
                                                only after all the facilities from the
                                                ``candidate_facilities_layer`` have been used when
                                                allocating demand locations.

        -------------------------------------    ---------------------------------------------------------
        candidate_count                          Optional integer. The number of candidate facilities to
                                                choose when allocating demand locations. Note that the sum
                                                of the features in the ``required_facilities_capacity``
                                                and the value specified for ``candidate_count`` cannot
                                                exceed 100.

                                                The default value is 1.

        -------------------------------------    ---------------------------------------------------------
        candidate_facilities_capacity            Optional float. Specify how much demand every facility in
                                                the ``candidate_facilities_layer`` is capable of supplying.

                                                The default value is unlimited (2,147,483,647.0).

        -------------------------------------    ---------------------------------------------------------
        candidate_facilities_capacity_field      Optional string. A field on the ``candidate_facilities_layer``
                                                representing how much demand each facility in the
                                                ``candidate_facilities_layer`` is capable of supplying. This
                                                parameter takes precedence when ``candidate_facilities_capacity``
                                                parameter is also specified.
        -------------------------------------    ---------------------------------------------------------
        percent_demand_coverage                  Optional float. Specify the percentage of the total demand
                                                that you want the chosen and required facilities to capture.

                                                The default value is 100.
        -------------------------------------    ---------------------------------------------------------
        output_name                              Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------    ---------------------------------------------------------
        context                                  Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For choose_best_facilities, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                            context = {"extent": {"xmin": 3164569.408035,
                                                                                "ymin": -9187921.892449,
                                                                                "xmax": 3174104.927313,
                                                                                "ymax": -9175500.875353,
                                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                        "outSR": {"wkid": 3857},
                                                                        "overwrite": True}
        -------------------------------------    ---------------------------------------------------------
        gis                                      Optional, the GIS on which this tool runs. If not
                                                specified, the active GIS is used.
        -------------------------------------    ---------------------------------------------------------
        estimate                                 Optional boolean. Is true, the number of credits needed
                                                to run the operation will be returned as a float.
        -------------------------------------    ---------------------------------------------------------
        point_barrier_layer                      Optional layer. Specify one or more point features that
                                                act as temporary restrictions (in other words, barriers)
                                                when traveling on the underlying streets.

                                                A point barrier can model a fallen tree, an accident, a
                                                downed electrical line, or anything that completely blocks
                                                traffic at a specific position along the street. Travel is
                                                permitted on the street but not through the barrier.
                                                See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------    ---------------------------------------------------------
        line_barrier_layer                       Optional layer. Specify one or more line features that prohibit travel anywhere the lines intersect the streets.

                                                A line barrier prohibits travel anywhere the barrier intersects the streets. For example, a parade or protest that blocks traffic across several street
                                                segments can be modeled with a line barrier. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------    ---------------------------------------------------------
        polygon_barrier_layer                    Optional string. Specify one or more polygon features that completely restrict travel on the streets intersected by the polygons.

                                                One use of this type of barrier is to model floods covering areas of the street network and making road travel there impossible. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------    ---------------------------------------------------------
        future                                   Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =====================================    =========================================================


        :return: When an output_name is specified, a :class:`~arcgis.features.FeatureCollection` with 3 layers is returned (see dictionary below for details), else a dict with the following keys:

        "allocated_demand_locations_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        "allocation_lines_layer"  : layer (:class:`~arcgis.features.FeatureCollection`)

        "assigned_facilities_layer"   : layer (:class:`~arcgis.features.FeatureCollection`)

        """
        task = "ChooseBestFacilities"
        if isinstance(travel_mode, str):
            route_service = arcgis.network.RouteLayer(
                self._gis.properties.helperServices.route.url, gis=self._gis
            )
            travel_mode = [
                i
                for i in route_service.retrieve_travel_modes()["supportedTravelModes"]
                if i["name"] == travel_mode
            ][0]
        demand_locations_layer = self._feature_input(demand_locations_layer)
        if required_facilities_layer:
            required_facilities_layer = self._feature_input(required_facilities_layer)
        if candidate_facilities_layer:
            candidate_facilities_layer = self._feature_input(candidate_facilities_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        if point_barrier_layer:
            point_barrier_layer = self._feature_input(point_barrier_layer)
        if line_barrier_layer:
            line_barrier_layer = self._feature_input(line_barrier_layer)
        if polygon_barrier_layer:
            polygon_barrier_layer = self._feature_input(polygon_barrier_layer)

        if estimate:
            params = {}
            if goal is not None:
                params["goal"] = goal
            params["demandLocationsLayer"] = demand_locations_layer
            if demand is not None:
                params["demand"] = demand
            if demand_field is not None:
                params["demandField"] = demand_field
            if max_travel_range is not None:
                params["maxTravelRange"] = max_travel_range
            if max_travel_range_field is not None:
                params["maxTravelRangeField"] = max_travel_range_field
            if max_travel_range_units is not None:
                params["maxTravelRangeUnits"] = max_travel_range_units
            if travel_mode is not None:
                params["travelMode"] = travel_mode
            if time_of_day is not None:
                params["timeOfDay"] = time_of_day
            if time_zone_for_time_of_day is not None:
                params["timeZoneForTimeOfDay"] = time_zone_for_time_of_day
            if travel_direction is not None:
                params["travelDirection"] = travel_direction
            if required_facilities_layer is not None:
                params["requiredFacilitiesLayer"] = required_facilities_layer
            if required_facilities_capacity is not None:
                params["requiredFacilitiesCapacity"] = required_facilities_capacity
            if required_facilities_capacity_field is not None:
                params[
                    "requiredFacilitiesCapacityField"
                ] = required_facilities_capacity_field
            if candidate_facilities_layer is not None:
                params["candidateFacilitiesLayer"] = candidate_facilities_layer
            if candidate_count is not None:
                params["candidateCount"] = candidate_count
            if candidate_facilities_capacity is not None:
                params["candidateFacilitiesCapacity"] = candidate_facilities_capacity
            if candidate_facilities_capacity_field is not None:
                params[
                    "candidateFacilitiesCapacityField"
                ] = candidate_facilities_capacity_field
            if percent_demand_coverage is not None:
                params["percentDemandCoverage"] = percent_demand_coverage
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if point_barrier_layer is not None:
                params["pointBarrierLayer"] = point_barrier_layer
            if line_barrier_layer is not None:
                params["lineBarrierLayer"] = line_barrier_layer
            if polygon_barrier_layer is not None:
                params["polygonBarrierLayer"] = polygon_barrier_layer
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)

        gpjob = self._tbx.choose_best_facilities(
            goal=goal,
            demand_locations_layer=demand_locations_layer,
            demand=demand,
            demand_field=demand_field,
            max_travel_range=max_travel_range,
            max_travel_range_field=max_travel_range_field,
            max_travel_range_units=max_travel_range_units,
            travel_mode=travel_mode,
            time_of_day=time_of_day,
            time_zone_for_time_of_day=time_zone_for_time_of_day,
            travel_direction=travel_direction,
            required_facilities_layer=required_facilities_layer,
            required_facilities_capacity=required_facilities_capacity,
            required_facilities_capacity_field=required_facilities_capacity_field,
            candidate_facilities_layer=candidate_facilities_layer,
            candidate_count=candidate_count,
            candidate_facilities_capacity=candidate_facilities_capacity,
            candidate_facilities_capacity_field=candidate_facilities_capacity_field,
            percent_demand_coverage=percent_demand_coverage,
            output_name=output_name,
            context=context,
            point_barrier_layer=point_barrier_layer,
            line_barrier_layer=line_barrier_layer,
            polygon_barrier_layer=polygon_barrier_layer,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "allocatedDemandLocationsLayer" in ret:
            return ret["allocatedDemandLocationsLayer"]
        return ret

    # ----------------------------------------------------------------------
    def connect_origins_to_destinations(
        self,
        origins_layer,
        destinations_layer,
        measurement_type="DrivingTime",
        origins_layer_route_id_field=None,
        destinations_layer_route_id_field=None,
        time_of_day=None,
        time_zone_for_time_of_day="GeoLocal",
        output_name=None,
        context=None,
        estimate=False,
        point_barrier_layer=None,
        line_barrier_layer=None,
        polygon_barrier_layer=None,
        include_route_layers=False,
        route_shape=None,
        future=False,
    ):
        """
        The Connect Origins to Destinations task measures the travel time or distance between pairs of points. Using this tool, you can

        * Calculate the total distance or time commuters travel on their home-to-work trips.
        * Measure how far customers are traveling to shop at your stores. Use this information to define your market reach, especially when targeting advertising campaigns or choosing new store locations.
        * Calculate the expected trip mileage for your fleet of vehicles. Afterward, run the Summarize Within tool to report mileage by state or other region.

        You provide starting and ending points, and the tool returns a layer containing route lines, including measurements, between the
        paired origins and destinations.

        ===================================     ===============================================================
        **Parameter**                            **Description**
        -----------------------------------     ---------------------------------------------------------------
        origins_layer                           Required layer. The starting point or points of the
                                                routes to be generated. See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------------
        destinations_layer                      Required layer. The routes end at points in the
                                                destinations layer. See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------------
        measurement_type                        Required string. The origins and destinations can be connected by measuring straight-line distance,
                                                or by measuring travel time or travel distance along a street network using various modes of transportation known as travel modes.

                                                Valid values are a string, StraightLine, which indicates Euclidean distance to be used as distance measure or a Python dictionary representing settings for a travel mode.

                                                When using a travel mode for the measurement_type, you need to specify a dictionary
                                                containing the settings for a travel mode supported by your organization. The code in the example section below generates
                                                a valid Python dictionary and then passes it as the value for the measurement_type parameter.

                                                Supported travel modes: ['Driving Distance', 'Driving Time', 'Rural Driving Distance', 'Rural Driving Time',
                                                'Trucking Distance', 'Trucking Time', 'Walking Distance', 'Walking Time']
        -----------------------------------     ---------------------------------------------------------------
        origins_layer_route_id_field            Optional string. Specify the field in the origins layer
                                                containing the IDs that pair origins with destinations.

                                                * The ID values must uniquely identify points in the origins layer

                                                * Each ID value must also correspond with exactly one route ID value in the destinations layer.
                                                Route IDs that match across the layers create origin-destination pairs, which the tool connects
                                                together.

                                                * Specifying origins_layer_route_id_field is optional when there is exactly one point feature in
                                                the origins or destinations layer. The tool will connect all origins to the one destination or the
                                                one origin to all destinations, depending on which layer contains one point.

        -----------------------------------     ---------------------------------------------------------------
        destinations_layer_route_id_field       Optional string. Specify the field in the destinations layer containing the IDs that pair origins
                                                with destinations.

                                                * The ID values must uniquely identify points in the destinations layer.

                                                * Each ID value must also correspond with exactly one route ID value in the origins layer. Route
                                                IDs that match across the layers create origin-destination pairs, which the tool connects together.

                                                * Specifying destinations_layer_route_id_field is optional when there is exactly one point
                                                feature in the origins or destinations layer. The tool will connect all origins to the one
                                                destination or the one origin to all destinations, depending on which layer contains one point.
        -----------------------------------     ---------------------------------------------------------------
        time_of_day                             Optional datetime.datetime. Specify whether travel times should consider traffic conditions. To use
                                                traffic in the analysis,
                                                set measurement_type to a travel mode object whose impedance_attribute_name property is set to
                                                travel_time and assign a value
                                                to time_of_day. (A travel mode with other impedance_attribute_name values don't support traffic.) The time_of_day value represents
                                                the time at which travel begins, or departs, from the origin points. The time is specified as datetime.datetime.

                                                The service supports two kinds of traffic: typical and live. Typical traffic references travel speeds that are made up of historical
                                                averages for each five-minute interval spanning a week. Live traffic retrieves speeds from a traffic feed that processes phone probe
                                                records, sensors, and other data sources to record actual travel speeds and predict speeds for the near future.

                                                The `data coverage <http://www.arcgis.com/home/webmap/viewer.html?webmap=b7a893e8e1e04311bd925ea25cb8d7c7>`_ page shows the countries
                                                Esri currently provides traffic data for.

                                                Typical Traffic:

                                                To ensure the task uses typical traffic in locations where it is available, choose a time and day of the week, and then convert the day
                                                of the week to one of the following dates from 1990:

                                                * Monday - 1/1/1990
                                                * Tuesday - 1/2/1990
                                                * Wednesday - 1/3/1990
                                                * Thursday - 1/4/1990
                                                * Friday - 1/5/1990
                                                * Saturday - 1/6/1990
                                                * Sunday - 1/7/1990
                                                Set the time and date as datetime.datetime.

                                                For example, to solve for 1:03 p.m. on Thursdays, set the time and date to 1:03 p.m., 4 January 1990; and convert to
                                                datetime eg. datetime.datetime(1990, 1, 4, 1, 3).

                                                Live Traffic:

                                                To use live traffic when and where it is available, choose a time and date and convert to datetime.

                                                Esri saves live traffic data for 4 hours and references predictive data extending 4 hours into the future. If the time and date you
                                                specify for this parameter is outside the 24-hour time window, or the travel time in the analysis continues past the predictive data window, the task falls back to typical traffic speeds.

                                                # Examples:
                                                from datetime import datetime

                                                * "time_of_day": datetime(1990, 1, 4, 1, 3) # 13:03, 4 January 1990. Typical traffic on
                                                Thursdays at 1:03 p.m.
                                                * "time_of_day": datetime(1990, 1, 7, 17, 0) # 17:00, 7 January 1990. Typical traffic on Sundays at
                                                5:00 p.m.
                                                * "time_of_day": datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. If the current time is
                                                between 8:00 p.m., 21 Oct. 2014 and 8:00 p.m., 22 Oct. 2014,
                                                live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are
                                                referenced.
                                                * "time_of_day": datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015. If the current time is
                                                between 10:20 p.m., 17 Mar. 2015 and 10:20 p.m., 18 Mar. 2015, live traffic speeds are
                                                referenced in the analysis; otherwise, typical traffic speeds are referenced.
        -----------------------------------     ---------------------------------------------------------------
        time_zone_for_time_of_day               Optional string. Specify the time zone or zones of the timeOfDay parameter.
                                                Choice list: ['GeoLocal', 'UTC']

                                                GeoLocal-refers to the time zone in which the originsLayer points are located.

                                                UTC-refers to Coordinated Universal Time.
        -----------------------------------     ---------------------------------------------------------------
        include_route_layers                    Optional Boolean. When include_route_layers is set to True,
                                                each route from the result is also saved as a route layer item.
                                                A route layer includes all the information for a particular route
                                                such as the stops assigned to the route as well as the travel directions.
                                                Creating route layers is useful if you want to share individual
                                                routes with other members in your organization.
                                                The route layers use the output feature service name provided in the ```output_name```
                                                parameter as a prefix and the route name generated as part of the analysis is added to create a
                                                unique name for each route layer.

                                                Caution: Route layers cannot be created when the output is a feature collection.
                                                The task will raise an error if output_name is not specified
                                                (which indicates feature collection output) and include_route_layers is True.

                                                The maximum number of route layers that can be created is 1,000.
                                                If the result contains more than 1,000 routes and include_route_layers is True,
                                                the task will only create the output feature service.
        -----------------------------------     ---------------------------------------------------------------
        output_name                             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -----------------------------------     ---------------------------------------------------------------
        context                                 Optional dict. Additional settings such as processing extent
                                                and output spatial reference.
                                                For connect_origins_to_destinations, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the
                                                input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to
                                                by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new
                                                feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        -----------------------------------     ---------------------------------------------------------------
        gis                                     Optional, the GIS on which this tool runs. If not specified,
                                                the active GIS is used.
        -----------------------------------     ---------------------------------------------------------------
        estimate                                Optional Boolean. Is True, the number of credits needed
                                                to run the operation will be returned as a float.
        -----------------------------------     ---------------------------------------------------------------
        point_barrier_layer                     Optional layer. Specify one or more point features that
                                                act as temporary restrictions (in other words, barriers) when
                                                traveling on the underlying streets.

                                                A point barrier can model a fallen tree, an accident, a downed
                                                electrical line, or anything that completely blocks traffic at
                                                a specific position along the street. Travel is permitted on the
                                                street but not through the barrier. See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------------
        line_barrier_layer                      Optional layer. Specify one or more line features that prohibit
                                                travel anywhere the lines intersect the streets.

                                                A line barrier prohibits travel anywhere the barrier intersects the
                                                streets. For example, a parade or protest that blocks traffic across
                                                several street
                                                segments can be modeled with a line barrier.
                                                See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------------
        polygon_barrier_layer                   Optional string. Specify one or more polygon features
                                                that completely restrict travel on the streets intersected
                                                by the polygons.

                                                One use of this type of barrier is to model floods covering
                                                areas of the street network and making road travel there impossible.
                                                See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------------
        future                                  Optional boolean. If True, the result will be a GPJob object
                                                and results will be returned asynchronously.
        -----------------------------------     ---------------------------------------------------------------
        route_shape                             Optional String. Specify the shape of the route that connects
                                                each origin to it's destination when using a travel mode.

                                                Values: FollowStreets or StraightLine

                                                Default: FollowStreets

                                                + FollowStreets - The shape is based on the underlying street network.
                                                    This option is best when you want to generate the routes between
                                                    origins and destinations. This is the default value when using a
                                                    travel mode.
                                                + StraightLine - The shape is a straight line connecting
                                                    the origin-destination pair. This option is best when you want to g
                                                    enerate spider diagrams or desire lines (for example, to show which
                                                    stores customers are visiting). This is the default value when not using
                                                    a travel mode.

                                                The best route between an origin and it's matched destination is always calculated based on the travel mode, regardless of which route shape is chosen.
        ===================================     ===============================================================


        :return: A dictionary with the following keys:

            "routes_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "unassigned_origins_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "unassigned_destinations_layer" : layer (:class:`~arcgis.features.FeatureCollection`)
        """
        task = "ConnectOriginsToDestinations"
        origins_layer = self._feature_input(origins_layer)
        destinations_layer = self._feature_input(destinations_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        if point_barrier_layer:
            point_barrier_layer = self._feature_input(point_barrier_layer)
        if line_barrier_layer:
            line_barrier_layer = self._feature_input(line_barrier_layer)
        if polygon_barrier_layer:
            polygon_barrier_layer = self._feature_input(polygon_barrier_layer)
        if estimate:
            params = {}

            params["originsLayer"] = origins_layer
            params["destinationsLayer"] = destinations_layer
            params["measurementType"] = measurement_type
            if route_shape is not None:
                params["routeShape"] = route_shape
            if origins_layer_route_id_field is not None:
                params["originsLayerRouteIDField"] = origins_layer_route_id_field
            if destinations_layer_route_id_field is not None:
                params[
                    "destinationsLayerRouteIDField"
                ] = destinations_layer_route_id_field
            if time_of_day is not None:
                params["timeOfDay"] = time_of_day
            if time_zone_for_time_of_day is not None:
                params["timeZoneForTimeOfDay"] = time_zone_for_time_of_day
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if point_barrier_layer is not None:
                params["pointBarrierLayer"] = point_barrier_layer
            if line_barrier_layer is not None:
                params["lineBarrierLayer"] = line_barrier_layer
            if polygon_barrier_layer is not None:
                params["polygonBarrierLayer"] = polygon_barrier_layer

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.connect_origins_to_destinations(
            origins_layer=origins_layer,
            destinations_layer=destinations_layer,
            measurement_type=measurement_type,
            origins_layer_route_id_field=origins_layer_route_id_field,
            destinations_layer_route_id_field=destinations_layer_route_id_field,
            time_of_day=time_of_day,
            time_zone_for_time_of_day=time_zone_for_time_of_day,
            output_name=output_name,
            context=context,
            include_route_layers=include_route_layers,
            point_barrier_layer=point_barrier_layer,
            line_barrier_layer=line_barrier_layer,
            polygon_barrier_layer=polygon_barrier_layer,
            route_shape=route_shape,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "routesLayer" in ret:
            return ret["routesLayer"]
        return ret

    # ----------------------------------------------------------------------
    def create_drive_time_areas(
        self,
        input_layer,
        break_values=[5, 10, 15],
        break_units="Minutes",
        travel_mode="Driving Time",
        overlap_policy="Overlap",
        time_of_day=None,
        time_zone_for_time_of_day="GeoLocal",
        output_name=None,
        context=None,
        estimate=False,
        point_barrier_layer=None,
        line_barrier_layer=None,
        polygon_barrier_layer=None,
        future=False,
        travel_direction=False,
        show_holes=False,
        include_reachable_streets=False,
    ):
        """
        The ``create_drive_time_areas`` method creates areas that can be reached within a
        given drive time or drive distance. It can help you answer questions such as:

        * How far can I drive from here in five minutes?
        * What areas are covered within a three-mile drive distance of my stores?
        * What areas are within four minutes of our fire stations?

        See `Create Drive-Time Areas <https://developers.arcgis.com/rest/analysis/api-reference/create-drivetime.htm>`_
        for details on the `Spatial Analysis Service <https://developers.arcgis.com/rest/analysis/api-reference/getting-started.htm>`_
        that runs this task.

        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        input_layer                  Required point feature layer. The points around which travel areas
                                    based on a mode of transportation will be drawn.
                                    See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        travel_mode                  Optional string or dict. Specify the mode of transportation for the analysis.

                                    Choice list: ['Driving Distance', 'Driving Time', 'Rural Driving Distance', 'Rural Driving Time', 'Trucking Distance', 'Trucking Time', 'Walking Distance', 'Walking Time']

                                    The default is 'Driving Time'.
        -------------------------    ---------------------------------------------------------
        break_values                 Optional list of floats. The size of the polygons to create.
                                    The units for break_values is specified with the break_units parameter.

                                    By setting many unique values in the list, polygons of different sizes are generated around each input location.

                                    The default is [5, 10, 15].
        -------------------------    ---------------------------------------------------------
        break_units                  Optional string. The units of the break_values parameter.

                                    To create areas showing how far you can go along roads or walkways within a given time, specify a time unit.
                                    Alternatively, specify a distance unit to generate areas bounded by a maximum travel distance.

                                    When the travel_mode is time based, a time unit should be specified for the break_units. When the
                                    travel_mode is distance based, a distance unit should be specified for the break_units.

                                    Choice list: ['Seconds', 'Minutes', Hours', 'Feet', 'Meters', 'Kilometers', 'Feet', 'Miles', 'Yards']

                                    The default is 'Minutes'.
        -------------------------    ---------------------------------------------------------
        overlap_policy               Optional string. Determines how overlapping areas are processed.

                                    Choice list: ['Overlap', 'Dissolve', 'Split']

                                    +---------------+-----------------------------------------------------------------------------------------------------+
                                    | |Overlap|     | ``Overlap``-Overlapping areas are kept. This is the default.                                        |
                                    +---------------+-----------------------------------------------------------------------------------------------------+
                                    | |Dissolve|    | ``Dissolve``-Overlapping areas are combined by break value. Because the areas are dissolved,        |
                                    |               | use this option when you need to know the areas that can be reached within a                        |
                                    |               | given time or distance, but you don't need to know which input points are nearest.                  |
                                    +---------------+-----------------------------------------------------------------------------------------------------+
                                    | |Split|       | ``Split``-Overlapping areas are split in the middle. Use this option when you need to know          |
                                    |               | the one nearest input location to the covered area.                                                 |
                                    +---------------+-----------------------------------------------------------------------------------------------------+

                                    The default is 'Overlap'

        -------------------------    ---------------------------------------------------------
        time_of_day                  Optional datetime.datetime. Specify whether travel times should consider traffic conditions. To use traffic in the analysis,
                                    set measurement_type to a travel mode object whose impedance_attribute_name property is set to travel_time and assign a value
                                    to time_of_day. (A travel mode with other impedance_attribute_name values don't support traffic.) The time_of_day value represents
                                    the time at which travel begins, or departs, from the origin points. The time is specified as datetime.datetime.

                                    The service supports two kinds of traffic: typical and live. Typical traffic references travel speeds that are made up of historical
                                    averages for each five-minute interval spanning a week. Live traffic retrieves speeds from a traffic feed that processes phone probe
                                    records, sensors, and other data sources to record actual travel speeds and predict speeds for the near future.

                                    The `data coverage <http://www.arcgis.com/home/webmap/viewer.html?webmap=b7a893e8e1e04311bd925ea25cb8d7c7>`_ page shows the countries
                                    Esri currently provides traffic data for.

                                    Typical Traffic:

                                    To ensure the task uses typical traffic in locations where it is available, choose a time and day of the week, and then convert the day
                                    of the week to one of the following dates from 1990:

                                    * Monday - 1/1/1990
                                    * Tuesday - 1/2/1990
                                    * Wednesday - 1/3/1990
                                    * Thursday - 1/4/1990
                                    * Friday - 1/5/1990
                                    * Saturday - 1/6/1990
                                    * Sunday - 1/7/1990
                                    Set the time and date as datetime.datetime.

                                    For example, to solve for 1:03 p.m. on Thursdays, set the time and date to 1:03 p.m., 4 January 1990; and convert to
                                    datetime eg. datetime.datetime(1990, 1, 4, 1, 3).

                                    Live Traffic:

                                    To use live traffic when and where it is available, choose a time and date and convert to datetime.

                                    Esri saves live traffic data for 4 hours and references predictive data extending 4 hours into the future. If the time and date you
                                    specify for this parameter is outside the 24-hour time window, or the travel time in the analysis continues past the predictive data window, the task falls back to typical traffic speeds.

                                    Examples:
                                    from datetime import datetime

                                    * "time_of_day": datetime(1990, 1, 4, 1, 3) # 13:03, 4 January 1990. Typical traffic on Thursdays at 1:03 p.m.
                                    * "time_of_day": datetime(1990, 1, 7, 17, 0) # 17:00, 7 January 1990. Typical traffic on Sundays at 5:00 p.m.
                                    * "time_of_day": datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. If the current time is between 8:00 p.m., 21 Oct. 2014 and 8:00 p.m., 22 Oct. 2014,
                                    live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
                                    * "time_of_day": datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015. If the current time is between 10:20 p.m., 17 Mar. 2015 and 10:20 p.m., 18 Mar. 2015,
                                    live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
        -------------------------    ---------------------------------------------------------
        time_zone_for_time_of_day    Optional string. Specify the time zone or zones of the time_of_day parameter.

                                    Choice list: ['GeoLocal', 'UTC']

                                    GeoLocal-refers to the time zone in which the originsLayer points are located.

                                    UTC-refers to Coordinated Universal Time.

                                    The default is 'GeoLocal'.
        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent
                                    and output spatial reference.
                                    For create_drive_time_areas, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                    .. code-block:: python

                                        # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        point_barrier_layer          Optional layer. Specify one or more point features that act as temporary restrictions (in other words, barriers)
                                    when traveling on the underlying streets.

                                    A point barrier can model a fallen tree, an accident, a downed electrical line, or anything that completely blocks
                                    traffic at a specific position along the street. Travel is permitted on the street but not through the barrier. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        line_barrier_layer           Optional layer. Specify one or more line features that prohibit travel anywhere the lines intersect the streets.

                                    A line barrier prohibits travel anywhere the barrier intersects the streets. For example, a parade or protest that blocks traffic across several street
                                    segments can be modeled with a line barrier. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        polygon_barrier_layer        Optional string. Specify one or more polygon features that completely restrict travel on the streets intersected by the polygons.

                                    One use of this type of barrier is to model floods covering areas of the street network and making road travel there impossible. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        -------------------------    ---------------------------------------------------------
        travel_direction             Optiona String. Specify whether the direction of travel used to generate the travel areas is toward or away from the input locations.

                                    Values: AwayFromFacility or TowardsFacility

                                    The travel direction can influence how the areas are generated. CreateDriveTimeAreas will obey one-way streets, avoid illegal turns, and follow other rules based on the direction of travel. You should select the direction of travel based on the type of input locations and the context of your analysis. For example, the drive-time area for a pizza delivery store should be created away from the facility, whereas the drive-time area for a hospital should be created toward the facility.
        -------------------------    ---------------------------------------------------------
        show_holes                   Optional boolean. When set to true, the output areas will include holes if some streets couldn't be reached without exceeding the cutoff or due to travel restrictions imposed by the travel mode.
        -------------------------    ---------------------------------------------------------
        include_reachable_streets    Optional string. Only applicable if :attr:`output_name` is specified.
                                    When `True` (and :attr:`output_name` is specified), a second layer named
                                    `Reachable Streets` is created in the output :class:`Feature Layer<arcgis.features.FeatureLayerCollection>`.

                                    This layer contains the streets that were used to define the drive time
                                    area polygons. Set this to true if you want a potentially more accurate
                                    result of which streets are actually covered within a specific travel
                                    distance than what the drive-time areas would contain.
        =========================    =========================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else Feature Collection.

        """

        # kwargs = locals()
        params = {}
        task = "CreateDriveTimeAreas"

        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        if point_barrier_layer:
            point_barrier_layer = self._feature_input(point_barrier_layer)
        if polygon_barrier_layer:
            polygon_barrier_layer = self._feature_input(polygon_barrier_layer)
        if line_barrier_layer:
            line_barrier_layer = self._feature_input(line_barrier_layer)
        if estimate:
            params["inputLayer"] = input_layer
            if break_values is not None:
                params["breakValues"] = break_values
            if break_units is not None:
                params["breakUnits"] = break_units
            if travel_mode is not None:
                params["travelMode"] = travel_mode
            if overlap_policy is not None:
                params["overlapPolicy"] = overlap_policy
            if time_of_day is not None:
                params["timeOfDay"] = time_of_day
            if time_zone_for_time_of_day is not None:
                params["timeZoneForTimeOfDay"] = time_zone_for_time_of_day
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if point_barrier_layer is not None:
                params["pointBarrierLayer"] = point_barrier_layer
            if line_barrier_layer is not None:
                params["lineBarrierLayer"] = line_barrier_layer
            if polygon_barrier_layer is not None:
                params["polygonBarrierLayer"] = polygon_barrier_layer
            if travel_direction is not None:
                params["travelDirection"] = travel_direction
            if show_holes is not None:
                params["showHoles"] = show_holes
            if include_reachable_streets is not None:
                params["includeReachableStreets"] = include_reachable_streets

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)

        params = {
            "input_layer": input_layer,
            "break_values": break_values,
            "break_units": break_units,
            "travel_mode": travel_mode,
            "overlap_policy": overlap_policy,
            "time_of_day": time_of_day,
            "time_zone_for_time_of_day": time_zone_for_time_of_day,
            "output_name": output_name,
            "context": context,
            "point_barrier_layer": point_barrier_layer or "",
            "line_barrier_layer": line_barrier_layer or "",
            "polygon_barrier_layer": polygon_barrier_layer or "",
            "gis": self._gis,
            "future": True,
            "travel_direction": travel_direction,
            "show_holes": show_holes,
            "include_reachable_streets": include_reachable_streets,
        }
        params = inspect_function_inputs(fn=self._tbx.create_drive_time_areas, **params)
        params["future"] = True
        params.pop("estimate", None)
        gpjob = self._tbx.create_drive_time_areas(**params)
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "driveTimeAreasLayer" in ret:
            return ret["driveTimeAreasLayer"]
        return ret

    # ----------------------------------------------------------------------
    def create_route_layers(
        self,
        route_data_item,
        delete_route_data_item=False,
        output_name=None,
        estimate=False,
        context=None,
        future=False,
    ):
        """
        The ``create_route_layers`` method creates route layer items on the portal from the input route data.

        A route layer includes all the information for a particular route such as the stops assigned to
        the route as well as the travel directions. Creating route layers is useful if you want to share
        individual routes with other members in your organization.


        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        route_data                   Required item. The item id for the route data item that is used to create route layer items.
                                    Before running this task, the route data must be added to your portal as an item.
        -------------------------    ---------------------------------------------------------
        delete_route_data_item       Required boolean. Indicates if the input route data item should be deleted. You may want to
                                    delete the route data in case it is no longer required after the route layers have been created from it.

                                    When ``delete_route_data_item`` is set to true and the task fails to delete the route data item,
                                    it will return a warning message but still continue execution.

                                    The default value is False.
        -------------------------    ---------------------------------------------------------
        tags                         Optional string. Tags used to describe and identify the route layer items.
                                    Individual tags are separated using a comma. The route name is always
                                    added as a tag even when a value for this argument is not specified.
        -------------------------    ---------------------------------------------------------
        summary                      Optional string. The summary displayed as part of the item information for the route layer item.
                                    If a value for this argument is not specified, a default summary text "Route and directions for <Route Name>" is used.
        -------------------------    ---------------------------------------------------------
        route_name_prefix            Optional string. A qualifier added to the title of every route layer item. This can be used to designate all routes that are shared for a
                                    specific purpose to have the same prefix in the title. The name of the route is always appended after this qualifier.
                                    If a value for the route_name_prefix is not specified, the title for the route layer item is created using only the route name.
        -------------------------    ---------------------------------------------------------
        folder_name                  Optional string. The folder within your personal online workspace (My Content in your ArcGIS Online or Portal for ArcGIS organization) where the
                                    route layer items will be created. If a folder with the specified name does not exist, a new folder will be created.
                                    If a folder with the specified name exists, the items will be created in the existing folder.
                                    If a value for folder_name is not specified, the route layer items are created in the root folder of your online workspace.
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================

        :return: result_layer : A list (items) or an :class:`~arcgis.gis.Item`
        """
        if route_data_item:
            route_data_item = {"itemId": route_data_item.itemid}

        if estimate:
            params = {}
            if context:
                params["context"] = context
            params["routeData"] = route_data_item
            params["deleteRouteData"] = delete_route_data_item
            params["outputName"] = output_name
            from arcgis.features._credits import _estimate_credits

            task = "CreateRouteLayers"
            return _estimate_credits(task=task, parameters=params)

        gpjob = self._tbx.create_route_layers(
            route_data=route_data_item,
            delete_route_data=delete_route_data_item,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        # "routeLayers"
        ret = gpjob.result()
        return ret

    # ----------------------------------------------------------------------
    def create_buffers(
        self,
        input_layer,
        distances=[],
        field=None,
        units="Meters",
        dissolve_type="None",
        ring_type="Disks",
        side_type="Full",
        end_type="Round",
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``create_buffers`` task creates polygons that cover a given distance from a point,
        line, or polygon feature. Buffers are typically used to create areas that can be
        further analyzed using a tool such as ``overlay_layers``. For example, if the question
        is "What buildings are within one mile of the school?", the answer can be found by
        creating a one-mile buffer around the school and overlaying the buffer with the layer
        containing building footprints. The end result is a layer of those buildings within
        one mile of the school.

        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        input_layer                  Required point, line or polygon feature layer. The input features to be buffered. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        distances                    Optional list of floats to buffer the input features. The distance(s) that will be buffered. You must supply values
                                    for either the ``distances`` or ``field`` parameter. You can enter a single distance value or multiple values.
                                    The units of the distance values is suppied by the units parameter.
        -------------------------    ---------------------------------------------------------
        field                        Optional string. A field on the ``input_layer`` containing a buffer distance. Buffers will be created using field values.
                                    Unlike the ``distances`` parameter, multiple distances are not supported on field input.
        -------------------------    ---------------------------------------------------------
        units                        Optional string. The linear unit to be used with the distance value(s) specified in distances or contained in the field value.

                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Miles', 'NauticalMiles', 'Yards']

                                    The default is 'Meters'.
        -------------------------    ---------------------------------------------------------
        dissolve_type                Optional string. Determines how overlapping buffers are processed.

                                    Choice list: ['None', 'Dissolve']

                                    +------------+---------------------------------------------------------------------------------+
                                    | |None|     | ``None``-Overlapping areas are kept. This is the default.                       |
                                    +------------+---------------------------------------------------------------------------------+
                                    | |Dissolve| | ``Dissolve``-Overlapping areas are combined.                                    |
                                    +------------+---------------------------------------------------------------------------------+

        -------------------------    ---------------------------------------------------------
        ring_type                    Optional string. Determines how multiple-distance buffers are processed.

                                    Choice list: ['Disks', 'Rings']

                                    +-----------+--------------------------------------------------------------------------------------------------+
                                    | |Disks|   | ``Disks``-buffers are concentric and will overlap. For example, if your distances are 10 and 14, |
                                    |           | the result will be two buffers, one from 0 to 10 and one from 0 to 14. This is the default.      |
                                    +-----------+--------------------------------------------------------------------------------------------------+
                                    | |Rings|   | ``Rings`` buffers will not overlap. For example, if your distances are 10 and 14, the result will|
                                    |           | be two buffers, one from 0 to 10 and one from 10 to 14.                                          |
                                    +-----------+--------------------------------------------------------------------------------------------------+

        -------------------------    ---------------------------------------------------------
        side_type                    Optional string. When buffering line features, you can choose which side of the line to buffer.

                                    Typically, you choose both sides (Full, which is the default). Left and right are determined as
                                    if you were walking from the first x,y coordinate of the line (the start coordinate) to the last
                                    x,y coordinate of the line (the end coordinate). Choosing left or right usually means you know
                                    that your line features were created and stored in a particular direction (for example, upstream
                                    or downstream in a river network).

                                    When buffering polygon features, you can choose whether the buffer includes or excludes the polygon
                                    being buffered.

                                    Choice list: ['Full', 'Left', 'Right', 'Outside']

                                    +---------------+----------------------------------------------------------------------------------------------------+
                                    | |Full|        | ``Full``-both sides of the line will be buffered. This is the default for line featuress.          |
                                    |               |                                                                                                    |
                                    +---------------+----------------------------------------------------------------------------------------------------+
                                    | |Left|        | ``Left``-only the right side of the line will be buffered.                                         |
                                    +---------------+----------------------------------------------------------------------------------------------------+
                                    | |Right|       | ``Right``-only the right side of the line will be buffered.                                        |
                                    +---------------+----------------------------------------------------------------------------------------------------+
                                    | |Outside|     | ``Outside`` when buffering a polygon, the polygon being buffered is excluded in the result buffer. |
                                    +---------------+----------------------------------------------------------------------------------------------------+
                                    | |Unspecified| | If ``side_type`` not supplied, the polygon being buffered is included in the result buffer.        |
                                    |               | This is the  default for polygon features.                                                         |
                                    +---------------+----------------------------------------------------------------------------------------------------+

        -------------------------    ---------------------------------------------------------
        end_type                     Optional string. The shape of the buffer at the end of line input features. This parameter is not
                                    valid for polygon input features. At the ends of lines the buffer can be rounded (Round) or be
                                    straight across (Flat).

                                    Choice list: ['Round', 'Flat']

                                    +---------+-------------------------------------------------------------------------------+
                                    | |Round| | ``Round``-buffers will be rounded at the ends of lines. This is the default.  |
                                    +---------+-------------------------------------------------------------------------------+
                                    | |Flat|  | ``Flat``-buffers will be flat at the ends of lines.                           |
                                    +---------+-------------------------------------------------------------------------------+

        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent
                                    and output spatial reference.
                                    For create_buffers, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "CreateBuffers"
        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        if estimate:
            params = {}

            params["inputLayer"] = input_layer
            if distances is not None:
                params["distances"] = distances
            if field is not None:
                params["field"] = field
            if units is not None:
                params["units"] = units
            if dissolve_type is not None:
                params["dissolveType"] = dissolve_type
            if ring_type is not None:
                params["ringType"] = ring_type
            if side_type is not None:
                params["sideType"] = side_type
            if end_type is not None:
                params["endType"] = end_type
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.create_buffers(
            input_layer=input_layer,
            distances=distances,
            field=field,
            units=units,
            dissolve_type=dissolve_type,
            ring_type=ring_type,
            side_type=side_type,
            end_type=end_type,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "bufferLayer" in ret:
            return ret["bufferLayer"]
        return ret

    # ----------------------------------------------------------------------
    def calculate_density(
        self,
        input_layer,
        field=None,
        cell_size=None,
        cell_size_units="Meters",
        radius=None,
        radius_units=None,
        bounding_polygon_layer=None,
        area_units=None,
        classification_type="EqualInterval",
        num_classes=10,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The calculate_density function creates a density map from point or line features by spreading known quantities of
        some phenomenon (represented as attributes of the points or lines) across the map. The result is a layer of areas
        classified from least dense to most dense.

        For point input, each point should represent the location of some event	or incident, and the result layer represents
        a count of the incident per unit area. A higher density value in a new location means that there are more points near
        that location. In many cases, the result layer can be interpreted as a risk surface for future events. For example,
        if the input points represent locations of lightning strikes, the result layer can be interpreted as a risk surface
        for future lightning strikes.

        For line input, the line density surface represents the total amount of line that is near each location. The units of
        the calculated density values are the length of line per unit area. For example, if the lines represent rivers, the
        result layer will represent the total length of rivers that are within the search radius. This result can be used to
        identify areas that are hospitable to grazing animals.

        =========================    =========================================================
        **Parameter**                 **Description**
        -------------------------    ---------------------------------------------------------
        input_layer                  Required layer. The point or line features from which to calculate density. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        field                        Optional string. A numeric field name specifying the number of incidents at each location.  For example, if you have points that represent cities, you can use a field representing the population of the city as the count field, and the resulting population density layer will calculate larger population densities near cities with larger populations. If not specified, each location will be assumed to represent a single count.
        -------------------------    ---------------------------------------------------------
        cell_size                    Optional float. This value is used to create a mesh of points where density values are calculated. The default is approximately 1/1000th of the smaller of the width and height of the analysis extent as defined in the context parameter. The smaller the value, the smoother the polygon boundaries will be. Conversely, with larger values, the polygon boundaries will be more coarse and jagged.
        -------------------------    ---------------------------------------------------------
        cell_size_units              Optional string. The units of the cell_size value.
                                    Choice list: ['Miles', 'Feet', 'Kilometers',  'Meters']
        -------------------------    ---------------------------------------------------------
        radius                       Optional float. A distance specifying how far to search to find point or line features when calculating density values.
        -------------------------    ---------------------------------------------------------
        radius_units                 Optional string. The units of the radius parameter. If no distance is provided, a default will be calculated that is based on the locations of the input features and the values in the count field (if a count field is provided).
                                    Choice list: ['Miles', 'Feet', 'Kilometers',  'Meters']
        -------------------------    ---------------------------------------------------------
        bounding_polygon_layer       Optional layer. A layer specifying the polygon(s) where you want densities to be calculated. For example, if you are interpolating densities of fish within a lake, you can use the boundary of the lake in this parameter and the output will only draw within the boundary of the lake. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        area_units                   Optional string. The units of the calculated density values.
                                    Choice list: ['areaUnits', 'SquareMiles']
        -------------------------    ---------------------------------------------------------
        classification_type          Optional string. Determines how density values will be classified into polygons.
                                    Choice list: ['EqualInterval', 'GeometricInterval', 'NaturalBreaks', 'EqualArea', 'StandardDeviation']
                                        * EqualInterval - Polygons are created such that the range of density values is equal for each area.
                                        * GeometricInterval - Polygons are based on class intervals that have a geometric series. This method ensures that each class range has approximately the same number of values within each class and that the change between intervals is consistent.
                                        * NaturalBreaks - Class intervals for polygons are based on natural groupings of the data. Class break values are identified that best group similar values and that maximize the differences between classes.
                                        * EqualArea - Polygons are created such that the size of each area is equal. For example, if the result has more high density values than low density values, more polygons will be created for high densities.
                                        * StandardDeviation - Polygons are created based upon the standard deviation of the predicted density values.
        -------------------------    ---------------------------------------------------------
        num_classes                  Optional int. This value is used to divide the range of predicted values into distinct classes. The range of values in each class is determined by the classification_type parameter.
        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For calculate_density, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional Boolean. Is true, the number of credits needed to run the operation will be returned as a float.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================


        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "CalculateDensity"

        params = {}
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        input_layer = self._feature_input(input_layer)
        if bounding_polygon_layer:
            bounding_polygon_layer = self._feature_input(bounding_polygon_layer)

        if estimate:
            params["inputLayer"] = input_layer
            if field is not None:
                params["field"] = field
            if cell_size is not None:
                params["cellSize"] = cell_size
            if cell_size_units is not None:
                params["cellSizeUnits"] = cell_size_units
            if radius is not None:
                params["radius"] = radius
            if radius_units is not None:
                params["radiusUnits"] = radius_units
            if bounding_polygon_layer is not None:
                params["boundingPolygonLayer"] = bounding_polygon_layer
            if area_units is not None:
                params["areaUnits"] = area_units
            if classification_type is not None:
                params["classificationType"] = classification_type
            if num_classes is not None:
                params["numClasses"] = num_classes
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.calculate_density(
            input_layer=input_layer,
            field=field,
            cell_size=cell_size,
            cell_size_units=cell_size_units,
            radius=radius,
            radius_units=radius_units,
            bounding_polygon_layer=bounding_polygon_layer,
            area_units=area_units,
            classification_type=classification_type,
            num_classes=num_classes,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "resultLayer" in ret:
            return ret["resultLayer"]
        elif isinstance(ret, FeatureCollection):
            return ret
        elif isinstance(ret, Item):
            return ret
        else:
            res = {}
            for fld in ret._fields:
                res[fld] = getattr(ret, fld)
            return res
        return ret

    # ----------------------------------------------------------------------
    def create_viewshed(
        self,
        input_layer,
        dem_resolution="Finest",
        maximum_distance=None,
        max_distance_units="Meters",
        observer_height=None,
        observer_height_units="Meters",
        target_height=None,
        target_height_units="Meters",
        generalize=True,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The create_viewshed method identifies visible areas based on the observer locations you provide.
        The results are areas where the observers can see the observed objects (and the observed objects can see the observers).

        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        input_layer                  Required point feature layer. The features to use as the observer locations. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        dem_resolution               Optional string. The approximate spatial resolution (cell size) of the source elevation data used for the calculation.

                                    The resolution values are an approximation of the spatial resolution of the digital elevation model.
                                    While many elevation sources are distributed in units of arc seconds, the keyword is an approximation
                                    of those resolutions in meters for easier understanding.

                                    Choice list: ['FINEST', '10m', '24m', '30m', '90m']

                                    The default is the finest resolution available.
        -------------------------    ---------------------------------------------------------
        maximum_distance             Optional float. This is a cutoff distance where the computation of visible areas stops. Beyond this distance, it is unknown whether the analysis points and the other objects can see each other.

                                    It is useful for modeling current weather conditions or a given time of day, such as dusk. Large values increase computation time.

                                    Unless specified, a default maximum distance will be computed based on the resolution and extent of the source DEM. The allowed maximum value is 50 kilometers.
                                    Use max_distance_units to set the units for maximum_distance.
        -------------------------    ---------------------------------------------------------
        max_distance_units           Optional string. The units for the maximum_distance parameter.

                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Miles', 'Yards']

                                    The default is 'Meters'.
        -------------------------    ---------------------------------------------------------
        observer_height              Optional float. This is the height above the ground of the observer locations.

                                    The default is 1.75 meters, which is approximately the average height of a person. If you are looking from an elevated location, such as an observation tower or a tall building, use that height instead.

                                    Use observer_height_units to set the units for observer_height.

        -------------------------    ---------------------------------------------------------
        observer_height_units        Optional string. The units for the observer_height parameter.

                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Miles', 'Yards']

                                    The default is 'Meters'.
        -------------------------    ---------------------------------------------------------
        target_height                Optional float. This is the height of structures or people on the ground used to
                                    establish visibility. The result viewshed are those areas where an input point can see these other objects.
                                    The converse is also true; the other objects can see an input point.

                                    * If your input points represent wind turbines and you want to determine where people standing on the
                                    ground can see the turbines, enter the average height of a person (approximately 6 feet).
                                    The result is those areas where a person standing on the ground can see the wind turbines.
                                    * If your input points represent fire lookout towers and you want to determine which lookout
                                    towers can see a smoke plume 20 feet high or higher, enter 20 feet for the height. The result
                                    is those areas where a fire lookout tower can see a smoke plume at least 20 feet high.
                                    * If your input points represent scenic overlooks along roads and trails and you want to determine
                                    where wind turbines 400 feet high or higher can be seen, enter 400 feet for the height. The result
                                    is those areas where a person standing at a scenic overlook can see a wind turbine at least 400 feet high.
                                    * If your input points represent scenic overlooks and you want to determine how much area on the ground
                                    people standing at the overlook can see, enter zero. The result is those areas that can be seen from the scenic overlook.

                                    Use target_height_units to set the units for target_height.

        -------------------------    ---------------------------------------------------------
        target_height_units          Optional string. The units for the target_height parameter.

                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Miles', 'Yards']

                                    The default is 'Meters'.
        -------------------------    ---------------------------------------------------------
        generalize                   Optional boolean. Determines whether or not the viewshed polygons are to be generalized.

                                    The viewshed calculation is based on a raster elevation model that creates a result with stair-stepped edges.
                                    To create a more pleasing appearance and improve performance, the default behavior is to generalize the polygons.
                                    The generalization process smooths the boundary of the visible areas and may remove some single-cell visible areas.

                                    The default value is True.
        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For create_viewshed, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================

        :returns result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """
        task = "CreateViewshed"
        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params = {}

            params["inputLayer"] = input_layer
            if dem_resolution is not None:
                params["demResolution"] = dem_resolution
            if maximum_distance is not None:
                params["maximumDistance"] = maximum_distance
            if max_distance_units is not None:
                params["maxDistanceUnits"] = max_distance_units
            if observer_height is not None:
                params["observerHeight"] = observer_height
            if observer_height_units is not None:
                params["observerHeightUnits"] = observer_height_units
            if target_height is not None:
                params["targetHeight"] = target_height
            if target_height_units is not None:
                params["targetHeightUnits"] = target_height_units
            if generalize is not None:
                params["generalize"] = generalize
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)

        gpjob = self._tbx.create_viewshed(
            input_layer=input_layer,
            dem_resolution=dem_resolution,
            maximum_distance=maximum_distance,
            max_distance_units=max_distance_units,
            observer_height=observer_height,
            observer_height_units=observer_height_units,
            target_height=target_height,
            target_height_units=target_height_units,
            generalize=generalize,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob

        ret = gpjob.result()
        if output_name is not None and "viewshedLayer" in ret:
            return ret["viewshedLayer"]
        return ret

    # ----------------------------------------------------------------------
    def create_watersheds(
        self,
        input_layer,
        search_distance=None,
        search_units="Meters",
        source_database="FINEST",
        generalize=True,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``create_watersheds`` method determines the watershed, or upstream contributing area, for each point
        in your analysis layer. For example, suppose you have point features representing locations
        of waterborne contamination, and you want to find the likely sources of the contamination.
        Since the source of the contamination must be somewhere within the watershed upstream of the
        point, you would use this tool to define the watersheds containing the sources of the contaminant.


        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        input_layer                  Required point feature layer. The point features used for calculating watersheds.
                                    These are referred to as pour points, because it is the location at which water pours out of the watershed.
                                    See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        search_distance              Optional float. The maximum distance to move the location of an input point.
                                    Use search_units to set the units for search_distance.

                                    If your input points are located away from a drainage line, the resulting watersheds
                                    are likely to be very small and not of much use in determining the upstream source of
                                    contamination. In most cases, you want your input points to snap to the nearest drainage
                                    line in order to find the watersheds that flows to a point located on the drainage line.
                                    To find the closest drainage line, specify a search distance. If you do not specify a
                                    search distance, the tool will compute and use a conservative search distance.

                                    To use the exact location of your input point, specify a search distance of zero.

                                    For analysis purposes, drainage lines have been precomputed by Esri using standard
                                    hydrologic models. If there is no drainage line within the search distance, the location
                                    containing the highest flow accumulation within the search distance is used.
        -------------------------    ---------------------------------------------------------
        search_units                 Optional string. The linear units specified for the search distance.

                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Miles', 'Yards']
        -------------------------    ---------------------------------------------------------
        source_database              Optional string. Keyword indicating the data source resolution that will be used in the analysis.

                                    Choice list: ['Finest', '30m', '90m']

                                    * Finest (Default): Finest resolution available at each location from all possible data sources.
                                    * 30m: The hydrologic source was built from 1 arc second - approximately 30 meter resolution, elevation data.
                                    * 90m: The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data.
        -------------------------    ---------------------------------------------------------
        generalize                   Optional boolean. Determines if the output watersheds will be smoothed into simpler shapes or conform
                                    to the cell edges of the original DEM.

                                    * True: The polygons will be smoothed into simpler shapes. This is the default.
                                    * False: The edge of the polygons will conform to the edges of the original DEM.

                                    The default value is True.
        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For create_watersheds, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================

        :returns result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """
        task = "CreateWatersheds"

        params = {}
        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            if search_distance is not None:
                params["searchDistance"] = search_distance
            if search_units is not None:
                params["searchUnits"] = search_units
            if source_database is not None:
                params["sourceDatabase"] = source_database
            if generalize is not None:
                params["generalize"] = generalize
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.create_watersheds(
            input_layer=input_layer,
            search_distance=search_distance,
            search_units=search_units,
            source_database=source_database,
            generalize=generalize,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        ret = gpjob.result()
        if output_name is not None and "snapPourPtsLayer" in ret:
            return ret["snapPourPtsLayer"]
        return ret

    # ----------------------------------------------------------------------
    def derive_new_locations(
        self,
        input_layers=[],
        expressions=[],
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``derive_new_locations`` method derives new features from the input layers that meet a query you specify. A query is
        made up of one or more expressions. There are two types of expressions: attribute and spatial. An example of an
        attribute expression is that a parcel must be vacant, which is an attribute of the Parcels layer
        (STATUS = 'VACANT'). An example of a spatial expression is that the parcel must also be within a certain
        distance of a river (Parcels within a distance of 0.75 Miles from Rivers).

        The ``derive_new_locations`` method is very similar to the ``find_existing_locations`` method, the main difference is that
        the result of ``derive_new_locations`` can contain partial features.

        * In both methods, the attribute expression  ``where`` and the spatial relationships within and contains return the same result.
        This is because these relationships return entire features.
        * When ``intersects`` or ``within_distance`` is used, ``derive_new_locations`` creates new features
        in the result. For example, when intersecting a parcel feature and a flood zone area that partially overlap each other,
        ``find_existing_locations`` will return the entire parcel whereas ``derive_new_locations`` will return just the portion of
        the parcel that is within the flood zone.

        =====================================    ======================================================================================================
        **Parameter**                             **Description**
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        input_layers                             Required list of feature layers. A list of layers that will be used in the expressions parameter.
                                                Each layer in the list can be:

                                                * a feature service layer with an optional filter to select specific features, or
                                                * a feature collection
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        expressions                              Required dict. There are two types of expressions, attribute and spatial.

                                                Example attribute expression:

                                                {
                                                    "operator": "and",
                                                    "layer": 0,
                                                    "where": "STATUS = 'VACANT'"
                                                }

                                                **Note**
                                                * operator can be either ``and`` or ``or``
                                                * layer is the index of the layer in the ``input_layers`` parameter.
                                                * The where clause must be surrounded by double quotes.
                                                * When dealing with text fields, values must be single-quoted ('VACANT').
                                                * Date fields support all queries except LIKE. Dates are strings in YYYY:MM:DD hh:mm:ss format. Here's an example using the date field ObsDate:
                                                "where": "ObsDate >= '1998-04-30 13:30:00' "

                                                +----------+------------------------------------------------------------------+
                                                | =        | Equal                                                            |
                                                +----------+------------------------------------------------------------------+
                                                | >        | Greater than                                                     |
                                                +----------+------------------------------------------------------------------+
                                                | <        | Less than                                                        |
                                                +----------+------------------------------------------------------------------+
                                                | >=       | Greater than or equal to                                         |
                                                +----------+------------------------------------------------------------------+
                                                | <=       | Less than or equal to                                            |
                                                +----------+------------------------------------------------------------------+
                                                | <>       | Not equal                                                        |
                                                +----------+------------------------------------------------------------------+
                                                | LIKE '%  | A percent symbol (%) signifies a wildcard, meaning that          |
                                                | <string>'| anything is acceptable in its place-one character, a             |
                                                |          | hundred characters, or no character. This expression             |
                                                |          | would select Mississippi and Missouri among USA                  |
                                                |          | state names: STATE_NAME LIKE 'Miss%'                             |
                                                +----------+------------------------------------------------------------------+
                                                | BETWEEN  | Selects a record if it has a value greater than or equal         |
                                                | <value1> | to <value1> and less than or equal to <value2>.                  |
                                                | AND      | For example, this expression selects all records with            |
                                                | <value2> | an HHSIZE value greater than or equal to 3 and less              |
                                                |          | than or equal to 10:                                             |
                                                |          |                                                                  |
                                                |          | HHSIZE BETWEEN 3 AND 10                                          |
                                                |          |                                                                  |
                                                |          | The above is equivalent to:                                      |
                                                |          |                                                                  |
                                                |          | HHSIZE >= 3 AND HHSIZE <= 10                                     |
                                                |          | This operator applies to numeric or date fields.                 |
                                                |          | Here is an example of a date query on the field ObsDate:         |
                                                |          |                                                                  |
                                                |          | ObsDate BETWEEN '1998-04-30 00:00:00' AND '1998-04-30 23:59:59'  |
                                                |          |                                                                  |
                                                |          | Time is optional.                                                |
                                                +----------+------------------------------------------------------------------+
                                                | NOT      | Selects a record if it has a value outside the range between     |
                                                | BETWEEN  | <value1> and less than or equal to <value2>.                     |
                                                | <value1> | For example, this expression selects all records whose           |
                                                | AND      | HHSIZE value is less than 5 and greater than 7.                  |
                                                | <value2> |                                                                  |
                                                |          | HHSIZE NOT BETWEEN 5 AND 7                                       |
                                                |          |                                                                  |
                                                |          | The above is equivalent to:                                      |
                                                |          |                                                                  |
                                                |          | HHSIZE < 5 OR HHSIZE > 7                                         |
                                                |          | This operator applies to numeric or date fields.                 |
                                                |          |                                                                  |
                                                |          | **Note**                                                         |
                                                |          |                                                                  |
                                                |          | You can use the contains relationship with points and lines.     |
                                                |          | For example, you have a layer of street centerlines (lines) and  |
                                                |          | a layer of manhole covers (points), and you want to find streets |
                                                |          | that contain a manhole cover. You could use contains to find     |
                                                |          | streets that contain manhole covers, but in order for a line to  |
                                                |          | contain a point, the point must be exactly on the line (that is, |
                                                |          | in GIS terms, they are snapped to each other). If there is any   |
                                                |          | doubt about this, use the withinDistance relationship with a     |
                                                |          | suitable distance value.                                         |
                                                +----------+------------------------------------------------------------------+

                                                Example spatial expression:
                                                {
                                                    "operator": "and",
                                                    "layer": 0,
                                                    "spatialRel": "withinDistance",
                                                    "selectingLayer": 1,
                                                    "distance": 10,
                                                    "units": "miles"
                                                }

                                                * operator can be either ``and`` or ``or``
                                                * layer is the index of the layer in ``the input_layers`` parameter. The result of the expression is features in this layer.
                                                * spatialRel is the spatial relationship. There are nine spatial relationships.
                                                * distance is the distance to use for the withinDistance and notWithinDistance spatial relationship.
                                                * units is the units for distance.

                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | spatialRel        | Description                                                                            |
                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | intersects        | |intersect|                                                                            |
                                                |                   |                                                                                        |
                                                |                   | A feature in layer passes the intersect test if it overlaps                            |
                                                | notIntersects     | any part of a feature in selectingLayer, including touches                             |
                                                |                   | (where features share a common point).                                                 |
                                                |                   |                                                                                        |
                                                |                   | * intersects-If a feature in layer intersects a feature in                             |
                                                |                   |   selectingLayer, the portion of the feature in layer that                             |
                                                |                   |   intersects the feature in selectingLayer is included in                              |
                                                |                   |   the output.                                                                          |
                                                |                   | * notintersects-If a feature in layer intersects a feature in                          |
                                                |                   |   selectingLayer, the portion of the feature in layer that                             |
                                                |                   |   intersects the feature in selectingLayer is excluded from                            |
                                                |                   |   the output.                                                                          |
                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | withinDistance    | |distance|                                                                             |
                                                |                   |                                                                                        |
                                                |                   | The within a distance relationship uses the straight-line                              |
                                                | notWithinDistance | distance between features in layer to those in selectingLayer.                         |
                                                |                   | withinDistance-The portion of the feature in layer that is                             |
                                                |                   | within the specified distance of a feature in selectingLayer                           |
                                                |                   | is included in the output.                                                             |
                                                |                   | notwithinDistance-The portion of the feature in layer that is                          |
                                                |                   | within the specified distance of a feature in selectingLayer is                        |
                                                |                   | excluded from output. You can think of this relationship as                            |
                                                |                   | "is farther away than".                                                                |
                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | contains          | |intersect|                                                                            |
                                                |                   |                                                                                        |
                                                |                   | A feature in layer passes this test if it completely                                   |
                                                | notContains       | surrounds a feature in selectingLayer. No portion of the                               |
                                                |                   | containing feature; however, the contained feature is allowed                          |
                                                |                   | to touch the containing feature (that is, share a common                               |
                                                |                   | point along its boundary).                                                             |
                                                |                   |                                                                                        |
                                                |                   | contains-If a feature in layer contains a feature in                                   |
                                                |                   | selectingLayer, the feature in layer is included in the output.                        |
                                                |                   | notcontains-If a feature in layer contains a feature in                                |
                                                |                   | selectingLayer, the feature in the first layer is excluded                             |
                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | within            | |within|                                                                               |
                                                |                   |                                                                                        |
                                                |                   | A feature in layer passes this test if it is completely                                |
                                                | notWithin         | surrounded by a feature in selectingLayer. The entire feature                          |
                                                |                   | layer must be within the containing feature; however, the two                          |
                                                |                   | features are allowed to touch (that is, share a common point                           |
                                                |                   | along its boundary).                                                                   |
                                                |                   |                                                                                        |
                                                |                   | * within-If a feature in layer is completely within a feature in                       |
                                                |                   |   selectingLayer, the feature in layer is included in the output.                      |
                                                |                   | * notwithin-If a feature in layer is completely within a feature                       |
                                                |                   |   in selectingLayer, the feature in layer is excluded from the                         |
                                                |                   |   output.                                                                              |
                                                |                   |                                                                                        |
                                                |                   | **Note:**                                                                              |
                                                |                   |                                                                                        |
                                                |                   | can use the within relationship for points and lines, just as                          |
                                                |                   | you can with the contains relationship. For example, your first                        |
                                                |                   | layer contains points representing manhole covers and you want                         |
                                                |                   | to find the manholes that are on street centerlines (as opposed                        |
                                                |                   | to parking lots or other non-street features). You could use                           |
                                                |                   | within to find manhole points within street centerlines, but                           |
                                                |                   | in order for a point to contain a line, the point must be exactly                      |
                                                |                   | on the line (that is, in GIS terms, they are snapped to each                           |
                                                |                   | other). If there is any doubt about this, use the withinDistance                       |
                                                |                   | relationship with a suitable distance value.                                           |
                                                +-------------------+----------------------------------------------------------------------------------------+
                                                | nearest           | |nearest|                                                                              |
                                                |                   |                                                                                        |
                                                |                   | feature in the first layer passes this test if it is nearest                           |
                                                |                   | to a feature in the second layer.                                                      |
                                                |                   |                                                                                        |
                                                |                   | * nearest-If a feature in the first layer is nearest to a                              |
                                                |                   |   feature in the second layer, the feature in the first layer                          |
                                                |                   |   is included in the output.                                                           |
                                                +-------------------+----------------------------------------------------------------------------------------+

        -------------------------------------    ------------------------------------------------------------------------------------------------------
        output_name                              Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        context                                  Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For derive_new_locations, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        gis                                      Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        estimate                                 Optional boolean. Is true, the number of credits needed to run the operation will be returned as a float.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        future                                   Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =====================================    ======================================================================================================

        :return: :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "DeriveNewLocations"

        params = {}

        input_layers_param = []
        for input_lyr in input_layers:
            input_layers_param.append(self._feature_input(input_lyr))
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayers"] = input_layers_param
            params["expressions"] = expressions
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.derive_new_locations(
            input_layers=input_layers_param,
            expressions=expressions,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def dissolve_boundaries(
        self,
        input_layer,
        dissolve_fields=[],
        summary_fields=[],
        output_name=None,
        context=None,
        estimate=False,
        multi_part_features=True,
        future=False,
    ):
        """
        The dissolve_boundaries method finds polygons that overlap or share a common boundary and merges them together to form a single polygon.

        You can control which boundaries are merged by specifying a field. For example, if you have a layer of counties, and each county
        has a State_Name attribute, you can dissolve boundaries using the State_Name attribute. Adjacent counties will be merged together
        if they have the same value for State_Name. The end result is a layer of state boundaries.

        ====================================     =====================================================================================
        **Parameter**                            **Description**
        ------------------------------------     -------------------------------------------------------------------------------------
        input_layer                              Required layer. The layer containing polygon features that will be dissolved. See :ref:`Feature Input<FeatureInput>`.
        ------------------------------------     -------------------------------------------------------------------------------------
        dissolve_fields                          Optional list of strings. One or more fields on the input_layer that control which polygons
                                                are merged. If you don't supply dissolve_fields , or you supply an empty list of fields, polygons
                                                that share a common border (that is, they are adjacent) or polygon areas that overlap will be dissolved into one polygon.

                                                If you do supply values for the dissolve_fields parameter, polygons that share a common border
                                                and contain the same value in one or more fields will be dissolved. For example, if you have a layer of counties,
                                                and each county has a State_Name attribute, you can dissolve boundaries using the State_Name attribute.
                                                Adjacent counties will be merged together if they have the same value for State_Name. The end result is a layer of
                                                state boundaries.If two or more fields are specified, the values in these fields must be the same for the boundary to be dissolved.
        ------------------------------------     -------------------------------------------------------------------------------------
        summary_fields                           | Optional list of strings.
                                                A list of field names and statistical summary types that you
                                                wish to calculate from the polygons that are dissolved together:

                                                | *["fieldName summary type", "fieldName2 summaryType"]*

                                                `fieldName` is the name of one of the numeric fields found in the
                                                input_layer.
                                                `summary type` is one of the following:

                                                * ``Sum`` - Adds the total value of all the points in each polygon
                                                * ``Mean`` - Calculates the average of all the points in each polygon.
                                                * ``Min`` - Finds the smallest value of all the points in each polygon.
                                                * ``Max`` - Finds the largest value of all the points in each polygon.
                                                * ``Stddev`` - Finds the standard deviation of all the points in each polygon.

                                                For example, if you are dissolving counties based on `State_Name`, and each
                                                county has a `Population` field, you can sum the `Population` for all the
                                                counties sharing the same `State_Name` attribute. The result would be a
                                                layer of state boundaries with total population.

                                                .. code-block:: python
                                                    :emphasize-lines: 5

                                                    # Usage Example

                                                    >>> dissolve_boundaries(input_layer="US_Counties",
                                                                            dissolve_fields="State_Name",
                                                                            summary_fields=["Population Sum"],
                                                                            output_name="US_States")
        ------------------------------------     -------------------------------------------------------------------------------------
        output_name                              Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ------------------------------------     -------------------------------------------------------------------------------------
        context                                  Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For calculate_density, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 11+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        ------------------------------------     -------------------------------------------------------------------------------------
        gis                                      Optional, the :class:`~arcgis.gis.GIS` on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     -------------------------------------------------------------------------------------
        estimate                                 Optional Boolean. If True, the number of credits to run the operation will be returned.
        ------------------------------------     -------------------------------------------------------------------------------------
        multi_part_features                      Optional boolean. Specifies whether multipart features (i.e. features which
                                                share a common attribute table but are not visibly connected) are allowed in
                                                the output feature class.

                                                | Choice list: [``True``, ``False``]

                                                * ``True``: Specifies multipart features are allowed.
                                                * ``False``: Specifies multipart features are not allowed. Instead of creating multipart features, individual features will be created for each part.

                                                The default value is ``True``.
        ------------------------------------     -------------------------------------------------------------------------------------
        future                                   Optional boolean. If True, the result will be a :class:`~arcgis.geoprocessing.GPJob` object and results will be returned asynchronously.
        ====================================     =====================================================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`Feature Collection <arcgis.features.FeatureCollection>`.

        """

        input_layer = self._feature_input(input_layer)
        if self._gis.version > [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            task = "DissolveBoundaries"

            params = {}

            params["inputLayer"] = input_layer
            if dissolve_fields is not None:
                params["dissolveFields"] = dissolve_fields
            if summary_fields is not None:
                params["summaryFields"] = summary_fields
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if multi_part_features:
                params["multiPartFeatures"] = multi_part_features

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.dissolve_boundaries(
            input_layer=input_layer,
            dissolve_fields=dissolve_fields,
            summary_fields=summary_fields,
            multi_part_features=multi_part_features,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def enrich_layer(
        self,
        input_layer,
        data_collections=[],
        analysis_variables=[],
        country=None,
        buffer_type=None,
        distance=None,
        units=None,
        output_name=None,
        context=None,
        estimate=False,
        return_boundaries=False,
        future=False,
    ):
        """
        The ``enrich_layer`` method enriches your data by getting facts about the people, places, and businesses that surround
        your data locations. For example: What kind of people live here? What do people like to do in this area? What are
        their habits and lifestyles? What kind of businesses are there in this area?

        The result will be a new layer of input features that includes all demographic and geographic information from given data collections.

        =====================================================================     ====================================================================
        **Parameter**                                                             **Description**
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        input_layer                                                               Required layer. The features to enrich with new data. See :ref:`Feature Input<FeatureInput>`.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        data_collections                                                          Optional list of strings. This optional parameter defines the collections of data you want to use to enrich your features.
                                                                                Its value is a list of strings. If you don't provide this parameter, you must provide the analysis_variables parameter.

                                                                                For more information about data collections and the values for this parameter, visit the `Esri Demographics site <http://doc.arcgis.com/en/esri-demographics/data/data-browser.htm>`_.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        analysis_variables                                                        Optional list of strings. The parameter defines the specific variables within a data collection you want to use to
                                                                                your features. Its value is a list of strings in the form of "dataCollection.VariableName". If you don't provide
                                                                                this parameter, you must provide the dataCollections parameter. You can provide both parameters.
                                                                                For example, if you want all variables in the KeyGlobalFacts data collection, specify it in the dataCollections
                                                                                parameter and use this parameter for specific variables in other collections.

                                                                                For more information about variables in data collections, visit the `Esri Demographics site <http://doc.arcgis.com/en/esri-demographics/data/data-browser.htm>`_. Each data collection
                                                                                has a PDF file describing variables and their names.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        country                                                                   Optional string. This optional parameter further defines what is returned from data collection.
                                                                                For example, your input features may be countries in Western Europe, and you want to enrich them with the
                                                                                KeyWEFacts data collection. However, you only want data for France, not every country in your input layer.
                                                                                The value is the two-character country code.

                                                                                For more information about data collections and the values for this parameter, visit the `Esri Demographics site <http://doc.arcgis.com/en/esri-demographics/data/data-browser.htm>`_.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        buffer_type (Required if input_layer contains point or line features)     Optional string. If your input features are points or lines, you must define an area around your features that you want to enrich. Features that are within (or equal to) the distances you enter will be enriched.

                                                                                Choice list: ['StraightLine', 'Driving Distance', 'Driving Time ', 'Rural Driving Distance', 'Rural Driving Time', 'Trucking Distance', 'Trucking Time', 'Walking Distance', 'Walking Time']
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        distance (Required if input_layer contains point or line features)        Optional float. A value that defines the search distance or time. The units of the distance value is supplied by the units parameter.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        units                                                                     Optional string. The linear unit to be used with the distance value(s) specified in the distance parameter.

                                                                                Choice list: ['Meters', 'Kilometers', 'Feet', 'Yards', 'Miles', 'Seconds', 'Minutes'. 'Hours']
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        output_name                                                               Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        context                                                                   Optional dict. Additional settings such as processing extent and output spatial reference.
                                                                                For enrich_layer, there are three settings.

                                                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 11+


                                                                                .. code-block:: python
                                                                                    # Example Usage

                                                                                                            "ymin": -9187921.892449,
                                                                                                            "xmax": 3174104.927313,
                                                                                                            "ymax": -9175500.875353,
                                                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                                                    "outSR": {"wkid": 3857},
                                                                                                    "overwrite": True}

        ---------------------------------------------------------------------     --------------------------------------------------------------------
        gis                                                                       Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        return_boundaries                                                         Optional boolean. Applies only for point and line input features. If True, a result layer of areas is returned.
                                                                                The returned areas are defined by the specified buffer_type. For example, if using a buffer_type of StraightLine with
                                                                                a distance of 5 miles, your result will contain areas with a 5 mile radius around the input features and requested
                                                                                analysis_variables variables. If False, the resulting layer will return the same features as the input layer with
                                                                                analysis_variables variables.

                                                                                The default value is False.
        ---------------------------------------------------------------------     --------------------------------------------------------------------
        future                                                                    Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =====================================================================     ====================================================================

        :returns :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "EnrichLayer"

        params = {}

        input_layer = self._feature_input(input_layer)
        if self._gis.version > [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            if data_collections is not None:
                params["dataCollections"] = data_collections
            if analysis_variables is not None:
                params["analysisVariables"] = analysis_variables
            if country is not None:
                params["country"] = country
            if buffer_type is not None:
                params["bufferType"] = buffer_type
            if distance is not None:
                params["distance"] = distance
            if units is not None:
                params["units"] = units
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if return_boundaries:
                params["returnBoundaries"] = return_boundaries

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.enrich_layer(
            input_layer=input_layer,
            data_collections=data_collections,
            analysis_variables=analysis_variables,
            country=country,
            buffer_type=buffer_type,
            distance=distance,
            units=units,
            return_boundaries=return_boundaries,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def extract_data(
        self,
        input_layers=[],
        extent=None,
        clip=False,
        data_format=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``extract_data`` method is used to extract data from one or more layers within a given extent.
        The extracted data format can be a file geodatabase, shapefiles, csv, or kml.
        File geodatabases and shapefiles are added to a zip file that can be downloaded.

        ===================================     =========================================================
        **Parameter**                            **Description**
        -----------------------------------     ---------------------------------------------------------
        input_layers                            Required list of strings. A list of input layers to be extracted. See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------
        extent                                  Optional layer. The extent is the area of interest used to extract the input features. If not specified, all features from each input layer are extracted. See :ref:`Feature Input<FeatureInput>`.
        -----------------------------------     ---------------------------------------------------------
        clip                                    Optional boolean. A Boolean value that specifies whether the features within the input layer are clipped
                                                within the extent. By default, features are not clipped and all features intersecting the extent are returned.

                                                The default is false.
        -----------------------------------     ---------------------------------------------------------
        data_format                             Optional string. A keyword defining the output data format for your extracted data.

                                                Choice list: ``['FileGeodatabase', 'ShapeFile', 'KML', 'CSV']``

                                                The default is 'CSV'.

                                                If *FileGeodatase* is specified *and* the input layer has `attachments: <https://enterprise.arcgis.com/en/portal/latest/use/manage-hosted-layers.htm#ESRI_SECTION2_EF4F7A72F7B74E47B5CBCC1F343445E2>`_

                                                    * if *clip=False*, the attachments will be extracted to the output file
                                                    * if *clip=True*, the attachments will not be extracted
        -----------------------------------     ---------------------------------------------------------
        output_name                             Optional string or dict.

                                                When ``output_name`` is a string, the output item in your My contents page
                                                will be named by the value. Other item properties will receive default values.

                                                .. code-block:: python

                                                    output_name = "my_extracted_item"

                                                To explicitly provide other item properties, use a dict with the following Syntax.

                                                .. code-block:: python

                                                    output_name = {"title": "<title>",
                                                                    "tag": "<tags>",
                                                                    "snippet": "<snippet>",
                                                                    "description": "<description>"}

                                                For more information on these and other item properties, see the Item resource page in the `ArcGIS REST API. <https://developers.arcgis.com/rest/users-groups-and-items/item.htm>`_
        -----------------------------------     ---------------------------------------------------------
        gis                                     Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -----------------------------------     ---------------------------------------------------------
        estimate                                Optional boolean. If True, the number of credits to run the operation will be returned.
        -----------------------------------     ---------------------------------------------------------
        future                                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ===================================     =========================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`Feature Collection <arcgis.features.FeatureCollection>`.

        """

        task = "ExtractData"

        params = {}

        input_layers_param = []
        for input_lyr in input_layers:
            input_layers_param.append(self._feature_input(input_lyr))

        params["inputLayers"] = input_layers_param
        if extent is not None:
            extent = self._feature_input(extent)
            params["extent"] = extent
        if clip is not None:
            params["clip"] = clip
        if data_format is not None:
            params["dataFormat"] = data_format
        if output_name is None:
            output_name = "Extracted_data_" + _id_generator()

        if data_format.upper() == "SHAPEFILE":
            if isinstance(output_name, dict):
                params["outputName"] = {"itemProperties": output_name}
            else:
                params["outputName"] = {
                    "itemProperties": {
                        "title": output_name,
                        "description": "File generated from running the Extract Data tool.",
                        "tags": "Analysis Results, Extract Data",
                        "snippet": "Analysis file item generated from running the Extract Data tool.",
                        "folderId": "",
                    }
                }
            output_name = params["outputName"]
        elif isinstance(output_name, dict) and "title" in output_name:
            params["outputName"] = {"itemProperties": output_name}
            output_name = params["outputName"]
        else:
            params["outputName"] = {
                "serviceProperties": {"name": output_name},
                "itemProperties": {
                    "title": output_name,
                    "description": "File generated from running the Extract Data solution.",
                    "tags": "Analysis Result, Extract Data",
                    "snippet": "Analysis File item generated from Extract Data",
                    "folderId": "",
                },
            }

            output_name = params["outputName"]
        if context is not None:
            params["context"] = context

        if estimate:
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.extract_data(
            input_layers=input_layers_param,
            extent=extent,
            clip=clip,
            data_format=data_format.upper(),
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def field_calculator(
        self,
        input_layer,
        expressions,
        output_name=None,
        context=None,
        estimate=False,
        future=True,
    ):
        """
        Calculates existing fields or creates and calculates new fields.

        Parameters
        ----------
        input_layer : Required layer (see Feature Input in documentation)

        expressions : Required string

        output_name : Optional string

        context: Optional dict

        estimate: Optional Boolean. Returns the number of credit for the operation.

        Returns
        -------
        result_layer : layer (FeatureCollection)
        """

        task = "FieldCalculator"

        params = {}
        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            params["expressions"] = expressions
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.field_calculator(
            input_layer=input_layer,
            expressions=expressions,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_centroids(
        self,
        input_layer,
        point_location=False,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``find_centroids`` method that finds and generates points from the representative center (centroid) of
        each input multipoint, line, or area feature. Finding the centroid of a feature is very common for many analytical
        workflows where the resulting points can then be used in other analytic workflows.

        For example, polygon features that contain demographic data can be converted to centroids that can be used in network analysis.

        ================    ===============================================================
        **Parameter**        **Description**
        ----------------    ---------------------------------------------------------------
        input_layer         Required feature layer. The multipoint, line, or polygon features that will be used to generate centroid point features. See :ref:`Feature Input<FeatureInput>`.
        ----------------    ---------------------------------------------------------------
        point_location      Optional boolean. A Boolean value that determines the output location of the points.

                            + True - Output points will be the nearest point to the actual centroid, but located inside or contained by the bounds of the input feature.
                            + False - Output point locations will be determined by the calculated geometric center of each input feature. This is the default.
        ----------------    ---------------------------------------------------------------
        output_name         Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                            feature layer will cause the new layer to be appended to the Feature Service.
                            If overwrite is True in context, new layer will overwrite existing layer.
                            If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ----------------    ---------------------------------------------------------------
        context             Optional dict. Additional settings such as processing extent and output spatial reference.
                            For find_centroids, there are three settings.

                            - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                            - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                            - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                .. code-block:: python

                                    # Example Usage
                                    context = {"extent": {"xmin": 3164569.408035,
                                                        "ymin": -9187921.892449,
                                                        "xmax": 3174104.927313,
                                                        "ymax": -9175500.875353,
                                                        "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                "outSR": {"wkid": 3857},
                                                "overwrite": True}
        ----------------    ---------------------------------------------------------------
        estimate            Optional boolean. If True, the number of credits to run the operation will be returned.
        ----------------    ---------------------------------------------------------------
        future              Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ================    ===============================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else :class:`~arcgis.features.FeatureCollection`.

        """
        task = "FindCentroids"

        params = {}
        input_layer = self._feature_input(input_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            params["pointLocation"] = point_location
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        if context is None:
            gpjob = self._tbx.find_centroids(
                input_layer=input_layer,
                point_location=point_location,
                output_name=output_name,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.find_centroids(
                input_layer=input_layer,
                point_location=point_location,
                output_name=output_name,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_existing_locations(
        self,
        input_layers=None,
        expressions=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``find_existing_locations`` method selects features in the input layer that meet a query you specify.
        A query is made up of one or more expressions. There are two types of expressions: attribute and spatial.
        An example of an attribute expression is that a parcel must be vacant, which is an attribute of the Parcels layer (where STATUS = 'VACANT').
        An example of a spatial expression is that the parcel must also be within a certain distance of a river (Parcels within a distance of 0.75 Miles from Rivers).

        =====================================    ======================================================================================================
        **Parameter**                             **Description**
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        input_layers                             Required list of feature layers. A list of layers that will be used in the expressions parameter.
                                                    Each layer in the list can be:

                                                    * a feature service layer with an optional filter to select specific features, or
                                                    * a feature collection

                                                    See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        expressions                              Required dict. There are two types of expressions, attribute and spatial.

                                                    Example attribute expression:

                                                    {
                                                    "operator": "and",
                                                    "layer": 0,
                                                    "where": "STATUS = 'VACANT'"
                                                    }

                                                    **Note**

                                                    * operator can be either ``and`` or ``or``
                                                    * layer is the index of the layer in the ``input_layers`` parameter.
                                                    * The where clause must be surrounded by double quotes.
                                                    * When dealing with text fields, values must be single-quoted ('VACANT').
                                                    * Date fields support all queries except LIKE. Dates are strings in YYYY:MM:DD hh:mm:ss format.
                                                    Here's an example using the date field ObsDate:

                                                    "where": "ObsDate >= '1998-04-30 13:30:00' "

                                                    +----------+------------------------------------------------------------------+
                                                    | =        | Equal                                                            |
                                                    +----------+------------------------------------------------------------------+
                                                    | >        | Greater than                                                     |
                                                    +----------+------------------------------------------------------------------+
                                                    | <        | Less than                                                        |
                                                    +----------+------------------------------------------------------------------+
                                                    | >=       | Greater than or equal to                                         |
                                                    +----------+------------------------------------------------------------------+
                                                    | <=       | Less than or equal to                                            |
                                                    +----------+------------------------------------------------------------------+
                                                    | <>       | Not equal                                                        |
                                                    +----------+------------------------------------------------------------------+
                                                    | LIKE '%  | A percent symbol (%) signifies a wildcard, meaning that          |
                                                    | <string>'| anything is acceptable in its place-one character, a             |
                                                    |          | hundred characters, or no character. This expression             |
                                                    |          | would select Mississippi and Missouri among USA                  |
                                                    |          | state names: STATE_NAME LIKE 'Miss%'                             |
                                                    +----------+------------------------------------------------------------------+
                                                    | BETWEEN  | Selects a record if it has a value greater than or equal         |
                                                    | <value1> | to <value1> and less than or equal to <value2>.                  |
                                                    | AND      | For example, this expression selects all records with            |
                                                    | <value2> | an HHSIZE value greater than or equal to 3 and less              |
                                                    |          | than or equal to 10:                                             |
                                                    |          |                                                                  |
                                                    |          | HHSIZE BETWEEN 3 AND 10                                          |
                                                    |          |                                                                  |
                                                    |          | The above is equivalent to:                                      |
                                                    |          |                                                                  |
                                                    |          | HHSIZE >= 3 AND HHSIZE <= 10                                     |
                                                    |          | This operator applies to numeric or date fields.                 |
                                                    |          | Here is an example of a date query on the field ObsDate:         |
                                                    |          |                                                                  |
                                                    |          | ObsDate BETWEEN '1998-04-30 00:00:00' AND '1998-04-30 23:59:59'  |
                                                    |          |                                                                  |
                                                    |          | Time is optional.                                                |
                                                    +----------+------------------------------------------------------------------+
                                                    | NOT      | Selects a record if it has a value outside the range between     |
                                                    | BETWEEN  | <value1> and less than or equal to <value2>.                     |
                                                    | <value1> | For example, this expression selects all records whose           |
                                                    | AND      | HHSIZE value is less than 5 and greater than 7.                  |
                                                    | <value2> |                                                                  |
                                                    |          | HHSIZE NOT BETWEEN 5 AND 7                                       |
                                                    |          |                                                                  |
                                                    |          | The above is equivalent to:                                      |
                                                    |          |                                                                  |
                                                    |          | HHSIZE < 5 OR HHSIZE > 7                                         |
                                                    |          | This operator applies to numeric or date fields.                 |
                                                    |          |                                                                  |
                                                    |          | **Note**                                                         |
                                                    |          |                                                                  |
                                                    |          | You can use the contains relationship with points and lines.     |
                                                    |          | For example, you have a layer of street centerlines (lines) and  |
                                                    |          | a layer of manhole covers (points), and you want to find streets |
                                                    |          | that contain a manhole cover. You could use contains to find     |
                                                    |          | streets that contain manhole covers, but in order for a line to  |
                                                    |          | contain a point, the point must be exactly on the line (that is, |
                                                    |          | in GIS terms, they are snapped to each other). If there is any   |
                                                    |          | doubt about this, use the withinDistance relationship with a     |
                                                    |          | suitable distance value.                                         |
                                                    +----------+------------------------------------------------------------------+

                                                    Example spatial expression:
                                                    {
                                                    "operator": "and",
                                                    "layer": 0,
                                                    "spatialRel": "withinDistance",
                                                    "selectingLayer": 1,
                                                    "distance": 10,
                                                    "units": "miles"
                                                    }

                                                    * operator can be either ``and`` or ``or``
                                                    * layer is the index of the layer in ``the input_layers`` parameter. The result of the expression is features in this layer.
                                                    * spatialRel is the spatial relationship. There are nine spatial relationships.
                                                    * distance is the distance to use for the withinDistance and notWithinDistance spatial relationship.
                                                    * units is the units for distance.

                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | spatialRel        | Description                                                                            |
                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | intersects        | |intersect|                                                                            |
                                                    |                   |                                                                                        |
                                                    |                   | A feature in layer passes the intersect test if it overlaps                            |
                                                    | notIntersects     | any part of a feature in selectingLayer, including touches                             |
                                                    |                   | (where features share a common point).                                                 |
                                                    |                   |                                                                                        |
                                                    |                   | * intersects-If a feature in layer intersects a feature in                             |
                                                    |                   |   selectingLayer, the portion of the feature in layer that                             |
                                                    |                   |   intersects the feature in selectingLayer is included in                              |
                                                    |                   |   the output.                                                                          |
                                                    |                   | * notintersects-If a feature in layer intersects a feature in                          |
                                                    |                   |   selectingLayer, the portion of the feature in layer that                             |
                                                    |                   |   intersects the feature in selectingLayer is excluded from                            |
                                                    |                   |   the output.                                                                          |
                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | withinDistance    | |distance|                                                                             |
                                                    |                   |                                                                                        |
                                                    |                   | The within a distance relationship uses the straight-line                              |
                                                    | notWithinDistance | distance between features in layer to those in selectingLayer.                         |
                                                    |                   | withinDistance-The portion of the feature in layer that is                             |
                                                    |                   | within the specified distance of a feature in selectingLayer                           |
                                                    |                   | is included in the output.                                                             |
                                                    |                   | notwithinDistance-The portion of the feature in layer that is                          |
                                                    |                   | within the specified distance of a feature in selectingLayer is                        |
                                                    |                   | excluded from output. You can think of this relationship as                            |
                                                    |                   | "is farther away than".                                                                |
                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | contains          | |intersect|                                                                            |
                                                    |                   |                                                                                        |
                                                    |                   | A feature in layer passes this test if it completely                                   |
                                                    | notContains       | surrounds a feature in selectingLayer. No portion of the                               |
                                                    |                   | containing feature; however, the contained feature is allowed                          |
                                                    |                   | to touch the containing feature (that is, share a common                               |
                                                    |                   | point along its boundary).                                                             |
                                                    |                   |                                                                                        |
                                                    |                   | contains-If a feature in layer contains a feature in                                   |
                                                    |                   | selectingLayer, the feature in layer is included in the output.                        |
                                                    |                   | notcontains-If a feature in layer contains a feature in                                |
                                                    |                   | selectingLayer, the feature in the first layer is excluded                             |
                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | within            | |within|                                                                               |
                                                    |                   |                                                                                        |
                                                    |                   | A feature in layer passes this test if it is completely                                |
                                                    | notWithin         | surrounded by a feature in selectingLayer. The entire feature                          |
                                                    |                   | layer must be within the containing feature; however, the two                          |
                                                    |                   | features are allowed to touch (that is, share a common point                           |
                                                    |                   | along its boundary).                                                                   |
                                                    |                   |                                                                                        |
                                                    |                   | * within-If a feature in layer is completely within a feature in                       |
                                                    |                   |   selectingLayer, the feature in layer is included in the output.                      |
                                                    |                   | * notwithin-If a feature in layer is completely within a feature                       |
                                                    |                   |   in selectingLayer, the feature in layer is excluded from the                         |
                                                    |                   |   output.                                                                              |
                                                    |                   |                                                                                        |
                                                    |                   | **Note:**                                                                              |
                                                    |                   |                                                                                        |
                                                    |                   | can use the within relationship for points and lines, just as                          |
                                                    |                   | you can with the contains relationship. For example, your first                        |
                                                    |                   | layer contains points representing manhole covers and you want                         |
                                                    |                   | to find the manholes that are on street centerlines (as opposed                        |
                                                    |                   | to parking lots or other non-street features). You could use                           |
                                                    |                   | within to find manhole points within street centerlines, but                           |
                                                    |                   | in order for a point to contain a line, the point must be exactly                      |
                                                    |                   | on the line (that is, in GIS terms, they are snapped to each                           |
                                                    |                   | other). If there is any doubt about this, use the withinDistance                       |
                                                    |                   | relationship with a suitable distance value.                                           |
                                                    +-------------------+----------------------------------------------------------------------------------------+
                                                    | nearest           | |nearest|                                                                              |
                                                    |                   |                                                                                        |
                                                    |                   | feature in the first layer passes this test if it is nearest                           |
                                                    |                   | to a feature in the second layer.                                                      |
                                                    |                   |                                                                                        |
                                                    |                   | * nearest-If a feature in the first layer is nearest to a                              |
                                                    |                   |   feature in the second layer, the feature in the first layer                          |
                                                    |                   |   is included in the output.                                                           |
                                                    +-------------------+----------------------------------------------------------------------------------------+

                                                    * ``distance`` is the distance to use for the withinDistance and notWithinDistance spatial relationship.
                                                    * ``units`` is the units for distance.

                                                    Choice list: ['Meters', 'Kilometers', 'Feet', 'Yards', 'Miles']

                                                    An expression may be a list, which denotes a group. The first operator in the group indicates how the group expression
                                                    is added to the previous expression. Grouping expressions is only necessary when you need to create two or more distinct
                                                    sets of features from the same layer. One way to think of grouping is that without grouping, you would have to execute
                                                    ``find_existing_locations`` multiple times and merge the results.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        output_name                              Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                    feature layer will cause the new layer to be appended to the Feature Service.
                                                    If overwrite is True in context, new layer will overwrite existing layer.
                                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        context                                  Optional dict. Additional settings such as processing extent and output spatial reference.
                                                    For find_existing_locations, there are three settings.

                                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        gis                                      Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------    ------------------------------------------------------------------------------------------------------
        estimate                                 Optional boolean. Is true, the number of credits needed to run the operation will be returned as a float.
        =====================================    ======================================================================================================

        :return: :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """
        if input_layers is None:
            input_layers = []
        if expressions is None:
            expressions = []

        input_layers_param = []
        for input_lyr in input_layers:
            input_layers_param.append(self._feature_input(input_lyr))
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            from arcgis.features._credits import _estimate_credits

            task = "FindExistingLocations"
            params = {}
            params["inputLayers"] = input_layers_param
            params["expressions"] = expressions
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_existing_locations(
            input_layers=input_layers_param,
            expressions=expressions,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_hot_spots(
        self,
        analysis_layer,
        analysis_field=None,
        divided_by_field=None,
        bounding_polygon_layer=None,
        aggregation_polygon_layer=None,
        output_name=None,
        context=None,
        estimate=False,
        shape_type=None,
        cell_size=None,
        cell_size_unit=None,
        distance_band=None,
        distance_band_units=None,
        future=False,
    ):
        """
        The ``find_hot_spots`` method analyzes point data (such as crime incidents, traffic accidents, or trees) or field values associated with
        points or area features (such as the number of people in each census tract or the total sales for retail stores). It finds statistically
        significant spatial clusters of high values (hot spots) and low values (cold spots). For point data when no field is specified, hot spots
        are locations with lots of points and cold spots are locations with very few points.

        The result map layer shows hot spots in red and cold spots in blue. The darkest red features indicate the strongest clustering of high values
        or point densities; you can be 99 percent confident that the clustering associated with these features could not be the result of random chance.
        Similarly, the darkest blue features are associated with the strongest spatial clustering of low values or the lowest point densities.
        Features that are beige are not part of a statistically significant cluster; the spatial pattern associated with these features could very likely
        be the result of random processes and random chance.

        ===================================================================     =========================================================
        **Parameter**                                                            **Description**
        -------------------------------------------------------------------     ---------------------------------------------------------
        analysis_layer (Required if the analysis_layer contains polygons)       Required layer. The point or polygon feature layer for which hot spots will be calculated. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------------------------------------     ---------------------------------------------------------
        analysis_field                                                          Optional string. The numeric field that will be analyzed. The field you select might represent:

                                                                                    + counts (such as the number of traffic accidents)
                                                                                    + rates (such as the number of crimes per square mile)
                                                                                    + averages (such as the mean math test score)
                                                                                    + indices (such as a customer satisfaction score)

                                                                                If an ``analysis_field`` is not supplied, hot spot results are based on point densities only.
        -------------------------------------------------------------------     ---------------------------------------------------------
        divided_by_field                                                        Optional string. The numeric field in the ``analysis_layer`` that will be used to normalize your data.
                                                                                For example, if your points represent crimes, dividing by total population would result in an analysis of crimes per capita rather than raw crime counts.

                                                                                You can use esriPopulation to geoenrich each area feature with the most recent population values, which will then be
                                                                                used as the attribute to divide by. This option will use credits.
        -------------------------------------------------------------------     ---------------------------------------------------------
        bounding_polygon_layer                                                  Optional layer. When the analysis layer is points and no ``analysis_field`` is specified, you can provide polygons features that define where incidents could have occurred.
                                                                                For example, if you are analyzing boating accidents in a harbor, the outline of the harbor might provide a good boundary for where accidents could occur.
                                                                                When no bounding areas are provided, only locations with at least one point will be included in the analysis. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------------------------------------     ---------------------------------------------------------
        aggregation_polygon_layer                                               Optional layer. When the ``analysis_layer`` contains points and no ``analysis_field`` is specified,
                                                                                you can provide polygon features into which the points will be aggregated and analyzed, such as administrative units.
                                                                                The number of points that fall within each polygon are counted, and the point count in each polygon is analyzed. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------------------------------------     ---------------------------------------------------------
        output_name                                                             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------------------------------------     ---------------------------------------------------------
        context                                                                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                                                                For find_hot_spots, there are three settings.

                                                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online and ArcGIS Enterprise 11.1+.

                                                                                    .. code-block:: python

                                                                                        # Example Usage
                                                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                                                            "ymin": -9187921.892449,
                                                                                                            "xmax": 3174104.927313,
                                                                                                            "ymax": -9175500.875353,
                                                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                                                    "outSR": {"wkid": 3857},
                                                                                                    "overwrite": True}
        -------------------------------------------------------------------     ---------------------------------------------------------
        gis                                                                     Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------     ---------------------------------------------------------
        estimate                                                                Optional Boolean. Is true, the number of credits needed to run the operation will be returned as a float.
        -------------------------------------------------------------------     ---------------------------------------------------------
        shape_type                                                              Optional string. The shape of the polygon mesh the input features will be aggregated into.

                                                                                * ``Fishnet``-The input features will be aggregated into a grid of square (fishnet) cells.
                                                                                * ``Hexagon``-The input features will be aggregated into a grid of hexagonal cells.
        -------------------------------------------------------------------     ---------------------------------------------------------
        cell_size                                                               Optional float. The size of the grid cells used to aggregate your features.
                                                                                When aggregating into a hexagon grid, this distance is used as the height to construct the hexagon polygons.
        -------------------------------------------------------------------     ---------------------------------------------------------
        cell_size_unit                                                          Optional string. The units of the ``cell_size`` value. You must provide a value if ``cell_size`` has been set.

                                                                                Choice list: ['Meters', 'Miles', 'Feet', 'Kilometers']
        -------------------------------------------------------------------     ---------------------------------------------------------
        distance_band                                                           Optional float. The spatial extent of the analysis neighborhood. This value determines which features are analyzed together
                                                                                in order to assess local clustering.
        -------------------------------------------------------------------     ---------------------------------------------------------
        distance_band_unit                                                      Optional string. The units of the ``distance_band`` value. You must provide a value if ``distance_band`` has been set.
        -------------------------------------------------------------------     ---------------------------------------------------------
        future                                                                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ===================================================================     =========================================================

        :return: :class:`~arcgis.features.FeatureLayer` if output_name is specified, else a dictionary with a :class:`~arcgis.features.FeatureCollection` and processing messages.

        """
        analysis_layer = self._feature_input(analysis_layer)
        if bounding_polygon_layer:
            bounding_polygon_layer = self._feature_input(bounding_polygon_layer)
        if aggregation_polygon_layer:
            aggregation_polygon_layer = self._feature_input(aggregation_polygon_layer)
        if self._gis.version > [10, 3] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        task = "FindHotSpots"

        if estimate:
            params = {}

            params["analysisLayer"] = analysis_layer
            if analysis_field is not None:
                params["analysisField"] = analysis_field
            if divided_by_field is not None:
                params["dividedByField"] = divided_by_field
            if bounding_polygon_layer is not None:
                params["boundingPolygonLayer"] = bounding_polygon_layer
            if aggregation_polygon_layer is not None:
                params["aggregationPolygonLayer"] = aggregation_polygon_layer
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if shape_type is not None:
                params["shapeType"] = shape_type
            if cell_size is not None:
                params["cellSize"] = cell_size
            if cell_size_unit is not None:
                params["cellSizeUnit"] = cell_size_unit
            if distance_band is not None:
                params["distanceBand"] = distance_band
            if distance_band_units is not None:
                params["distanceBandUnits"] = distance_band_units
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_hot_spots(
            analysis_layer=analysis_layer,
            analysis_field=analysis_field,
            divided_by_field=divided_by_field,
            bounding_polygon_layer=bounding_polygon_layer,
            aggregation_polygon_layer=aggregation_polygon_layer,
            shape_type=shape_type,
            cell_size=cell_size,
            cell_size_units=cell_size_unit,
            distance_band=distance_band,
            distance_band_units=distance_band_units,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        result = gpjob.result()
        if "hot_spots_result_layer" in result and output_name:
            return result["hot_spots_result_layer"]
        return result

    # ----------------------------------------------------------------------
    def find_nearest(
        self,
        analysis_layer,
        near_layer,
        measurement_type="StraightLine",
        max_count=100,
        search_cutoff=2147483647,
        search_cutoff_units=None,
        time_of_day=None,
        time_zone_for_time_of_day="GeoLocal",
        output_name=None,
        context=None,
        estimate=False,
        include_route_layers=None,
        point_barrier_layer=None,
        line_barrier_layer=None,
        polygon_barrier_layer=None,
        future=False,
    ):
        """
        The ``find_nearest`` method measures the straight-line distance, driving distance, or driving time from
        features in the analysis layer to features in the near layer, and copies the nearest features in the near
        layer to a new layer. Connecting lines showing the measured path are returned as well. ``find_nearest`` also
        reports the measurement and relative rank of each nearest feature. There are options to limit the number
        of nearest features to find or the search range in which to find them. The results from this method can help
        you answer the following kinds of questions:

        * What is the nearest park from here?
        * Which hospital can I reach in the shortest drive time? And how long would the trip take on a Tuesday at 5:30 p.m. during rush hour?
        * What are the road distances between major European cities?
        * Which of these patients reside within two miles of these chemical plants?

        Find Nearest returns a layer containing the nearest features and a line layer that links the start locations to their nearest locations.
        The connecting line layer contains information about the start and nearest locations and the distances between.

        =========================    =========================================================
        **Parameter**                **Description**
        -------------------------    ---------------------------------------------------------
        analysis_layer               Required layer. The features from which the nearest locations are found. This layer can have point, line, or polygon features. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        near_layer                   Required layer. The nearest features are chosen from this layer. This layer can have point, line, or polygon features. See :ref:`Feature Input<FeatureInput>`.
        -------------------------    ---------------------------------------------------------
        measurement_type             Required string. Specify the mode of transportation for the analysis.

                                    Choice list: ['StraightLine', 'Driving Distance', 'Driving Time ', 'Rural Driving Distance', 'Rural Driving Time', 'Trucking Distance', 'Trucking Time', 'Walking Distance', 'Walking Time']

                                    The default is 'StraightLine'.
        -------------------------    ---------------------------------------------------------
        max_count                    Optional string. The maximum number of nearest locations to find for each feature in ``analysis_layer``. The default is the maximum cutoff allowed by the service, which is 100.

                                    Note that setting a maxCount for this parameter doesn't guarantee that many features will be found. The ``search_cutoff`` and other constraints may also reduce the number of features found.
        -------------------------    ---------------------------------------------------------
        search_cutoff                Optional float. The maximum range to search for nearest locations from each feature in the ``analysis_layer``.
                                    The units for this parameter is always minutes when ``measurement_type`` is set to a time based travel mode;
                                    otherwise the units are set in the ``search_cutoff_units`` parameter.

                                    The default is to search without bounds.
        -------------------------    ---------------------------------------------------------
        search_cutoff_units          The units of the ``search_cutoff`` parameter. This parameter is ignored when ``measurement_type`` is set to a time based travel
                                    mode because the units for ``search_cutoff`` are always minutes in those cases. If ``measurement_type`` is set to StraightLine or another distance-based travel mode, and a value for ``search_cutoff`` is specified, set the cutoff units using this parameter.

                                    Choice list: ['Kilometers', 'Meters', 'Miles', 'Feet', '']

                                    The default value is null, which causes the service to choose either miles or kilometers according to the units property of the user making the request.
        -------------------------    ---------------------------------------------------------
        time_of_day                  Optional datetime.datetime. Specify whether travel times should consider traffic conditions. To use traffic in the analysis, set ``measurement_type`` to a travel mode object whose impedance_attribute_name property is set to travel_time and assign a value to ``time_of_day``. (A travel mode with other impedance_attribute_name values don't support traffic.) The ``time_of_day`` value represents the time at which travel begins, or departs, from the origin points. The time is specified as datetime.datetime.

                                    The service supports two kinds of traffic: typical and live. Typical traffic references travel speeds that are made up of historical averages for each five-minute interval spanning a week. Live traffic retrieves speeds from a traffic feed that processes phone probe records, sensors, and other data sources to record actual travel speeds and predict speeds for the near future.

                                    The `data coverage <http://www.arcgis.com/home/webmap/viewer.html?webmap=b7a893e8e1e04311bd925ea25cb8d7c7>`_ page shows the countries Esri currently provides traffic data for.

                                    Typical Traffic:

                                    To ensure the task uses typical traffic in locations where it is available, choose a time and day of the week, and then convert the day of the week to one of the following dates from 1990:

                                    * Monday - 1/1/1990
                                    * Tuesday - 1/2/1990
                                    * Wednesday - 1/3/1990
                                    * Thursday - 1/4/1990
                                    * Friday - 1/5/1990
                                    * Saturday - 1/6/1990
                                    * Sunday - 1/7/1990
                                    Set the time and date as datetime.datetime.

                                    For example, to solve for 1:03 p.m. on Thursdays, set the time and date to 1:03 p.m., 4 January 1990; and convert to datetime eg. datetime.datetime(1990, 1, 4, 1, 3).

                                    Live Traffic:

                                    To use live traffic when and where it is available, choose a time and date and convert to datetime.

                                    Esri saves live traffic data for 4 hours and references predictive data extending 4 hours into the future. If the time and date you specify for this parameter is outside the 24-hour time window, or the travel time in the analysis continues past the predictive data window, the task falls back to typical traffic speeds.

                                    Examples:
                                    from datetime import datetime

                                    * "time_of_day": datetime(1990, 1, 4, 1, 3) # 13:03, 4 January 1990. Typical traffic on Thursdays at 1:03 p.m.
                                    * "time_of_day": datetime(1990, 1, 7, 17, 0) # 17:00, 7 January 1990. Typical traffic on Sundays at 5:00 p.m.
                                    * "time_of_day": datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. If the current time is between 8:00 p.m., 21 Oct. 2014 and 8:00 p.m., 22 Oct. 2014, live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
                                    * "time_of_day": datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015. If the current time is between 10:20 p.m., 17 Mar. 2015 and 10:20 p.m., 18 Mar. 2015, live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.

        -------------------------    ---------------------------------------------------------
        time_zone_for_time_of_day    Optional string. Specify the time zone or zones of the ``time_of_day`` parameter.

                                    Choice list: ['GeoLocal', 'UTC']

                                    ``GeoLocal``-refers to the time zone in which the origins_layer points are located.

                                    ``UTC``-refers to Coordinated Universal Time.
        -------------------------    ---------------------------------------------------------
        include_route_layers         Optional boolean. When ``include_route_layers`` is set to True, each route from the result is also saved as a route layer item.
                                    A route layer includes all the information for a particular route such as the stops assigned to the route as well
                                    as the travel directions. Creating route layers is useful if you want to share individual routes with other members in your organization.
                                    The route layers use the output feature service name provided in the ``output_name`` parameter as a prefix and the route name generated as part
                                    of the analysis is added to create a unique name for each route layer.

                                    **Caution:**

                                    Route layers cannot be created when the output is a feature collection. The task will raise an error if ``output_name`` is not
                                    specified (which indicates feature collection output) and ``include_route_layers`` is True.

                                    The maximum number of route layers that can be created is 1,000. If the result contains more than 1,000 routes
                                    and ``include_route_layers`` is True, the task will only create the output feature service.
        -------------------------    ---------------------------------------------------------
        point_barrier_layer          Optional layer. Specify one or more point features that act as temporary restrictions (in other words, barriers) when traveling on the underlying streets.

                                    A point barrier can model a fallen tree, an accident, a downed electrical line, or anything that completely blocks traffic at a specific
                                    position along the street. Travel is permitted on the street but not through the barrier.
        -------------------------    ---------------------------------------------------------
        line_barrier_layer           Optional layer. Specify one or more line features that prohibit travel anywhere the lines intersect the streets.

                                    A line barrier prohibits travel anywhere the barrier intersects the streets. For example, a parade or protest that blocks traffic across
                                    several street segments can be modeled with a line barrier.
        -------------------------    ---------------------------------------------------------
        polygon_barrier_layer        Optional layer. Specify one or more polygon features that completely restrict travel on the streets intersected by the polygons.

                                    One use of this type of barrier is to model floods covering areas of the street network and making road travel there impossible.
        -------------------------    ---------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent
                                    and output spatial reference.
                                    For find_nearest, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------    ---------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------    ---------------------------------------------------------
        estimate                     Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        -------------------------    ---------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================    =========================================================

        :return: A dictionary with the following keys:

        "nearest_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        "connecting_lines_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        """

        task = "FindNearest"

        params = {}
        analysis_layer = self._feature_input(analysis_layer)
        near_layer = self._feature_input(near_layer)
        params["analysisLayer"] = analysis_layer  # self._feature_input(analysis_layer)
        params["nearLayer"] = near_layer  # self._feature_input(near_layer)
        if point_barrier_layer:
            point_barrier_layer = self._feature_input(point_barrier_layer)
        if line_barrier_layer:
            line_barrier_layer = self._feature_input(line_barrier_layer)
        if polygon_barrier_layer:
            polygon_barrier_layer = self._feature_input(polygon_barrier_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["measurementType"] = measurement_type
            if max_count is not None:
                params["maxCount"] = max_count
            if search_cutoff is not None:
                params["searchCutoff"] = search_cutoff
            if search_cutoff_units is not None:
                params["searchCutoffUnits"] = search_cutoff_units
            if time_of_day is not None:
                params["timeOfDay"] = time_of_day
            if time_zone_for_time_of_day is not None:
                params["timeZoneForTimeOfDay"] = time_zone_for_time_of_day
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if include_route_layers is not None:
                params["includeRouteLayers"] = include_route_layers
            if point_barrier_layer is not None:
                params["pointBarrierLayer"] = point_barrier_layer
            if line_barrier_layer is not None:
                params["lineBarrierLayer"] = line_barrier_layer
            if polygon_barrier_layer is not None:
                params["polygonBarrierLayer"] = polygon_barrier_layer
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_nearest(
            analysis_layer=analysis_layer,
            near_layer=near_layer,
            measurement_type=measurement_type,
            max_count=max_count,
            search_cutoff=search_cutoff,
            search_cutoff_units=search_cutoff_units,
            time_of_day=time_of_day,
            time_zone_for_time_of_day=time_zone_for_time_of_day,
            output_name=output_name,
            context=context,
            include_route_layers=include_route_layers,
            point_barrier_layer=point_barrier_layer,
            line_barrier_layer=line_barrier_layer,
            polygon_barrier_layer=polygon_barrier_layer,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_outliers(
        self,
        analysis_layer,
        analysis_field=None,
        divided_by_field=None,
        bounding_polygon_layer=None,
        aggregation_polygon_layer=None,
        permutations=None,
        shape_type=None,
        cell_size=None,
        cell_units=None,
        distance_band=None,
        distance_band_units=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``find_outliers`` method analyzes point data (such as crime incidents, traffic accidents, or trees) or field values associated with points
        or area features (such as the number of people in each census tract or the total sales for retail stores). It finds statistically significant
        spatial clusters of high values and low values and statistically significant high or low spatial outliers within those clusters.

        The result map layer shows high outliers in red and low outliers in dark blue. Clusters of high values appear pink and clusters of low values
        appear light blue. Features that are beige are not a statistically significant outlier and not part of a statistically significant cluster; the
        spatial pattern associated with these features could very likely be the result of random processes and random chance.

        ==================================================================  ===============================================================
        **Parameter**                                                        **Description**
        ------------------------------------------------------------------  ---------------------------------------------------------------
        analysis_layer                                                      Required feature layer. The point or polygon feature layer for which outliers will be calculated. See :ref:`Feature Input<FeatureInput>`.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        analysis_field (Required if the analysis_layer contains polygons)   Optional string. The numeric field that will be analyzed. The field you select might represent:

                                                                            * counts (such as the number of traffic accidents)
                                                                            * rates (such as the number of crimes per square mile)
                                                                            * averages (such as the mean math test score)
                                                                            * indices (such as a customer satisfaction score)

                                                                            If an ``analysis_field`` is not supplied, hot spot results are based on point densities only.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        divided_by_field                                                    Optional string. The numeric field in the ``analysis_layer`` that will be used to normalize your data.
                                                                            For example, if your points represent crimes, dividing by total population would result in an analysis
                                                                            of crimes per capita rather than raw crime counts.

                                                                            You can use esriPopulation to geoenrich each area feature with the most recent population values,
                                                                            which will then be used as the attribute to divide by. This option will use credits.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        bounding_polygon_layer                                              Optional layer. When the analysis layer is points and no ``analysis_field`` is specified, you can provide polygon features that define where incidents could have occurred.
                                                                            For example, if you are analyzing boating accidents in a harbor, the outline of the harbor might provide a good boundary for where accidents could occur.
                                                                            When no bounding areas are provided, only locations with at least one point will be included in the analysis. See :ref:`Feature Input<FeatureInput>`.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        aggregation_polygon_layer                                           Optional layer. When the ``analysis_layer`` contains points and no ``analysis_field`` is specified, you can provide polygon features into which the
                                                                            points will be aggregated and analyzed, such as administrative units. The number of points that fall within each polygon
                                                                            are counted, and the point count in each polygon is analyzed. See :ref:`Feature Input<FeatureInput>`.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        permutations                                                        Optional string. Permutations are used to determine how likely it would be to find the actual spatial distribution of the values you are analyzing.
                                                                            Choosing the number of permutations is a balance between precision and increased processing time. A lower number of permutations
                                                                            can be used when first exploring a problem, but it is best practice to increase the permutations to the highest number feasible for final results.

                                                                            Choice list: ['Speed', 'Balance', 'Presision']

                                                                            * ``Speed`` - implements 199 permutations and results in p-values with a precision of 0.01.
                                                                            * ``Balance`` - implements 499 permutations and results in p-values with a precision of 0.002.
                                                                            * ``Precision`` - implements 999 permutations and results in p-values with a precision of 0.001.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        shape_type                                                          Optional string. The shape of the polygon mesh the input features will be aggregated into.

                                                                            * ``Fishnet`` - The input features will be aggregated into a grid of square (fishnet) cells.
                                                                            * ``Hexagon`` -  The input features will be aggregated into a grid of hexagonal cells.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        cell_size                                                           Optional float. The size of the grid cells used to aggregate your features. When aggregating into a hexagon grid, this distance is used as the height to construct the hexagon polygons.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        cell_units                                                          Optional string. The units of the ``cell_size`` value. You must provide a value if ``cell_size`` has been set.

                                                                            Choice list: ['Meters', 'Miles', 'Feet', 'Kilometers']
        ------------------------------------------------------------------  ---------------------------------------------------------------
        distance_band                                                       Optional float. The spatial extent of the analysis neighborhood. This value determines which features are analyzed together
                                                                            in order to assess local clustering.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        band_units                                                          Optional string. The units of the ``distance_band`` value. You must provide a value if ``distance_band`` has been set.

                                                                            Choice list: ['Meters', 'Miles', 'Feet', 'Kilometers']
        ------------------------------------------------------------------  ---------------------------------------------------------------
        output_name                                                         Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                                            feature layer will cause the new layer to be appended to the Feature Service.
                                                                            If overwrite is True in context, new layer will overwrite existing layer.
                                                                            If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        context                                                             Optional dict. Additional settings such as processing extent and output spatial reference.
                                                                            For find_outliers, there are three settings.

                                                                            - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                                            - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                                            - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online and ArcGIS Enterprise 11.1+.

                                                                                .. code-block:: python

                                                                                    # Example Usage
                                                                                    context = {"extent": {"xmin": 3164569.408035,
                                                                                                        "ymin": -9187921.892449,
                                                                                                        "xmax": 3174104.927313,
                                                                                                        "ymax": -9175500.875353,
                                                                                                        "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                                                "outSR": {"wkid": 3857},
                                                                                                "overwrite": True}
        ------------------------------------------------------------------  ---------------------------------------------------------------
        estimate                                                            Optional boolean. Returns the number of credit for the operation.
        ------------------------------------------------------------------  ---------------------------------------------------------------
        future                                                              Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ==================================================================  ===============================================================

        :return:
            :class:`~arcgis.features.FeatureLayer` if output_name is set. else results in a dict with the following keys:

            "find_outliers_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "process_info" : list of messages

        """

        task = "FindOutliers"

        params = {}
        analysis_layer = self._feature_input(analysis_layer)
        if bounding_polygon_layer:
            bounding_polygon_layer = self._feature_input(bounding_polygon_layer)
        if aggregation_polygon_layer:
            aggregation_polygon_layer = self._feature_input(aggregation_polygon_layer)
        if self._gis.version > [10, 3] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["analysisLayer"] = analysis_layer
            if analysis_field is not None:
                params["analysisField"] = analysis_field
            if divided_by_field is not None:
                params["dividedByField"] = divided_by_field
            if bounding_polygon_layer is not None:
                params["boundingPolygonLayer"] = bounding_polygon_layer
            if aggregation_polygon_layer is not None:
                params["aggregationPolygonLayer"] = aggregation_polygon_layer
            if output_name is not None:
                params["outputName"] = output_name
            if permutations is not None:
                params["permutations"] = permutations
            if shape_type:
                params["shapeType"] = shape_type
            if cell_size:
                params["cellSize"] = cell_size
            if cell_units:
                params["cellSizeUnits"] = cell_units
            if distance_band:
                params["distanceBand"] = distance_band
            if distance_band_units:
                params["distanceBandUnits"] = distance_band_units
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_outliers(
            analysis_layer=analysis_layer,
            analysis_field=analysis_field,
            divided_by_field=divided_by_field,
            bounding_polygon_layer=bounding_polygon_layer,
            aggregation_polygon_layer=aggregation_polygon_layer,
            permutations=permutations,
            shape_type=shape_type,
            cell_size=cell_size,
            cell_size_units=cell_units,
            distance_band=distance_band,
            distance_band_units=distance_band_units,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_point_clusters(
        self,
        analysis_layer,
        min_features_cluster,
        search_distance=None,
        search_distance_unit=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``find_point_clusters`` method finds clusters of point features within surrounding
        noise based on their spatial distribution.

        This method uses unsupervised machine learning clustering algorithms to detect
        patterns of point features based purely on spatial location and, optionally,
        the distance to a specified number of features.

        The result map shows each cluster identified as well as features considered
        noise. Multiple clusters will be assigned each color. Colors will be assigned
        and repeated so that each cluster is visually distinct from its neighboring clusters.

        This method utilizes two related algorithms. By default the HDBSCAN algorithm is
        used to find clusters. If a ``search_distance`` is specified, the DBSCAN algorithm
        is used. DBSCAN is only appropriate if there is a very clear search distance to use
        for your analysis and will return clusters with similar densities. When
        no ``search_distance`` is specified, HDBSCAN will use a range of distances to separate clusters
        of varying densities from sparser noise resulting in more data-driven clusters.

        ====================    =========================================================
        **Parameter**            **Description**
        --------------------    ---------------------------------------------------------
        analysis_layer          Required layer. The point feature layer for which
                                density-based clustering will be calculated.
                                See :ref:`Feature Input<FeatureInput>`.
        --------------------    ---------------------------------------------------------
        min_features_cluster    Required integer. The minimum number of features to be
                                considered a cluster. Any cluster with fewer features
                                than the number provided will be considered noise.
        --------------------    ---------------------------------------------------------
        search_distance         Optional float. The maximum distance to consider. The
                                Minimum Features per Cluster specified must be found
                                within this distance for cluster membership. Individual
                                clusters will be separated by at least this distance. If
                                a feature is located further than this distance from the
                                next closest feature in the cluster, it will not be
                                included in the cluster.
        --------------------    ---------------------------------------------------------
        search_distance_unit    Optional string. The linear unit to be used with the distance
                                value specified for ``search_distance``. You must provide a
                                value if ``search_distance`` has been set.

                                Choice list: ['Feet', 'Miles', 'Meters', 'Kilometers']

                                The default is 'Miles'.
        --------------------    ---------------------------------------------------------
        output_name             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                feature layer will cause the new layer to be appended to the Feature Service.
                                If overwrite is True in context, new layer will overwrite existing layer.
                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        --------------------    ---------------------------------------------------------
        context                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                For find_point_clusters, there are three settings.

                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 11+

                                    .. code-block:: python

                                        # Example Usage
                                        context = {"extent": {"xmin": 3164569.408035,
                                                            "ymin": -9187921.892449,
                                                            "xmax": 3174104.927313,
                                                            "ymax": -9175500.875353,
                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                    "outSR": {"wkid": 3857},
                                                    "overwrite": True}
        --------------------    ---------------------------------------------------------
        gis                     Optional, the GIS on which this tool runs. If not
                                specified, the active GIS is used.
        --------------------    ---------------------------------------------------------
        estimate                Optional Boolean. If True, the number of credits to run the operation will be returned.
        --------------------    ---------------------------------------------------------
        future                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ====================    =========================================================

        :return: :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "FindPointClusters"

        params = {}
        analysis_layer = self._feature_input(analysis_layer)
        if self._gis.version > [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["analysisLayer"] = analysis_layer
            params["minFeaturesCluster"] = min_features_cluster
            if search_distance is not None:
                params["searchDistance"] = search_distance
            if search_distance_unit is not None:
                params["searchDistanceUnit "] = search_distance_unit
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_point_clusters(
            analysis_layer=analysis_layer,
            min_features_cluster=min_features_cluster,
            search_distance=search_distance,
            search_distance_unit=search_distance_unit,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def find_similar_locations(
        self,
        input_layer,
        search_layer,
        analysis_fields=[],
        input_query=None,
        number_of_results=0,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``find_similar_locations`` method measures the similarity of candidate locations to one or more reference locations.

        Based on criteria you specify, Find ``find_similar_locations`` can answer questions such as the following:

        Which of your stores are most similar to your top performers with regard to customer profiles?
        Based on characteristics of villages hardest hit by the disease, which other villages are high risk?
        To answer questions such as these, you provide the reference locations (the ``input_layer`` parameter), the candidate
        locations (the ``search_layer`` parameter), and the fields representing the criteria you want to match. For example,
        the ``input_layer`` might be a layer containing your top performing stores or the villages hardest hit by the disease.
        The ``search_layer`` contains your candidate locations to search. This might be all of your stores or all other villages.
        Finally, you supply a list of fields to use for measuring similarity. ``find_similar_locations`` will rank all of the
        candidate locations by how closely they match your reference locations across all of the fields you have selected.

        =======================     ===========================================================================================
        **Parameter**                **Description**
        -----------------------     -------------------------------------------------------------------------------------------
        input_layer                 Required feature layer. The ``input_layer`` contains one or more
                                    reference locations against which features in the ``search_layer``
                                    will be evaluated for similarity. For example, the ``input_layer``
                                    might contain your top performing stores or the villages hardest
                                    hit by a disease.
                                    It is not uncommon that the ``input_layer`` and ``search_layer`` are the
                                    same feature service. For example, the feature service contains
                                    locations of all stores, one of which is your top performing store.
                                    If you want to rank the remaining stores from most to least similar
                                    to your top performing store, you can provide a filter for both the
                                    inputLayer and the ``search_layer``. The filter on the ``input_layer`` would
                                    select the top performing store while the filter on the ``search_layer``
                                    would select all stores except for the top performing store. You can
                                    also use the optional ``input_query`` parameter to specify reference locations.

                                    If there is more than one reference location, similarity will be based
                                    on averages for the fields you specify in the ``analysis_fields`` parameter.
                                    So, for example, if there are two reference locations and you are
                                    interested in matching population, the task will look for candidate
                                    locations in the ``search_layer`` with populations that are most like the
                                    average population for both reference locations. If the values for the
                                    reference locations are 100 and 102, for example, the method will look
                                    for candidate locations with populations near 101. Consequently, you
                                    will want to use fields for the reference locations fields that have
                                    similar values. If, for example, the population values for one reference
                                    location is 100 and the other is 100,000, the tool will look for candidate
                                    locations with population values near the average of those two values: 50,050.
                                    Notice that this averaged value is nothing like the population for either
                                    of the reference locations. See :ref:`Feature Input<FeatureInput>`.
        -----------------------     -------------------------------------------------------------------------------------------
        search_layer                Required feature layer. The layer containing candidate locations that
                                    will be evaluated against the reference locations. See :ref:`Feature Input<FeatureInput>`.
        -----------------------     -------------------------------------------------------------------------------------------
        analysis_fields             Required list of strings. A list of fields whose values are used to determine similarity.
                                    They must be numeric fields and the fields must exist on both the ``input_layer`` and
                                    the ``search_layer``. The method will find features in the ``search_layer`` that have field
                                    values closest to those of the features in your ``input_layer``.
        -----------------------     -------------------------------------------------------------------------------------------
        input_query                 Optional string. In the situation where the ``input_layer`` and the ``search_layer`` are the same feature service,
                                    this parameter allows you to input a query on the ``input_layer`` to specify which features are the reference locations.
                                    The reference locations specified by this query will not be analyzed as candidates.
                                    The syntax of ``input_query`` is the same as a filter.
        -----------------------     -------------------------------------------------------------------------------------------
        number_of_results           Optional int. The number of ranked candidate locations output to the ``similar_result_layer``.
                                    If ``number_of_results`` is not specified, or set to zero, all candidate locations will be ranked and output.
        -----------------------     -------------------------------------------------------------------------------------------
        output_name                 Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -----------------------     -------------------------------------------------------------------------------------------
        context                     Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For find_similar_locations, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online and ArcGIS Enterprise 11.1+.

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -----------------------     -------------------------------------------------------------------------------------------
        estimate                    Optional boolean. If True, the number of credits to run the operation will be returned.
        -----------------------     -------------------------------------------------------------------------------------------
        future                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =======================     ===========================================================================================

        :return: :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else Python dictionary with the following keys:

            "similar_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "process_info" : list of message

        """

        task = "FindSimilarLocations"
        input_layer = self._feature_input(input_layer)
        search_layer = self._feature_input(search_layer)
        if self._gis.version > [10, 3] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params = {}
            params["inputLayer"] = input_layer
            params["searchLayer"] = search_layer
            params["analysisFields"] = analysis_fields
            if input_query is not None:
                params["inputQuery"] = input_query
            if number_of_results is not None:
                params["numberOfResults"] = number_of_results
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.find_similar_locations(
            input_layer=input_layer,
            search_layer=search_layer,
            analysis_fields=analysis_fields,
            input_query=input_query,
            number_of_results=number_of_results,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def generate_tesselation(
        self,
        extent_layer,
        bin_size,
        bin_size_unit="SquareKilimeters",
        bin_type="SQUARE",
        intersect_study_area=False,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
        bin_resolution=None,
    ):
        """
        Generates a tessellated grid of regular polygons.

        ====================================     ====================================================================
        **Parameter**                            **Description**
        ------------------------------------     --------------------------------------------------------------------
        extent_layer                             Optional layer. A layer defining the processing extent.
        ------------------------------------     --------------------------------------------------------------------
        bin_size                                 Optional Float. The size of each individual shape that makes up the tessellation.
        ------------------------------------     --------------------------------------------------------------------
        bin_size_unit                            Optional String. Size unit of each individual shape. The allowed
                                                values are: 'SquareKilometers', 'Hectares', 'SquareMeters',
                                                'SquareMiles', 'Acres', 'SquareYards', 'SquareFeet', 'SquareInches',
                                                'Miles', 'Yards', 'Feet', 'Kilometers', 'Meters', and
                                                'NauticalMiles'.
        ------------------------------------     --------------------------------------------------------------------
        bin_type                                 Optional String. The type of shape to tessellate.
                                                Allowed values are: 'SQUARE', 'HEXAGON', 'TRIANGLE', 'DIAMOND', or
                                                'TRANSVERSEHEXAGON'.
        ------------------------------------     --------------------------------------------------------------------
        intersect_study_area                     Optional Boolean. A boolean defines whether to keep only tessellations intersect with the study area.

        ------------------------------------     --------------------------------------------------------------------
        output_name                              Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For calculate_density, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     --------------------------------------------------------------------
        estimate                                 Optional Boolean. If True, the number of credits to run the operation will be returned.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ------------------------------------     --------------------------------------------------------------------
        bin_resolution                           Optional Integer. This becomes required when H3_HEXAGON is used.
                                                 The H3 resolution of the hexagons. Resolution ranges from 0 to 15.
                                                 With each increasing resolution size, the area of the polygons will
                                                 be one seventh the size.
        ====================================     ====================================================================

        .. note::
                The tool requires either an 'extent' given in the `context` or an `extent_layer`.

        :return:
            :class:`~arcgis.features.FeatureLayer` if out_put name specified or
            a :class:`~arcgis.features.FeatureLayerCollection`

        """
        if extent_layer:
            extent_layer = self._feature_input(extent_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            task = "GenerateTessellations"
            params = {}
            if bin_type:
                params["binType"] = bin_type
            if bin_size:
                params["binSize"] = bin_size
            if bin_size_unit:
                params["binSizeUnit"] = bin_size_unit
            if bin_resolution:
                params["binResolution"] = bin_resolution
            if extent_layer:
                params["extentLayer"] = extent_layer
            params["intersectStudyArea"] = intersect_study_area
            if output_name:
                params["outputName"] = output_name
            if context:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        params = {}
        for key in list(self._tbx.generate_tessellations.__annotations__.keys()):
            if "return" != key:
                params[key] = eval(key)

        params["future"] = True

        gpjob = self._tbx.generate_tessellations(**params)
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def interpolate_points(
        self,
        input_layer,
        field,
        interpolate_option="5",
        output_prediction_error=False,
        classification_type="GeometricInterval",
        num_classes=10,
        class_breaks=[],
        bounding_polygon_layer=None,
        predict_at_point_layer=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``interpolate_points`` method allows you to predict values at new locations based on measurements
        from a collection of points. The method takes point data with values at each point and returns
        areas classified by predicted values. For example:

        * An air quality management district has sensors that measure pollution levels.
        ``interpolate_points`` can be used to predict pollution levels at locations that don't have sensors,
        such as locations with at-risk populations, schools, or hospitals, for example.
        * Predict heavy metal concentrations in crops based on samples taken from individual plants.
        * Predict soil nutrient levels (nitrogen, phosphorus, potassium, and so on) and other
        indicators (such as electrical conductivity) in order to study their relationships to crop yield
        and prescribe precise amounts of fertilizer for each location in the field.
        * Meteorological applications include prediction of temperatures, rainfall,
        and associated variables (such as acid rain).

        ``interpolate_points`` uses the `Empirical Bayesian
        Kriging <http://desktop.arcgis.com/en/arcmap/latest/tools/geostatistical-analyst-toolbox/empirical-bayesian-kriging.htm>`_
        geoprocessing tool to perform the interpolation. The parameters that are supplied to
        the Empirical Bayesian Kriging tool are controlled by the ``interpolate_option`` request parameter.

        If a value of 1 is provided for ``interpolate_option``, empirical Bayesian kriging will
        use the following parameters:

        * transformation_type - NONE
        * semivariogram_model_type - POWER
        * max_local_points - 50
        * overlap_factor - 1
        * number_semivariograms - 30
        * nbrMin - 8
        * nbrMax - 8

        If a value of 5 is provided for ``interpolate_option``, empirical Bayesian kriging
        will use the following parameters:

        * transformation_type - NONE
        * semivariogram_model_type - POWER
        * max_local_points 75
        * overlap_factor - 1.5
        * number_semivariograms - 100
        * nbrMin - 10
        * nbrMax - 10

        If a value of 9 is provided for ``interpolate_option``, empirical Bayesian kriging
        will use the following parameters:

        * transformation_type - EMPIRICAL
        * semivariogram_model_type - K_BESSEL
        * max_local_points - 200
        * overlap_factor - 3
        * number_semivariograms - 200
        * nbrMin - 15
        * nbrMax - 15

        ===========================  ===========================================================================================
        **Parameter**                 **Description**
        ---------------------------  -------------------------------------------------------------------------------------------
        input_layer                  Required layer. The point layer whose features will be interpolated. See :ref:`Feature Input<FeatureInput>`.
        ---------------------------  -------------------------------------------------------------------------------------------
        field                        Required string. Name of the numeric field containing the values you wish to interpolate.
        ---------------------------  -------------------------------------------------------------------------------------------
        interpolate_option           Optional integer. Integer value declaring your preference for speed versus accuracy, from 1 (fastest) to 9 (most accurate).
                                    More accurate predictions take longer to calculate.

                                    Choice list: [1, 5, 9].

                                    The default is 5.
        ---------------------------  -------------------------------------------------------------------------------------------
        output_prediction_error      Optional boolean. If True, a polygon layer of standard errors for the interpolation
                                    predictions will be returned in the ``prediction_error`` output parameter.

                                    Standard errors are useful because they provide information about the reliability of the predicted values.
                                    A simple rule of thumb is that the true value will fall within two standard errors of the predicted
                                    value 95 percent of the time. For example, suppose a new location gets a predicted value of 50 with a
                                    standard error of 5. This means that this task's best guess is that the true value at that location is 50,
                                    but it reasonably could be as low as 40 or as high as 60. To calculate this range of reasonable values,
                                    multiply the standard error by 2, add this value to the predicted value to get the upper end of the range,
                                    and subtract it from the predicted value to get the lower end of the range.
        ---------------------------  -------------------------------------------------------------------------------------------
        classification_type          Optional string. Determines how predicted values will be classified into areas.

                                    * ``EqualArea`` - Polygons are created such that the number of data values in each area is equal.
                                    For example, if the data has more large values than small values, more areas will be created for large values.
                                    * ``EqualInterval`` - Polygons are created such that the range of predicted values is equal for each area.
                                    * ``GeometricInterval`` - Polygons are based on class intervals that have a geometrical series.
                                    This method ensures that each class range has approximately the same number of values within
                                    each class and that the change between intervals is consistent.
                                    * ``Manual`` - You to define your own range of values for areas. These values will be entered in
                                    the ``class_breaks`` parameter below.

                                    Choice list: ['EqualArea', 'EqualInterval', 'GeometricInterval', 'Manual']

                                    The default is 'GeometricInterval'.
        ---------------------------  -------------------------------------------------------------------------------------------
        num_classes                  Optional integer. This value is used to divide the range of interpolated values into distinct classes.
                                    The range of values in each class is determined by the ``classification_type`` parameter.
                                    Each class defines the boundaries of the result polygons.

                                    The default is 10. The maximum value is 32.
        ---------------------------  -------------------------------------------------------------------------------------------
        class_breaks                 Optional list of floats. If ``classification_type`` is Manual, supply desired class break values separated by spaces.
                                    These values define the upper limit of each class, so the number of classes will equal the number of entered values.
                                    Areas will not be created for any locations with predicted values above the largest entered break value.
                                    You must enter at least two values and no more than 32.
        ---------------------------  -------------------------------------------------------------------------------------------
        bounding_polygon_layer       Optional layer. A layer specifying the polygon(s) where you want values to be interpolated.  For example,
                                    if you are interpolating densities of fish within a lake, you can use the boundary of the lake in this
                                    parameter and the output will only contain polygons within the boundary of the lake. See :ref:`Feature Input<FeatureInput>`.
        ---------------------------  -------------------------------------------------------------------------------------------
        predict_at_point_layer       Optional layer. An optional layer specifying point locations to calculate prediction values.
                                    This allows you to make predictions at specific locations of interest. For example, if the ``input_layer`` represents
                                    measurements of pollution levels, you can use this parameter to predict the pollution levels of locations with large
                                    at-risk populations, such as schools or hospitals. You can then use this information to give recommendations to health
                                    officials in those locations.

                                    If supplied, the output ``predicted_point_layer`` will contain predictions at the specified locations. See :ref:`Feature Input<FeatureInput>`.
        ---------------------------  -------------------------------------------------------------------------------------------
        output_name                  Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------    ---------------------------------------------------------
        context                      Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For interpolate_points, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        ---------------------------  -------------------------------------------------------------------------------------------
        gis                          Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ---------------------------  -------------------------------------------------------------------------------------------
        estimate                     Optional boolean. If True, the number of credits to run the operation will be returned.
        ---------------------------  -------------------------------------------------------------------------------------------
        future                       Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ===========================  ===========================================================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else Python dictionary with the following keys:

            "result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "prediction_error" : layer (:class:`~arcgis.features.FeatureCollection`)

            "predicted_point_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        """

        task = "InterpolatePoints"

        params = {}
        input_layer = self._feature_input(input_layer)
        if bounding_polygon_layer:
            bounding_polygon_layer = self._feature_input(bounding_polygon_layer)
        if predict_at_point_layer:
            predict_at_point_layer = self._feature_input(predict_at_point_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            params["field"] = field
            if interpolate_option is not None:
                params["interpolateOption"] = interpolate_option
            if output_prediction_error is not None:
                params["outputPredictionError"] = output_prediction_error
            if classification_type is not None:
                params["classificationType"] = classification_type
            if num_classes is not None:
                params["numClasses"] = num_classes
            if class_breaks is not None:
                params["classBreaks"] = class_breaks
            if bounding_polygon_layer is not None:
                params["boundingPolygonLayer"] = bounding_polygon_layer
            if predict_at_point_layer is not None:
                params["predictAtPointLayer"] = predict_at_point_layer
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.interpolate_points(
            input_layer=input_layer,
            field=field,
            interpolate_option=interpolate_option,
            output_prediction_error=output_prediction_error,
            classification_type=classification_type,
            num_classes=num_classes,
            class_breaks=class_breaks,
            bounding_polygon_layer=bounding_polygon_layer,
            predict_at_point_layer=predict_at_point_layer,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def join_features(
        self,
        target_layer,
        join_layer,
        spatial_relationship=None,
        spatial_relationship_distance=None,
        spatial_relationship_distance_units=None,
        attribute_relationship=None,
        join_operation="JoinOneToOne",
        summary_fields=None,
        output_name=None,
        context=None,
        estimate=False,
        records_to_match=None,
        future=False,
        join_type=None,
    ):
        """
        The ``join_features`` method works with two layers and joins the attributes
        from one feature to another based on spatial and attribute relationships.

        ============================================================================================    =================================================================================================================================
        **Parameter**                                                                                   **Description**
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        target_layer                                                                                    Required layer. The point, line, polygon or table layer that will have attributes from
                                                                                                        the ``join_layer`` appended to its table. See :ref:`Feature Input<FeatureInput>`.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        join_layer                                                                                      Required layer. The point, line, polygon or table layer that will be joined to the ``target_layer``. See :ref:`Feature Input<FeatureInput>`.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        spatial_relationship                                                                            Required string. Defines the spatial relationship used to spatially join features.

                                                                                                        Choice list: ['identicalto', 'intersects', 'completelycontains', 'completelywithin', 'withindistance']
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        spatial_relationship_distance                                                                   Optional float. A float value used for the search distance to determine if the target features are near or within a
        (Required if ``spatial_relationship`` is withindistance)                                        specified distance of the join features.
                                                                                                        This is only applied if Within a distance of is the selected ``spatial_relationship``.
                                                                                                        You can only enter a single distance value. The units of the distance values are supplied by the
                                                                                                        ``spatial_relationship_distance_units`` parameter.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        spatial_relationship_distance_units                                                             Optional string. The linear unit to be used with the distance value specified in ``spatial_relationship_distance``.
        (Required if ``spatial_relationship`` is withindistance)                                        Choice list: ['Miles', 'Yards', 'Feet', 'NauticalMiles', 'Meters', 'Kilometers']

                                                                                                        The default is 'Miles'.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        attribute_relationship                                                                          Optional list of dicts. Defines an attribute relationship used to join features. Features are matched when the field
                                                                                                        values in the join layer are equal to field values in the target layer.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        join_operation                                                                                  Optional string. A string representing the type of join that will be applied.

                                                                                                        Choice list: ['JoinOneToOne', 'JoinOneToMany']

                                                                                                        * ``JoinOneToOne`` - If multiple join features are found that have the same relationships with a
                                                                                                            single target feature, the attributes from the multiple join features will be aggregated using
                                                                                                            the specified summary statistics. For example, if a point target feature is found within two
                                                                                                            separate polygon join features, the attributes from the two polygons will be aggregated before
                                                                                                            being transferred to the output point feature class. If one polygon has an attribute value of
                                                                                                            3 and the other has a value of 7, and a SummaryField of sum is selected, the aggregated value
                                                                                                            in the output feature class will be 10. There will always be a Count field calculated, with a
                                                                                                            value of 2, for the number of features specified. This is the default.

                                                                                                        * ``JoinOneToMany`` - If multiple join features are found that have the same relationship with
                                                                                                            a single target feature, the output feature class will contain multiple copies (records) of
                                                                                                            the target feature. For example, if a single point target feature is found within two separate
                                                                                                            polygon join features, the output feature class will contain two copies of the target feature:
                                                                                                            one record with the attributes of the first polygon, and another record with the attributes of
                                                                                                            the second polygon. There are no summary statistics calculated with this method.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        summary_fields                                                                                  Optional list of dicts. A list of field names and statistical summary types that you want to calculate.
                                                                                                        Note that the count is always returned by default.

                                                                                                        fieldName is the name of one of the numeric fields found in the input join layer.

                                                                                                        statisticType is one of the following:

                                                                                                        * ``SUM`` - Adds the total value of all the points in each polygon
                                                                                                        * ``MEAN`` - Calculates the average of all the points in each polygon
                                                                                                        * ``MIN`` - Finds the smallest value of all the points in each polygon
                                                                                                        * ``MAX`` - Finds the largest value of all the points in each polygon
                                                                                                        * ``STDDEV`` - Finds the standard deviation of all the points in each polygon
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        output_name                                                                                     Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                                                                        feature layer will cause the new layer to be appended to the Feature Service.
                                                                                                        If overwrite is True in context, new layer will overwrite existing layer.
                                                                                                        If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        context                                                                                         Optional dict. Additional settings such as processing extent and output spatial reference.
                                                                                                        For join_features, there are three settings.

                                                                                                        - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                                                                        - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                                                                        - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                                                                            .. code-block:: python

                                                                                                                # Example Usage
                                                                                                                context = {"extent": {"xmin": 3164569.408035,
                                                                                                                                    "ymin": -9187921.892449,
                                                                                                                                    "xmax": 3174104.927313,
                                                                                                                                    "ymax": -9175500.875353,
                                                                                                                                    "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                                                                            "outSR": {"wkid": 3857},
                                                                                                                            "overwrite": True}
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        estimate                                                                                        Optional boolean. If True, the number of credits to run the operation will be returned.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        future                                                                                          Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        join_type                                                                                       Optional String.  Determines the type of join performed on the datasets.  The allowed values are INNER or LEFT.
        --------------------------------------------------------------------------------------------    ---------------------------------------------------------------------------------------------------------------------------------
        records_to_match                                                                                Optional Dict. Defines how two features are joined.
                                                                                                        Example: {"groupByFields":"","orderByFields":"objectid ASC","topCount":1}
        ============================================================================================    =================================================================================================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection`.

        """
        task = "JoinFeatures"
        params = {}
        target_layer = self._feature_input(target_layer)
        join_layer = self._feature_input(join_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        # Not needed if input and join are table layers so only add if needed
        if spatial_relationship is not None:
            params["spatialRelationship"] = spatial_relationship
        if spatial_relationship_distance is not None:
            params["spatialRelationshipDistance"] = spatial_relationship_distance
        if spatial_relationship_distance_units is not None:
            params[
                "spatialRelationshipDistanceUnits"
            ] = spatial_relationship_distance_units
        if estimate:
            params["targetLayer"] = target_layer
            params["joinLayer"] = join_layer
            if attribute_relationship is not None:
                params["attributeRelationship"] = attribute_relationship
            if summary_fields is not None:
                params["summaryFields"] = summary_fields
            if join_operation is not None:
                params["joinOperation"] = join_operation
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if join_type:
                params["join_type"] = join_type
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        params = {
            "target_layer": target_layer,
            "join_layer": join_layer,
            "attribute_relationship": attribute_relationship,
            "join_operation": join_operation,
            "summary_fields": summary_fields,
            "output_name": output_name,
            "context": context,
            "records_to_match": records_to_match,
            "spatial_relationship": spatial_relationship,
            "spatial_relationship_distance": spatial_relationship_distance,
            "spatial_relationship_distance_units": spatial_relationship_distance_units,
            "future": future,
            "join_type": join_type,
        }
        params = _inspect_function_inputs(self._tbx.join_features, **params)
        params["future"] = True
        gpjob = self._tbx.join_features(**params)
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def merge_layers(
        self,
        input_layer,
        merge_layer,
        merging_attributes=[],
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``merge_layers`` method copies features from two layers into a new layer.
        The layers to be merged must all contain the same feature types (points, lines, or polygons).
        You can control how the fields from the input layers are joined and copied. For example:

        * I have three layers for England, Wales, and Scotland and I want a single layer of Great Britain.
        * I have two layers containing parcel information for contiguous townships. I want to join them together into a single layer, keeping only the fields that have the same name and type on the two layers.

        ================    ===============================================================
        **Parameter**        **Description**
        ----------------    ---------------------------------------------------------------
        input_layer         Required feature layer. The point, line or polygon features with the ``merge_layer``. See :ref:`Feature Input<FeatureInput>`.
        ----------------    ---------------------------------------------------------------
        merge_layer         Required feature layer. The point, line, or polygon features to merge with the ``input_layer``.
                            The ``merge_layer`` must contain the same feature type (point, line, or polygon) as the ``input_layer``. See :ref:`Feature Input<FeatureInput>`.
        ----------------    ---------------------------------------------------------------
        merge_attributes    Optional list. Defines how the fields in ``merge_layer`` will be
                            modified. By default, all fields from both inputs will be
                            included in the output layer.

                            If a field exists in one layer but not the other, the output
                            layer will still contain the field. The output field will
                            contain null values for the input features that did not have the
                            field. For example, if the ``input_layer`` contains a field named
                            TYPE but the ``merge_layer`` does not contain TYPE, the output will
                            contain TYPE, but its values will be null for all the features
                            copied from the ``merge_layer``.

                            You can control how fields in the ``merge_layer`` are written to the
                            output layer using the following merge types that operate on a
                            specified ``merge_layer`` field:

                            + ``Remove`` - The field in the ``merge_layer`` will be removed from the output layer.
                            + ``Rename`` - The field in the ``merge_layer`` will be renamed in the output layer.
                                You cannot rename a field in the ``merge_layer`` to a field in the ``input_layer``. If you want to make field names equivalent, use Match.
                            + ``Match`` - A field in the ``merge_layer`` is made equivalent to a field in the ``input_layer`` specified by merge value.
                                For example, the ``input_layer`` has a field named CODE and the ``merge_layer`` has a field named STATUS.
                                You can match STATUS to CODE, and the output will contain the CODE field with values of the STATUS field used for features copied from the ``merge_layer``.
                                Type casting is supported (for example, float to integer, integer to string) except for string to numeric.
        ----------------    ---------------------------------------------------------------
        output_name         Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                            feature layer will cause the new layer to be appended to the Feature Service.
                            If overwrite is True in context, new layer will overwrite existing layer.
                            If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ----------------    ---------------------------------------------------------------
        context             Optional dict. Additional settings such as processing extent and output spatial reference.
                            For calculate_density, there are three settings.

                            - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                            - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                            - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                .. code-block:: python

                                    # Example Usage
                                    context = {"extent": {"xmin": 3164569.408035,
                                                        "ymin": -9187921.892449,
                                                        "xmax": 3174104.927313,
                                                        "ymax": -9175500.875353,
                                                        "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                "outSR": {"wkid": 3857},
                                                "overwrite": True}
        ----------------    ---------------------------------------------------------------
        gis                 Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ----------------    ---------------------------------------------------------------
        estimate            Optional boolean. If True, the estimated number of credits required to run the operation will be returned.
        ----------------    ---------------------------------------------------------------
        future              Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ================    ===============================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else Feature Collection.

        """

        task = "MergeLayers"

        input_layer = self._feature_input(input_layer)
        merge_layer = self._feature_input(merge_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params = {}
            params["inputLayer"] = input_layer
            params["mergeLayer"] = merge_layer
            if merging_attributes is not None:
                params["mergingAttributes"] = merging_attributes
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context

            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.merge_layers(
            input_layer=input_layer,
            merge_layer=merge_layer,
            merging_attributes=merging_attributes,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def overlay_layers(
        self,
        input_layer,
        overlay_layer,
        overlay_type="Intersect",
        snap_to_input=False,
        output_type="Input",
        tolerance=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``overlay_layers`` method combines two or more layers into one single layer.
        You can think of overlay as peering through a stack of maps and creating a single map containing
        all the information found in the stack. In fact, before the advent of GIS, cartographers would
        literally copy maps onto clear acetate sheets, overlay these sheets on a light table, and hand
        draw a new map from the overlaid data. Overlay is much more than a merging of line work; all the
        attributes of the features taking part in the overlay are carried through to the final product.
        Overlay is used to answer one of the most basic questions of geography, "what is on top of what?" For example:

        + What parcels are within the 100-year floodplain? (Within is just another way of saying on top of.)
        + What roads are within what counties?
        + What land use is on top of what soil type?
        + What wells are within abandoned military bases?

        ================    ===============================================================
        **Parameter**        **Description**
        ----------------    ---------------------------------------------------------------
        input_layer         Required layer. The point, line, or polygon features that will be
                            overlayed with the ``overlay_layer``. See :ref:`Feature Input<FeatureInput>`.
        ----------------    ---------------------------------------------------------------
        overlay_layer       Required layer. The features that will be overlaid with the ``input_layer`` features. See :ref:`Feature Input<FeatureInput>`.
        ----------------    ---------------------------------------------------------------
        overlay_type        Optional string. The type of overlay to be performed.

                            Choice list: ['Intersect', 'Union', 'Erase']

                            +--------------+--------------------------------------------------------------------------------------------------------+
                            | |Intersect|  | ``Intersect``-Computes a geometric intersection of the input layers. Features or portions of           |
                            |              | features which overlap in both the ``input_layer`` and ``overlay_layer`` layer will be written         |
                            |              | to the output layer. This is the default.                                                              |
                            +--------------+--------------------------------------------------------------------------------------------------------+
                            | |Union|      | ``Union``-Computes a geometric union of the input layers. All features and their attributes will       |
                            |              | be written to the output layer. This option is only valid if both the ``input_layer`` and              |
                            |              | the ``overlay_layer`` contain polygon features.                                                        |
                            +--------------+--------------------------------------------------------------------------------------------------------+
                            | |Erase|      | ``Erase``-Only those features or portions of features in the ``overlay_layer`` that are not within the |
                            |              | features in the ``input_layer`` layer are written to the output.                                       |
                            +--------------+--------------------------------------------------------------------------------------------------------+

                            The default value is 'Intersect'.

        ----------------    ---------------------------------------------------------------
        snap_to_input       Optional boolean. A Boolean value indicating if feature vertices in the ``input_layer`` are allowed to move.
                            The default is false and means if the distance between features is less than the ``tolerance`` value, all features from both
                            layers can move to allow snapping to each other. When set to true, only features in ``overlay_layer`` can move to snap to the ``input_layer`` features.
        ----------------    ---------------------------------------------------------------
        output_type         Optional string. The type of intersection you want to find.
                            This parameter is only valid when the ``overlay_type`` is Intersect.

                            Choice list: ['Input', 'Line', 'Point']

                                *  ``Input`` - The features returned will be the same geometry type as
                                the ``input_layer`` or ``overlay_layer`` with the lowest dimension geometry.
                                If all inputs are polygons, the output will contain polygons. If one or more of
                                the inputs are lines and none of the inputs are points, the output will be line.
                                If one or more of the inputs are points, the output will contain points. This is the default.
                                *  ``Line`` - Line intersections will be returned. This is only valid if none of the inputs are points.
                                *  ``Point`` - Point intersections will be returned. If the inputs are line or polygon, the output will be a multipoint layer.
        ----------------    ---------------------------------------------------------------
        tolerance           Optional float. A float value of the minimum distance separating all feature coordinates
                            as well as the distance a coordinate can move in X or Y (or both). The units of tolerance are the same as the units of the ``input_layer``.
        ----------------    ---------------------------------------------------------------
        output_name         Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                            feature layer will cause the new layer to be appended to the Feature Service.
                            If overwrite is True in context, new layer will overwrite existing layer.
                            If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ----------------    ---------------------------------------------------------------
        context             Optional dict. Additional settings such as processing extent and output spatial reference.
                            For calculate_density, there are three settings.

                            - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                            - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                            - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                .. code-block:: python

                                    # Example Usage
                                    context = {"extent": {"xmin": 3164569.408035,
                                                        "ymin": -9187921.892449,
                                                        "xmax": 3174104.927313,
                                                        "ymax": -9175500.875353,
                                                        "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                "outSR": {"wkid": 3857},
                                                "overwrite": True}
        ----------------    ---------------------------------------------------------------
        gis                 Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ----------------    ---------------------------------------------------------------
        future              Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ================    ===============================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else Feature Collection.

        """

        task = "OverlayLayers"

        params = {}
        input_layer = self._feature_input(input_layer)
        overlay_layer = self._feature_input(overlay_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            params["overlayLayer"] = overlay_layer
            if overlay_type is not None:
                params["overlayType"] = overlay_type
            if snap_to_input is not None:
                params["snapToInput"] = snap_to_input
            if output_type is not None:
                params["outputType"] = output_type
            if tolerance is not None:
                params["tolerance"] = tolerance
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.overlay_layers(
            input_layer=input_layer,
            overlay_layer=overlay_layer,
            overlay_type=overlay_type,
            snap_to_input=snap_to_input,
            output_type=output_type,
            tolerance=tolerance,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def plan_routes(
        self,
        stops_layer,
        route_count,
        max_stops_per_route,
        route_start_time,
        start_layer,
        start_layer_route_id_field=None,
        return_to_start=True,
        end_layer=None,
        end_layer_route_id_field=None,
        travel_mode="Driving Time",
        stop_service_time=0,
        max_route_time=525600,
        include_route_layers=False,
        output_name=None,
        context=None,
        estimate=False,
        point_barrier_layer=None,
        line_barrier_layer=None,
        polygon_barrier_layer=None,
        future=False,
    ):
        """
        The ``plan_routes`` method determines how to efficiently divide tasks among a mobile workforce.

        You provide the input, which includes a set of stops and the number of vehicles available to
        visit the stops, and the tool assigns the stops to vehicles and returns routes showing how each
        vehicle can reach their assigned stops in the least amount of time.

        With ``plan_routes``, mobile workforces reach more jobsites in less time, which increases
        productivity and improves customer service. Organizations often use ``plan_routes`` to:

        * Inspect homes, restaurants, and construction sites
        * Provide repair, installation, and technical services
        * Deliver items and small packages
        * Make sales calls
        * Provide van transportation from spectators' homes to events

        The output from ``plan_routes`` includes a layer of routes showing the shortest paths to visit
        the stops; a layer of the stops assigned to routes, as well as any stops that couldn't be reached
        due to the given parameter settings; and a layer of directions containing the travel itinerary for each route.

        ============================    ==================================================================================================
        **Parameter**                   **Description**
        ----------------------------    --------------------------------------------------------------------------------------------------
        stops_layer                     Required feature layer. The points that the vehicles, drivers, or routes, should visit.
                                        The fields on the input stops are included in the output stops, so if your input
                                        layer has a field such as Name, Address, or ProductDescription, that information
                                        will be available in the results. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        route_count                     Required integer. The number of vehicles that are available to visit the stops.
                                        The method supports up to 100 vehicles.

                                        The default value is 0.

                                        The method may be able to find and return a solution that uses fewer vehicles than
                                        the number you specify for this parameter. The number of vehicles returned also
                                        depends on four other parameters: the total number of stops in ``stops_layer``, the
                                        number of stops per vehicle you allow (``max_stops_per_route``), the travel time between
                                        stops, the time spent at each stop (``stop_service_time``), and any limit you set on the
                                        total route time per vehicle (``max_route_time``).
        ----------------------------    --------------------------------------------------------------------------------------------------
        max_stops_per_route             Required integer. The maximum number of stops a route, or vehicle, is allowed to visit.
                                        The largest value you can specify is 200. The default value is zero.

                                        This is one of two parameters that balance the overall workload across routes.
                                        The other is ``max_route_time``.

                                        By lowering the maximum number of stops that can be assigned to each vehicle, the vehicles
                                        are more likely to have an equal number of stops assigned to them. This helps
                                        balance workloads among drivers. The drawback, however, is that it may result in a
                                        solution that is less efficient.

                                        By increasing the stops per vehicle, the tool has more freedom to find more efficient solutions;
                                        however, the workload may be unevenly distributed among drivers and vehicles. Note that you can
                                        balance workloads by time instead of number of stops by specifying a value for the ``max_route_time`` parameter.

                                        The following examples demonstrate the effects of limiting the maximum stops per vehicle or the
                                        total time per vehicle. In all of these examples, two routes start at the same location
                                        and visit a total of six stops.

                                        +----------------------------+----------------------------------------------------------------------------------------------------------+
                                        | |balanced|                 | Balanced travel times and stops per route:                                                               |
                                        |                            |                                                                                                          |
                                        |                            | The stops are more or less uniformly spread apart, so setting ``max_stops_per_route``=3 to evenly        |
                                        |                            | distribute the workload results in routes that are roughly the same duration.                            |
                                        |                            |                                                                                                          |
                                        +----------------------------+----------------------------------------------------------------------------------------------------------+
                                        | |partially_balanced|       | Balanced stops per route but unbalanced travel times:                                                    |
                                        |                            |                                                                                                          |
                                        |                            | Five of the six stops are clustered near the starting location, but one stop is set apart                |
                                        |                            | and requires a much longer drive to be reached. Dividing the stops equally between the two               |
                                        |                            | routes (``max_stops_per_route``=3) causes unbalanced travel times.                                       |
                                        +----------------------------+----------------------------------------------------------------------------------------------------------+
                                        | |unbalanced|               | Unbalanced stops per route but balanced travel times:                                                    |
                                        |                            |                                                                                                          |
                                        |                            | The stops are in the same location as the previous graphic. By increasing the value of                   |
                                        |                            | ``max_stops_per_route`` to 4, and limiting the total travel time per vehicle (``max_route_time``),       |
                                        |                            | the travel times are balanced even though one route visits more stops.                                   |
                                        +----------------------------+----------------------------------------------------------------------------------------------------------+

        ----------------------------    --------------------------------------------------------------------------------------------------
        route_start_time                Required datetime.datetime. Specify when the vehicles or people start their routes.
                                        The time is specified as datetime.
                                        The starting time value is the same for all routes; that is, all routes start at the same time.

                                        Time zones affect what value you assign to ``route_start_time``. The time zone for the start time is
                                        based on the time zone in which the starting point is geographically located. For instance,
                                        if you have one route starting location and it is located in Pacific Standard Time (PST),
                                        the time you specify for ``route_start_time`` is in PST.

                                        There are a couple of scenarios to beware of given that starting times are based on where
                                        the starting points are located. One situation to be careful of is when you are located in
                                        one time zone but your starting locations are in another times zone. For instance, assume
                                        you are in Pacific Standard Time (UTC-8:00) and the vehicles you are routing are stationed
                                        in Mountain Standard Time (UTC-7:00). If it is currently 9:30 a.m. PST (10:30 a.m. MST)
                                        and your vehicles need to begin their routes in 30 minutes, you would set the start time
                                        to 11:00 a.m. That is, the starting locations for the routes are in the Mountain time zone,
                                        and it is currently 10:30 a.m. there, therefore, a starting time of 30 minutes from now is 11:00 a.m.
                                        Make sure you set the parameter according to the proper time zone.

                                        The other situation that requires caution is where starting locations are spread across
                                        multiple time zones. The time you set for ``route_start_time`` is specific to the time zone in
                                        which the starting location is regardless of whether there are one or more starting locations
                                        in the problem you submit. For instance, if one route starts from a point in PST and another
                                        route starts from MST, and you enter 11:00 a.m. as the start time, the route in PST will start
                                        at 11:00 a.m. PST and the route in MST will start at 11:00 a.m. MST a one-hour difference. The
                                        starting times are the same in local time, but offset in actual time, or UTC.

                                        The service automatically determines the time zones of the input starting locations (``start_layer``) for you.

                                        Examples:

                                        * datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. Routes will depart their
                                        starting locations at 8:00 a.m., 22 October. Any routes with starting points in Mountain
                                        Standard Time start at 8:00 a.m., 22 October 2014 MST; any routes with starting points in
                                        Pacific Standard Time start at 8:00 a.m. 22 October 2014 PST, and so on.
                                        * datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015.
        ----------------------------    --------------------------------------------------------------------------------------------------
        start_layer                     Required feature layer. Provide the locations where the people or vehicles start their routes.
                                        You can specify one or many starting locations.

                                        If specifying one, all routes will start from the one location. If specifying many starting
                                        locations, each route needs exactly one predefined starting location, and the following criteria must be met:

                                        The number of routes (``route_count``) must equal the number of points in ``start_layer``. (However,
                                        when only one point is included in ``start_layer``, it is assumed that all routes start from
                                        the same location, and the two numbers can be different.)
                                        The starting location for each route must be identified with the ``start_layer_route_id_field``
                                        parameter. This implies that the input points in ``start_layer`` have a unique identifier.
                                        Bear in mind that if you also have many ending locations, those locations need to be
                                        predetermined as well. The predetermined start and end locations of each route are
                                        paired together by matching route ID values.
                                        See the the section of this topic entitled Starting and ending locations of
                                        routes to learn more. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        start_layer_route_id_field      Optional string. Choose a field that uniquely identifies points in start_layer.
                                        This parameter is required when ``start_layer`` has more than one point; it is ignored otherwise.

                                        The ``start_layer_route_id_field`` parameter helps identify where routes begin and
                                        indicates the names of the output routes.

                                        See the the section of this topic entitled Starting and ending locations
                                        of routes to learn more.
        ----------------------------    --------------------------------------------------------------------------------------------------
        return_to_start                 Optional boolean. A True value indicates each route must end its trip at the same place where
                                        it started. The starting location is defined by the ``start_layer`` and ``start_layer_route_id_field`` parameters.

                                        The default value is True.
        ----------------------------    --------------------------------------------------------------------------------------------------
        end_layer                       Optional layer. Provide the locations where the people or vehicles end their routes.

                                        If ``end_layer`` is not specified, ``return_to_start`` must be set to True.

                                        You can specify one or many ending locations.

                                        If specifying one, all routes will end at the one location. If specifying many ending
                                        locations, each route needs exactly one predefined ending location, and the following criteria must be met:

                                        + The number of routes (``route_count``) must equal the number of points in ``end_layer``.
                                        (However, when only one point is included in ``end_layer``, it is assumed that all routes
                                        end at the same location, and the two numbers can be different.)
                                        + The ending location for each route must be identified with the ``start_layer_route_id_field``
                                        parameter. This implies that the input points in endLayer have a unique identifier.
                                        Bear in mind that if you also have many starting locations, those locations need to be
                                        predetermined as well. The predetermined start and end locations of each route are paired
                                        together by matching route ID values. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        end_layer_route_id_field        Optional string. Choose a field that uniquely identifies points in ``end_layer``.
                                        This parameter is required when ``end_layer`` has more than one point; it is ignored
                                        if there is one point or if ``return_to_start`` is True.

                                        The ``end_layer_route_id_field`` parameter helps identify where routes end and indicates the names of the output routes.

                                        See the the section of this topic entitled Starting and ending locations of routes to learn more.
        ----------------------------    --------------------------------------------------------------------------------------------------
        travel_mode                     Optional string. Optional string. Specify the mode of transportation for the analysis.

                                        Choice list: ['Driving Distance', 'Driving Time', 'Rural Driving Distance', 'Rural Driving Time', 'Trucking Distance', 'Trucking Time', 'Walking Distance', 'Walking Time']
        ----------------------------    --------------------------------------------------------------------------------------------------
        stop_service_time               Optional float. Indicates how much time, in minutes, is spent at each stop.
                                        The units are minutes. All stops are assinged the same service duration from
                                        this parameter unique values for individual stops cannot be specified with this service.
        ----------------------------    --------------------------------------------------------------------------------------------------
        max_route_time                  Optional float. The amount of time you specify here limits the maximum duration of each route.
                                        The maximum route time is an accumulation of travel time and the total service time at visited
                                        stops (``stop_service_time``). This parameter is commonly used to prevent drivers from working
                                        too many hours or to balance workloads across routes or drivers.

                                        The units are 'minutes'. The default value, which is also the maximum value, is 525600 minutes, or one year.
        ----------------------------    --------------------------------------------------------------------------------------------------
        include_route_layers            Optional boolean. When ``include_route_layers`` is set to True, each route from the result is also
                                        saved as a route layer item. A route layer includes all the information for a particular route such as the stops assigned to
                                        the route as well as the travel directions. Creating route layers is useful if you want to share individual routes with other
                                        members in your organization. The route layers use the output feature service name provided in the ``output_name`` parameter as a
                                        prefix and the route name generated as part of the analysis is added to create a unique name for each route layer.
        ----------------------------    --------------------------------------------------------------------------------------------------
        output_name                     Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                        feature layer will cause the new layer to be appended to the Feature Service.
                                        If overwrite is True in context, new layer will overwrite existing layer.
                                        If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        ----------------------------    --------------------------------------------------------------------------------------------------
        context                         Optional dict. Additional settings such as processing extent and output spatial reference.
                                        For plan_routes, there are three settings.

                                        - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                        - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                        - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                            .. code-block:: python

                                                # Example Usage
                                                context = {"extent": {"xmin": 3164569.408035,
                                                                    "ymin": -9187921.892449,
                                                                    "xmax": 3174104.927313,
                                                                    "ymax": -9175500.875353,
                                                                    "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                            "outSR": {"wkid": 3857},
                                                            "overwrite": True}
        ----------------------------    --------------------------------------------------------------------------------------------------
        gis                             Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        ----------------------------    --------------------------------------------------------------------------------------------------
        estimate                        Optional boolean. If True, the number of credits to run the operation will be returned.
        ----------------------------    --------------------------------------------------------------------------------------------------
        point_barrier_layer             Optional feature layer. Specify one or more point features that act as temporary restrictions (in other words, barriers) when traveling on the underlying streets.

                                        A point barrier can model a fallen tree, an accident, a downed electrical line, or anything that completely blocks traffic at a specific position along the street. Travel is permitted on the street but not through the barrier. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        line_barrier_layer              Optional feature layer. Specify one or more line features that prohibit travel anywhere the lines intersect the streets.

                                        A line barrier prohibits travel anywhere the barrier intersects the streets. For example, a parade or protest that blocks traffic across several street segments can be modeled with a line barrier. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        polygon_barrier_layer           Optional feature layer. Specify one or more polygon features that completely restrict travel on the streets intersected by the polygons.

                                        One use of this type of barrier is to model floods covering areas of the street network and making road travel there impossible. See :ref:`Feature Input<FeatureInput>`.
        ----------------------------    --------------------------------------------------------------------------------------------------
        future                          Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ============================    ==================================================================================================

        :return: :class:`~arcgis.features.FeatureLayer` if ``output_name`` is specified, else dict with the following keys:


            "routes_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "assigned_stops_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

            "unassigned_stops_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        """

        task = "PlanRoutes"

        params = {}
        stops_layer = self._feature_input(stops_layer)
        if start_layer:
            start_layer = self._feature_input(start_layer)
        if end_layer:
            end_layer = self._feature_input(end_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)
        if point_barrier_layer:
            point_barrier_layer = self._feature_input(point_barrier_layer)
        if line_barrier_layer:
            line_barrier_layer = self._feature_input(line_barrier_layer)
        if polygon_barrier_layer:
            polygon_barrier_layer = self._feature_input(polygon_barrier_layer)

        if estimate:
            params["stopsLayer"] = stops_layer
            params["routeCount"] = route_count
            params["maxStopsPerRoute"] = max_stops_per_route
            params["routeStartTime"] = route_start_time
            params["startLayer"] = start_layer
            params["includeRouteLayers"] = include_route_layers
            if start_layer_route_id_field is not None:
                params["startLayerRouteIDField"] = start_layer_route_id_field
            if return_to_start is not None:
                params["returnToStart"] = return_to_start
            if end_layer is not None:
                params["endLayer"] = end_layer
            if end_layer_route_id_field is not None:
                params["endLayerRouteIDField"] = end_layer_route_id_field
            if travel_mode is not None:
                params["travelMode"] = travel_mode
            if stop_service_time is not None:
                params["stopServiceTime"] = stop_service_time
            if max_route_time is not None:
                params["maxRouteTime"] = max_route_time
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if point_barrier_layer:
                params["pointBarrierLayer"] = point_barrier_layer
            if line_barrier_layer:
                params["lineBarrierLayer"] = line_barrier_layer
            if polygon_barrier_layer:
                params["polygonBarrierLayer"] = polygon_barrier_layer
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.plan_routes(
            stops_layer=stops_layer,
            route_count=route_count,
            max_stops_per_route=max_stops_per_route,
            route_start_time=route_start_time,
            start_layer=start_layer,
            start_layer_route_id_field=start_layer_route_id_field,
            return_to_start=return_to_start,
            end_layer=end_layer,
            end_layer_route_id_field=end_layer_route_id_field,
            travel_mode=travel_mode,
            stop_service_time=stop_service_time,
            max_route_time=max_route_time,
            output_name=output_name,
            context=context,
            include_route_layers=include_route_layers,
            point_barrier_layer=point_barrier_layer,
            line_barrier_layer=line_barrier_layer,
            polygon_barrier_layer=polygon_barrier_layer,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def summarize_center_and_dispersion(
        self,
        analysis_layer,
        summarize_type=["CentralFeature"],
        ellipse_size=None,
        weight_field=None,
        group_field=None,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``summarize_center_and_dispersion`` method finds central features and directional distributions. It can be used to answer questions such as:

        * Where is the center?
        * Which feature is the most accessible from all other features?
        * How dispersed, compact, or integrated are the features?
        * Are there directional trends?

        ====================    =========================================================
        **Parameter**            **Description**
        --------------------    ---------------------------------------------------------
        analysis_layer          Required feature layer. The point, line, or polygon features to be analyzed. See :ref:`Feature Input<FeatureInput>`.
        --------------------    ---------------------------------------------------------
        summarize_type          Required list of strings. The method with which to summarize the ``analysis_layer``.

                                Choice list: ["CentralFeature", "MeanCenter", "MedianCenter", "Ellipse"]
        --------------------    ---------------------------------------------------------
        ellipse_size            Optional string. The size of the output ellipse in standard deviations.

                                Choice list: ['1 standard deviations', '2 standard deviations', '3 standard deviations']

                                The default ellipse size is '1 standard deviations'.
        --------------------    ---------------------------------------------------------
        weight_field            Optional field. A numeric field in the ``analysis_layer`` to be used to
                                weight locations according to their relative importance.
        --------------------    ---------------------------------------------------------
        group_field             Optional field. The field used to group features for separate directional
                                distribution calculations. The ``group_field`` can be of
                                integer, date, or string type.
        --------------------    ---------------------------------------------------------
        output_name             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                feature layer will cause the new layer to be appended to the Feature Service.
                                If overwrite is True in context, new layer will overwrite existing layer.
                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        --------------------    ---------------------------------------------------------
        context                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                For summarize_center_and_dispersion, there are three settings.

                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online and ArcGIS Enterprise 11+.

                                    .. code-block:: python

                                        # Example Usage
                                        context = {"extent": {"xmin": 3164569.408035,
                                                            "ymin": -9187921.892449,
                                                            "xmax": 3174104.927313,
                                                            "ymax": -9175500.875353,
                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                    "outSR": {"wkid": 3857},
                                                    "overwrite": True}
        --------------------    ---------------------------------------------------------
        estimate                Optional boolean. If True, the number of credits to run the operation will be returned.
        --------------------    ---------------------------------------------------------
        future                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ====================    =========================================================

        :return: list of items if ``output_name`` is supplied else, a Python dictionary with the following keys:
            "central_feature_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)
            "mean_feature_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)
            "median_feature_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)
            "ellipse_feature_result_layer" : layer (:class:`~arcgis.features.FeatureCollection`)

        """

        task = "SummarizeCenterAndDispersion"

        params = {}
        analysis_layer = self._feature_input(analysis_layer)

        if self._gis.version > [10, 1] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["analysisLayer"] = analysis_layer
            params["summarizeType"] = summarize_type

            if ellipse_size is not None:
                params["ellipseSize"] = ellipse_size
            if weight_field is not None:
                params["weightField"] = weight_field
            if group_field is not None:
                params["groupField"] = group_field
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.summarize_center_and_dispersion(
            analysis_layer=analysis_layer,
            summarize_type=summarize_type,
            ellipse_size=ellipse_size,
            weight_field=weight_field,
            group_field=group_field,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def summarize_within(
        self,
        summary_layer,
        sum_within_layer=None,
        sum_shape=True,
        shape_units=None,
        summary_fields=[],
        group_by_field=None,
        minority_majority=False,
        percent_shape=False,
        output_name=None,
        context=None,
        estimate=False,
        bin_size=None,
        bin_type="SQUARE",
        bin_size_unit=None,
        future=False,
    ):
        """
        The ``summarize_within`` method finds the point, line, or polygon features (or portions of these features)
        that are within the boundaries of polygons in another layer. For example:

            * Given a layer of watershed boundaries and a layer of land-use boundaries by land-use type, calculate total acreage of land-use type for each watershed.
            * Given a layer of parcels in a county and a layer of city boundaries, summarize the average value of vacant parcels within each city boundary.
            * Given a layer of counties and a layer of roads, summarize the total mileage of roads by road type within each county.

        You can think of ``summarize_within`` as taking two layers and stacking them on top of each other.
        One of the layers, the ``sum_within_layer`` must be a polygon layer, and imagine that these polygon
        boundaries are all colored red. The other layer, the ``summary_layer``, can be any feature type point,
        line, or polygon. After stacking these layers on top of each other, you peer down through the stack
        and count the number of features in the ``summary_layer`` that fall within the polygons with the red
        boundaries (the ``sum_within_layer``). Not only can you count the number of features, you can calculate
        simple statistics about the attributes of the features in the ``summary_layer``, such as sum, mean, minimum, maximum, and so on.

        =====================================   =========================================================
        **Parameter**                            **Description**
        -------------------------------------   ---------------------------------------------------------
        sum_within_layer                        Required feature layer. The polygon features. Features, or
                                                portions of features, in the ``summary_layer`` (below) that fall within
                                                the boundaries of these polygons will be summarized. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------   ---------------------------------------------------------
        summary_layer                           Required feature layer. Point, line, or polygon features that will be summarized for each polygon in the ``sum_within_layer``.
                                                See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------   ---------------------------------------------------------
        sum_shape                               Optional boolean. A boolean value that instructs the task to calculate statistics
                                                based on shape type of the ``summary_layer``, such as the length of lines or areas of
                                                polygons of the ``summary_layer`` within each polygon in ``sum_within_layer``.

                                                The default is True.
        -------------------------------------   ---------------------------------------------------------
        shape_units                             Optional string. Specify units to summarize the length or areas when ``sum_shape`` is set to true. Units is not required to summarize
                                                points.

                                                * When ``summary_layer`` contains polygons: ['Acres', 'Hectares', 'SquareMeters', 'SquareKilometers', 'SquareMiles', 'SquareYards', 'SquareFeet']

                                                * When ``summary_layer`` contains lines: ['Meters', 'Kilometers', 'Feet', 'Yards', 'Miles']
        -------------------------------------   ---------------------------------------------------------
        summary_fields                          Optional list of strings. A list of field names and statistical summary type that you wish
                                                to calculate for all features in the ``summary_layer`` that are within each polygon in the ``sum_within_layer`` .

                                                Example: ["fieldname1 summary", "fieldname2 summary"]
        -------------------------------------   ---------------------------------------------------------
        group_by_field                          Optional string. This is a field of the ``summary_layer`` features that you can use to calculate statistics separately
                                                for each unique attribute value. For example, suppose the ``sum_within_layer`` contains city boundaries and
                                                the ``summary_layer`` features are parcels. One of the fields of the parcels is Status which contains
                                                two values: VACANT and OCCUPIED. To calculate the total area of vacant and occupied parcels within the
                                                boundaries of cities, use Status as the ``group_by_field`` field.
        -------------------------------------   ---------------------------------------------------------
        minority_majority                       Optional boolean. This boolean parameter is applicable only when a ``group_by_field`` is specified.
                                                If true, the minority (least dominant) or the majority (most dominant) attribute values for each group
                                                field are calculated. Two new fields are added to the ``result_layer`` prefixed with `Majority_` and `Minority_`.

                                                The default is False.
        -------------------------------------   ---------------------------------------------------------
        percent_shape                           Optional boolean. This Boolean parameter is applicable only when a ``group_by_field`` is specified.
                                                If set to true, the percentage of each unique ``group_by_field`` value is calculated for
                                                each ``sum_within_layer`` polygon.

                                                The default is False.
        -------------------------------------   ---------------------------------------------------------
        output_name                             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------   ---------------------------------------------------------
        context                                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For summarize_within, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        -------------------------------------   ---------------------------------------------------------
        estimate                                Optional boolean. If True, the number of credits to run the operation will be returned.
        -------------------------------------   ---------------------------------------------------------
        future                                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        -------------------------------------   ---------------------------------------------------------
        bin_type                                Required string. The type of bin used to calculate density.

                                                Choice list: ['Hexagon', 'Square'].
        -------------------------------------   ---------------------------------------------------------
        bin_size                                Required float. The distance for the bins that the ``input_layer`` will be analyzed using.
                                                When generating bins, for Square, the number and units specified determine the
                                                height and length of the square. For ``Hexagon``, the number and units specified
                                                determine the distance between parallel sides.
        -------------------------------------   ---------------------------------------------------------
        bin_size_unit                           Required string. The distance unit for the bins for which the density will be calculated.
                                                The linear unit to be used with the value specified in ``bin_size``.

                                                The default is 'Meters'.
        =====================================   =========================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection` dictionary.

            dict with the following keys:

                "result_layer" : layer (FeatureCollection)

                "group_by_summary" : layer (FeatureCollection)

        """

        task = "SummarizeWithin"

        params = {}
        if sum_within_layer:
            sum_within_layer = self._feature_input(sum_within_layer)
        summary_layer = self._feature_input(summary_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["sumWithinLayer"] = sum_within_layer
            params["summaryLayer"] = summary_layer
            if sum_shape is not None:
                params["sumShape"] = sum_shape
            if shape_units is not None:
                params["shapeUnits"] = shape_units
            if summary_fields is not None:
                params["summaryFields"] = summary_fields
            if group_by_field is not None:
                params["groupByField"] = group_by_field
            if minority_majority is not None:
                params["minorityMajority"] = minority_majority
            if percent_shape is not None:
                params["percentShape"] = percent_shape
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            if bin_size:
                params["binSize"] = bin_size
            if bin_size_unit:
                params["binSizeUnit"] = bin_size_unit
            if bin_type:
                params["binType"] = bin_type
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.summarize_within(
            sum_within_layer=sum_within_layer,
            summary_layer=summary_layer,
            sum_shape=sum_shape,
            shape_units=shape_units,
            summary_fields=summary_fields,
            group_by_field=group_by_field,
            minority_majority=minority_majority,
            percent_shape=percent_shape,
            output_name=output_name,
            context=context,
            bin_type=bin_type,
            bin_size=bin_size,
            bin_size_unit=bin_size_unit,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def trace_downstream(
        self,
        input_layer,
        split_distance=None,
        split_units="Kilometers",
        max_distance=None,
        max_distance_units="Kilometers",
        bounding_polygon_layer=None,
        source_database=None,
        generalize=True,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``trace_downstream`` method determines the trace, or flow path, in a downstream direction from the points in your analysis layer.

        For example, suppose you have point features representing sources of contamination and you want to determine where in your study
        area the contamination will flow. You can use ``trace_downstream`` to identify the path the contamination will take. This trace
        can also be divided into individual line segments by specifying a distance value and units. The line being returned can be the
        total length of the flow path, a specified maximum trace length, or clipped to area features such as your study area. In many
        cases, if the total length of the trace path is returned, it will be from the source all the way to the ocean.

        =====================================   =========================================================
        **Parameter**                            **Description**
        -------------------------------------   ---------------------------------------------------------
        input_layer                             Required feature layer. The point features used for the starting location of a downstream trace.
                                                See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------   ---------------------------------------------------------
        split_distance                          Optional float. The trace line will be split into multiple lines where each line is of the specified length.
                                                The resulting trace will have multiple line segments, each with fields FromDistance and ToDistance.
        -------------------------------------   ---------------------------------------------------------
        split_units                             Optional string. The units used to specify split distance.

                                                Choice list: ['Meters', 'Kilometers', 'Feet' 'Yards', 'Miles'].

                                                The default is 'Kilometers'.
        -------------------------------------   ---------------------------------------------------------
        max_distance                            Optional float. Determines the total length of the line that will be returned. If you provide a
                                                ``bounding_polygon_layer`` to clip the trace, the result will be clipped to the features in ``bounding_polygon_layer``,
                                                regardless of the distance you enter here.
        -------------------------------------   ---------------------------------------------------------
        max_distance_units                      Optional string. The units used to specify maximum distance.

                                                Choice list: ['Meters', 'Kilometers', 'Feet' 'Yards', 'Miles'].

                                                The default is 'Kilometers'.
        -------------------------------------   ---------------------------------------------------------
        bounding_polygon_layer                  Optional feature layer. A polygon layer specifying the area(s) where you want the trace
                                                downstreams to be calculated in. For example, if you only want to calculate the trace downstream
                                                with in a county polygon, provide a layer containing the county polygon and the resulting trace
                                                lines will be clipped to the county boundary. See :ref:`Feature Input<FeatureInput>`.
        -------------------------------------   ---------------------------------------------------------
        source_database                         Optional string. Keyword indicating the data source resolution that will be used in the analysis.

                                                Choice list: ['Finest', '30m', '90m'].

                                                * Finest: Finest resolution available at each location from all possible data sources.

                                                * 30m: The hydrologic source was built from 1 arc second - approximately 30 meter resolution, elevation data.

                                                * 90m: The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data.

                                                The default is 'Finest'.
        -------------------------------------   ---------------------------------------------------------
        generalize                              Optional boolean. Determines if the output trace downstream lines will be smoothed
                                                into simpler lines or conform to the cell edges of the original DEM.
        -------------------------------------   ---------------------------------------------------------
        output_name                             Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                                feature layer will cause the new layer to be appended to the Feature Service.
                                                If overwrite is True in context, new layer will overwrite existing layer.
                                                If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------------------   ---------------------------------------------------------
        context                                 Optional dict. Additional settings such as processing extent and output spatial reference.
                                                For trace_downstream, there are three settings.

                                                - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                                - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                                - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                                    .. code-block:: python

                                                        # Example Usage
                                                        context = {"extent": {"xmin": 3164569.408035,
                                                                            "ymin": -9187921.892449,
                                                                            "xmax": 3174104.927313,
                                                                            "ymax": -9175500.875353,
                                                                            "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                                    "outSR": {"wkid": 3857},
                                                                    "overwrite": True}
        -------------------------------------   ---------------------------------------------------------
        estimate                                Optional boolean. If True, the number of credits to run the operation will be returned.
        -------------------------------------   ---------------------------------------------------------
        future                                  Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =====================================   =========================================================

        :return: :class:`~arcgis.features.FeatureLayer` if ``output_name`` is set, else :class:`~arcgis.features.FeatureCollection`.

        """

        task = "TraceDownstream"

        params = {}
        input_layer = self._feature_input(input_layer)
        if bounding_polygon_layer:
            bounding_polygon_layer = self._feature_input(bounding_polygon_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params["inputLayer"] = input_layer
            if split_distance is not None:
                params["splitDistance"] = split_distance
            if split_units is not None:
                params["splitUnits"] = split_units
            if max_distance is not None:
                params["maxDistance"] = max_distance
            if max_distance_units is not None:
                params["maxDistanceUnits"] = max_distance_units
            if bounding_polygon_layer is not None:
                params["boundingPolygonLayer"] = bounding_polygon_layer
            if source_database is not None:
                params["sourceDatabase"] = source_database
            if generalize is not None:
                params["generalize"] = generalize
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)
        gpjob = self._tbx.trace_downstream(
            input_layer=input_layer,
            split_distance=split_distance,
            split_units=split_units,
            max_distance=max_distance,
            max_distance_units=max_distance_units,
            bounding_polygon_layer=bounding_polygon_layer,
            source_database=source_database,
            generalize=generalize,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()

    # ----------------------------------------------------------------------
    def summarize_nearby(
        self,
        sum_nearby_layer,
        summary_layer,
        near_type="StraightLine",
        distances=[],
        units="Meters",
        time_of_day=None,
        time_zone_for_time_of_day="GeoLocal",
        return_boundaries=True,
        sum_shape=True,
        shape_units=None,
        summary_fields=[],
        group_by_field=None,
        minority_majority=False,
        percent_shape=False,
        output_name=None,
        context=None,
        estimate=False,
        future=False,
    ):
        """
        The ``summarize_nearby`` method finds features that are within a specified distance of features in the input layer.
        Distance can be measured as a straight-line distance, a drive-time distance (for example, within 10 minutes), or a
        drive distance (within 5 kilometers). Statistics are then calculated for the nearby features. For example:

        * Calculate the total population within five minutes of driving time of a proposed new store location.
        * Calculate the number of freeway access ramps within a one-mile driving distance of a proposed new store location to use as a measure of
        store accessibility.

        =========================   ====================================================================================================================
        **Parameter**               **Description**
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        sum_nearby_layer            Required feature layer. Point, line, or polygon features from which distances will be measured to features in the ``summary_layer``. See :ref:`Feature Input<FeatureInput>`.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        summary_layer               Required layer. Point, line, or polygon features. Features in this layer that are within the specified distance to features in the ``sum_nearby_layer`` will be summarized. See :ref:`Feature Input<FeatureInput>`.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        near_type                   Optional string.
                                    Defines what kind of distance measurement you want to use, either straight-line distance, travel
                                    time or travel distance along a street network using various modes of transportation known as travel modes.
                                    The default is ``StraightLine``.

                                    Choice list:

                                    * ``StraightLine``,
                                    * ``Driving Distance``,
                                    * ``Driving Time``,
                                    * ``Rural Driving Distance``,
                                    * ``Rural Driving Time``,
                                    * ``Trucking Distance``,
                                    * ``Trucking Time``,
                                    * ``Walking Distance``,
                                    * ``Walking Time``
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        distances                   Optional list of float values. Defines the search distance for 'StraightLine' and distance-based travel modes, or time
                                    duration for time-based travel modes. You can enter single or multiple values, separating each value with a space.
                                    Features that are within (or equal to) the distances you enter will be summarized. The unit for `distances` is
                                    supplied by the units parameter.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        units                       Optional string. If :attr:`near_type` is `StraightLine` or a distance-based travel mode, this is the linear unit to be
                                    used with the distance value(s) specified in distances.

                                    Choice list:
                                    | [``Meters``, ``Kilometers``, ``Feet``, ``Yards``, ``Miles``]

                                    If ``near_type`` is a time-based travel mode, the following values can be used as units:

                                    Choice list:

                                    | [``Seconds``, ``Minutes``, ``Hours``]

                                    The default is 'Meters'.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        time_of_day                 Optional datetime.datetime. Specify whether travel times should consider traffic conditions. To use traffic in the analysis,
                                    set ``near_type`` to a travel mode object whose impedance_attribute_name property is set to travel_time and assign a value
                                    to ``time_of_day``. (A travel mode with other impedance_attribute_name values don't support traffic.) The ``time_of_day`` value represents
                                    the time at which travel begins, or departs, from the origin points. The time is specified as datetime.datetime.

                                    The service supports two kinds of traffic: typical and live. Typical traffic references travel speeds that are made up of historical
                                    averages for each five-minute interval spanning a week. Live traffic retrieves speeds from a traffic feed that processes phone probe
                                    records, sensors, and other data sources to record actual travel speeds and predict speeds for the near future.

                                    The `data coverage <http://www.arcgis.com/home/webmap/viewer.html?webmap=b7a893e8e1e04311bd925ea25cb8d7c7>`_ page shows the countries
                                    Esri currently provides traffic data for.

                                    Typical Traffic:

                                    To ensure the task uses typical traffic in locations where it is available, choose a time and day of the week, and then convert the day
                                    of the week to one of the following dates from 1990:

                                    * Monday - 1/1/1990
                                    * Tuesday - 1/2/1990
                                    * Wednesday - 1/3/1990
                                    * Thursday - 1/4/1990
                                    * Friday - 1/5/1990
                                    * Saturday - 1/6/1990
                                    * Sunday - 1/7/1990
                                    Set the time and date as datetime.datetime.

                                    For example, to solve for 1:03 p.m. on Thursdays, set the time and date to 1:03 p.m., 4 January 1990; and convert to
                                    datetime eg. datetime.datetime(1990, 1, 4, 1, 3).

                                    Live Traffic:

                                    To use live traffic when and where it is available, choose a time and date and convert to datetime.

                                    Esri saves live traffic data for 4 hours and references predictive data extending 4 hours into the future. If the time and date you
                                    specify for this parameter is outside the 24-hour time window, or the travel time in the analysis continues past the predictive data window,
                                    the task falls back to typical traffic speeds.

                                    Examples:
                                    from datetime import datetime

                                    * ``time_of_day``- datetime(1990, 1, 4, 1, 3) # 13:03, 4 January 1990. Typical traffic on Thursdays at 1:03 p.m.
                                    * ``time_of_day``- datetime(1990, 1, 7, 17, 0) # 17:00, 7 January 1990. Typical traffic on Sundays at 5:00 p.m.
                                    * ``time_of_day``- datetime(2014, 10, 22, 8, 0) # 8:00, 22 October 2014. If the current time is between 8:00 p.m., 21 Oct. 2014 and 8:00 p.m., 22 Oct. 2014,
                                    live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
                                    * ``time_of_day``- datetime(2015, 3, 18, 10, 20) # 10:20, 18 March 2015. If the current time is between 10:20 p.m., 17 Mar. 2015 and 10:20 p.m., 18 Mar. 2015,
                                    live traffic speeds are referenced in the analysis; otherwise, typical traffic speeds are referenced.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        time_zone_for_time_of_day   Optional string. Specify the time zone or zones of the ``time_of_day`` parameter.

                                    Choice list: ['GeoLocal', 'UTC']

                                    GeoLocal-refers to the time zone in which the originsLayer points are located.

                                    UTC-refers to Coordinated Universal Time.

                                    The default is 'GeoLocal'.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        return_boundaries           Optional boolean. If true, the ``result_layer`` will contain areas defined by the specified ``near_type``. For example, if using 'StraightLine' of 5 miles,
                                    the ``result_layer`` will contain areas with a 5 mile radius around the input ``sum_nearby_layer`` features.

                                    If False, the ``result_ayer`` will contain the same features as the ``sum_nearby_layer``.

                                    The default is True.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        sum_shape                   Optional boolean. A boolean value that instructs the task to calculate statistics based on shape type of the ``summary_layer``,
                                    such as the length of lines or areas of polygons of the ``summary_layer`` within each polygon in ``sum_within_layer``.

                                    The default is True.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        shape_units                 Optional string. If ``sum_shape`` is true, you must specify the units of the shape summary.
                                    Values:

                                    * When ``summary_layer`` contains polygons: Values: ['Acres', 'Hectares', 'SquareMeters', 'SquareKilometers', 'SquareFeet', 'SquareYards', 'SquareMiles']
                                    * When ``summary_layer`` contains lines: Values: ['Meters', 'Kilometers', 'Feet', 'Yards', 'Miles']
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        summary_fields              Optional list of strings.A list of field names and statistical summary types that you want to calculate.
                                    Note that the count is always returned by default.

                                    fieldName is the name of one of the numeric fields found in the input join layer.

                                    statisticType is one of the following:

                                    * ``SUM``-Adds the total value of all the points in each polygon
                                    * ``MEAN``-Calculates the average of all the points in each polygon
                                    * ``MIN``-Finds the smallest value of all the points in each polygon
                                    * ``MAX``-Finds the largest value of all the points in each polygon
                                    * ``STDDEV``-Finds the standard deviation of all the points in each polygon

                                    Example: ["fieldName summaryType","fieldName summaryType", ...]
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        group_by_field              Optional string. This is a field of the ``summary_layer`` features that you can use to calculate statistics separately for each unique attribute value.
                                    For example, suppose the ``summary_layer`` contains point locations of businesses that store hazardous materials, and one of the fields is HazardClass
                                    containing codes that describe the type of hazardous material stored. To calculate summaries by each unique value of HazardClass, use HazardClass as
                                    the ``group_by_field`` field.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        minority_majority           Optional boolean. This boolean parameter is applicable only when a ``group_by_field`` is specified. If true, the minority (least dominant) or the
                                    majority (most dominant) attribute values for each group field within each nearby area are calculated. Two new fields are added to
                                    the ``result_layer`` prefixed with `Majority_` and `Minority_`.

                                    The default is False.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        percent_shape               Optional boolean. This Boolean parameter is applicable only when a ``group_by_field`` is specified. If set to true,
                                    the percentage of each unique ``group_by_field`` value is calculated for each ``sum_nearby_layer`` feature.

                                    The default is False.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        output_name                 Optional string or :class:`~arcgis.features.FeatureLayer`. Existing
                                    feature layer will cause the new layer to be appended to the Feature Service.
                                    If overwrite is True in context, new layer will overwrite existing layer.
                                    If output_name not indicated then new :class:`~arcgis.features.FeatureCollection` created.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        context                     Optional dict. Additional settings such as processing extent and output spatial reference.
                                    For summarize_nearby, there are three settings.

                                    - ``extent`` - a bounding box that defines the analysis area. Only those features in the input_layer that intersect the bounding box will be analyzed.
                                    - ``outSR`` - the output features will be projected into the output spatial reference referred to by the `wkid`.
                                    - ``overwrite`` - if True, then the feature layer in output_name will be overwritten with new feature layer. Available for ArcGIS Online or Enterprise 10.9.1+

                                        .. code-block:: python

                                            # Example Usage
                                            context = {"extent": {"xmin": 3164569.408035,
                                                                "ymin": -9187921.892449,
                                                                "xmax": 3174104.927313,
                                                                "ymax": -9175500.875353,
                                                                "spatialReference":{"wkid":102100,"latestWkid":3857}},
                                                        "outSR": {"wkid": 3857},
                                                        "overwrite": True}
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        estimate                    Optional boolean. Returns the number of credit for the operation.
        -------------------------   --------------------------------------------------------------------------------------------------------------------
        future                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================   ====================================================================================================================

        :return: result_layer : :class:`~arcgis.features.FeatureLayer` if output_name is specified, else :class:`~arcgis.features.FeatureCollection` dictionary.

            dict with the following keys:

                "result_layer" : layer (FeatureCollection)

                "group_by_summary" : layer (FeatureCollection)

        """

        task = "SummarizeNearby"

        sum_nearby_layer = self._feature_input(sum_nearby_layer)
        summary_layer = self._feature_input(summary_layer)
        if self._gis.version >= [9, 2] or self._gis._is_agol:
            overwrite = context.pop("overwrite", False) if context else False
        else:
            # Remove if in context but default to False in all cases.
            overwrite = context.pop("overwrite", False) if context else False
            overwrite = False
        output_name = self._output_name_dict(output_name, overwrite)

        if estimate:
            params = {}
            if sum_nearby_layer:
                params["sumNearbyLayer"] = sum_nearby_layer
            if summary_layer:
                params["summaryLayer"] = summary_layer
            if near_type is not None:
                params["nearType"] = near_type
            params["distances"] = distances
            if units is not None:
                params["units"] = units
            if time_of_day is not None:
                params["timeOfDay"] = time_of_day
            if time_zone_for_time_of_day is not None:
                params["timeZoneForTimeOfDay"] = time_zone_for_time_of_day
            if return_boundaries is not None:
                params["returnBoundaries"] = return_boundaries
            if sum_shape is not None:
                params["sumShape"] = sum_shape
            if shape_units is not None:
                params["shapeUnits"] = shape_units
            if summary_fields is not None:
                params["summaryFields"] = summary_fields
            if group_by_field is not None:
                params["groupByField"] = group_by_field
            if minority_majority is not None:
                params["minorityMajority"] = minority_majority
            if percent_shape is not None:
                params["percentShape"] = percent_shape
            if output_name is not None:
                params["outputName"] = output_name
            if context is not None:
                params["context"] = context
            from arcgis.features._credits import _estimate_credits

            return _estimate_credits(task=task, parameters=params)

        gpjob = self._tbx.summarize_nearby(
            sum_nearby_layer=sum_nearby_layer,
            summary_layer=summary_layer,
            near_type=near_type,
            distances=distances,
            units=units,
            time_of_day=time_of_day,
            time_zone_for_time_of_day=time_zone_for_time_of_day,
            return_boundaries=return_boundaries,
            sum_shape=sum_shape,
            shape_units=shape_units,
            summary_fields=summary_fields,
            group_by_field=group_by_field,
            minority_majority=minority_majority,
            percent_shape=percent_shape,
            output_name=output_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_fa = True
        if future:
            return gpjob
        return gpjob.result()


###########################################################################
class _PackagingTools(object):
    """Exposes the Publishing Geoprocessing tools"""

    _gptbx = None
    _url = None
    _gis = None
    _properties = None

    # ----------------------------------------------------------------------
    def __init__(self, url, gis, verbose=False):
        """initializer"""
        if gis is None:
            gis = arcgis.env.active_gis
        if url is None:
            url = gis.properties["helperServices"]["packaging"]["url"]
        self._url = url
        self._gis = gis
        self._con = gis._con
        self._verbose = verbose

    # ----------------------------------------------------------------------
    def _refresh(self):
        params = {"f": "json"}
        try:
            dictdata = self._con.post(self._url, params)
        except:
            dictdata = self._con.get(self._url, params)
        self._properties = PropertyMap(dictdata)

    # ----------------------------------------------------------------------
    @property
    def properties(self):
        """returns the services properties"""
        if self._properties is None:
            self._refresh()
        return self._properties

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbx is None:
            self._gptbx = import_toolbox(
                url_or_item=self._url, gis=self._gis, verbose=self._verbose
            )
        return self._gptbx

    # ----------------------------------------------------------------------
    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._con.token)

    # ----------------------------------------------------------------------
    @property
    def _tools(self):
        return self.properties.tasks

    # ----------------------------------------------------------------------
    def create_map_area(
        self,
        map_item_id,
        bookmark=None,
        extent=None,
        area_type="BOOKMARK",
        area=None,
        output_name=None,
        gis=None,
        future=False,
    ):
        """
        The Create Map Area task is used to create a map area item from a
        web map with a given extent in ArcGIS Online or ArcGIS Enterprise.
        Web map needs to be enabled for offline use with an Offline type
        keyword on the item. A maximum of 16 map areas can be created from
        a web map. This tool adds a Map2Area relationship between the web
        map and output map area item. A web map can only have one map area
        for a given extent.

        The task is only available to the owner of the web map and
        organization administrators.

        ========================   ====================================================================
        **Parameter**               **Description**
        ------------------------   --------------------------------------------------------------------
        map_item_id                    Required Item. The ID/Item of the web map item.
        ------------------------   --------------------------------------------------------------------
        bookmark                   Optional String. Map extent specified using a bookmark from the map,
                                   or extent parameter can be used instead.  **DEPRECATED AT ARCGIS 10.6.1+**
        ------------------------   --------------------------------------------------------------------
        extent                     Optional Envelope. Map extent specified using a JSON envelope object
                                   to create output item. Extent is required specified using either
                                   bookmark or extent. If both are available, extent will take
                                   precedence over bookmark.  **DEPRECATED AT ARCGIS 10.6.1+**
        ------------------------   --------------------------------------------------------------------
        area_type                  Required String. Specifies what type of area to use. Can be set to
                                   bookmark, envelope, or polygon.
        ------------------------   --------------------------------------------------------------------
        area                       Required String/Object. Species the map area using an envelope, a
                                   polygon, or the name of a bookmark.

                                   Bookmark example:

                                   {
                                    "name" : "Redlands bookmark"
                                   }

                                   Envelope example:

                                   {
                                      "spatialReference" : {"latestWkid" : 3857, "wkid" : 102100},
                                      "xmin" : 10782717.18820468,
                                      "ymin" : 5024701.169341451,
                                      "xmax" : 13016716.535830744,
                                      "ymax" : 6818423.383336087
                                   }

                                   Polygon example:

                                   {
                                      "spatialReference" : {"latestWkid":3857, "wkid":102100},
                                      "rings" : [[[-1091028.5893342558,5441435.281784553],
                                                [-155031.7985004736,5428390.228011981],
                                                [480924.27683202364,5118565.274308054],
                                                [-1009495.8089271132,4365201.923529557],
                                                [-1123641.8209299697,4733730.514956484],
                                                [-1091028.5893342558,5441435.281784553]]]
                                   }

        ------------------------   --------------------------------------------------------------------
        output_name                Optional Dict. A JSON object that contains information specified for
                                   the output item including title, snippet, tags, description,
                                   folderId, and packageRefreshSchedule.

                                   packageRefreshSchedule defines a schedule for keeping the map area
                                   up to date for download and is defined using a standard cron
                                   expression (the maximum frequency cannot be set to less than
                                   daily). If createPkgDeltas is enabled it also defines how often
                                   scheduled updates are created and available for download.

                                   For example, if the packageRefreshSchedule is once per week, then
                                   the package is updated with changes since the last update on a
                                   weekly basis. If createPkgDeltas is also enabled each update will
                                   contain one weeks worth of changes.

                                   The output item is created in the same folder as the web map by default.

                                   Example:

                                    {
                                      "title": "Highland Earthquake",
                                      "folderId": "bd259c0a138d45189ad71ba06fe0097d"
                                      "packageRefreshSchedule" : "0 0 12 ? * MON *"
                                    }
        ------------------------   --------------------------------------------------------------------
        gis                        Optional GIS.  The connection object to the WebGIS site.
        ------------------------   --------------------------------------------------------------------
        future                     Optional Boolean.  If true, the operation will occur in an asynchronous manner.
        ========================   ====================================================================

        :return: ToolOutput or GPJob

        """
        import uuid

        if output_name is None:
            output_name = {}
            output_name["title"] = uuid.uuid4().hex
        if gis is None and self._gis:
            gis = self._gis
        elif gis is None and self._gis is None:
            gis = arcgis.env.active_gis
        if isinstance(map_item_id, arcgis.gis.Item):
            map_item_id = map_item_id.itemid
        inputs = locals()
        function_args = {}
        for key in self._tbx.create_map_area.__annotations__.keys():
            if key in inputs:
                function_args[key] = inputs[key]
            del key
        function_args["future"] = True
        function_args["gis"] = gis
        res = self._tbx.create_map_area(**function_args)
        if future:
            return res
        return res.result()

    # ----------------------------------------------------------------------
    def refresh_map_area_package(self, packages, gis=None, future=False):
        """
        The Refresh Map Area Package task refreshes existing map area packages to update them with changes made on the source data since the last time those packages were created or refreshed.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        packages                                                                    List Items.  A JSON array consisting of packages that need to be refreshed, specified with the ID of each package item. Packages can belong to different map areas. A package item should only be listed if you are the owner of the package item or organization administrators.
        =========================================================================   ===========================================================================

        :return: Job, ToolOutput or Dict

        """
        res = []
        if gis is None and self._gis:
            gis = self._gis
        elif gis is None and self._gis is None:
            gis = arcgis.env.active_gis
        if isinstance(packages, (tuple, list)):
            for package in packages:
                if isinstance(package, Item):
                    res.append({"itemId": package.itemid})
                elif isinstance(package, str):
                    res.append({"itemId": package})
            res = json.dumps(res)
        else:
            res = packages
        job = self._tbx.refresh_map_area_package(packages=res, gis=gis, future=True)
        if future:
            return job
        return job.result()

    # ----------------------------------------------------------------------
    def setup_map_area(
        self,
        map_area_item_id,
        map_layers_to_ignore=None,
        tile_services=None,
        feature_services=None,
        gis=None,
        future=False,
    ):
        """
        The Setup Map Area task creates packages for each service in the web map within the map area extent. The tool generates tile packages (.tpk or .vtpk) for exportTilesAllowed tiled services (i.e. basemap layer) and SQLite Geodatabase format replica packages for sync enabled feature services (i.e. operational layers). It adds an Area2Package relationship between the map area item and created package items.

        Existing package related to a map area will remain unchanged after running the task a second time as long as its corresponding layer remain existed in the web map and therefore a valid map layer. For example, if a web map is updated with a feature layer removed after a package is created for that layer using the GP tool, the package will be removed the next time executing it since its corresponding layer no longer exists in the web map thus becomes invalid.

        This task is available for the map area item owner and organization administrators in ArcGIS Online and ArcGIS Enterprise.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        map_area_item_id                                                            Required String. Map area item ID, created by the Create Map Area tool.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        map_layers_to_ignore                                                        Optional List. A list of individual layers, specified with their service URLs, in the map to ignore. The task generates packages for all map layers by default.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        tile_services                                                               Optional List. An array of JSON objects that contains additional export tiles enabled tile services for which tile packages (.tpk or .vtpk) need to be created. Each tile service is specified with its URL and desired level of details.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        feature_services                                                            Optional List. An array of JSON objects that contains additional sync enabled feature services for which replicas need to be created. It can also be used to override layer definition query in the map. Each feature service is specified with its url, layers, layerQueries, returnAttachments, attachmentsSyncDirection, syncModel, syncDirection, and createPkgDeltas properties. See create replica for details of each.

                                                                                    Example:

                                                                                    [
                                                                                      {
                                                                                        "url":"https://services.arcgis.com/ERmEceOGq5cHrItq/arcgis/rest/services/LandUse/FeatureServer",
                                                                                        "layers":[0,1],
                                                                                        "returnAttachments":false,
                                                                                        "attachmentsSyncDirection":"upload",
                                                                                        "syncModel":"perLayer"
                                                                                      }
                                                                                    ]
                                                                                    Feature services support scheduled updates (version 10.7.1 and higher). Scheduled updates allow apps to download server side changes based on a fixed schedule. This is to support download-only workflows where you only need a copy of the data for reference even if the service supports editing. It is not intended for workflows where clients make edits and sync. If createPkgDeltas is configured, apps downloading the map area can periodically download changes from the server based on a schedule.

                                                                                    The maxDeltaAge property of createPkgDeltas is used to set the number of days that individual deltas are kept on the server and are available for download (the default is 5 days). If an app goes too long without downloading updates they may be required to re-download the full map area again.

                                                                                    Example:

                                                                                    [
                                                                                      {
                                                                                        "url": "https://services.arcgis.com/ERmEceOGq5cHrItq/arcgis/rest/services/LandUse/FeatureServer",
                                                                                        "layers": [0,1],
                                                                                        "returnAttachments": false,
                                                                                        "attachmentsSyncDirection": "upload",
                                                                                        "syncModel": "perLayer",
                                                                                        "createPkgDeltas": {
                                                                                          "maxDeltaAge": 5
                                                                                        }
                                                                                      }
                                                                                    ]
        =========================================================================   ===========================================================================

        :return: Job, ToolOutput or Dict


        """
        inputs = locals()

        function_args = {}
        for key in self._tbx.setup_map_area.__annotations__.keys():
            if key in inputs:
                function_args[key] = inputs[key]
            del key
        function_args["future"] = True
        function_args["gis"] = gis
        job = self._tbx.setup_map_area(**function_args)
        if future:
            return job
        return job.result()


###########################################################################
class _HydrologyTool:
    """Exposes the Orthmapping Geoprocessing tools"""

    _gptbx = None
    _url = None
    _gis = None
    _properties = None
    _return_item = None

    # ----------------------------------------------------------------------
    def __init__(self, url, gis, verbose=False):
        """initializer"""
        if gis is None:
            gis = arcgis.env.active_gis
        if url is None:
            url = gis.properties.helperServices["hydrology"]["url"]
        self._url = url
        self._gis = gis
        self._con = gis._con
        self._verbose = verbose

    # ----------------------------------------------------------------------
    def _refresh(self):
        params = {"f": "json"}
        try:
            dictdata = self._con.post(self._url, params)
        except:
            dictdata = self._con.get(self._url, params)
        self._properties = PropertyMap(dictdata)

    # ----------------------------------------------------------------------
    @property
    def properties(self):
        """returns the services properties"""
        if self._properties is None:
            self._refresh()
        return self._properties

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbx is None:
            self._gptbx = import_toolbox(
                url_or_item=self._url, gis=self._gis, verbose=self._verbose
            )
            self._gptbx._is_ra = True
        return self._gptbx

    # ----------------------------------------------------------------------
    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._con.token)

    # ----------------------------------------------------------------------
    @property
    def _tools(self):
        return self.properties.tasks

    # ----------------------------------------------------------------------
    def _evaluate_spatial_input(self, input_points):
        """
        Helper function to determine if the input is either a FeatureSet or Spatially Enabled DataFrame, and
        output to FeatureSet for subsequent processing.
        :param input_points: FeatureSet or Spatially Enabled DataFrame
        :return: FeatureSet
        """
        from arcgis.features import FeatureSet
        from arcgis.features.geo._accessor import _is_geoenabled
        from pandas import DataFrame

        if isinstance(input_points, FeatureSet):
            return input_points

        elif isinstance(input_points, DataFrame) and _is_geoenabled(input_points):
            return input_points.spatial.to_featureset()

        elif isinstance(input_points, DataFrame) and not _is_geoenabled(input_points):
            raise Exception(
                (
                    "input_points is a DataFrame, but does not appear to be spatially enabled. "
                    "Using the <df>.spatial.set_geometry(col, sr=None) may help. (https://esri.github.io/arcgis-p"
                    "ython-api/apidoc/html/arcgis.features.toc.html#arcgis.features.GeoAccessor.set_geometry)"
                )
            )

        else:
            raise Exception(
                "input_points must be either a FeatureSet or Spatially Enabled DataFrame instead of {}".format(
                    type(input_points)
                )
            )

    # ----------------------------------------------------------------------
    def trace_downstream(
        self,
        input_points,
        point_id_field=None,
        data_source_resolution=None,
        generalize=False,
        gis=None,
        future=False,
    ):
        """

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        input_points                                                                Required FeatureSet. The point features used for calculating watersheds. These are referred to as pour points, because it is the location at which water pours out of the watershed.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        point_id_field                                                              Optional String. The field used to identify to the input points.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        data_source_resolution                                                      Optional String. Keyword indicating the source data that will be used in the analysis.

                                                                                    The keyword is an approximation of the spatial resolution of the digital
                                                                                    elevation model used to build the foundation hydrologic database. Since many
                                                                                    elevation sources are distributed with units of arc seconds, we provide an
                                                                                    approximation in meters for easier understanding.

                                                                                    Values : The values for this parameter are:

                                                                                      - `None` : The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data. This is the default.
                                                                                      - `Finest` : Finest resolution available at each location from all possible data sources.
                                                                                      - `10m` : The hydrologic source was built from 1/3 arc second - approximately 10 meter resolution, elevation data.
                                                                                      - `30m` : The hydrologic source was built from 1 arc second - approximately 30 meter resolution, elevation data.
                                                                                      - `90m` : The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        generalize                                                                  Optional Boolean. Determines if the output watersheds will be smoothed into
                                                                                    simpler shapes or conform to the cell edges of the original DEM.

                                                                                      - `True` : The polygons will be smoothed into simpler shapes. This is the default.
                                                                                      - `False` : The edge of the polygons will conform to the edges of the original DEM.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return:


        """
        tool = self._tbx.trace_downstream
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))
        if point_id_field is None:
            point_id_field = defaults["point_id_field"]
        if data_source_resolution is None:
            data_source_resolution = defaults["data_source_resolution"]
        if generalize is None:
            generalize = defaults["generalize"]
        input_points = self._evaluate_spatial_input(input_points=input_points)
        job = tool(
            input_points=input_points,
            point_id_field=point_id_field,
            data_source_resolution=data_source_resolution,
            generalize=generalize,
            gis=gis,
            future=True,
        )
        if future:
            return job
        return job.result()

    # ----------------------------------------------------------------------
    def watershed(
        self,
        input_points,
        point_id_field=None,
        snap_distance=None,
        snap_distance_units=None,
        data_source_resolution=None,
        generalize=False,
        return_snapped_points=True,
        gis=None,
        future=False,
    ):
        """
        The 'watershed' task is used to identify catchment areas based on a particular
        location you provide and ArcGIS Online Elevation data.


        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        input_points                                                                Required FeatureSet. The point features used for calculating watersheds. These are referred to as pour points, because it is the location at which water pours out of the watershed.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        point_id_field                                                              Optional String. The field used to identify to the input points.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        snap_distance                                                               Optional Double. The maximum distance to move the location of an input point.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        snap_distance_units                                                         Optional String. The linear units specified for the snap distance. The
                                                                                    values for this parameter are: Meters, Kilometers, Feet, Yards, or Miles
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        data_source_resolution                                                      Optional String. Keyword indicating the source data that will be used in the analysis.

                                                                                    The keyword is an approximation of the spatial resolution of the digital
                                                                                    elevation model used to build the foundation hydrologic database. Since many
                                                                                    elevation sources are distributed with units of arc seconds, we provide an
                                                                                    approximation in meters for easier understanding.

                                                                                    Values : The values for this parameter are:

                                                                                      - `None` : The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data. This is the default.
                                                                                      - `Finest` : Finest resolution available at each location from all possible data sources.
                                                                                      - `10m` : The hydrologic source was built from 1/3 arc second - approximately 10 meter resolution, elevation data.
                                                                                      - `30m` : The hydrologic source was built from 1 arc second - approximately 30 meter resolution, elevation data.
                                                                                      - `90m` : The hydrologic source was built from 3 arc second - approximately 90 meter resolution, elevation data.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        generalize                                                                  Optional Boolean. Determines if the output watersheds will be smoothed into
                                                                                    simpler shapes or conform to the cell edges of the original DEM.

                                                                                      - `True` : The polygons will be smoothed into simpler shapes. This is the default.
                                                                                      - `False` : The edge of the polygons will conform to the edges of the original DEM.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        return_snapped_points                                                       Optional Boolean. Determines if a point feature at the watershed's pour
                                                                                    point will be returned. If snapping is enabled, this might not be the same
                                                                                    as the input point.

                                                                                      - `True` : A point feature will be returned. This is the default.
                                                                                      - `False` : No point features will be returned.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return:

        """
        tool = self._tbx.watershed
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))
        if data_source_resolution is None:
            data_source_resolution = defaults["data_source_resolution"]
        if snap_distance_units is None:
            snap_distance_units = defaults["snap_distance_units"]
        input_points = self._evaluate_spatial_input(input_points=input_points)
        job = tool(
            input_points=input_points,
            point_id_field=point_id_field,
            snap_distance=snap_distance,
            snap_distance_units=snap_distance_units,
            data_source_resolution=data_source_resolution,
            generalize=generalize,
            return_snapped_points=return_snapped_points,
            gis=self._gis,
            future=True,
        )
        if future:
            return job
        return job.result()


###########################################################################
class _OrthoMappingTools:
    """Exposes the Orthmapping Geoprocessing tools"""

    _gptbx = None
    _url = None
    _gis = None
    _properties = None
    _return_item = None

    # ----------------------------------------------------------------------
    def __init__(self, url, gis, verbose=False):
        """initializer"""
        if gis is None:
            gis = arcgis.env.active_gis
        if url is None:
            url = gis.properties.helperServices["orthoMapping"]["url"]
        self._url = url
        self._gis = gis
        self._con = gis._con
        self._verbose = verbose

    # ----------------------------------------------------------------------
    def _refresh(self):
        params = {"f": "json"}
        try:
            dictdata = self._con.post(self._url, params)
        except:
            dictdata = self._con.get(self._url, params)
        self._properties = PropertyMap(dictdata)

    # ----------------------------------------------------------------------
    @property
    def properties(self):
        """returns the services properties"""
        if self._properties is None:
            self._refresh()
        return self._properties

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbx is None:
            self._gptbx = import_toolbox(
                url_or_item=self._url, gis=self._gis, verbose=self._verbose
            )
            self._gptbx._is_ra = True
        return self._gptbx

    # ----------------------------------------------------------------------
    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._con.token)

    # ----------------------------------------------------------------------
    @property
    def _tools(self):
        return self.properties.tasks

    # ----------------------------------------------------------------------
    def _create_output_image_service(
        self, output_name, task, folder=None, output_properties=None
    ):
        gis = self._gis
        ok = gis.content.is_service_name_available(output_name, "Image Service")
        if not ok:
            raise RuntimeError(
                "An Image Service by this name already exists: " + output_name
            )

        create_parameters = {
            "name": output_name,
            "description": "",
            "capabilities": "Image, Metadata",
            "properties": {"path": "@", "description": "", "copyright": ""},
        }

        tiles_only = None

        if output_properties is not None:
            if "tiles_only" in output_properties:
                tiles_only = output_properties["tiles_only"]

        if tiles_only is None:
            if gis._con._product == "AGOL":
                create_parameters["capabilities"] = "Image, Metadata, TilesOnly"
        else:
            if gis._con._product == "AGOL":
                if isinstance(tiles_only, bool):
                    if tiles_only == True:
                        create_parameters["capabilities"] = "Image, Metadata, TilesOnly"

        output_service = gis.content.create_service(
            output_name,
            create_params=create_parameters,
            service_type="imageService",
            folder=folder,
        )
        description = "Image Service generated from running the " + task + " tool."
        item_properties = {
            "description": description,
            "tags": "Analysis Result, " + task,
            "snippet": "Analysis Image Service generated from " + task,
        }
        output_service.update(item_properties)
        return output_service

    # ----------------------------------------------------------------------
    def _set_image_collection_param(self, image_collection, param_name=None):
        if isinstance(image_collection, str):
            # doesnotexist = gis.content.is_service_name_available(image_collection, "Image Service")
            # if doesnotexist:
            # raise RuntimeError("The input image collection does not exist")
            if "http:" in image_collection or "https:" in image_collection:
                return json.dumps({"url": image_collection})
            else:
                return json.dumps({"uri": image_collection})
        elif isinstance(image_collection, Item):
            return json.dumps({"itemId": image_collection.itemid})
        else:
            if param_name is not None:
                raise TypeError(
                    str(param_name) + " should be a string (url or uri) or Item"
                )
            else:
                raise TypeError(
                    "image_collection should be a string (url or uri) or Item"
                )

        return image_collection

    # ----------------------------------------------------------------------
    def _set_output_raster(self, output_name, task, output_properties=None):
        gis = self._gis
        output_service = None
        output_raster = None

        task_name = task

        folder = None
        folderId = None

        if output_properties is not None:
            if "folder" in output_properties:
                folder = output_properties["folder"]
        if folder is not None:
            user = gis.properties.user.username
            if isinstance(folder, dict):
                if "id" in folder and "title" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                folderId = gis._portal.get_folder_id(user, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, user)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]

        if output_name is None:
            output_name = str(task_name) + "_" + _id_generator()
            output_service = self._create_output_image_service(
                output_name,
                task,
                folder=folder,
                output_properties=output_properties,
            )
            output_raster = {
                "serviceProperties": {
                    "name": output_service.name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        elif isinstance(output_name, str):
            output_service = self._create_output_image_service(
                output_name,
                task,
                folder=folder,
                output_properties=output_properties,
            )
            output_raster = {
                "serviceProperties": {
                    "name": output_service.name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        elif isinstance(output_name, arcgis.gis.Item):
            output_service = output_name
            output_raster = {"itemProperties": {"itemId": output_service.itemid}}
        else:
            raise TypeError("output_raster should be a string (service name) or Item")

        if folderId is not None:
            output_raster["itemProperties"].update({"folderId": folderId})
        output_raster = json.dumps(output_raster)
        return output_raster, output_service

    # ----------------------------------------------------------------------

    def alter_processing_states(
        self,
        image_collection,
        new_states=None,
        gis=None,
        future=False,
        **kwargs,
    ):
        """
        The `alter_processing_states` operation is a service tool that sets the processing states of
        a mosaic dataset. The states are stored as the Ortho Mapping key property. The state's
        content is a JSON dictionary that includes several properties that can be set based on the
        processes run on the image collection.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        new_states                                                                  Required Dict. The new states that are set on the image collection. Supported properties are adjustment, dem, gcp, seamlines, and colorcorrection.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================
        """
        gis = self._gis
        image_collection = self._set_image_collection_param(
            image_collection=image_collection
        )

        new_states_allowed_values = [
            "blockadjustment",
            "dem",
            "gcp",
            "seamlines",
            "colorcorrection",
            "adjust_index",
            "imagetype",
        ]

        for key in new_states:
            if not key in new_states_allowed_values:
                raise RuntimeError(
                    "new_states can only be one of the following: "
                    + str(new_states_allowed_values)
                )

        job = self._tbx.alter_processing_states(
            image_collection=image_collection,
            new_states=new_states,
            gis=gis,
            future=True,
        )
        job._is_ortho = True
        omjob = OMJob(job)
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def compute_color_correction(
        self,
        image_collection,
        color_correction_method=None,
        dodging_surface=None,
        target_image=None,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """

        The `compute_color_correction` operation is a service tool that computes color corrections
        for the mosaic dataset, usually after the mosaic has been block adjusted. Images that are
        used for generating orthoimage mosaics typically have color correction computed.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        color_correction_method                                                     Optional String. The method used to compute color correction. The following are the color correction options:

                                                                                      - Dodging: Changes each pixel's value toward a target color. With this option, you must also choose the type of target color surface, which affects the target color. Dodging tends to give the best result in most cases.
                                                                                      - Histogram: Changes each pixel's value according to its relationship with a target histogram. The target histogram can be derived from all the rasters, or you can specify a single target raster. This option works well when all the rasters have a similar histogram.
                                                                                      - Standard_Deviation: Changes each pixel's value according to its relationship with the histogram of the target raster, within one standard deviation. The standard deviation can be calculated from all the rasters in the mosaic dataset, or you can specify a target raster. This option works best when all the rasters have normal distributions.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        dodging_surface                                                             Optional String. When Dodging is used, each pixel needs a target color, which is determined by the surface type. The following are the available options:

                                                                                      - Single_Color: All the pixels are altered toward a single color point, the average of all pixels. Use this option when there are a small number of raster datasets and a few types of ground objects. If there are too many raster datasets or too many types of ground features, the output color may become blurred.
                                                                                      - Color_Grid: The pixels are altered toward multiple target colors, which are distributed across the mosaic dataset. Use this option when you have a large number of raster datasets, or areas with a large number of diverse ground objects.
                                                                                      - First_Order: All pixels are altered toward many points obtained from the two-dimensional polynomial slanted plane. This option tends to create a smooth color change and uses less storage in the auxiliary table, but it may take longer to process compared to the Color_Grid surface.
                                                                                      - Second_Order: All input pixels are altered toward a set of multiple points obtained from the two-dimensional polynomial parabolic surface. This option tends to create smoother color changes and uses less storage in the auxiliary table, but it may take longer to process compared to the Color_Grid surface.
                                                                                      - Third_Order: All input pixels are altered toward multiple points obtained from the cubic surface. This option tends to create smoother color changes and uses less storage in the auxiliary table, but it may take longer to process compared to the Color_Grid surface.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        target_image                                                                Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Contains additional settings that allow you to customize the statistics computation settings.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.compute_color_correction

        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if color_correction_method is None:
            color_correction_method = defaults["color_correction_method"]
        else:
            color_correction_allowed_values = (
                self._tbx.choice_list.compute_color_correction[
                    "color_correction_method"
                ]
            )
            if [element.lower() for element in color_correction_allowed_values].count(
                color_correction_method.lower()
            ) <= 0:
                raise RuntimeError(
                    "color_correction_method can only be one of the following: "
                    + str(color_correction_allowed_values)
                )
            for element in color_correction_allowed_values:
                if color_correction_method.lower() == element.lower():
                    color_correction_method = element

        if dodging_surface is None:
            dodging_surface = defaults["dodging_surface"]
        else:
            dodging_surface_type_allowed_values = (
                self._tbx.choice_list.compute_color_correction["dodging_surface"]
            )
            if [
                element.lower() for element in dodging_surface_type_allowed_values
            ].count(dodging_surface.lower()) <= 0:
                raise RuntimeError(
                    "dodging_surface_type can only be one of the following:  "
                    + str(dodging_surface_type_allowed_values)
                )
            for element in dodging_surface_type_allowed_values:
                if dodging_surface.lower() == element.lower():
                    dodging_surface = element

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )
        if target_image:
            target_image = self._set_image_collection_param(
                image_collection=target_image, param_name="target_image"
            )

        job = tool(
            image_collection=image_collection,
            color_correction_method=color_correction_method,
            dodging_surface=dodging_surface,
            target_image=target_image,
            context=context,
            gis=gis,
            future=True,
        )
        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def compute_control_points(
        self,
        image_collection,
        reference_image=None,
        image_location_accuracy=None,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `compute_control_points` operation is a service tool that's used to compute matching control points between images in an image collection, and for matching control points between the image collection's images and the reference image.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        reference_image                                                             Optional String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_location_accuracy                                                     Optional String. Allows you to specify the location accuracy of your image. This option allows you to specify the GPS location accuracy level of the source image. It determines how far the tool will search for neighboring matching images for calculating tie points and block adjustments. The following are the available options:

                                                                                     - High: GPS accuracy of 0 to 10 meters, and the tool uses a maximum of 4 by 3 images.
                                                                                     - Medium: GPS accuracy of 10 to 20 meters, and the tool uses a maximum of 4 by 6 images.
                                                                                     - Low: GPS accuracy of 20 to 50 meters, and the tool uses a maximum of 4 by 12 images.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Contains additional environment settings (pointSimilarity, pointDensity, pointDistribution) that affect the generation of output control points.

                                                                                      - pointSimilarity: Sets LOW, MEDIUM, or HIGH tolerance for computing control points with varying levels of potential error. LOW tolerance will produce the most control point, but may have a higher level of error. HIGH tolerance will produce the least number of control point, but each matching pair will have a lower level of error. MEDIUM tolerance will set the similarity tolerance to medium.
                                                                                      - pointDensity: Sets the number of tie points (LOW, MEDIUM, or HIGH), to be created. LOW point density will create the fewest number of tie points. MEDIUM point density will create a moderate number of tie points. HIGH point density will create the highest number of tie points.
                                                                                      - pointDistribution: Randomly generates points that are better for overlapping areas with irregular shapes. RANDOM will generate points that are better for overlapping areas with irregular shapes. REGULAR will generate points based on a fixed pattern and uses the point density to determine how frequently to create points.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.compute_control_points

        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_location_accuracy is None:
            image_location_accuracy = defaults["image_location_accuracy"]
        else:
            image_location_accuracy_allowed_values = (
                self._tbx.choice_list.compute_control_points["image_location_accuracy"]
            )
            if [
                element.lower() for element in image_location_accuracy_allowed_values
            ].count(image_location_accuracy.lower()) <= 0:
                raise RuntimeError(
                    "location_accuracy can only be one of the following:"
                    + str(image_location_accuracy_allowed_values)
                )
            for element in image_location_accuracy_allowed_values:
                if image_location_accuracy.lower() == element.lower():
                    image_location_accuracy = element

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        if reference_image is not None:
            reference_image = self._set_image_collection_param(
                image_collection=reference_image,
                param_name="reference_image",
            )

        job = tool(
            image_collection=image_collection,
            reference_image=reference_image,
            image_location_accuracy=image_location_accuracy,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def compute_seamlines(
        self,
        image_collection,
        seamlines_method,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """

        The `compute_seamlines` operation can be used to compute seamlines between overlapping
        images comprising an image collection, usually after the image collection is block adjusted.
        Seamlines-used to generate the seamless, mosaicked dispaly of overlapped images in a image
        collection-are only computed for candidates that will eventually be used for generating the
        resulting mosaicked orthoimage.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        seamlines_method                                                            Required String. The method for generating the image collection's seamlines. The supported methods are:

                                                                                      - VORONOI - Generates seamlines using the area Voronoi diagram.
                                                                                      - DISPARITY - Generates seamlines based on the disparity images of stereo pairs. This method can avoid seamlines cutting through buildings.
                                                                                      - GEOMETRY - Generates seamlines for overlapping areas based on the intersection of footprints. Areas with no overlapping imagery will merge the footprints. This is the default.
                                                                                      - RADIOMETRY - Generates seamlines based on the spectral patterns of features within the imagery.
                                                                                      - EDGE_DETECTION - Generates seamlines over intersection areas based on the edges of features in the area.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Contains additional settings that allow you to customize the generation of seamlines. These additional settings include:

                                                                                      - minRegionSize - Any seamline polygons smaller than this specified threshold will be removed in the seamline result.
                                                                                      - pixelSize - Generates seamlines for raster datasets that fall within the specified spatial resolution size.
                                                                                      - blendType - Determine how to blend one image into another (Both, Inside, or Outside) over the seamlines. Inside blends pixels inside the seamline, while Outside blends outside the seamline. Both will blend pixels on either side of the seamline.
                                                                                      - blendWidth - Specifies how many pixels will be blended relative to the seamline. Blending (feathering) occurs along a seamline between pixels of overlapping images.
                                                                                      - blendUnit - Specifies the unit of measurement for blendWidth. Pixels measures using the number of pixels, and Ground measures using the same units as the image collection.
                                                                                      - requestSizeType - Sets the units for requestSize. Pixels modifies requestSize based on the pixel size. This resamples the closest image based on the raster pixel size. Pixel scaling factor modifiers requestSize by specifying a scaling factor. This operation resamples the closest image by multiplying the raster pixel size with the pixel size factor.
                                                                                      - requestSize - Specifies the number of columns and rows for resampling. Though the maximum value is 5,000, this value can increase or decreased based on the complexity of your raster data. A greater image resolution provides more detail in the raster dataset but increases the processing time.
                                                                                      - minThinnessRatio - Defines how thin a polygon can be before its considered a sliver. This is based on a scale from 0 to 1.0, where a value of 0.0 represents a polygon that's almost a straight line, and a value of 1.0 represents a polygon that's a circle.
                                                                                      - maxSliverSize - Defines how large a Sliver can be before its considered a polygon. This uses the same scale as minThinnessRatio.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Dictionary

        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.compute_seamlines
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        seamlines_method_allowed_values = self._tbx.choice_list.compute_seamlines[
            "seamlines_method"
        ]
        if [element.lower() for element in seamlines_method_allowed_values].count(
            seamlines_method.lower()
        ) <= 0:
            raise RuntimeError(
                "seamlines_method can only be one of the following: "
                + str(seamlines_method_allowed_values)
            )
        for element in seamlines_method_allowed_values:
            if seamlines_method.lower() == element.lower():
                seamlines_method = element

        job = tool(
            image_collection=image_collection,
            seamlines_method=seamlines_method,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def compute_sensor_model(
        self,
        image_collection,
        mode="Quick",
        location_accuracy="High",
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `compute_sensor_model` operation is a service that computes the bundle block adjustment for the image collection and applies the frame transformation to the images. It also generates the control point, solution, solution points, and flight path tables, though these tables are not published as portal items.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        mode                                                                        Optional String. The bundle block adjustment mode keyword. It is used when the image collection type is UAV/UAS or UNKNOWN at the same time that the block adjustment status is Raw or Quick. The following modes are supported:

                                                                                      - Quick: Computes tie points and adjustments at one-eighth of the source imagery resolution.
                                                                                      - Full: Adjusts the images using the Quick mode solution at the full resolution of the source imagery.
                                                                                      - Refine: Computes tie points and adjustments of the source imagery at full resolution.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        location_accuracy                                                           Optional String. This parameter allows you to specify the GPS location accuracy level of the source image. The following options determine how far the tool will search for neighboring matching images for calculating tie points and block adjustments:

                                                                                      - High: GPS accuracy is 0 to 10 meters, and the tool uses a maximum of 4 by 3 images.
                                                                                      - Medium: GPS accuracy of 10 to 20 meters, and the tool uses a maximum of 4 by 6 images.
                                                                                      - Low: GPS accuracy of 20 to 50 meters, and the tool uses a maximum of 4 by 12 images.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Used to configure additional client settings for block adjustments.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.compute_sensor_model
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        mode_allowed_values = self._tbx.choice_list.compute_sensor_model["mode"]
        if [element.lower() for element in mode_allowed_values].count(
            mode.lower()
        ) <= 0:
            raise RuntimeError(
                "mode can only be one of the following: " + str(mode_allowed_values)
            )
        for element in mode_allowed_values:
            if mode.lower() == element.lower():
                mode = element

        location_accuracy_allowed_values = self._tbx.choice_list.compute_sensor_model[
            "location_accuracy"
        ]
        if [element.lower() for element in location_accuracy_allowed_values].count(
            location_accuracy.lower()
        ) <= 0:
            raise RuntimeError(
                "location_accuracy can only be one of the following: "
                + str(location_accuracy_allowed_values)
            )
        for element in location_accuracy_allowed_values:
            if location_accuracy.lower() == element.lower():
                location_accuracy = element

        job = tool(
            image_collection=image_collection,
            mode=mode,
            location_accuracy=location_accuracy,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def edit_control_points(
        self,
        image_collection,
        input_control_points,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """

        The `edit_control_points` is a service tool that is used to append additional
        ground control points set to the image collection's control points. A complete
        ground control point (GCP) set should have one GCP and more than three tie
        points. `edit_control_points` can also be used to edit tie point sets. The input
        control points' JSON will always replace the points in the tie points table if
        the point Id already exists.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        input_control_points                                                        Required List. The existing GCP sets written as a JSON object. The control point sets
                                                                                    provided in this tool will replace the control points in the existing control points
                                                                                    table if they already exist.

                                                                                    Syntax: The schema of the control points' JSON object follows the schema of the
                                                                                    mosaic dataset control point table. The control points must contain one geometry
                                                                                    and one attribute set. Two types of control points are allowed, determined by the
                                                                                    type attribute: tie points or ground control points.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Used to configure additional client settings for block adjustments.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.edit_control_points
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        input_control_points = json.dumps(input_control_points)

        job = tool(
            image_collection=image_collection,
            input_control_points=input_control_points,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def generate_dem(
        self,
        image_collection,
        cell_size,
        output_dem=None,
        surface_type=None,
        matching_method="SGM",
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """

        The `generate_dem` operation is a service tool that allows you to generate
        DEM products from a previously adjusted image collection. It creates point
        clouds using adjusted image collections and interpolates the point clouds
        to create DEM surface products using the designated method.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        cell_size                                                                   Optional Integer. The cell size of the output digital elevation model image.
                                                                                    This is a single numeric input. The cell size unit will be the unit used by
                                                                                    the image collection's spatial reference. Only square cell sizes are supported.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        output_name                                                                 Optional String. The service name of the resulting DEM.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        surface_type                                                                Optional String. The output surface type of the digital elevation model.
                                                                                    The following are the available choices:

                                                                                      - `DTM` - Digital Terrain Model : The elevation is bare earth only and does not include structures above the surface.
                                                                                      - `DSM` - Digital Surface Model : The elevation includes bare earth and features above the surface - for example, buildings, vegetation, bridges, and utility towers.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        matching_method                                                             Optional String. The method used to generate 3D points.

                                                                                    The following are the available options:

                                                                                      - `ETM`: A feature-based stereo matching method that uses the Harris operator to detect feature points. It's recommended for DTM generation.
                                                                                      - `SGM`: Produces more points and detail than the ETM method. It's suitable for generating a DSM for urban areas. This is more computationally intensive than the ETM method.
                                                                                      - `MVM`: Based on the SGM matching method followed by a fusion step in which the redundant depth estimations across single stereo model are merged. It produces dense 3D points and is computationally efficient.

        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Used to configure additional client settings for block adjustments.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        task = "GenerateDEM"
        from arcgis.raster._util import _id_generator

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.generate_dem
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        if surface_type is None:
            surface_type = defaults["surface_type"]
        else:
            surface_type_allowed_values = self._tbx.choice_list.generate_dem[
                "surface_type"
            ]
            if [element.lower() for element in surface_type_allowed_values].count(
                surface_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "surface_type can only be one of the following: "
                    + str(surface_type_allowed_values)
                )
            for element in surface_type_allowed_values:
                if surface_type.lower() == element.lower():
                    surface_type = element

        if matching_method is None:
            matching_method = defaults["matching_method"]
        else:
            matching_method_allowed_values = self._tbx.choice_list.generate_dem[
                "matching_method"
            ]
            if [element.lower() for element in matching_method_allowed_values].count(
                matching_method.lower()
            ) <= 0:
                raise RuntimeError(
                    "matching_method can only be one of the following: "
                    + str(matching_method_allowed_values)
                )
            for element in matching_method_allowed_values:
                if matching_method.lower() == element.lower():
                    matching_method = element

        if isinstance(output_dem, Item):
            output_dem = json.dumps({"itemId": output_dem.itemid})
        elif isinstance(output_dem, str):
            if ("/") in output_dem or ("\\") in output_dem:
                if "http:" in output_dem or "https:" in output_dem:
                    output_dem = json.dumps({"url": output_dem})
                else:
                    output_dem = json.dumps({"uri": output_dem})
            else:
                result = gis.content.search(
                    "title:" + str(output_dem), item_type="Imagery Layer"
                )
                output_dem_result = None
                for element in result:
                    if str(output_dem) == element.title:
                        output_dem_result = element
                if output_dem_result is not None:
                    output_dem = json.dumps({"itemId": output_dem_result.itemid})
                else:
                    output_dem, output_service = self._set_output_raster(
                        output_name=output_dem,
                        task=task,
                        output_properties=kwargs,
                    )

        job = tool(
            image_collection=image_collection,
            cell_size=cell_size,
            output_dem=output_dem,
            surface_type=surface_type,
            matching_method=matching_method,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        job._item_properties = True
        item = None
        if output_dem:
            item = output_dem

        omjob = OMJob(job, item=item)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def generate_orthomosaic(
        self,
        image_collection,
        output_ortho_image=None,
        regen_seamlines=None,
        recompute_color_correction=None,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `generate_orthomosaic` is a service tool that's used to generate a single orthorectified, mosaicked image from an image collection after the block adjustment.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        output_ortho_image                                                          Optional String. The name of the service to create.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        regen_seamlines                                                             Optional Boolean. Specifies whether seamlines are applied before the
                                                                                    orthomosaic image generation. The seamlines are regenerated if this flag is
                                                                                    turned on. You can set the seamline options through the context parameter.
                                                                                    If the seamline generation options are not set, the default is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        recompute_color_correction                                                  Optional Boolean. Specifies whether color correction settings are applied
                                                                                    to the output orthoimage. Color correction is recomputed if this option is
                                                                                    turned on. You can configure the compute color correction settings through
                                                                                    the context parameter. If there is no color collection setting, the default
                                                                                    is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Used to configure additional client settings for block adjustments.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        task = "GenerateOrthoMosaic"

        from arcgis.raster._util import _id_generator

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.generate_orthomosaic
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if regen_seamlines is None:
            regen_seamlines = defaults["regen_seamlines"]
        else:
            if not isinstance(regen_seamlines, bool):
                raise TypeError("The 'regen_seamlines' parameter must be a boolean")

        if recompute_color_correction is None:
            recompute_color_correction = defaults["recompute_color_correction"]
        else:
            if not isinstance(recompute_color_correction, bool):
                raise TypeError(
                    "The 'recompute_color_correction' parameter must be a boolean"
                )

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        if isinstance(output_ortho_image, Item):
            output_ortho_image = json.dumps({"itemId": output_ortho_image.itemid})
        elif isinstance(output_ortho_image, str):
            if ("/") in output_ortho_image or ("\\") in output_ortho_image:
                if "http:" in output_ortho_image or "https:" in output_ortho_image:
                    output_ortho_image = json.dumps({"url": output_ortho_image})
                else:
                    output_ortho_image = json.dumps({"uri": output_ortho_image})
            else:
                result = gis.content.search(
                    "title:" + str(output_ortho_image),
                    item_type="Imagery Layer",
                )
                output_ortho_image_result = None
                for element in result:
                    if str(output_ortho_image) == element.title:
                        output_ortho_image_result = element
                if output_ortho_image_result is not None:
                    output_ortho_image = json.dumps(
                        {"itemId": output_ortho_image_result.itemid}
                    )
                else:
                    (
                        output_ortho_image,
                        output_service,
                    ) = self._set_output_raster(
                        output_name=output_ortho_image,
                        task=task,
                        output_properties=kwargs,
                    )

        job = tool(
            image_collection=image_collection,
            output_ortho_image=output_ortho_image,
            regen_seamlines=regen_seamlines,
            recompute_color_correction=recompute_color_correction,
            context=context,
            gis=gis,
            future=True,
        )

        job._is_ortho = True
        job._item_properties = True
        item = None
        if output_ortho_image:
            item = output_ortho_image

        omjob = OMJob(job, item=item)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def generate_report(
        self,
        image_collection,
        report_format=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """

        The `generate_report` is a tool that generates an Ortho Mapping report with an image
        collection that has been block adjusted. The report contains information about the
        quality of the adjusted images, the distribution of the control points, and more. The
        output of this service tool is a downloadable file.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        report_format                                                               Optional String. The response format.  This can be `HTML` or `PDF`.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        tool = self._tbx.generate_report
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))

        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        if report_format is None:
            report_format = defaults["report_format"]
        else:
            report_format_allowed_values = self._tbx.choice_list.generate_report[
                "report_format"
            ]
            if [element.lower() for element in report_format_allowed_values].count(
                report_format.lower()
            ) <= 0:
                raise RuntimeError(
                    "report_format can only be one of the following: "
                    + str(report_format_allowed_values)
                )
            for element in report_format_allowed_values:
                if report_format.lower() == element.lower():
                    report_format = element

        job = tool(
            image_collection=image_collection,
            report_format=report_format,
            gis=gis,
            future=True,
        )
        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def get_processing_states(self, image_collection, gis=None, future=False, **kwargs):
        """
        The `get_processing_states` obtains the processing states of the image
        collection (mosaic dataset) in JSON.


        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple


        """
        gis = self._gis
        tool = self._tbx.get_processing_states
        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )
        job = tool(image_collection=image_collection, gis=gis, future=True)
        job._is_ortho = True
        omjob = OMJob(job)
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def match_control_points(
        self,
        image_collection,
        control_points,
        similarity=None,
        context=None,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `match_control_points` is a tool that takes a collection of ground control points in JSON as input, and at least on of the ground control points has matching tie points. The service will compute the remaining matching tie points.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        control_points                                                              Require List. The ground control points (GCP) sets written as a JSON object. The similarity tolerance for finding control points will be low.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        similarity                                                                  Optional String. Sets the similarity tolerance level for your matching control point. Low tolerance will produce the most control points, but some may have a higher level of error. Medium tolerance will produce a medium amount of control points. High tolerance will produce the least number of control points, but each matching pair will have a lower level of error. High tolerance is the default value.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        context                                                                     Optional Dict. Used to configure additional client settings for block adjustments.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple



        """
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        tool = self._tbx.match_control_points
        defaults = dict(zip(tool.__annotations__.keys(), tool.__defaults__))
        if image_collection:
            image_collection = self._set_image_collection_param(
                image_collection=image_collection
            )

        control_points = json.dumps(control_points)
        if similarity is None:
            similarity = defaults["similarity"]
        else:
            similarity_allowed_values = self._tbx.choice_list.match_control_points[
                "similarity"
            ]
            if [element.lower() for element in similarity_allowed_values].count(
                similarity.lower()
            ) <= 0:
                raise RuntimeError(
                    "similarity can only be one of the following: "
                    + str(similarity_allowed_values)
                )
            for element in similarity_allowed_values:
                if similarity.lower() == element.lower():
                    similarity = element

        job = tool(
            image_collection=image_collection,
            input_control_points=control_points,
            similarity=similarity,
            context=context,
            gis=gis,
            future=True,
        )
        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def query_camera_info(self, camera_query=None, gis=None, future=False, **kwargs):
        """
        The `query_camera_info` queries specific records or the entire digital camera database.
        The digital camera database contains the specifications of digital camera sensors
        that are used to capture drone images.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        query                                                                       Optional String. This is a SQL query statement that can be used to filter a portion of the digital camera database.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: GPJob or Pandas' DataFrame

        """
        gis = self._gis
        if camera_query is not None:
            if not isinstance(camera_query, str):
                raise TypeError("The 'camera_query' parameter must be of type string")

        job = self._tbx.query_camera_info(query=camera_query, gis=gis, future=True)
        job._is_ortho = True
        omjob = OMJob(job)
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def query_control_points(
        self,
        image_collection,
        where,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `query_control_points` allows users to use a SQL query to query certain control
        points from the image collection's control point table.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        where                                                                       Required String. This is a SQL query statement that can be used to filter a portion of the digital camera database.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        if not isinstance(where, str):
            raise TypeError("The 'query' parameter must be of type string")

        image_collection = self._set_image_collection_param(
            image_collection=image_collection
        )
        job = self._tbx.query_control_points(
            image_collection=image_collection,
            where=where,
            gis=gis,
            future=True,
        )
        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()

    # ----------------------------------------------------------------------
    def reset_image_collection(
        self,
        image_collection,
        gis=None,
        future=False,
        flight_json_details=None,
        **kwargs,
    ):
        """
        The `reset_image_collection` resets the image collection to its original state.
        Resetting the image collection includes removing the block adjustment that's
        computed for the images, rebuilding the footprints, and removing seamlines and
        mosaic candidates. The image collection can be adjusted during the Ortho Mapping
        workflow. If you're not satisfied with the result, you can clear any existing
        adjustment settings and return the images to an unadjusted state.

        =========================================================================   ===========================================================================
        **Parameter**                                                                **Description**
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        image_collection                                                            Required String/Item.  The image collection Item or URL to the service endpoint.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        gis                                                                         Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        -------------------------------------------------------------------------   ---------------------------------------------------------------------------
        future                                                                      Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        =========================================================================   ===========================================================================

        :return: Named Tuple

        """
        gis = self._gis
        image_collection = self._set_image_collection_param(
            image_collection=image_collection
        )
        job = self._tbx.reset_image_collection(
            image_collection=image_collection, gis=gis, future=True
        )
        job._is_ortho = True
        omjob = OMJob(job)
        omjob._flight_details = flight_json_details
        if future:
            return omjob
        return omjob.result()


###########################################################################
class _RasterAnalysisTools(BaseAnalytics):
    """FA Tools"""

    _gptbx = None
    _url = None
    _gis = None
    _properties = None
    _return_item = None

    # ----------------------------------------------------------------------
    def __init__(self, url, gis, verbose=False):
        """initializer"""
        self._url = url
        self._gis = gis
        self._con = gis._con
        self._verbose = verbose

    # ----------------------------------------------------------------------
    def _refresh(self):
        params = {"f": "json"}
        try:
            dictdata = self._con.post(self._url, params)
        except:
            dictdata = self._con.get(self._url, params)
        self._properties = PropertyMap(dictdata)

    # ----------------------------------------------------------------------
    @property
    def properties(self):
        """returns the services properties"""
        if self._properties is None:
            self._refresh()
        return self._properties

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbx is None:
            self._gptbx = import_toolbox(
                url_or_item=self._url, gis=self._gis, verbose=self._verbose
            )
            self._gptbx._is_ra = True
        return self._gptbx

    # ----------------------------------------------------------------------
    @property
    def _current_version(self):
        if "currentVersion" in self._gis._tools.rasteranalysis.properties.keys():
            return self._gis._tools.rasteranalysis.properties["currentVersion"]
        else:
            return None

    # ----------------------------------------------------------------------
    def __str__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def __repr__(self):
        return '<%s url:"%s">' % (type(self).__name__, self._url)

    # ----------------------------------------------------------------------
    def invoke(self, method, **kwargs):
        """Invokes the specified method on this service passing in parameters from the kwargs name-value pairs"""
        url = self._url + "/" + method
        params = {"f": "json"}
        if len(kwargs) > 0:
            for k, v in kwargs.items():
                params[k] = v
                del k, v
        return self._con.post(path=url, postdata=params, token=self._con.token)

    @property
    def _tools(self):
        return self.properties.tasks

    # --Helper Method --------------------------------------------------------------------
    def _create_output_feature_service(
        self,
        output_name,
        output_service_name="Analysis feature service",
        task="RasterAnalytics",
        folder=None,
    ):
        gis = self._gis
        ok = gis.content.is_service_name_available(output_name, "Feature Service")
        if not ok:
            raise RuntimeError(
                "A Feature Service by this name already exists: " + output_name
            )

        createParameters = {
            "currentVersion": 10.2,
            "serviceDescription": "",
            "hasVersionedData": False,
            "supportsDisconnectedEditing": False,
            "hasStaticData": True,
            "maxRecordCount": 2000,
            "supportedQueryFormats": "JSON",
            "capabilities": "Query",
            "description": "",
            "copyrightText": "",
            "allowGeometryUpdates": False,
            "syncEnabled": False,
            "editorTrackingInfo": {
                "enableEditorTracking": False,
                "enableOwnershipAccessControl": False,
                "allowOthersToUpdate": True,
                "allowOthersToDelete": True,
            },
            "xssPreventionInfo": {
                "xssPreventionEnabled": True,
                "xssPreventionRule": "InputOnly",
                "xssInputRule": "rejectInvalid",
            },
            "tables": [],
            "name": output_service_name.replace(" ", "_"),
        }

        output_service = gis.content.create_service(
            output_name,
            create_params=createParameters,
            service_type="featureService",
            folder=folder,
        )
        description = "Feature Service generated from running the " + task + " tool."
        item_properties = {
            "description": description,
            "tags": "Analysis Result, " + task,
            "snippet": output_service_name,
        }
        output_service.update(item_properties)
        return output_service

    def _layer_input(self, input_layer):
        # Will be used exclusively by RA tools
        input_param = input_layer

        url = ""
        from arcgis.raster import Raster

        if isinstance(input_layer, Raster):
            if hasattr(input_layer, "_engine_obj"):
                input_layer = input_layer._engine_obj
        if isinstance(input_layer, Item):
            if input_layer.type == "Image Collection":
                input_param = {"itemId": input_layer.itemid}
            else:
                if "layers" in input_layer:
                    input_param = input_layer.layers[0]._lyr_dict
                    try:
                        if (
                            isinstance(input_param, dict)
                            and "url" in input_param.keys()
                        ):
                            url = input_param["url"]
                            if "token" not in url:
                                from arcgis.raster.functions.utility import (
                                    _generate_layer_token,
                                )

                                token = _generate_layer_token(input_layer, url)
                                if token is not None:
                                    if input_layer.type == "Feature Service":
                                        input_param.update({"serviceToken": token})
                                    else:
                                        url = input_param["url"] + "?token=" + token
                                input_param.update({"url": url})
                    except:
                        pass
                else:
                    raise TypeError("No layers in input layer Item")

        elif isinstance(input_layer, arcgis.features.FeatureLayerCollection):
            input_param = input_layer.layers[0]._lyr_dict

        elif isinstance(input_layer, arcgis.features.FeatureCollection):
            input_param = input_layer.properties

        elif isinstance(input_layer, arcgis.gis.Layer):
            input_param = input_layer._lyr_dict
            from arcgis.raster import ImageryLayer
            import json

            if isinstance(input_layer, ImageryLayer) or isinstance(input_layer, Raster):
                if "options" in input_layer._lyr_json:
                    if isinstance(
                        input_layer._lyr_json["options"], str
                    ):  # sometimes the rendering info is a string
                        # load json
                        layer_options = json.loads(input_layer._lyr_json["options"])
                    else:
                        layer_options = input_layer._lyr_json["options"]

                    if "imageServiceParameters" in layer_options:
                        # get renderingRule and mosaicRule
                        input_param.update(layer_options["imageServiceParameters"])

                try:
                    if isinstance(input_param, dict) and "url" in input_param.keys():
                        url = input_param["url"]
                        if "token" not in url:
                            from arcgis.raster.functions.utility import (
                                _generate_layer_token,
                            )

                            token = _generate_layer_token(input_layer, url)
                            if token is not None:
                                url = input_param["url"] + "?token=" + token
                            input_param.update({"url": url})
                except:
                    pass

        elif isinstance(input_layer, dict):
            input_param = input_layer

        elif isinstance(input_layer, str):
            if "http:" in input_layer or "https:" in input_layer:
                input_param = {"url": input_layer}
            else:
                input_param = {"uri": input_layer}

        else:
            raise Exception(
                "Invalid format of input layer. url string, layer Item, layer instance or dict supported"
            )

        if "url" in input_param:
            url = input_param["url"]
            if "/RasterRendering/" in url:
                url = input_layer._uri
                if "renderingRule" in input_param.keys():
                    input_param.update({"function": input_param["renderingRule"]})
                    input_param.pop("renderingRule", None)
                input_param.update({"uri": url})
                input_param.pop("url", None)
                input_param.pop("type", None)
                input_param.pop("uses_gbl", None)
                input_param.pop("raster", None)
                return input_param

        if "ImageServer" in url or "MapServer" in url:
            if "serviceToken" in input_param and "token" not in url:
                url = url + "?token=" + input_param["serviceToken"]
                input_param.update({"url": url})

        return input_param

    def _set_output_raster(self, output_name, task, output_properties=None):
        gis = self._gis
        output_service = None
        output_raster = None

        if task == "GenerateRaster":
            task_name = "GeneratedRasterProduct"
        else:
            task_name = task

        folder = None
        folderId = None

        if output_properties is not None:
            if "folder" in output_properties:
                folder = output_properties["folder"]
        if folder is not None:
            user = gis.properties.user.username
            if isinstance(folder, dict):
                if "id" in folder and "title" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                folderId = gis._portal.get_folder_id(user, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, user)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]

        if output_name is None:
            output_name = str(task_name) + "_" + _id_generator()
            output_service = self._create_output_image_service(
                output_name,
                task,
                folder=folder,
                output_properties=output_properties,
            )
            output_raster = {
                "serviceProperties": {
                    "name": output_service.name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        elif isinstance(output_name, str):
            output_service = self._create_output_image_service(
                output_name,
                task,
                folder=folder,
                output_properties=output_properties,
            )
            output_raster = {
                "serviceProperties": {
                    "name": output_service.name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        elif isinstance(output_name, arcgis.gis.Item):
            output_service = output_name
            output_raster = {"itemProperties": {"itemId": output_service.itemid}}
        else:
            raise TypeError("output_raster should be a string (service name) or Item")

        if folderId is not None:
            output_raster["itemProperties"].update({"folderId": folderId})
        output_raster = json.dumps(output_raster)
        return output_raster, output_service

    def _set_output_feature(self, output_name, task, output_properties=None):
        gis = self._gis
        output_feature = None
        output_service = None

        folder = None
        folderId = None

        if output_properties is not None:
            if "folder" in output_properties:
                folder = output_properties["folder"]
        if folder is not None:
            user = gis.properties.user.username
            if isinstance(folder, dict):
                if "id" in folder and "title" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                folderId = gis._portal.get_folder_id(user, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, user)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]

        if output_name is None or isinstance(output_name, str):
            if output_name is None:
                output_name = f"{str(task)}_{_id_generator()}"
            output_service = self._create_output_feature_service(
                output_name=output_name,
                output_service_name=output_name,
                task=task,
                folder=folder,
            )
            output_feature = {
                "serviceProperties": {
                    "name": output_service.name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        elif isinstance(output_name, Item):
            output_service = None
            output_feature = {
                "serviceProperties": {
                    "name": output_name.name,
                    "serviceUrl": output_name.url,
                },
                "itemProperties": {"itemId": output_name.itemid},
            }
        else:
            raise TypeError("output_name must be a string (service name) or Item.")

        if folderId is not None:
            output_feature["itemProperties"].update({"folderId": folderId})
        output_feature = json.dumps(output_feature)
        return output_feature, output_service

    def _set_image_collection_param(self, image_collection):
        if isinstance(image_collection, str):
            # doesnotexist = gis.content.is_service_name_available(image_collection, "Image Service")
            # if doesnotexist:
            # raise RuntimeError("The input image collection does not exist")
            if "http:" in image_collection or "https:" in image_collection:
                return json.dumps({"url": image_collection})
            else:
                return json.dumps({"uri": image_collection})
        elif isinstance(image_collection, Item):
            return json.dumps({"itemId": image_collection.itemid})
        else:
            raise TypeError("image_collection should be a string (url or uri) or Item")

        return image_collection

    def _create_output_image_service(
        self, output_name, task, folder=None, output_properties=None
    ):
        gis = self._gis

        ok = gis.content.is_service_name_available(
            output_name.replace(" ", "_"), "Image Service"
        )
        if not ok:
            raise RuntimeError(
                "An Image Service by this name already exists: " + output_name
            )

        create_parameters = {
            "name": output_name.replace(" ", "_"),
            "description": "",
            "capabilities": "Image, Metadata",
            "properties": {"path": "@", "description": "", "copyright": ""},
        }

        tiles_only = None

        if output_properties is not None:
            if "tiles_only" in output_properties:
                tiles_only = output_properties["tiles_only"]

        if tiles_only is None:
            if gis._con._product == "AGOL":
                create_parameters["capabilities"] = "Image, Metadata, TilesOnly"
        else:
            if gis._con._product == "AGOL":
                if isinstance(tiles_only, bool):
                    if tiles_only == True:
                        create_parameters["capabilities"] = "Image, Metadata, TilesOnly"

        output_service = gis.content.create_service(
            output_name,
            create_params=create_parameters,
            service_type="imageService",
            folder=folder,
            item_properties={"title": output_name},
        )
        if output_service is None:
            raise RuntimeError("Unable to create service")
        description = "Image Service generated from running the " + task + " tool."
        item_properties = {
            "description": description,
            "tags": "Analysis Result, " + task,
            "snippet": "Analysis Image Service generated from " + task,
        }
        output_service.update(item_properties)
        return output_service

    def _build_param_dictionary(
        self,
        input_rasters,
        raster_type_name,
        raster_type_params=None,
        image_collection_properties=None,
        use_input_rasters_by_ref=False,
        upload_properties=None,
        task=None,
    ):
        gis = self._gis
        input_raster_specified = False
        input_rasters_dict = {}
        raster_type_dict = {}
        upload_rasters_list = []
        items_on_server = False
        # input rasters

        # parse input and convert to url if it is imagery layer
        from arcgis.raster import ImageryLayer

        if isinstance(input_rasters, ImageryLayer):
            input_rasters = input_rasters.url
        elif isinstance(input_rasters, list):
            for pos, raster in enumerate(input_rasters):
                if isinstance(raster, ImageryLayer):
                    input_rasters[pos] = raster.url

        if isinstance(input_rasters, str):
            if os.path.exists(input_rasters):
                input_rasters = [input_rasters]
        if isinstance(input_rasters, list):
            # extract the IDs of all the input items
            # and then convert the list to JSON
            item_id_list = []
            url_list = []
            uri_list = []
            for item in input_rasters:
                if isinstance(item, Item):
                    item_id_list.append(item.itemid)
                elif isinstance(item, str):
                    if "http:" in item or "https:" in item:
                        if "blob.core" in item or all(
                            blob_string in item
                            for blob_string in [
                                "stg-arcgisazure",
                                "arcgis.com",
                            ]
                        ):
                            uri_list.append(item)
                        else:
                            url_list.append(item)
                    elif os.path.exists(item):
                        upload_rasters_list.append(item)
                    else:
                        uri_list.append(item)
            if upload_rasters_list != []:
                from arcgis.raster._util import (
                    _upload_imagery_agol,
                    _upload_imagery_enterprise,
                )

                if gis._con._product == "AGOL":
                    single_image = False
                    if raster_type_name is None:
                        single_image = True
                    if (
                        isinstance(raster_type_name, str)
                    ) and raster_type_name == "mosaic_dataset":
                        uri_list, md_data_info = _upload_imagery_agol(
                            upload_rasters_list,
                            gis,
                            upload_properties=upload_properties,
                            single_image=single_image,
                            raster_type=raster_type_name,
                            task=task,
                        )
                    else:
                        uri_list = _upload_imagery_agol(
                            upload_rasters_list,
                            gis,
                            upload_properties=upload_properties,
                            single_image=single_image,
                            raster_type=raster_type_name,
                            task=task,
                        )
                else:
                    item_id_list = _upload_imagery_enterprise(
                        upload_rasters_list, raster_type_name, gis
                    )
                    items_on_server = True

            if len(item_id_list) > 0:
                input_rasters_dict = {"itemIds": item_id_list}
                if items_on_server:
                    input_rasters_dict.update({"itemsOnServer": True})
                input_raster_specified = True
            elif len(url_list) > 0:
                if raster_type_name.lower() == "tiled imagery layer":
                    input_rasters_dict = {"tiled_urls": url_list}
                    raster_type_name = "Raster Dataset"
                else:
                    input_rasters_dict = {"urls": url_list}
                input_raster_specified = True
            elif len(uri_list) > 0:
                input_rasters_dict = {"uris": uri_list}
                input_raster_specified = True
        elif isinstance(input_rasters, str):
            # the input_rasters is a folder name; try and extract the folderID
            owner = gis.properties.user.username
            folderId = gis._portal.get_folder_id(owner, input_rasters)
            if folderId is None:
                if "http:" in input_rasters or "https:" in input_rasters:
                    if "blob.core" in input_rasters or all(
                        blob_string in input_rasters
                        for blob_string in ["stg-arcgisazure", "arcgis.com"]
                    ):
                        input_rasters_dict = {"uri": input_rasters}
                    elif raster_type_name.lower() == "tiled imagery layer":
                        input_rasters_dict = {"tiled_url": input_rasters}
                        raster_type_name = "Raster Dataset"
                    else:
                        input_rasters_dict = {"url": input_rasters}
                else:
                    input_rasters_dict = {"uri": input_rasters}
            else:
                input_rasters_dict = {"folderId": folderId}
            input_raster_specified = True

        if input_raster_specified is False:
            raise RuntimeError(
                "Input raster list to be added to the collection must be specified"
            )
        else:
            if use_input_rasters_by_ref:
                input_rasters_dict.update({"byref": True})

        # raster_type
        if raster_type_name is not None:
            if not isinstance(raster_type_name, str):
                raise RuntimeError("Invalid input raster_type parameter")

        elevation_set = 0
        if raster_type_params is not None:
            for element in raster_type_params.keys():
                if element.lower() == "constantz":
                    value = raster_type_params[element]
                    del raster_type_params[element]
                    raster_type_params.update({"ConstantZ": value})

                    elevation_set = 1
                    break
                elif element.lower() == "averagezdem":
                    value = raster_type_params[element]
                    del raster_type_params[element]
                    raster_type_params.update({"averagezdem": value})
                    elevation_set = 1
                    break

            if elevation_set == 0:
                if "orthomappingElevation" in gis.properties.helperServices.keys():
                    raster_type_params["averagezdem"] = gis.properties.helperServices[
                        "orthomappingElevation"
                    ]
                else:
                    raster_type_params["averagezdem"] = {
                        "url": "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
                    }
        else:
            if "orthomappingElevation" in gis.properties.helperServices.keys():
                raster_type_params = {
                    "averagezdem": gis.properties.helperServices[
                        "orthomappingElevation"
                    ]
                }
            else:
                raster_type_params = {
                    "averagezdem": {
                        "url": "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
                    }
                }

        raster_type_dict = {
            "rasterTypeName": raster_type_name,
            "rasterTypeParameters": raster_type_params,
        }
        if image_collection_properties is not None:
            raster_type_dict.update(
                {"imageCollectionProps": image_collection_properties}
            )

        raster_type_dict = json.dumps(raster_type_dict)
        if (isinstance(raster_type_name, str)) and raster_type_name == "mosaic_dataset":
            return input_rasters_dict, raster_type_dict, md_data_info
        return input_rasters_dict, raster_type_dict

    def _set_param(self, input_param):
        gis = self._gis
        param_value = None
        if isinstance(input_param, str):
            if "http:" in input_param or "https:" in input_param:
                param_value = json.dumps({"url": input_param})
            else:
                param_value = json.dumps({"uri": input_param})

        elif isinstance(input_param, arcgis.gis.Item):
            param_value = json.dumps({"itemId": input_param.itemid})

        elif isinstance(input_param, dict):
            param_value = input_param
        elif isinstance(input_param, arcgis.learn.Model):
            param_value = input_param._model
        else:
            raise TypeError(input_param + " should be a string (service url) or Item")

        return param_value

    def _set_multiple_raster_inputs(self, input_rasters):
        from arcgis.raster import ImageryLayer

        gis = self._gis
        input_rasters_dict = {}
        # input rasters
        if isinstance(input_rasters, list):
            # extract the IDs of all the input items
            # and then convert the list to JSON
            item_id_list = []
            url_list = []
            uri_list = []
            for item in input_rasters:
                if isinstance(item, Item):
                    item_id_list.append(item.itemid)
                elif isinstance(item, str):
                    if "http:" in item or "https:" in item:
                        url_list.append(item)
                    else:
                        uri_list.append(item)
                elif isinstance(item, ImageryLayer):
                    url = item.url
                    try:
                        if "token" not in url:
                            from arcgis.raster.functions.utility import (
                                _generate_layer_token,
                            )

                            token = _generate_layer_token(item, url)
                            if token is not None:
                                url = url + "?token=" + token
                    except:
                        pass
                    url_list.append(url)

            if len(item_id_list) > 0:
                input_rasters_dict = {"itemIds": item_id_list}
                input_raster_specified = True
            elif len(url_list) > 0:
                input_rasters_dict = {"urls": url_list}
                input_raster_specified = True
            elif len(uri_list) > 0:
                input_rasters_dict = {"uris": uri_list}
                input_raster_specified = True
        elif isinstance(input_rasters, str):
            # the input_rasters is a folder name; try and extract the folderID
            owner = gis.properties.user.username
            folderId = gis._portal.get_folder_id(owner, input_rasters)
            if folderId is None:
                if "http:" in input_rasters or "https:" in input_rasters:
                    input_rasters_dict = {"url": input_rasters}
                else:
                    input_rasters_dict = {"uri": input_rasters}
            else:
                input_rasters_dict = {"folderId": folderId}
            input_raster_specified = True

        return input_rasters_dict

    # ----------------------------------------------------------------------
    def add_image(
        self,
        image_collection,
        input_rasters,
        raster_type_name=None,
        raster_type_params=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Add a collection of images to an existing image_collection. Provides provision to use input rasters by reference
        and to specify image collection properties through context parameter.

        It can be used when new data is available to be included in the same
        orthomapping project. When new data is added to the image collection
        the entire image collection must be reset to the original state.

        ==================                   ====================================================================
        **Parameter**                         **Description**
        ------------------                   --------------------------------------------------------------------
        input_rasters                        Required, the list of input rasters to be added to
                                             the image collection being created. This parameter can
                                             be any one of the following:
                                             - List of portal Items of the images
                                             - An image service URL
                                             - Shared data path (this path must be accessible by the server)
                                             - Name of a folder on the portal
        ------------------                   --------------------------------------------------------------------
        image_collection                     Required, the item representing the image collection to add input_rasters to.

                                             The image collection must be an existing image collection.
                                             This is the output image collection (mosaic dataset) item or url or uri
        ------------------                   --------------------------------------------------------------------
        raster_type_name                     Required, the name of the raster type to use for adding data to
                                             the image collection.
        ------------------                   --------------------------------------------------------------------
        raster_type_params                   Optional,  additional raster_type specific parameters.

                                             The process of add rasters to the image collection can be
                                             controlled by specifying additional raster type arguments.

        ------------------                   --------------------------------------------------------------------
        context                               Optional, The context parameter is used to provide additional input parameters
                                                {"image_collection_properties": {"imageCollectionType":"Satellite"},"byref":True}

                                                use image_collection_properties key to set value for imageCollectionType.
                                                Note: the "imageCollectionType" property is important for image collection that will later on be adjusted by orthomapping system service.
                                                Based on the image collection type, the orthomapping system service will choose different algorithm for adjustment.
                                                Therefore, if the image collection is created by reference, the requester should set this
                                                property based on the type of images in the image collection using the following keywords.
                                                If the imageCollectionType is not set, it defaults to "UAV/UAS"

                                                If byref is set to True, the data will not be uploaded. If it is not set, the default is False
        ------------------                   --------------------------------------------------------------------
        gis                                  Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------                   --------------------------------------------------------------------
        future                               Optional Boolean.  If True, the job will return a RAJob.
        ==================                   ====================================================================

        :return:
             The imagery layer item

        """
        task = "AddImage"

        image_collection = self._set_image_collection_param(image_collection)
        image_collection_properties = None
        use_input_rasters_by_ref = None
        upload_properties = None

        if context is not None:
            if "image_collection_properties" in context:
                image_collection_properties = context["image_collection_properties"]
                del context["image_collection_properties"]
            if "byref" in context:
                use_input_rasters_by_ref = context["byref"]
                del context["byref"]
            if "upload_properties" in context:
                upload_properties = context["upload_properties"]
                del context["upload_properties"]

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_rasters, raster_type = self._build_param_dictionary(
            input_rasters=input_rasters,
            raster_type_name=raster_type_name,
            raster_type_params=raster_type_params,
            image_collection_properties=image_collection_properties,
            use_input_rasters_by_ref=use_input_rasters_by_ref,
            upload_properties=upload_properties,
        )

        gpjob = self._tbx.add_image(
            input_rasters=input_rasters,
            image_collection=image_collection,
            raster_type=raster_type,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def build_footprints(
        self,
        image_collection=None,
        computation_method="RADIOMETRY",
        value_range=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Computes the extent of every raster in a mosaic dataset.

        Parameters
        ----------
        image_collection : Required. The input image collection.The image_collection can be a
                           portal Item or an image service URL or a URI.
                           The image_collection must exist.

        computation_method : Optional. Refine the footprints using one of the following methods:
                             RADIOMETRY, GEOMETRY
                             Default: RADIOMETRY

        value_range: Optional. Parameter to specify the value range.

        context : Optional dictionary. Can be used to specify values for keys like:
                  whereClause, minValue, maxValue, numVertices, shrinkDistance, maintainEdge,
                  skipDerivedImages, updateBoundary, requestSize, minRegionSize, simplification,
                  edgeTorelance, maxSliverSize, minThinnessRatio

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        Returns
        -------
        output_raster : Image layer item
        """
        task = "BuildFootprints"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        image_collection = self._set_image_collection_param(image_collection)

        computation_method_values = ["RADIOMETRY", "GEOMETRY"]
        if not computation_method.upper() in computation_method_values:
            raise RuntimeError(
                "computation_method can only be one of the following: RADIOMETRY, GEOMETRY"
            )

        gpjob = self._tbx.build_footprints(
            image_collection=image_collection,
            computation_method=computation_method,
            value_range=value_range,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def build_overview(
        self,
        image_collection,
        cell_size=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """

        Parameters
        ----------
        image_collection : Required. The input image collection.The image_collection can be a
                           portal Item or an image service URL or a URI.
                           The image_collection must exist.

        cell_size : optional float or int, to set the cell size for overview.

        context : optional dictionary

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        Returns
        -------
        output_raster : Image layer item
        """
        task = "BuildOverview"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        image_collection = self._set_image_collection_param(image_collection)

        gpjob = self._tbx.build_overview(
            image_collection=image_collection,
            cell_size=cell_size,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return gpjob.result()

    # ----------------------------------------------------------------------
    def calculate_density(
        self,
        input_point_or_line_features,
        output_name=None,
        count_field=None,
        search_distance=None,
        output_area_units="SquareMiles",
        output_cell_size=None,
        context=None,
        input_barriers=None,
        future=False,
        **kwargs,
    ):
        """
        input_point_or_line_features: inputPointOrLineFeatures (FeatureSet). Required parameter.

        output_name: outputName (str). Required parameter.

        count_field: countField (str). Optional parameter.

        search_distance: searchDistance (LinearUnit). Optional parameter.

        output_area_units: outputAreaUnits (str). Optional parameter.

        output_cell_size: outputCellSize (LinearUnit). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """
        task = "CalculateDensity"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_point_or_line_features = self._feature_input(input_point_or_line_features)

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if input_barriers is not None:
            input_barriers = self._feature_input(input_barriers)

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version < 10.9:
                gpjob = self._tbx.calculate_density(
                    input_point_or_line_features=input_point_or_line_features,
                    output_name=output_raster,
                    count_field=count_field,
                    search_distance=search_distance,
                    output_area_units=output_area_units,
                    output_cell_size=output_cell_size,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.9:
                gpjob = self._tbx.calculate_density(
                    input_point_or_line_features=input_point_or_line_features,
                    output_name=output_raster,
                    count_field=count_field,
                    search_distance=search_distance,
                    output_area_units=output_area_units,
                    output_cell_size=output_cell_size,
                    context=context,
                    in_barriers=input_barriers,
                    gis=self._gis,
                    future=True,
                )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    @deprecated(deprecated_in="2.2.0", removed_in="3.0.0", current_version="2.2.0")
    def calculate_distance(
        self,
        input_source_raster_or_features,  #
        output_distance_name=None,
        maximum_distance=None,
        output_cell_size=None,
        output_direction_name=None,
        output_allocation_name=None,
        allocation_field=None,
        distance_method="PLANAR",
        input_barrier_raster_or_features=None,
        output_back_direction_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
           input_source_raster_or_features: inputSourceRasterOrFeatures (str). Required parameter.

           output_name: outputDistanceName (str). Required parameter.

           maximum_distance: maximumDistance (LinearUnit). Optional parameter.

           output_cell_size: outputCellSize (LinearUnit). Optional parameter.

           output_direction_name: outputDirectionName (str). Optional parameter.

           output_allocation_name: outputAllocationName (str). Optional parameter.

           allocation_field: allocationField (str). Optional parameter.

           context: context (str). Optional parameter.

           gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


           future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.


        Returns the following as a named tuple:
           output_distance_raster - outputDistanceRaster as a str
           output_direction_raster - outputDirectionRaster as a str
           output_allocation_raster - outputAllocationRaster as a str
        """
        task = "CalculateDistance"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_source_raster_or_features, _FEATURE_INPUTS):
            input_source_raster_or_features = self._feature_input(
                input_source_raster_or_features
            )
        elif isinstance(input_source_raster_or_features, Item):
            input_source_raster_or_features = {
                "itemId": input_source_raster_or_features.itemid
            }
        else:
            input_source_raster_or_features = self._layer_input(
                input_source_raster_or_features
            )

        if input_barrier_raster_or_features:
            if isinstance(input_barrier_raster_or_features, _FEATURE_INPUTS):
                input_barrier_raster_or_features = self._feature_input(
                    input_barrier_raster_or_features
                )
            elif isinstance(input_barrier_raster_or_features, Item):
                input_barrier_raster_or_features = {
                    "itemId": input_source_raster_or_features.itemid
                }
            else:
                input_barrier_raster_or_features = self._layer_input(
                    input_barrier_raster_or_features
                )

        (
            output_distance_raster,
            output_distance_service,
        ) = self._set_output_raster(
            output_name=output_distance_name,
            task=task,
            output_properties=kwargs,
        )
        output_direction_raster = None
        if output_direction_name is not None:
            (
                output_direction_raster,
                output_direction_service,
            ) = self._set_output_raster(
                output_name=output_direction_name,
                task=task,
                output_properties=kwargs,
            )

        output_allocation_raster = None
        if output_allocation_name is not None:
            (
                output_allocation_raster,
                out_allocation_service,
            ) = self._set_output_raster(
                output_name=output_allocation_name,
                task=task,
                output_properties=kwargs,
            )

        output_back_direction_raster = None
        if output_back_direction_name is not None:
            (
                output_back_direction_raster,
                out_back_direction_service,
            ) = self._set_output_raster(
                output_name=output_back_direction_name,
                task=task,
                output_properties=kwargs,
            )

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 10.8:
            gpjob = self._tbx.calculate_distance(
                input_source_raster_or_features=input_source_raster_or_features,
                output_distance_name=output_distance_raster,
                maximum_distance=maximum_distance,
                output_cell_size=output_cell_size,
                output_direction_name=output_direction_raster,
                output_allocation_name=output_allocation_raster,
                allocation_field=allocation_field,
                distance_method=distance_method,
                input_barrier_raster_or_features=input_barrier_raster_or_features,
                output_back_direction_name=output_back_direction_raster,
                context=context,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.calculate_distance(
                input_source_raster_or_features=input_source_raster_or_features,
                output_distance_name=output_distance_raster,
                maximum_distance=maximum_distance,
                output_cell_size=output_cell_size,
                output_direction_name=output_direction_raster,
                output_allocation_name=output_allocation_raster,
                allocation_field=allocation_field,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def calculate_statistics(
        self,
        image_collection,
        skip_factors=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        image_collection: imageCollection (str). Required parameter.

        skipfactors: skipfactors (str). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.


        Returns:
           Image Layer
        """
        task = "CalculateStatistics"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        image_collection = self._set_image_collection_param(image_collection)
        gpjob = self._tbx.calculate_statistics(
            image_collection=image_collection,
            skipfactors=skip_factors,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs
    def calculate_travel_cost(
        self,
        input_source_raster_or_features,
        output_name=None,
        input_cost_raster=None,
        input_surface_raster=None,
        maximum_distance=None,
        input_horizontal_raster=None,
        horizontal_factor=None,
        input_vertical_raster=None,
        vertical_factor=None,
        source_cost_multiplier=None,
        source_start_cost=None,
        source_resistance_rate=None,
        source_capacity=None,
        source_travel_direction=None,
        output_backlink_name=None,
        output_allocation_name=None,
        allocation_field=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """ """
        task = "CalculateTravelCost"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        (
            output_distance_raster,
            output_distance_service,
        ) = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if isinstance(input_source_raster_or_features, _FEATURE_INPUTS):
            input_source_raster_or_features = self._feature_input(
                input_source_raster_or_features
            )
        elif isinstance(input_source_raster_or_features, Item):
            input_source_raster_or_features = {
                "itemId": input_source_raster_or_features.itemid
            }
        else:
            input_source_raster_or_features = self._layer_input(
                input_source_raster_or_features
            )

        output_backlink_raster = None
        if output_backlink_name is not None:
            (
                output_backlink_raster,
                output_backlink_service,
            ) = self._set_output_raster(
                output_name=output_backlink_name,
                task=task,
                output_properties=kwargs,
            )

        output_allocation_raster = None
        if output_allocation_name is not None:
            (
                output_allocation_raster,
                out_allocation_service,
            ) = self._set_output_raster(
                output_name=output_allocation_name,
                task=task,
                output_properties=kwargs,
            )

        gpjob = self._tbx.calculate_travel_cost(
            input_source_raster_or_features=input_source_raster_or_features,
            output_distance_name=output_distance_raster,
            input_cost_raster=input_cost_raster,
            input_surface_raster=input_surface_raster,
            maximum_distance=maximum_distance,
            input_horizontal_raster=input_horizontal_raster,
            horizontal_factor=horizontal_factor,
            input_vertical_raster=input_vertical_raster,
            vertical_factor=vertical_factor,
            source_cost_multiplier=source_cost_multiplier,
            source_start_cost=source_start_cost,
            source_resistance_rate=source_resistance_rate,
            source_capacity=source_capacity,
            source_travel_direction=source_travel_direction,
            output_backlink_name=output_backlink_raster,
            output_allocation_name=output_allocation_raster,
            allocation_field=allocation_field,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs
    def classify(
        self,
        input_raster,
        input_classifier_definition,
        output_name=None,
        additional_input_raster=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        ----------
        input_raster : Required string

        input_classifier_definition : Required string

        additional_input_raster : Optional string

        output_name : Optional. If not provided, an Image Service is created by the method and used as the output raster.
            You can pass in an existing Image Service Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output Image Service that should be created by this method to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        Returns
        -------
        output_raster : Image layer item
        """

        task = "Classify"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_layer=input_raster)
        if additional_input_raster is not None:
            additional_input_raster = self._layer_input(
                input_layer=additional_input_raster
            )
        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if input_classifier_definition is not None:
            if isinstance(input_classifier_definition, Item):
                input_classifier_definition = {
                    "itemId": input_classifier_definition.itemid
                }

        gpjob = self._tbx.classify(
            input_raster=input_raster,
            input_classifier_definition=input_classifier_definition,
            output_name=output_raster,
            additional_input_raster=additional_input_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs
    def classify_pixels_using_deep_learning(
        self,
        input_raster,
        model,
        model_arguments=None,
        output_classified_raster=None,
        process_all_raster_items=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Function to classify input imagery data using a deep learning model.
        Note that the deep learning library needs to be installed separately,
        in addition to the server's built in Python 3.x library.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_raster                             Required. raster layer that needs to be classified
        ------------------------------------     --------------------------------------------------------------------
        model                                    Required model object.
        ------------------------------------     --------------------------------------------------------------------
        model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.

                                                 eg: {"name1":"value1", "name2": "value2"}

        ------------------------------------     --------------------------------------------------------------------
        output_name                              Optional. If not provided, an imagery layer is created by the method and used as the output .
                                                 You can pass in an existing Image Service Item from your GIS to use that instead.
                                                 Alternatively, you can pass in the name of the output Image Service that should be created by this method
                                                 to be used as the output for the tool.
                                                 A RuntimeError is raised if a service by that name already exists
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                   Dictionary can contain value for following keys:

                                                   - outSR - (Output Spatial Reference) Saves the result in the specified spatial reference

                                                   - snapRaster - Function will adjust the extent of output rasters so that they
                                                     match the cell alignment of the specified snap raster.

                                                   - cellSize - Set the output raster cell size, or resolution

                                                   - extent - Sets the processing extent used by the function

                                                   - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                                   - processorType - Sets the processor type. "CPU" or "GPU"

                                                   Eg: {"outSR" : {spatial reference}}

                                                   Setting context parameter will override the values set using arcgis.env
                                                   variable for this particular function.
        ------------------------------------     --------------------------------------------------------------------
        process_all_raster_items                 Optional bool. Specifies how all raster items in a mosaic dataset or an image service will be processed.

                                                  - False : all raster items in the mosaic dataset or image service will be mosaicked together and processed. This is the default.

                                                  - True : all raster items in the mosaic dataset or image service will be processed as separate images.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ====================================     ====================================================================

        :return:
            The classified imagery layer item

        """
        task = "ClassifyPixelsUsingDeepLearning"
        gis = self._gis

        input_raster = self._layer_input(input_layer=input_raster)

        if model is None:
            raise RuntimeError("model cannot be None")
        else:
            model_value = self._set_param(model)

        model_arguments_value = None
        if model_arguments:
            try:
                model_arguments_value = dict(
                    (str(k), str(v)) for k, v in model_arguments.items()
                )
            except:
                model_arguments_value = model_arguments

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        output_raster, output_service = self._set_output_raster(
            output_name=output_classified_raster,
            task=task,
            output_properties=kwargs,
        )

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 10.8:
            gpjob = self._tbx.classify_pixels_using_deep_learning(
                input_raster=input_raster,
                output_classified_raster=output_raster,
                model=model_value,
                model_arguments=model_arguments_value,
                process_all_raster_items=process_all_raster_items,
                context=context,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.classify_pixels_using_deep_learning(
                input_raster=input_raster,
                output_classified_raster=output_raster,
                model=model_value,
                model_arguments=model_arguments_value,
                context=context,
                gis=self._gis,
                future=True,
            )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    def convert_feature_to_raster(
        self,
        input_feature,
        output_cell_size,
        output_name=None,
        value_field=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Parameters:

        input_feature: inputFeature (str). Required parameter.

        output_name: outputName (str). Required parameter.

        output_cell_size: outputCellSize (LinearUnit). Required parameter.

        value_field: valueField (str). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.


        Returns:
             output_raster - outputRaster as a str

        """
        task = "ConvertFeatureToRaster"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        # output_raster = {"itemProperties":{ "itemId": output_service.itemid}}
        input_feature = self._feature_input(input_layer=input_feature)

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.convert_feature_to_raster(
            input_feature=input_feature,
            output_name=output_raster,
            output_cell_size=output_cell_size,
            value_field=value_field,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item)
        return RAJob(gpjob, item).result()

    # ----------------------------------------------------------------------
    def convert_raster_to_feature(
        self,
        input_raster,
        output_name=None,
        field="Value",
        output_type="Point",
        simplify_lines_or_polygons=True,
        context=None,
        create_multipart_features=False,
        max_vertices_per_feature=None,
        future=False,
        **kwargs,
    ):
        """
        This service tool converts imagery data to feature class vector data.

        Parameters
        ----------
        input_raster : Required. The input raster that will be converted to a feature dataset.

        field : Optional string - field that specifies which value will be used for the conversion.
            It can be any integer or a string field.
            A field containing floating-point values can only be used if the output is to a point dataset.
            Default is "Value"

        output_type : Optional string
            One of the following: ['Point', 'Line', 'Polygon']

        simplify : Optional bool, This option that specifies how the features should be smoothed. It is
                   only available for line and polygon output.
                   True, then the features will be smoothed out. This is the default.
                   if False, then The features will follow exactly the cell boundaries of the raster dataset.

        output_name : Optional. If not provided, an Feature layer is created by the method and used as the output .
            You can pass in an existing Feature Service Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output Feature Service that should be created by this method
            to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        Returns
        -------
        output_features : Image layer item
        """
        task = "ConvertRasterToFeature"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_raster)

        if output_name is None:
            output_service_name = "RasterToFeature_" + _id_generator()
            output_name = output_service_name.replace(" ", "_")
        else:
            output_service_name = output_name.replace(" ", "_")
        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]
        output_service = self._create_output_feature_service(
            output_name=output_name,
            output_service_name=output_name,
            task=task,
            folder=folder,
        )
        if folderId is not None:
            output_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                }
            )

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] <= 10.8:
            gpjob = self._tbx.convert_raster_to_feature(
                input_raster=input_raster,
                output_name=output_name,
                field=field,
                output_type=output_type,
                simplify_lines_or_polygons=simplify_lines_or_polygons,
                context=context,
                gis=self._gis,
                future=True,
            )

        else:
            gpjob = self._tbx.convert_raster_to_feature(
                input_raster=input_raster,
                output_name=output_name,
                field=field,
                output_type=output_type,
                simplify_lines_or_polygons=simplify_lines_or_polygons,
                create_multipart_features=create_multipart_features,
                max_vertices_per_feature=max_vertices_per_feature,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_ra = True
        gpjob._return_item = output_service
        gpjob._item_properties = True

        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs
    def create_image_collection(
        self,
        image_collection,
        input_rasters,
        raster_type_name,
        raster_type_params=None,
        out_sr=None,
        context=None,
        future=False,
        md_to_upload=None,
        **kwargs,
    ):
        """
        Create a collection of images that will participate in the ortho-mapping project.
        Provides provision to use input rasters by reference
        and to specify image collection properties through context parameter.

        ==================                   ====================================================================
        **Parameter**                         **Description**
        ------------------                   --------------------------------------------------------------------
        image_collection                     Required, the name of the image collection to create.

                                             The image collection can be an existing image service, in
                                             which the function will create a mosaic dataset and the existing
                                             hosted image service will then point to the new mosaic dataset.

                                             If the image collection does not exist, a new multi-tenant
                                             service will be created.

                                             This parameter can be the Item representing an existing image_collection
                                             or it can be a string representing the name of the image_collection
                                             (either existing or to be created.)
        ------------------                   --------------------------------------------------------------------
        input_rasters                        Required, the list of input rasters to be added to
                                             the image collection being created. This parameter can
                                             be any one of the following:
                                             - List of portal Items of the images
                                             - An image service URL
                                             - Shared data path (this path must be accessible by the server)
                                             - Name of a folder on the portal
        ------------------                   --------------------------------------------------------------------
        raster_type_name                     Required, the name of the raster type to use for adding data to
                                             the image collection.
        ------------------                   --------------------------------------------------------------------
        raster_type_params                   Optional,  additional raster_type specific parameters.

                                             The process of add rasters to the image collection can be
                                             controlled by specifying additional raster type arguments.

                                             The raster type parameters argument is a dictionary.
        ------------------                   --------------------------------------------------------------------
        out_sr                               Optional, additional parameters of the service.

                                             The following additional parameters can be specified:
                                             - Spatial reference of the image_collection; The well-known ID of
                                             the spatial reference or a spatial reference dictionary object for the
                                             input geometries.
                                             If the raster type name is set to "UAV/UAS", the spatial reference of the
                                             output image collection will be determined by the raster type parameters defined.
        ------------------                   --------------------------------------------------------------------
        context                               Optional, The context parameter is used to provide additional input parameters
                                                {"image_collection_properties": {"imageCollectionType":"Satellite"},"byref":True}

                                                use image_collection_properties key to set value for imageCollectionType.
                                                Note: the "imageCollectionType" property is important for image collection that will later on be adjusted by orthomapping system service.
                                                Based on the image collection type, the orthomapping system service will choose different algorithm for adjustment.
                                                Therefore, if the image collection is created by reference, the requester should set this
                                                property based on the type of images in the image collection using the following keywords.
                                                If the imageCollectionType is not set, it defaults to "UAV/UAS"

                                                If byref is set to True, the data will not be uploaded. If it is not set, the default is False
        ------------------                   --------------------------------------------------------------------
        gis                                  Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================                   ====================================================================

        :return:
            The imagery layer item

        """
        kwargs.update({"tiles_only": False})
        task = "CreateImageCollection"
        gis = self._gis
        output_service = None
        image_collection_properties = None
        use_input_rasters_by_ref = None
        upload_properties = None

        from arcgis.raster import RasterCollection
        from arcgis.raster._layer import _LocalRasterCollection

        if isinstance(input_rasters, RasterCollection):
            if input_rasters._ras_coll_engine == _LocalRasterCollection:
                raster_list = [ras["Raster"].catalog_path for ras in input_rasters]
                input_rasters = raster_list
                if context is None or not isinstance(context, dict):
                    context = {"byref": True}
                else:
                    context["byref"] = True

            else:
                raise RuntimeError(
                    "This type of RasterCollection input is not supported for create_image_collection()"
                )

        if context is not None:
            if "image_collection_properties" in context:
                image_collection_properties = context["image_collection_properties"]
                del context["image_collection_properties"]
            if "byref" in context:
                use_input_rasters_by_ref = context["byref"]
                del context["byref"]
            if "upload_properties" in context:
                upload_properties = context["upload_properties"]
                del context["upload_properties"]

        if isinstance(image_collection, Item):
            image_collection = json.dumps({"itemId": image_collection.itemid})
        elif isinstance(image_collection, str):
            if ("/") in image_collection or ("\\") in image_collection:
                if "http:" in image_collection or "https:" in image_collection:
                    image_collection = json.dumps({"url": image_collection})
                else:
                    image_collection = json.dumps({"uri": image_collection})
            else:
                result = gis.content.search(
                    "title:" + str(image_collection),
                    item_type="Imagery Layer",
                )
                image_collection_result = None
                for element in result:
                    if str(image_collection) == element.title:
                        image_collection_result = element
                if image_collection_result is not None:
                    image_collection = json.dumps(
                        {"itemId": image_collection_result.itemid}
                    )
                else:
                    (
                        image_collection,
                        output_service,
                    ) = self._set_output_raster(
                        output_name=image_collection,
                        task=task,
                        output_properties=kwargs,
                    )

        if out_sr is not None:
            if isinstance(out_sr, int):
                if context is not None:
                    context.update({"outSR": {"wkid": out_sr}})
                else:
                    context = {}
                    context["outSR"] = {"wkid": out_sr}
            else:
                if context is not None:
                    context.update({"outSR": out_sr})
                else:
                    context = {}
                    context["outSR"] = out_sr

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        md_data_path = []
        if md_to_upload is not None:
            if isinstance(input_rasters, str):
                md_data_path.append(os.path.dirname(input_rasters))
            elif isinstance(input_rasters, list):
                for ele in input_rasters:
                    md_data_path.append(os.path.dirname(ele))

            raster_type_name = "mosaic_dataset"

        md_data_info = []
        if (isinstance(raster_type_name, str)) and raster_type_name == "mosaic_dataset":
            (
                input_rasters,
                raster_type,
                md_data_info,
            ) = self._build_param_dictionary(
                input_rasters=input_rasters,
                raster_type_name=raster_type_name,
                raster_type_params=raster_type_params,
                image_collection_properties=None,
                use_input_rasters_by_ref=use_input_rasters_by_ref,
                upload_properties=upload_properties,
                task=task,
            )
        else:
            input_rasters, raster_type = self._build_param_dictionary(
                input_rasters=input_rasters,
                raster_type_name=raster_type_name,
                raster_type_params=raster_type_params,
                image_collection_properties=image_collection_properties,
                use_input_rasters_by_ref=use_input_rasters_by_ref,
                upload_properties=upload_properties,
                task=task,
            )

        mosaic_dataset_uploaded = md_to_upload
        if md_to_upload is not None:
            if gis._con._product == "AGOL":
                from arcgis.raster._util import _upload_imagery_agol

                if ".gdb" in md_to_upload:
                    gdb_path = os.path.dirname(md_to_upload)
                uploaded_list = _upload_imagery_agol(
                    [gdb_path], gis, upload_properties=upload_properties
                )
                if len(uploaded_list) == 1:
                    azure_upload_url = uploaded_list[0]
                    mosaic_dataset_uploaded = (
                        azure_upload_url
                        + "/"
                        + os.path.basename(gdb_path)
                        + "/"
                        + os.path.basename(md_to_upload)
                    )

            if len(md_data_path) == 1:
                md_data_path = md_data_path[0]
            input_rasters.update(
                {
                    "mosaic_dataset": mosaic_dataset_uploaded,
                    "data_path": md_data_info,
                }
            )

        if raster_type_name == "mosaic_dataset":
            raster_type = None

        gpjob = self._tbx.create_image_collection(
            input_rasters=input_rasters,
            image_collection=image_collection,
            raster_type=raster_type,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs
    def create_viewshed(
        self,
        input_elevation_surface,
        input_observer_features,
        output_name=None,
        optimize_for=None,
        maximum_viewing_distance=None,
        maximum_viewing_distance_field=None,
        minimum_viewing_distance=None,
        minimum_viewing_distance_field=None,
        viewing_distance_is3D=None,
        observers_elevation=None,
        observers_elevation_field=None,
        observers_height=None,
        observers_height_field=None,
        target_height=None,
        target_height_field=None,
        above_ground_level_output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """ """
        task = "CreateViewshed"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_elevation_surface = self._layer_input(input_elevation_surface)
        input_observer_features = self._layer_input(input_observer_features)

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )
        output_layers = [output_service]
        above_ground_level_raster = None
        if above_ground_level_output_name is not None:
            (
                above_ground_level_raster,
                above_ground_level_service,
            ) = self._set_output_raster(
                output_name=above_ground_level_output_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(above_ground_level_service)

        gpjob = self._tbx.create_viewshed(
            input_elevation_surface=input_elevation_surface,
            input_observer_features=input_observer_features,
            output_name=output_raster,
            optimize_for=optimize_for,
            maximum_viewing_distance=maximum_viewing_distance,
            maximum_viewing_distance_field=maximum_viewing_distance_field,
            minimum_viewing_distance=minimum_viewing_distance,
            minimum_viewing_distance_field=minimum_viewing_distance_field,
            viewing_distance_is3_d=viewing_distance_is3D,
            observers_elevation=observers_elevation,
            observers_elevation_field=observers_elevation_field,
            observers_height=observers_height,
            observers_height_field=observers_height_field,
            target_height=target_height,
            target_height_field=target_height_field,
            above_ground_level_output_name=above_ground_level_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_layers)
        return RAJob(gpjob, output_layers).result()

    # ----------------------------------------------------------------------
    def delete_image(self, image_collection, where, future=False, **kwargs):
        """
        delete_image allows users to remove existing images from the image collection (mosaic dataset).
        The function will only delete the raster item in the mosaic dataset and will not remove the
        source image.

        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        image_collection       Required, the input image collection from which to delete images
                               This can be the 'itemID' of an exisiting portal item or a url
                               to an Image Service or a uri
        ------------------     --------------------------------------------------------------------
        where                  Required string,  a SQL 'where' clause for selecting the images
                               to be deleted from the image collection
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            The imagery layer url

        """
        task = "DeleteImage"
        gis = self._gis

        image_collection = self._set_image_collection_param(image_collection)
        gpjob = self._tbx.delete_image(
            image_collection=image_collection,
            where=where,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs, doc
    def delete_image_collection(self, image_collection, future=False, **kwargs):
        """
        Delete the image collection. This service tool will delete the image collection
        image service, that is, the portal-hosted image layer item. It will not delete
        the source images that the image collection references.

        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        image_collection       Required, the input image collection to delete.

                               The image_collection can be a portal Item or an image service URL or a URI.

                               The image_collection must exist.
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            Boolean value indicating whether the deletion was successful or not

        """
        task = "DeleteImageCollection"

        gis = self._gis
        image_collection = self._set_image_collection_param(image_collection)
        gpjob = self._tbx.delete_image_collection(
            image_collection=image_collection, gis=self._gis, future=True
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs, doc
    def detect_objects_using_deep_learning(
        self,
        input_raster,
        model,
        output_objects=None,
        model_arguments=None,
        run_nms=False,
        confidence_score_field=None,
        class_value_field=None,
        max_overlap_ratio=None,
        process_all_raster_items=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Function can be used to generate feature service that contains polygons on detected objects
        found in the imagery data using the designated deep learning model. Note that the deep learning
        library needs to be installed separately, in addition to the server's built in Python 3.x library.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_raster                             Required. raster layer that contains objects that needs to be detected.
        ------------------------------------     --------------------------------------------------------------------
        model                                    Required model object.
        ------------------------------------     --------------------------------------------------------------------
        model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.

                                                 eg: {"name1":"value1", "name2": "value2"}
        ------------------------------------     --------------------------------------------------------------------
        output_objects                           Optional. If not provided, a Feature layer is created by the method and used as the output .
                                                 You can pass in an existing Feature Service Item from your GIS to use that instead.
                                                 Alternatively, you can pass in the name of the output Feature Service that should be created by this method
                                                 to be used as the output for the tool.
                                                 A RuntimeError is raised if a service by that name already exists
        ------------------------------------     --------------------------------------------------------------------
        run_nms                                  Optional bool. Default value is False. If set to True, runs the Non Maximum Suppression tool.
        ------------------------------------     --------------------------------------------------------------------
        confidence_score_field                   Optional string. The field in the feature class that contains the confidence scores as output by the object detection method.
                                                 This parameter is required when you set the run_nms to True
        ------------------------------------     --------------------------------------------------------------------
        class_value_field                        Optional string. The class value field in the input feature class.
                                                 If not specified, the function will use the standard class value fields
                                                 Classvalue and Value. If these fields do not exist, all features will
                                                 be treated as the same object class.
                                                 Set only if run_nms  is set to True
        ------------------------------------     --------------------------------------------------------------------
        max_overlap_ratio                        Optional integer. The maximum overlap ratio for two overlapping features.
                                                 Defined as the ratio of intersection area over union area.
                                                 Set only if run_nms  is set to True
        ------------------------------------     --------------------------------------------------------------------
        process_all_raster_items                 Optional bool. Specifies how all raster items in a mosaic dataset or an image service will be processed.

                                                  - False : all raster items in the mosaic dataset or image service will be mosaicked together and processed. This is the default.

                                                  - True : all raster items in the mosaic dataset or image service will be processed as separate images.
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                 Dictionary can contain value for following keys:

                                                 - cellSize - Set the output raster cell size, or resolution

                                                 - extent - Sets the processing extent used by the function

                                                 - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                                 - processorType - Sets the processor type. "CPU" or "GPU"

                                                 Eg: {"processorType" : "CPU"}

                                                 Setting context parameter will override the values set using arcgis.env
                                                 variable for this particular function.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ====================================     ====================================================================

        :return:
            The output feature layer item containing the detected objects

        """
        task = "DetectObjectsUsingDeepLearning"
        gis = self._gis

        input_raster = self._layer_input(input_layer=input_raster)

        if model is None:
            raise RuntimeError("model cannot be None")
        else:
            model_value = self._set_param(model)

        model_arguments_value = None
        if model_arguments:
            try:
                model_arguments_value = dict(
                    (str(k), str(v)) for k, v in model_arguments.items()
                )
            except:
                model_arguments_value = model_arguments

        if isinstance(run_nms, bool):
            if run_nms is False:
                confidence_score_field = None
                class_value_field = None
                max_overlap_ratio = None
        else:
            raise RuntimeError("run_nms value should be an instance of bool")

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if output_objects is None:
            output_service_name = "DetectObjectsUsingDeepLearning_" + _id_generator()
            output_objects = output_service_name.replace(" ", "_")
        else:
            output_service_name = output_objects.replace(" ", "_")

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]
        output_service = self._create_output_feature_service(
            output_name=output_objects,
            output_service_name=output_service_name,
            task="Detect Objects",
            folder=folder,
        )
        if folderId is not None:
            output_objects = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_objects = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                }
            )

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 10.8:
            gpjob = self._tbx.detect_objects_using_deep_learning(
                input_raster=input_raster,
                output_objects=output_objects,
                model=model_value,
                model_arguments=model_arguments_value,
                run_nms=run_nms,
                confidence_score_field=confidence_score_field,
                class_value_field=class_value_field,
                max_overlap_ratio=max_overlap_ratio,
                process_all_raster_items=process_all_raster_items,
                context=context,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.detect_objects_using_deep_learning(
                input_raster=input_raster,
                output_objects=output_objects,
                model=model_value,
                model_arguments=model_arguments_value,
                run_nms=run_nms,
                confidence_score_field=confidence_score_field,
                class_value_field=class_value_field,
                max_overlap_ratio=max_overlap_ratio,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_ra = True
        gpjob._item_properties = True
        gpjob._return_item = output_service
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def determine_optimum_travel_cost_network(
        self,
        input_regions_raster_or_features,
        input_cost_raster=None,
        output_optimum_network_name=None,
        output_neighbor_network_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        calculates the optimum cost network from a set of input regions.

        Parameters
        ----------
        input_regions_raster : The layer that defines the regions to find the optimum travel cost netork for.
                               The layer can be raster or feature.

        input_cost_raster  : A raster defining the impedance or cost to move planimetrically through each cell.

        output_optimum_network_name : Optional. If not provided, a feature layer is created by the method and used as the output.
            You can pass in an existing feature layer Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output feature layer  that should be created by this method to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        output_neighbor_network_name : Optional. This is the name of the output neighbour network feature layer that will be created.

        context: Context contains additional settings that affect task execution.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        Returns
        -------
        output_raster : Image layer item
        """
        task = "DetermineOptimumTravelCostNetwork"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_regions_raster_or_features = self._layer_input(
            input_regions_raster_or_features
        )

        if input_cost_raster is not None:
            input_cost_raster = self._layer_input(input_cost_raster)

        if output_optimum_network_name is None:
            output_optimum_network_service_name = (
                "Optimum Network Raster_" + _id_generator()
            )
            output_optimum_network_name = output_optimum_network_service_name.replace(
                " ", "_"
            )
        else:
            output_optimum_network_service_name = output_optimum_network_name.replace(
                " ", "_"
            )

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        output_optimum_network_service = self._create_output_feature_service(
            output_name=output_optimum_network_name,
            output_service_name=output_optimum_network_service_name,
            task="DetermineOptimumTravelCostNetwork",
            folder=folder,
        )
        if folderId is not None:
            output_optimum_network_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_optimum_network_service_name,
                        "serviceUrl": output_optimum_network_service.url,
                    },
                    "itemProperties": {"itemId": output_optimum_network_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_optimum_network_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_optimum_network_service_name,
                        "serviceUrl": output_optimum_network_service.url,
                    },
                    "itemProperties": {"itemId": output_optimum_network_service.itemid},
                }
            )

        if output_neighbor_network_name is None:
            output_neighbor_network_service_name = (
                "Neighbor Network Raster_" + _id_generator()
            )
            output_neighbor_network_name = output_neighbor_network_service_name.replace(
                " ", "_"
            )
        else:
            output_neighbor_network_service_name = output_neighbor_network_name.replace(
                " ", "_"
            )

        output_neighbor_network_service = self._create_output_feature_service(
            output_name=output_neighbor_network_name,
            output_service_name=output_neighbor_network_service_name,
            task="DetermineOptimumTravelCostNetwork",
            folder=folder,
        )
        if folderId is not None:
            output_neighbor_network_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_neighbor_network_service_name,
                        "serviceUrl": output_neighbor_network_service.url,
                    },
                    "itemProperties": {
                        "itemId": output_neighbor_network_service.itemid
                    },
                    "folderId": folderId,
                }
            )
        else:
            output_neighbor_network_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_neighbor_network_service_name,
                        "serviceUrl": output_neighbor_network_service.url,
                    },
                    "itemProperties": {
                        "itemId": output_neighbor_network_service.itemid
                    },
                }
            )

        gpjob = self._tbx.determine_optimum_travel_cost_network(
            input_regions_raster_or_features=input_regions_raster_or_features,
            input_cost_raster=input_cost_raster,
            output_optimum_network_name=output_optimum_network_name,
            output_neighbor_network_name=output_neighbor_network_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def determine_travel_cost_paths_to_destinations(
        self,
        input_destination_raster_or_features,
        input_cost_distance_raster=None,
        input_cost_backlink_raster=None,
        output_name=None,
        destination_field=None,
        path_type=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """DetermineTravelCostPathsToDestinations GPtool"""
        task = "DetermineTravelCostPathsToDestinations"
        gis = self._gis
        if context is None:
            context = {}
            _set_raster_context(params=context)
        gpjob = self._tbx.determine_travel_cost_paths_to_destinations(
            input_destination_raster_or_features=input_destination_raster_or_features,
            input_cost_distance_raster=input_cost_distance_raster,
            input_cost_backlink_raster=input_cost_backlink_raster,
            output_name=output_name,
            destination_field=destination_field,
            path_type=path_type,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def determine_travel_costpath_as_polyline(
        self,
        input_source_raster_or_features,
        input_cost_raster,
        input_destination_raster_or_features=None,
        output_polyline_name=None,
        path_type=None,
        destination_field=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Calculates the least cost polyline path between sources and known destinations.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_source_raster_or_features                        The layer that identifies the cells to determine the least
                                                 costly path from. This parameter can have either a raster input or
                                                 a feature input.
        ------------------------------------     --------------------------------------------------------------------
        input_cost_raster                        A raster defining the impedance or cost to move planimetrically through
                                                 each cell.

                                                 The value at each cell location represents the cost-per-unit distance for
                                                 moving through the cell. Each cell location value is multiplied by the
                                                 cell resolution while also compensating for diagonal movement to
                                                 obtain the total cost of passing through the cell.

                                                 The values of the cost raster can be an integer or a floating point, but they
                                                 cannot be negative or zero as you cannot have a negative or zero cost.
        ------------------------------------     --------------------------------------------------------------------
        input_destination_raster_or_features     The layer that defines the destinations used to calculate the distance.
                                                 This parameter can have either a raster input or a feature input.
        ------------------------------------     --------------------------------------------------------------------
        path_type                                A keyword defining the manner in which the values and zones on the
                                                 input destination data will be interpreted in the cost path calculations.

                                                 A string describing the path type, which can either be BEST_SINGLE,
                                                 EACH_CELL, or EACH_ZONE.

                                                 BEST_SINGLE: For all cells on the input destination data, the
                                                 least-cost path is derived from the cell with the minimum of
                                                 the least-cost paths to source cells. This is the default.

                                                 EACH_CELL: For each cell with valid values on the input
                                                 destination data, at least-cost path is determined and saved
                                                 on the output raster. With this option, each cell of the input
                                                 destination data is treated separately, and a least-cost path
                                                 is determined for each from cell.

                                                 EACH_ZONE: For each zone on the input destination data,
                                                 a least-cost path is determined and saved on the output raster.
                                                 With this option, the least-cost path for each zone begins at
                                                 the cell with the lowest cost distance weighting in the zone.
        ------------------------------------     --------------------------------------------------------------------
        output_polyline_name                     Optional. If not provided, a feature layer is created by the method
                                                 and used as the output.

                                                 You can pass in an existing feature layer Item from your GIS to use
                                                 that instead.

                                                 Alternatively, you can pass in the name of the output feature layer  that should be created by this method to be used as the output for the tool.
                                                 A RuntimeError is raised if a service by that name already exists
        ------------------------------------     --------------------------------------------------------------------
        destination_field                         The field used to obtain values for the destination locations.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. the GIS on which this tool runs. If not specified, the active GIS is used.
        ====================================     ====================================================================

        :return:
            The imagery layer url

        """
        task = "DetermineTravelCostPathAsPolyline"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_source_raster_or_features, _FEATURE_INPUTS):
            input_source_raster_or_features = self._feature_input(
                input_source_raster_or_features
            )
        elif isinstance(input_source_raster_or_features, Item):
            input_source_raster_or_features = {
                "itemId": input_source_raster_or_features.itemid
            }
        else:
            input_source_raster_or_features = self._layer_input(
                input_source_raster_or_features
            )

        input_cost_raster = self._layer_input(input_cost_raster)

        if isinstance(input_destination_raster_or_features, _FEATURE_INPUTS):
            input_destination_raster_or_features = self._feature_input(
                input_destination_raster_or_features
            )
        elif isinstance(input_destination_raster_or_features, Item):
            input_destination_raster_or_features = {
                "itemId": input_destination_raster_or_features.itemid
            }
        else:
            input_destination_raster_or_features = self._layer_input(
                input_destination_raster_or_features
            )

        if output_polyline_name is None:
            output_polyline_service_name = "Output Polyline_" + _id_generator()
            output_polyline_name = output_polyline_service_name.replace(" ", "_")
        else:
            output_polyline_service_name = output_polyline_name.replace(" ", "_")

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        output_polyline_service = self._create_output_feature_service(
            output_name=output_polyline_name,
            output_service_name=output_polyline_service_name,
            task="DetermineTravelCostPathAsPolyline",
            folder=folder,
        )
        if folderId is not None:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                }
            )

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 10.8:
            gpjob = self._tbx.determine_travel_cost_path_as_polyline(
                input_source_raster_or_features=input_source_raster_or_features,
                input_cost_raster=input_cost_raster,
                input_destination_raster_or_features=input_destination_raster_or_features,
                output_polyline_name=output_polyline_name,
                path_type=path_type,
                destination_field=destination_field,
                context=context,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.determine_travel_cost_path_as_polyline(
                input_source_raster_or_features=input_source_raster_or_features,
                input_cost_raster=input_cost_raster,
                input_destination_raster_or_features=input_destination_raster_or_features,
                output_polyline_name=output_polyline_name,
                path_type=path_type,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs, doc
    def export_training_data_for_deep_learning(
        self,
        input_raster,
        output_location,
        input_class_data=None,
        chip_format="TIFF",
        tile_size=None,
        stride_size=None,
        metadata_format="KITTI_rectangles",
        class_value_field=None,
        buffer_radius=0,
        input_mask_polygons=None,
        rotation_angle=0,
        reference_system="MAP_SPACE",
        process_all_raster_items=False,
        blacken_around_feature=False,
        fix_chip_size=True,
        additional_input_raster=None,
        input_instance_data=None,
        instance_class_value_field=None,
        min_polygon_overlap_ratio=0,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Function is designed to generate training sample image chips from the input imagery data with
        labeled vector data or classified images. The output of this service tool is the data store string
        where the output image chips, labels and metadata files are going to be stored.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_raster                             Required. Raster layer that needs to be exported for training
        ------------------------------------     --------------------------------------------------------------------
        input_class_data                         Labeled data, either a feature layer or image layer.
                                                 Vector inputs should follow a training sample format as
                                                 generated by the ArcGIS Pro Training Sample Manager.
                                                 Raster inputs should follow a classified raster format as generated by the Classify Raster tool.
        ------------------------------------     --------------------------------------------------------------------
        chip_format                              Optional string. The raster format for the image chip outputs.

                                                    - TIFF: TIFF format

                                                    - PNG: PNG format

                                                    - JPEG: JPEG format

                                                    - MRF: MRF (Meta Raster Format)
        ------------------------------------     --------------------------------------------------------------------
        tile_size                                Optional dictionary. The size of the image chips.

                                                    Example: {"x": 256, "y": 256}
        ------------------------------------     --------------------------------------------------------------------
        stride_size                              Optional dictionary. The distance to move in the X and Y when creating
                                                 the next image chip.
                                                 When stride is equal to the tile size, there will be no overlap.
                                                 When stride is equal to half of the tile size, there will be 50% overlap.

                                                   Example: {"x": 128, "y": 128}
        ------------------------------------     --------------------------------------------------------------------
        metadata_format                          Optional string. The format of the output metadata labels. There are 4 options for output metadata labels for the training data,
                                                   KITTI Rectangles, PASCAL VOCrectangles, Classified Tiles (a class map) and RCNN_Masks. If your input training sample data
                                                   is a feature class layer such as building layer or standard classification training sample file,
                                                   use the KITTI or PASCAL VOC rectangle option.

                                                   The output metadata is a .txt file or .xml file containing the training sample data contained
                                                   in the minimum bounding rectangle. The name of the metadata file matches the input source image
                                                   name. If your input training sample data is a class map, use the Classified Tiles as your output metadata format option.

                                                   - KITTI_rectangles: The metadata follows the same format as the Karlsruhe Institute of Technology and Toyota
                                                     Technological Institute (KITTI) Object Detection Evaluation dataset. The KITTI dataset is a vision benchmark suite.
                                                     This is the default.The label files are plain text files. All values, both numerical or strings, are separated by
                                                     spaces, and each row corresponds to one object.

                                                   - PASCAL_VOC_rectangles: The metadata follows the same format as the Pattern Analysis, Statistical Modeling and
                                                     Computational Learning, Visual Object Classes (PASCAL_VOC) dataset. The PASCAL VOC dataset is a standardized
                                                     image data set for object class recognition.The label files are XML files and contain information about image name,
                                                     class value, and bounding box(es).

                                                   - Classified_Tiles: This option will output one classified image chip per input image chip.
                                                     No other meta data for each image chip. Only the statistics output has more information on the
                                                     classes such as class names, class values, and output statistics.

                                                   - RCNN_Masks: This option will output image chips that have a mask on the areas where the sample exists.
                                                     The model generates bounding boxes and segmentation masks for each instance of an object in the image.
                                                     It's based on Feature Pyramid Network (FPN) and a ResNet101 backbone.

                                                   - Labeled_Tiles : This option will label each output tile with a specific class.
        ------------------------------------     --------------------------------------------------------------------
        classvalue_field                          Optional string. Specifies the field which contains the class values. If no field is specified,
                                                  the system will look for a 'value' or 'classvalue' field. If this feature does
                                                  not contain a class field, the system will presume all records belong the 1 class.
        ------------------------------------     --------------------------------------------------------------------
        buffer_radius                            Optional integer. Specifies a radius for point feature classes to specify training sample area.
        ------------------------------------     --------------------------------------------------------------------
        output_location                          This is the output location for training sample data.
                                                   It can be the server data store path or a shared file system path.

                                                   Example:

                                                   Server datastore path -
                                                    ``/fileShares/deeplearning/rooftoptrainingsamples``
                                                    ``/rasterStores/rasterstorename/rooftoptrainingsamples``
                                                    ``/cloudStores/cloudstorename/rooftoptrainingsamples``

                                                   File share path -
                                                    ``\\\\servername\\deeplearning\\rooftoptrainingsamples``
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                    Dictionary can contain value for following keys:

                                                    - exportAllTiles - Choose if the image chips with overlapped labeled data will be exported.
                                                        True - Export all the image chips, including those that do not overlap labeled data.
                                                        False - Export only the image chips that overlap the labelled data. This is the default.

                                                    - startIndex - Allows you to set the start index for the sequence of image chips.
                                                        This lets you append more image chips to an existing sequence. The default value is 0.

                                                    - cellSize - cell size can be set using this key in context parameter

                                                    - extent - Sets the processing extent used by the function

                                                    Setting context parameter will override the values set using arcgis.env
                                                    variable for this particular function.(cellSize, extent)

                                                    eg: {"exportAllTiles" : False, "startIndex": 0 }
        ------------------------------------     --------------------------------------------------------------------
        input_mask_polygons                       Optional feature layer. The feature layer that delineates the area where
                                                    image chips will be created.
                                                    Only image chips that fall completely within the polygons will be created.
        ------------------------------------     --------------------------------------------------------------------
        rotation_angle                           Optional float. The rotation angle that will be used to generate additional
                                                    image chips.

                                                    An image chip will be generated with a rotation angle of 0, which
                                                    means no rotation. It will then be rotated at the specified angle to
                                                    create an additional image chip. The same training samples will be
                                                    captured at multiple angles in multiple image chips for data augmentation.
                                                    The default rotation angle is 0.
        ------------------------------------     --------------------------------------------------------------------
        reference_system                         Optional string. Specifies the type of reference system to be used to interpret
                                                    the input image. The reference system specified should match the reference system
                                                    used to train the deep learning model.

                                                    - MAP_SPACE : The input image is in a map-based coordinate system. This is the default.

                                                    - IMAGE_SPACE : The input image is in image space, viewed from the direction of the sensor
                                                    that captured the image, and rotated such that the tops of buildings and trees point upward in the image.

                                                    - PIXEL_SPACE : The input image is in image space, with no rotation and no distortion.
        ------------------------------------     --------------------------------------------------------------------
        process_all_raster_items                 Optional bool. Specifies how all raster items in a mosaic dataset or an image service will be processed.

                                                    - False : all raster items in the mosaic dataset or image service will be mosaicked together and processed. This is the default.

                                                    - True : all raster items in the mosaic dataset or image service will be processed as separate images.
        ------------------------------------     --------------------------------------------------------------------
        blacken_around_feature                   Optional bool.

                                                    Specifies whether to blacken the pixels around each object or feature in each image tile.

                                                    This parameter only applies when the metadata format is set to Labeled_Tiles and an input feature class or classified raster has been specified.

                                                    - False : Pixels surrounding objects or features will not be blackened. This is the default.

                                                    - True : Pixels surrounding objects or features will be blackened.

        ------------------------------------     --------------------------------------------------------------------
        fix_chip_size                            Optional bool. Specifies whether to crop the exported tiles such that they are all the same size.

                                                    This parameter only applies when the metadata format is set to Labeled_Tiles and an input feature class or classified raster has been specified.

                                                    - True : Exported tiles will be the same size and will center on the feature. This is the default.

                                                    - False : Exported tiles will be cropped such that the bounding geometry surrounds only the feature in the tile.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ====================================     ====================================================================

        :return:
            Output string containing the location of the exported training data

        """
        task = "ExportTrainingDataforDeepLearning"
        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_layer=input_raster)

        if input_class_data is not None:
            input_class_data = self._layer_input(input_layer=input_class_data)

        if chip_format is not None:
            chipFormatAllowedValues = ["TIFF", "PNG", "JPEG", "MRF"]
            if not chip_format in chipFormatAllowedValues:
                raise RuntimeError(
                    "chip_format can only be one of the following: "
                    + str(chipFormatAllowedValues)
                )

        if metadata_format is not None:
            metadataFormatAllowedValues = [
                "KITTI_rectangles",
                "PASCAL_VOC_rectangles",
                "Classified_Tiles",
                "RCNN_Masks",
                "Labeled_Tiles",
                "MultiLabeled_Tiles",
                "Export_Tiles",
                "CycleGAN",
                "Imagenet",
                "Panoptic_Segmentation",
            ]
            if not metadata_format in metadataFormatAllowedValues:
                raise RuntimeError(
                    "metadata_format can only be one of the following: "
                    + str(metadataFormatAllowedValues)
                )

        if reference_system is not None:
            reference_system_allowed_values = [
                "MAP_SPACE",
                "IMAGE_SPACE",
                "PIXEL_SPACE",
            ]
            if not reference_system in reference_system_allowed_values:
                raise RuntimeError(
                    "reference_system can only be one of the following: "
                    + str(reference_system_allowed_values)
                )

        if additional_input_raster is not None:
            additional_input_raster = self._layer_input(
                input_layer=additional_input_raster
            )

        if input_instance_data is not None:
            input_instance_data = self._layer_input(input_layer=input_instance_data)

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 11.0:
            gpjob = self._tbx.export_training_datafor_deep_learning(
                input_raster=input_raster,
                output_location=output_location,
                input_class_data=input_class_data,
                chip_format=chip_format,
                tile_size=tile_size,
                stride_size=stride_size,
                metadata_format=metadata_format,
                class_value_field=class_value_field,
                buffer_radius=buffer_radius,
                input_mask_polygons=input_mask_polygons,
                rotation_angle=rotation_angle,
                reference_system=reference_system,
                process_all_raster_items=process_all_raster_items,
                blacken_around_feature=blacken_around_feature,
                fix_chip_size=fix_chip_size,
                additional_input_raster=additional_input_raster,
                input_instance_data=input_instance_data,
                instance_class_value_field=instance_class_value_field,
                min_polygon_overlap_ratio=min_polygon_overlap_ratio,
                context=context,
                gis=self._gis,
                future=True,
            )
        elif (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] >= 10.8:
            gpjob = self._tbx.export_training_datafor_deep_learning(
                input_raster=input_raster,
                output_location=output_location,
                input_class_data=input_class_data,
                chip_format=chip_format,
                tile_size=tile_size,
                stride_size=stride_size,
                metadata_format=metadata_format,
                class_value_field=class_value_field,
                buffer_radius=buffer_radius,
                input_mask_polygons=input_mask_polygons,
                rotation_angle=rotation_angle,
                reference_system=reference_system,
                process_all_raster_items=process_all_raster_items,
                blacken_around_feature=blacken_around_feature,
                fix_chip_size=fix_chip_size,
                context=context,
                gis=self._gis,
                future=True,
            )
        else:
            gpjob = self._tbx.export_training_datafor_deep_learning(
                input_raster=input_raster,
                output_location=output_location,
                input_class_data=input_class_data,
                chip_format=chip_format,
                tile_size=tile_size,
                stride_size=stride_size,
                metadata_format=metadata_format,
                class_value_field=class_value_field,
                buffer_radius=buffer_radius,
                context=context,
                gis=self._gis,
                future=True,
            )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def fill(
        self,
        input_surface_raster,
        output_name=None,
        z_limit=None,
        context=None,
        gis=None,
        future=False,
    ):
        """Fill GPtool"""
        task = "Fill"
        if context is None:
            context = {}
            _set_raster_context(params=context)
        gpjob = self._tbx.fill(
            input_surface_raster=input_surface_raster,
            output_name=output_name,
            z_limit=z_limit,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def flow_accumulation(
        self,
        input_flow_direction_raster,
        output_name=None,
        input_weight_raster=None,
        data_type="float",
        flow_direction_type="D8",
        context=None,
        gis=None,
        future=False,
    ):
        """flow accumulation GPtool"""
        task = "FlowAccumulation"
        if context is None:
            context = {}
            _set_raster_context(params=context)
        gpjob = self._tbx.flow_accumulation(
            input_flow_direction_raster=input_flow_direction_raster,
            output_name=output_name,
            input_weight_raster=input_weight_raster,
            data_type=data_type,
            flow_direction_type=flow_direction_type,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def flow_direction(
        self,
        input_surface_raster,
        output_flow_direction_name=None,
        force_flow=False,
        flow_direction_type="D8",
        output_drop_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Replaces cells of a raster corresponding to a mask
        with the values of the nearest neighbors.

        Parameters
        ----------
        input_surface_raster : The input raster representing a continuous surface.

        force_flow  : Boolean, Specifies if edge cells will always flow outward or follow normal flow rules.

        flow_direction_type : Specifies which flow direction type to use.
                                                      D8 - Use the D8 method. This is the default.
                                                      MFD - Use the Multi Flow Direction (MFD) method.
                                                      DINF - Use the D-Infinity method.

        output_drop_name : An optional output drop raster .
                                               The drop raster returns the ratio of the maximum change in elevation from each cell
                                               along the direction of flow to the path length between centers of cells, expressed in percentages.

        output_flow_direction_name : Optional. If not provided, an Image Service is created by the method and used as the output raster.
            You can pass in an existing Image Service Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output Image Service that should be created by this method to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        Returns
        -------
        output_raster : Image layer item
        """
        task = "FlowDirection"

        gis = self._gis
        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_surface_raster = self._layer_input(input_layer=input_surface_raster)

        if force_flow is not None:
            if isinstance(force_flow, bool):
                force_flow = force_flow
            elif isinstance(force_flow, str):
                if force_flow == "NORMAL":
                    force_flow = False
                elif force_flow == "FORCE":
                    force_flow = True

        flow_direction_type_AllowedValues = {"D8", "MFD", "DINF"}

        if not flow_direction_type in flow_direction_type_AllowedValues:
            raise RuntimeError(
                "flow_direction_type can only be one of the following: ".join(
                    flow_direction_type_AllowedValues
                )
            )

        (
            output_flow_direction_raster,
            output_flow_direction_service,
        ) = self._set_output_raster(
            output_name=output_flow_direction_name,
            task=task,
            output_properties=kwargs,
        )

        output_drop_raster = None
        if output_drop_name is not None:
            (
                output_drop_raster,
                output_drop_service,
            ) = self._set_output_raster(
                output_name=output_drop_name,
                task=task,
                output_properties=kwargs,
            )

        gpjob = self._tbx.flow_direction(
            input_surface_raster=input_surface_raster,
            output_flow_direction_name=output_flow_direction_raster,
            force_flow=force_flow,
            flow_direction_type=flow_direction_type,
            output_drop_name=output_drop_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            if output_drop_raster:
                return RAJob(
                    gpjob,
                    [output_flow_direction_service, output_drop_service],
                )
            return RAJob(gpjob, output_flow_direction_service)
        if output_drop_raster:
            return RAJob(
                gpjob, [output_flow_direction_service, output_drop_service]
            ).result()
        return RAJob(gpjob, output_flow_direction_service).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def flow_distance(
        self,
        input_stream_raster,
        input_surface_raster,
        output_name=None,
        input_flow_direction_raster=None,
        distance_type="VERTICAL",
        flow_direction_type="D8",
        statistics_type="MINIMUM",
        context=None,
        gis=None,
        future=False,
    ):
        """flow distance GPtool"""
        task = "FlowDistance"
        if context is None:
            context = {}
            _set_raster_context(params=context)
        gpjob = self._tbx.flow_distance(
            input_stream_raster,
            input_surface_raster,
            output_name=output_name,
            input_flow_direction_raster=input_flow_direction_raster,
            distance_type=distance_type,
            flow_direction_type=flow_direction_type,
            statistics_type=statistics_type,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def generate_raster(
        self,
        raster_function,
        function_arguments=None,
        output_raster_properties=None,
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """

        Parameters
        ----------
        raster_function : Required, see http://resources.arcgis.com/en/help/rest/apiref/israsterfunctions.html

        function_arguments : Optional,  for specifying input Raster alone, portal Item can be passed

        output_raster_properties : Optional

        output_name : Optional. If not provided, an Image Service is created by the method and used as the output raster.
            You can pass in an existing Image Service Item from your GIS to use that instead.

            Alternatively, you can pass in the name of the output Image Service that should be created by this method to be
            used as the output for the tool.

            A RuntimeError is raised if a service by that name already exists

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        Returns
        -------
        output_raster : Image layer item

        """
        task = "GenerateRaster"
        gis = self._gis

        if isinstance(function_arguments, arcgis.gis.Item):
            if function_arguments.type.lower() == "image service":
                function_arguments = {"Raster": {"itemId": function_arguments.itemid}}
            else:
                raise TypeError(
                    "The item type of function_arguments must be an image service"
                )
        function_arguments = json.dumps(function_arguments)

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.generate_raster(
            raster_function=raster_function,
            output_name=output_raster,
            function_arguments=function_arguments,
            output_raster_properties=output_raster_properties,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def generate_raster_collection(
        self,
        output_collection_name,
        collection_builder=None,
        collection_builder_arguments=None,
        raster_function=None,
        raster_function_arguments=None,
        collection_properties=None,
        generate_rasters="True",
        output_basename=None,
        gis=None,
        future=False,
    ):
        """Generate Raster Collection GPtool"""
        task = "GenerateRasterCollection"
        gpjob = self._tbx.generate_raster_collection(
            output_collection_name=output_collection_name,
            collection_builder=collection_builder,
            collection_builder_arguments=collection_builder_arguments,
            raster_function=raster_function,
            raster_function_arguments=raster_function_arguments,
            collection_properties=collection_properties,
            generate_rasters=generate_rasters,
            output_basename=output_basename,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def generate_table(
        self,
        raster_function,
        output_table_name=None,
        function_arguments=None,
        context=None,
        gis=None,
        future=False,
    ):
        """Generate Raster Collection GPtool"""
        task = "GenerateTable"
        if context is None:
            context = {}
            params = {"context": context}
            _set_raster_context(params)
            context.update(params["context"])
        gpjob = self._tbx.generate_table(
            raster_function=raster_function,
            output_table_name=output_table_name,
            function_arguments=function_arguments,
            context=context,
            gis=gis or self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def install_deep_learning_model(self, model_package, future=False, **kwargs):
        """
        Function is used to install the uploaded model package (*.dlpk). Optionally after inferencing
        the necessary information using the model, the model can be uninstalled by uninstall_model()


        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        model_package           item id in the form {"itemId":"<id>"}
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            Path where model is installed

        """
        task = "InstallDeepLearningModel"
        gis = self._gis
        gpjob = self._tbx.install_deep_learning_model(
            model_package=model_package, gis=gis, future=True
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def interpolate_points(
        self,
        input_point_features,
        interpolate_field=None,
        output_name=None,
        optimize_for="BALANCE",
        transform_data=False,
        size_of_local_models=None,
        number_of_neighbors=None,
        output_cell_size=None,
        output_prediction_error=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        This tool allows you to predict values at new locations based on measurements from a collection of points. The tool
        takes point data with values at each point and returns a raster of predicted values:

        * An air quality management district has sensors that measure pollution levels. Interpolate Points can be used to
            predict pollution levels at locations that don't have sensors, such as locations with at-risk populations-
            schools or hospitals, for example.
        * Predict heavy metal concentrations in crops based on samples taken from individual plants.
        * Predict soil nutrient levels (nitrogen, phosphorus, potassium, and so on) and other indicators (such as electrical
            conductivity) in order to study their relationships to crop yield and prescribe precise amounts of fertilizer
            for each location in the field.
        * Meteorological applications include prediction of temperatures, rainfall, and associated variables (such as acid
            rain).

        Parameters
        ----------
        input_point_features : Required point layer containing locations with known values
            The point layer that contains the points where the values have been measured.

        interpolate_field : Required string -  field to interpolate
            Choose the field whose values you wish to interpolate. The field must be numeric.

        optimize_for : Optional string - Choose your preference for speed versus accuracy.
            More accurate predictions take longer to calculate. This parameter alters the default values of several other
            parameters of Interpolate Points in order to optimize speed of calculation, accuracy of results, or a balance of
            the two. By default, the tool will optimize for balance.
            One of the following: ['SPEED', 'BALANCE', 'ACCURACY']

        transform_data : Optional bool - Choose whether to transform your data to the normal distribution.
            Interpolation is most accurate for data that follows a normal (bell-shaped) distribution. If your data does not
            appear to be normally distributed, you should perform a transformation.

        size_of_local_models : Optional int - Size of local models
            Interpolate Points works by building local interpolation models that are mixed together to create the final
            prediction map. This parameter controls how many points will be contained in each local model. Smaller values
            will make results more local and can reveal small-scale effects, but it may introduce some instability in the
            calculations. Larger values will be more stable, but some local effects may be missed.
            The value can range from 30 to 500, but typical values are between 50 and 200.

        number_of_neighbors : Optional int - Number of Neighbors
            Predictions are calculated based on neighboring points. This parameter controls how many points will be used in
            the calculation. Using a larger number of neighbors will generally produce more accurate results, but the
            results take longer to calculate.
            This value can range from 1 to 64, but typical values are between 5 and 15.

        output_cell_size : Optional LinearUnit - Output cell size
            Enter the cell size and unit for the output rasters.
            The available units are Feet, Miles, Meters, and Kilometers.

        output_prediction_error : Optional bool - Output prediction error
            Choose whether you want to create a raster of standard errors for the predicted values.
            Standard errors are useful because they provide information about the reliability of the predicted values.
            A simple rule of thumb is that the true value will fall within two standard errors of the predicted value 95
            percent of the time. For example, suppose a new location gets a predicted value of 50 with a standard error of
            5. This means that this tool's best guess is that the true value at that location is 50, but it reasonably could
            be as low as 40 or as high as 60. To calculate this range of reasonable values, multiply the standard error by
            2, add this value to the predicted value to get the upper end of the range, and subtract it from the predicted
            value to get the lower end of the range.

        output_name : Optional. If not provided, an Image Service is created by the method and used as the output raster.
            You can pass in an existing Image Service Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output Image Service that should be created by this method to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        Returns
        -------
        named tuple with name values being :

         - output_raster (the output_raster item description is updated with the process_info),

         - process_info (if run in a non-Jupyter environment, use process_info.data to get the HTML data) and

         - output_error_raster (if output_prediction_error is set to True).

        """
        task = "InterpolatePoints"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_point_features = self._layer_input(input_point_features)

        output_raster, output_service = self._set_output_raster(
            output_name, task, kwargs
        )

        gpjob = self._tbx.interpolate_points(
            input_point_features=input_point_features,
            interpolate_field=interpolate_field,
            output_name=output_raster,
            optimize_for=optimize_for,
            transform_data=transform_data,
            size_of_local_models=size_of_local_models,
            number_of_neighbors=number_of_neighbors,
            output_cell_size=output_cell_size,
            output_prediction_error=output_prediction_error,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def list_datastore_content(
        self, data_store_name=None, filter=None, future=False, **kwargs
    ):
        """
        List the contents of the datastore registered with the server (fileShares, cloudStores, rasterStores).

        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        datastore              Required. fileshare, rasterstore or cloudstore datastore from which the contents are to be listed.
                               It can be a string specifying the datastore path eg "/fileShares/SensorData", "/cloudStores/testcloud",
                               "/rasterStores/rasterstore"
                               or it can be a Datastore object containing a fileshare, rasterstore  or a cloudstore path.
                               eg:
                               ds=analytics.get_datastores()
                               ds_items =ds.search()
                               ds_items[1]
                               ds_items[1] may be specified as input for datastore
        ------------------     --------------------------------------------------------------------
        filter                 Optional. To filter out the raster contents to be displayed
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            List of contents in the datastore
        """

        task = "ListDatastoreContent"
        gis = self._gis

        if isinstance(data_store_name, arcgis.gis.Datastore):
            data_store_name = data_store_name.datapath

        elif isinstance(data_store_name, list):
            for i, datastore_item in enumerate(data_store_name):
                if isinstance(datastore_item, arcgis.gis.Datastore):
                    data_store_name[i] = datastore_item.datapath

        gpjob = self._tbx.list_datastore_content(
            data_store_name=data_store_name,
            filter=filter,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return gpjob
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def list_deep_learning_models(self, future=False, **kwargs):
        """
        Lists the deep learning models registered with the site

        :return: List

        """
        task = "ListDeepLearningModels"
        gis = self._gis
        gpjob = self._tbx.list_deep_learning_models(gis=gis, future=True)
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def nibble(
        self,
        input_raster,
        input_mask_raster,
        output_name=None,
        nibble_values=None,
        nibble_nodata=None,
        input_zone_raster=None,
        context=None,
        gis=None,
        future=False,
    ):
        """Nibble GP Tool"""
        task = "Nibble"
        gis = gis or self._gis
        if context is None:
            context = {}
            params = {"context": context}
            _set_raster_context(params)
            context.update(params["context"])
        gpjob = self._tbx.nibble(
            input_raster=input_raster,
            input_mask_raster=input_mask_raster,
            output_name=output_name,
            nibble_values=nibble_values,
            nibble_nodata=nibble_nodata,
            input_zone_raster=input_zone_raster,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def query_deep_learning_model_info(self, model, future=False, **kwargs):
        """
        Function is used to extract the deep learning model specific settings from the model package item or model definition file.


        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        model           item id in the form {"itemId":"<id>"}
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            Path where model is installed

        """
        task = "QueryDeepLearningModelInfo"
        gis = self._gis
        gpjob = self._tbx.query_deep_learning_model_info(
            model=model, gis=gis, future=True
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # Done: Format Inputs/ Outputs, doc
    def segment(
        self,
        input_raster,
        output_name=None,
        spectral_detail="15.5",
        spatial_detail="15",
        minimum_segment_size_in_pixels="20",
        band_indexes="0,1,2",
        remove_tiling_artifacts=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Groups together adjacent pixels having similar spectral and spatial characteristics into
        segments, known as objects.

        ================================     ====================================================================
        **Parameter**                         **Description**
        --------------------------------     --------------------------------------------------------------------
        input_raster                         Required ImageryLayer object
        --------------------------------     --------------------------------------------------------------------
        spectral_detail                      Optional float. Default is 15.5.
                                             Set the level of importance given to the spectral differences of
                                             features in your imagery. Valid values range from 1.0 to 20.0. A high
                                             value is appropriate when you have features you want to classify
                                             separately but have somewhat similar spectral characteristics.
                                             Smaller values create spectrally smoother outputs.

                                             For example, setting a higher spectral detail value for a forested
                                             scene, will preserve greater discrimination between the different tree
                                             species, resulting in more segments.
        --------------------------------     --------------------------------------------------------------------
        spatial_detail                       Optional float. Default is 15.
                                             Set the level of importance given to the proximity between features
                                             in your imagery. Valid values range from 1 to 20. A high value is
                                             appropriate for a scene where your features of interest are small
                                             and clustered together. Smaller values create spatially smoother
                                             outputs.

                                             For example, in an urban scene, you could classify an impervious
                                             surface using a smaller spatial detail, or you could classify
                                             buildings and roads as separate classes using a higher spatial detail.
        --------------------------------     --------------------------------------------------------------------
        minimum_segment_size_in_pixels       Optional float. Default is 20.
                                             Merge segments smaller than this size with their best fitting
                                             neighbor segment. This is related to the minimum mapping unit for a
                                             mapping project. Units are in pixels.
        --------------------------------     --------------------------------------------------------------------
        band_indexes                         Optional List of integers. Default is [0,1,2]
                                             Define which 3 bands are used in segmentation. Choose the bands that
                                             visually discriminate your features of interest best.
        --------------------------------     --------------------------------------------------------------------
        remove_tiling_artifacts              Optional Bool. Default is False.
                                             If False, the tool will not run to remove tiling artifacts after
                                             segmentation. The result may seem blocky at some tiling boundaries.
        --------------------------------     --------------------------------------------------------------------
        output_name                          Optional String. If specified, an Imagery Layer of given name is
                                             created. Else, an Image Service is created by the method and used
                                             as the output raster. You can pass in an existing Image Service Item
                                             from your GIS to use that instead. Alternatively, you can pass in
                                             the name of the output Image Service that should be created by this
                                             method to be used as the output for the tool. A RuntimeError is raised
                                             if a service by that name already exists
        --------------------------------     --------------------------------------------------------------------
        gis                                  Optional GIS object. If not speficied, the currently active connection
                                             is used.
        ================================     ====================================================================

        :return:
           output_raster : Imagery Layer item
        """
        task = "Segment"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_raster)

        if isinstance(spectral_detail, (float, int)):
            spectral_detail = str(spectral_detail)

        if isinstance(spatial_detail, (float, int)):
            spatial_detail = str(spatial_detail)

        if isinstance(minimum_segment_size_in_pixels, (float, int)):
            minimum_segment_size_in_pixels = str(minimum_segment_size_in_pixels)

        if isinstance(band_indexes, (list, tuple)):
            band_indexes = ",".join(str(e) for e in band_indexes)

        if isinstance(remove_tiling_artifacts, bool):
            remove_tiling_artifacts = str(remove_tiling_artifacts).lower()

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.segment(
            input_raster=input_raster,
            output_name=output_raster,
            spectral_detail=spectral_detail,
            spatial_detail=spatial_detail,
            minimum_segment_size_in_pixels=minimum_segment_size_in_pixels,
            band_indexes=band_indexes,
            remove_tiling_artifacts=remove_tiling_artifacts,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def stream_link(
        self,
        input_stream_raster,
        input_flow_direction_raster,
        output_name=None,
        context=None,
        gis=None,
        future=False,
    ):
        """Stream Link GP Tool"""
        task = "StreamLink"
        gis = gis or self._gis
        if context is None:
            context = {}
            params = {"context": context}
            _set_raster_context(params)
            context.update(params["context"])
        gpjob = self._tbx.stream_link(
            input_stream_raster=input_stream_raster,
            input_flow_direction_raster=input_flow_direction_raster,
            output_name=output_name,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def summarize_raster_within(
        self,
        input_zone_layer,
        input_raster_layer_to_summarize,
        zone_field="Value",
        output_name=None,
        statistic_type="Mean",
        ignore_missing_values=True,
        context=None,
        future=False,
        process_as_multidimensional=False,
        percentile_value=90,
        percentile_interpolation_type="AUTO_DETECT",
        circular_calculation=False,
        circular_wrap_value=360,
        **kwargs,
    ):
        """
        Parameters
        ----------
        input_zone_layer : Required layer - area layer to summarize a raster layer within defined boundaries.
            The layer that defines the boundaries of the areas, or zones, that will be summarized.
            The layer can be a raster or feature data. For rasters, the zones are defined by all locations in the input that
            have the same cell value. The areas do not have to be contiguous.

        input_raster_layer_to_summarize : Required  - raster layer to summarize.
            The raster cells in this layer will be summarized by the areas (zones) that they fall within.

        zone_field : Required string -  field to define the boundaries. This is the attribute of the layer that will be used
            to define the boundaries of the areas. For example, suppose the first input layer defines the management unit
            boundaries, with attributes that define the region, the district, and the parcel ID of each unit. You also have
            a raster layer defining a biodiversity index for each location. With the field you select, you can decide to
            calculate the average biodiversity at local, district, or regional levels.

        statistic_type : Optional string - statistic to calculate.
            You can calculate statistics of any numerical attribute of the points, lines, or areas within the input area
            layer. The available statistics types when the selected field is integer are
            Mean, Maximum, Median, Minimum, Minority, Range, Standard deviation(STD), Sum, and Variety. If the field is
            floating point, the options are Mean, Maximum, Minimum, Range, Standard deviation, and Sum.
            One of the following:
            ['Mean', 'Majority', 'Maximum', 'Median', 'Minimum', 'Minority', 'Range', 'STD', 'SUM', 'Variety']

        ignore_missing_values : Optional bool.
            If you choose to ignore missing values, only the cells that have a value in the layer to be summarized will be
            used in determining the output value for that area. Otherwise, if there are missing values anywhere in an area,
            it is deemed that there is insufficient information to perform statistical calculations for all the cells in
            that zone, and that area will receive a null (NoData) value in the output.

        output_name : Optional. If not provided, an Image Service is created by the method and used as the output raster.
            You can pass in an existing Image Service Item from your GIS to use that instead.
            Alternatively, you can pass in the name of the output Image Service that should be created by this method to be used as the output for the tool.
            A RuntimeError is raised if a service by that name already exists

        process_as_multidimensional: processAsMultidimensional (bool). Optional parameter.  Determines how the input rasters will be processed if they are multidimensional.Unchecked—Statistics will be calculated from the current slice of a multidimensional image service. This is the default.Checked—Statistics will be calculated for all dimensions (such as time or depth) of a multidimensional image service.CURRENT_SLICE— Statistics will be calculated from the current slice of a multidimensional image service. This is the default.ALL_SLICES— Statistics will be calculated for all dimensions (such as time or depth) of a multidimensional image service.

        percentile_value: percentileValue (float). Optional parameter.

        percentile_interpolation_type: percentileInterpolationType (str). Optional parameter.
          Choice list:AUTO_DETECT,NEAREST,LINEAR



        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        future: Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.

        Returns
        -------
        output_raster : Image layer item
        """
        task = "SummarizeRasterWithin"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_zone_layer = self._layer_input(input_zone_layer)
        input_raster_layer_to_summarize = self._layer_input(
            input_raster_layer_to_summarize
        )

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if percentile_interpolation_type is not None:
            if (
                "percentile_interpolation_type"
                in self._tbx.choice_list.summarize_raster_within.keys()
            ):
                percentile_interpolation_type_allowed_values = (
                    self._tbx.choice_list.summarize_raster_within[
                        "percentile_interpolation_type"
                    ]
                )
                if [
                    element.lower()
                    for element in percentile_interpolation_type_allowed_values
                ].count(percentile_interpolation_type.lower()) <= 0:
                    raise RuntimeError(
                        "percentile_interpolation_type can only be one of the following:  "
                        + str(percentile_interpolation_type_allowed_values)
                    )
                for element in percentile_interpolation_type_allowed_values:
                    if percentile_interpolation_type.lower() == element.lower():
                        percentile_interpolation_type = element

        current_version = None
        if "currentVersion" in self._gis._tools.rasteranalysis.properties.keys():
            current_version = self._gis._tools.rasteranalysis.properties[
                "currentVersion"
            ]

        if (current_version is not None) and current_version <= 10.8:
            gpjob = self._tbx.summarize_raster_within(
                input_zone_layer=input_zone_layer,
                zone_field=zone_field,
                input_raster_layerto_summarize=input_raster_layer_to_summarize,
                output_name=output_raster,
                statistic_type=statistic_type,
                ignore_missing_values=ignore_missing_values,
                context=context,
                gis=gis,
                future=True,
            )
        elif (current_version is not None) and (
            current_version > 10.8 and current_version < 10.9
        ):
            gpjob = self._tbx.summarize_raster_within(
                input_zone_layer=input_zone_layer,
                zone_field=zone_field,
                input_raster_layerto_summarize=input_raster_layer_to_summarize,
                output_name=output_raster,
                statistic_type=statistic_type,
                ignore_missing_values=ignore_missing_values,
                context=context,
                process_as_multidimensional=process_as_multidimensional,
                percentile_value=percentile_value,
                gis=gis,
                future=True,
            )
        elif (current_version is not None) and (
            current_version >= 10.9 and current_version < 11
        ):
            gpjob = self._tbx.summarize_raster_within(
                input_zone_layer=input_zone_layer,
                zone_field=zone_field,
                input_raster_layerto_summarize=input_raster_layer_to_summarize,
                output_name=output_raster,
                statistic_type=statistic_type,
                ignore_missing_values=ignore_missing_values,
                context=context,
                process_as_multidimensional=process_as_multidimensional,
                percentile_value=percentile_value,
                percentile_interpolation_type=percentile_interpolation_type,
                gis=gis,
                future=True,
            )
        elif (current_version is not None) and current_version >= 11:
            gpjob = self._tbx.summarize_raster_within(
                input_zone_layer=input_zone_layer,
                zone_field=zone_field,
                input_raster_layerto_summarize=input_raster_layer_to_summarize,
                output_name=output_raster,
                statistic_type=statistic_type,
                ignore_missing_values=ignore_missing_values,
                context=context,
                process_as_multidimensional=process_as_multidimensional,
                percentile_value=percentile_value,
                percentile_interpolation_type=percentile_interpolation_type,
                circular_calculation=circular_calculation,
                circular_wrap_value=circular_wrap_value,
                gis=gis,
                future=True,
            )

        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def train_classifier(
        self,
        input_raster,
        input_training_sample_json,
        classifier_parameters,
        segmented_raster=None,
        segment_attributes="COLOR;MEAN",
        dimension_value_field=None,
        future=False,
        output_ecd_item_name=None,
        **kwargs,
    ):
        """


        Parameters
        ----------
        input_raster : Required string

        input_training_sample_json : Required string

        segmented_raster : Optional string

        classifier_parameters : Required string

        segment_attributes : Required string

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        Returns
        -------
        output_classifier_definition
        """

        task = "TrainClassifier"
        gis = self._gis

        input_raster = self._layer_input(input_raster)

        if segmented_raster is not None:
            segmented_raster = self._layer_input(segmented_raster)

        if output_ecd_item_name is not None:
            if isinstance(output_ecd_item_name, Item):
                output_ecd_item_name = {
                    "name": output_ecd_item_name.name,
                    "itemId": output_ecd_item_name.itemid,
                }
            elif isinstance(output_ecd_item_name, str):
                output_ecd_item_name = json.dumps({"name": output_ecd_item_name})

        if self._current_version is not None:
            current_version = self._current_version
            if current_version is not None and current_version >= 11.1:
                gpjob = self._tbx.train_classifier(
                    input_raster=input_raster,
                    input_training_sample_json=input_training_sample_json,
                    classifier_parameters=classifier_parameters,
                    segmented_raster=segmented_raster,
                    segment_attributes=segment_attributes,
                    dimension_value_field=dimension_value_field,
                    output_ecd_item_name=output_ecd_item_name,
                    gis=gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.9:
                gpjob = self._tbx.train_classifier(
                    input_raster=input_raster,
                    input_training_sample_json=input_training_sample_json,
                    classifier_parameters=classifier_parameters,
                    segmented_raster=segmented_raster,
                    segment_attributes=segment_attributes,
                    dimension_value_field=dimension_value_field,
                    gis=gis,
                    future=True,
                )
            elif (current_version is not None) and current_version < 10.9:
                gpjob = self._tbx.train_classifier(
                    input_raster=input_raster,
                    input_training_sample_json=input_training_sample_json,
                    classifier_parameters=classifier_parameters,
                    segmented_raster=segmented_raster,
                    segment_attributes=segment_attributes,
                    gis=gis,
                    future=True,
                )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------

    def transfer_files(
        self,
        input_files,
        output_datastore=None,
        tf_filter=None,
        return_first_file=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Transfer Files GP Tool

        input_files: inputFiles (str). Required parameter.

        output_datastore: outputDatastore (str). Optional parameter.

        tf_filter: filter (str). Optional parameter.

        return_first_file: returnFirstFile (bool). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """
        task = "TransferFiles"
        gis = self._gis

        if context is None:
            context = {}
            params = {"context": context}
            _set_raster_context(params)
            context.update(params["context"])

        if isinstance(input_files, str):
            input_files = {"uri": input_files}

        gpjob = self._tbx.transfer_files(
            input_files=input_files,
            output_datastore=output_datastore,
            filter=tf_filter,
            return_first_file=return_first_file,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def uninstall_deep_learning_model(self, model_item_id, future=False, **kwargs):
        """
        Function is used to uninstall the uploaded model package that was installed using the install_model()
        This function will delete the named deep learning model from the server but not the portal item.

        ==================     ====================================================================
        **Parameter**           **Description**
        ------------------     --------------------------------------------------------------------
        model_item_id          item id in the form {"itemId":"<id>"}
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ==================     ====================================================================

        :return:
            itemId of the uninstalled model package item

        """
        task = "UninstallDeepLearningModel"
        gis = self._gis
        gpjob = self._tbx.uninstall_deep_learning_model(
            model_item_id=model_item_id, gis=gis, future=True
        )
        gpjob._is_ra = True
        gpjob._item_properties = False
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    # TODO: Format Inputs/ Outputs, doc
    def watershed(
        self,
        input_flow_direction_raster,
        in_pour_point_raster_or_features,
        output_name=None,
        pour_point_field=None,
        context=None,
        gis=None,
        future=False,
    ):
        """Watershed GP Tool"""
        task = "Watershed"
        gis = gis or self._gis
        if context is None:
            context = {}
            params = {"context": context}
            _set_raster_context(params)
            context.update(params["context"])
        gpjob = self._tbx.watershed(
            input_flow_direction_raster=input_flow_direction_raster,
            in_pour_point_raster_or_features=in_pour_point_raster_or_features,
            output_name=output_name,
            pour_point_field=pour_point_field,
            context=context,
            gis=gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    # ----------------------------------------------------------------------
    def copy_raster(
        self,
        input_raster=None,
        output_cellsize=None,
        resampling_method="NEAREST",
        clip_setting=None,
        output_name=None,
        context=None,
        future=False,
        raster_type_name=None,
        raster_type_params=None,
        md_to_upload=None,
        **kwargs,
    ):
        """
         input_raster: inputRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        output_cellsize: outputCellsize (str). Optional parameter.

        resampling_method: resamplingMethod (str). Optional parameter.  Choice list:['NEAREST', 'BILINEAR', 'CUBIC', 'MAJORITY']

        clip_setting: clipSetting (str). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.


        Returns:
            output_raster - outputRaster as a str

        """

        task = "CopyRaster"
        gis = self._gis
        upload_properties = None
        use_input_rasters_by_ref = None
        if context is not None:
            if "byref" in context:
                use_input_rasters_by_ref = context["byref"]
                del context["byref"]
            if "upload_properties" in context:
                upload_properties = context["upload_properties"]
                del context["upload_properties"]

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if (
            not isinstance(input_raster, str)
            and not isinstance(input_raster, list)
            and not raster_type_name
        ):
            input_raster = self._layer_input(input_layer=input_raster)

        else:
            md_data_path = []
            if md_to_upload is not None:
                if isinstance(input_raster, str):
                    md_data_path.append(os.path.dirname(input_raster))
                elif isinstance(input_raster, list):
                    for ele in input_raster:
                        md_data_path.append(os.path.dirname(ele))

                raster_type_name = "mosaic_dataset"
            md_data_info = []
            if (
                isinstance(raster_type_name, str)
            ) and raster_type_name == "mosaic_dataset":
                (
                    input_raster,
                    raster_type,
                    md_data_info,
                ) = self._build_param_dictionary(
                    input_rasters=input_raster,
                    raster_type_name=raster_type_name,
                    raster_type_params=raster_type_params,
                    image_collection_properties=None,
                    use_input_rasters_by_ref=use_input_rasters_by_ref,
                    upload_properties=upload_properties,
                    task=task,
                )
            else:
                input_raster, raster_type = self._build_param_dictionary(
                    input_rasters=input_raster,
                    raster_type_name=raster_type_name,
                    raster_type_params=raster_type_params,
                    image_collection_properties=None,
                    use_input_rasters_by_ref=use_input_rasters_by_ref,
                    upload_properties=upload_properties,
                    task=task,
                )

            mosaic_dataset_uploaded = md_to_upload
            if md_to_upload is not None:
                if gis._con._product == "AGOL":
                    from arcgis.raster._util import _upload_imagery_agol

                    if ".gdb" in md_to_upload:
                        gdb_path = os.path.dirname(md_to_upload)
                    uploaded_list = _upload_imagery_agol(
                        [gdb_path], gis, upload_properties=upload_properties
                    )
                    if len(uploaded_list) == 1:
                        azure_upload_url = uploaded_list[0]
                        mosaic_dataset_uploaded = (
                            azure_upload_url
                            + "/"
                            + os.path.basename(gdb_path)
                            + "/"
                            + os.path.basename(md_to_upload)
                        )

                input_raster.update(
                    {
                        "mosaic_dataset": mosaic_dataset_uploaded,
                        "data_path": md_data_info,
                    }
                )

            if raster_type_name == "mosaic_dataset":
                raster_type = None

            if isinstance(raster_type, str):
                try:
                    raster_type = json.loads(raster_type)
                except:
                    pass
            if raster_type_name:
                if isinstance(input_raster, dict) and isinstance(raster_type, dict):
                    input_raster.update({"rasterType": raster_type})

        gpjob = self._tbx.copy_raster(
            input_raster=input_raster,
            output_name=output_raster,
            output_cellsize=output_cellsize,
            resampling_method=resampling_method,
            clip_setting=clip_setting,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def aggregate_multidimensional_raster(
        self,
        input_multidimensional_raster=None,
        output_name=None,
        dimension=None,
        aggregation_method="MEAN",
        variables=None,
        aggregation_definition="ALL",
        interval_keyword=None,
        interval_value=None,
        interval_unit=None,
        interval_ranges=None,
        aggregation_function=None,
        ignore_nodata=True,
        context=None,
        future=False,
        dimensionless=False,
        percentile_value=90,
        percentile_interpolation_type="NEAREST",
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        dimension: dimension (str). Required parameter.

        aggregation_method: aggregationMethod (str). Optional parameter.
          Choice list:['MEAN', 'MAXIMUM', 'MAJORITY', 'MINIMUM', 'MINORITY', 'MEDIAN', 'RANGE', 'STD', 'SUM', 'VARIETY', 'CUSTOM']

        variables: variables (str). Optional parameter.

        aggregation_definition: aggregationDefinition (str). Optional parameter.
          Choice list:['INTERVAL_KEYWORD', 'INTERVAL_VALUE', 'INTERVAL_RANGES', 'ALL']

        interval_keyword: intervalKeyword (str). Optional parameter.
          Choice list:['HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY', 'RECURRING_DAILY', 'RECURRING_WEEKLY',
                       'RECURRING_MONTHLY', 'RECURRING_QUARTERLY', 'DEKADLY', 'PENTADLY']

        interval_value: intervalValue (str). Optional parameter.

        interval_unit: intervalUnit (str). Optional parameter.

        interval_ranges: intervalRanges (str). Optional parameter.

        aggregation_function: aggregationFunction (str). Optional parameter.

        ignore_nodata: ignoreNodata (bool). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "AggregateMultidimensionalRaster"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        aggregation_method_val = aggregation_method
        if aggregation_method is not None:
            aggregation_method_allowed_values = [
                "MEAN",
                "MAXIMUM",
                "MAJORITY",
                "MINIMUM",
                "MINORITY",
                "MEDIAN",
                "RANGE",
                "STD",
                "SUM",
                "VARIETY",
                "CUSTOM",
                "PERCENTILE",
            ]
            if [element.lower() for element in aggregation_method_allowed_values].count(
                aggregation_method.lower()
            ) <= 0:
                raise RuntimeError(
                    "aggregation_method can only be one of the following: "
                    + str(aggregation_method_allowed_values)
                )
            for element in aggregation_method_allowed_values:
                if aggregation_method.upper() == element:
                    aggregation_method_val = element

        aggregation_definition_val = aggregation_definition
        if aggregation_definition is not None:
            aggregation_def_allowed_values = [
                "INTERVAL_KEYWORD",
                "INTERVAL_VALUE",
                "INTERVAL_RANGES",
                "ALL",
            ]

            if [element.lower() for element in aggregation_def_allowed_values].count(
                aggregation_definition.lower()
            ) <= 0:
                raise RuntimeError(
                    "aggregation_definition can only be one of the following: "
                    + str(aggregation_def_allowed_values)
                )
            for element in aggregation_def_allowed_values:
                if aggregation_definition.upper() == element:
                    aggregation_definition_val = element

        interval_keyword_val = interval_keyword
        if interval_keyword is not None:
            interval_keyword_allowed_values = [
                "HOURLY",
                "DAILY",
                "WEEKLY",
                "MONTHLY",
                "QUARTERLY",
                "YEARLY",
                "RECURRING_DAILY",
                "RECURRING_WEEKLY",
                "RECURRING_MONTHLY",
                "RECURRING_QUARTERLY",
                "PENTADLY",
                "DEKADLY",
            ]
            if [element.lower() for element in interval_keyword_allowed_values].count(
                interval_keyword.lower()
            ) <= 0:
                raise RuntimeError(
                    "interval_keyword can only be one of the following: "
                    + str(interval_keyword_allowed_values)
                )
            interval_keyword_val = interval_keyword
            for element in interval_keyword_allowed_values:
                if interval_keyword.upper() == element:
                    interval_keyword_val = element

        percentile_interpolation_type_val = percentile_interpolation_type
        if percentile_interpolation_type is not None:
            percentile_interpolation_type_allowed_values = [
                "NEAREST",
                "LINEAR",
            ]
            if [
                element.lower()
                for element in percentile_interpolation_type_allowed_values
            ].count(percentile_interpolation_type.lower()) <= 0:
                raise RuntimeError(
                    "percentile_interpolation_type can only be one of the following: "
                    + str(percentile_interpolation_type_allowed_values)
                )
            percentile_interpolation_type_val = percentile_interpolation_type
            for element in percentile_interpolation_type_allowed_values:
                if percentile_interpolation_type.upper() == element:
                    percentile_interpolation_type_val = element

        if isinstance(aggregation_function, Item):
            aggregation_function = {"itemId": aggregation_function.itemid}

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version < 10.91:
                gpjob = self._tbx.aggregate_multidimensional_raster(
                    input_multidimensional_raster=input_multidimensional_raster,
                    output_name=output_raster,
                    dimension=dimension,
                    aggregation_method=aggregation_method_val,
                    variables=variables,
                    aggregation_definition=aggregation_definition_val,
                    interval_keyword=interval_keyword_val,
                    interval_value=interval_value,
                    interval_unit=interval_unit,
                    interval_ranges=interval_ranges,
                    aggregation_function=aggregation_function,
                    ignore_nodata=ignore_nodata,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.91:
                gpjob = self._tbx.aggregate_multidimensional_raster(
                    input_multidimensional_raster=input_multidimensional_raster,
                    output_name=output_raster,
                    dimension=dimension,
                    aggregation_method=aggregation_method_val,
                    variables=variables,
                    aggregation_definition=aggregation_definition_val,
                    interval_keyword=interval_keyword_val,
                    interval_value=interval_value,
                    interval_unit=interval_unit,
                    interval_ranges=interval_ranges,
                    aggregation_function=aggregation_function,
                    ignore_nodata=ignore_nodata,
                    dimensionless=dimensionless,
                    percentile_value=percentile_value,
                    percentile_interpolation_type=percentile_interpolation_type,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def generate_multidimensional_anomaly(
        self,
        input_multidimensional_raster=None,
        output_name=None,
        variables=None,
        method=None,
        calculation_interval=None,
        ignore_nodata=True,
        context=None,
        reference_mean_raster=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        variables: variables (str). Optional parameter.

        method: method (str). Optional parameter.
           Choice list:['DIFFERENCE_FROM_MEAN', 'PERCENT_DIFFERENCE_FROM_MEAN', 'PERCENT_OF_MEAN', 'Z_SCORE', 'DIFFERENCE_FROM_MEDIAN', 'PERCENT_DIFFERENCE_FROM_MEDIAN', 'PERCENT_OF_MEDIAN']

        temporal_interval: temporalInterval (str). Optional parameter.
           Choice list: ['ALL', 'HOURLY', 'RECURRING_DAILY', 'RECURRING_WEEKLY', 'RECURRING_MONTHLY', 'YEARLY']

        ignore_nodata: ignoreNodata (bool). Optional parameter.

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "GenerateMultidimensionalAnomaly"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        method_val = method
        if method is not None:
            method_allowed_values = [
                "DIFFERENCE_FROM_MEAN",
                "PERCENT_DIFFERENCE_FROM_MEAN",
                "PERCENT_OF_MEAN",
                "Z_SCORE",
                "DIFFERENCE_FROM_MEDIAN",
                "PERCENT_DIFFERENCE_FROM_MEDIAN",
                "PERCENT_OF_MEDIAN",
            ]
            if [element.lower() for element in method_allowed_values].count(
                method.lower()
            ) <= 0:
                raise RuntimeError(
                    "method can only be one of the following: "
                    + str(method_allowed_values)
                )

            for element in method_allowed_values:
                if method.upper() == element:
                    method_val = element

        calculation_interval_val = calculation_interval
        if calculation_interval is not None:
            interval_keyword_allowed_values = [
                "ALL",
                "HOURLY",
                "RECURRING_DAILY",
                "RECURRING_WEEKLY",
                "RECURRING_MONTHLY",
                "YEARLY",
                "EXTERNAL_RASTER",
            ]
            if [element.lower() for element in interval_keyword_allowed_values].count(
                calculation_interval.lower()
            ) <= 0:
                raise RuntimeError(
                    "calculation_interval can only be one of the following: "
                    + str(interval_keyword_allowed_values)
                )

            for element in interval_keyword_allowed_values:
                if calculation_interval.upper() == element:
                    calculation_interval_val = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if reference_mean_raster is not None:
            reference_mean_raster = self._layer_input(input_layer=reference_mean_raster)

        if (
            "currentVersion" in self._gis._tools.rasteranalysis.properties.keys()
        ) and self._gis._tools.rasteranalysis.properties["currentVersion"] <= 10.8:
            gpjob = self._tbx.generate_multidimensional_anomaly(
                input_multidimensional_raster=input_multidimensional_raster,
                output_name=output_raster,
                variables=variables,
                method=method_val,
                calculation_interval=calculation_interval_val,
                ignore_nodata=ignore_nodata,
                context=context,
                gis=self._gis,
                future=True,
            )

        else:
            gpjob = self._tbx.generate_multidimensional_anomaly(
                input_multidimensional_raster=input_multidimensional_raster,
                output_name=output_raster,
                variables=variables,
                method=method_val,
                calculation_interval=calculation_interval_val,
                ignore_nodata=ignore_nodata,
                reference_mean_raster=reference_mean_raster,
                context=context,
                gis=self._gis,
                future=True,
            )

        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)
        return RAJob(gpjob, item=item).result()

    def build_multidimensional_transpose(
        self,
        input_multidimensional_raster=None,
        context=None,
        future=False,
        delete_transpose=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "BuildMultidimensionalTranspose"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        current_version = None
        if "currentVersion" in self._gis._tools.rasteranalysis.properties.keys():
            current_version = self._gis._tools.rasteranalysis.properties[
                "currentVersion"
            ]

        if (current_version is not None) and current_version <= 10.81:
            gpjob = self._tbx.build_multidimensional_transpose(
                input_multidimensional_raster=input_multidimensional_raster,
                context=context,
                gis=self._gis,
                future=True,
            )
        elif (current_version is not None) and current_version >= 10.9:
            if not isinstance(delete_transpose, bool):
                raise RuntimeError("delete_transpose must be an instance of boolean")
            gpjob = self._tbx.build_multidimensional_transpose(
                input_multidimensional_raster=input_multidimensional_raster,
                context=context,
                delete_transpose=delete_transpose,
                gis=self._gis,
                future=True,
            )

        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def generate_trend_raster(
        self,
        input_multidimensional_raster=None,
        output_name=None,
        dimension=None,
        variables=None,
        trend_line_type="LINEAR",
        frequency=None,
        ignore_nodata=True,
        context=None,
        cycle_length=None,
        cycle_unit="YEARS",
        rmse=True,
        r2=False,
        slope_p_value=False,
        seasonal_period="DAYS",
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        dimension: dimension (str). Required parameter.

        variables: variables (str). Optional parameter.

        trend_line_type: trendLineType (str). Optional parameter.
            Choice list:['LINEAR', 'HARMONIC', 'POLYNOMIAL']

        frequency: frequency (int). Optional parameter.

        ignore_nodata: ignoreNodata (bool). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "GenerateTrendRaster"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        trend_line_type_val = trend_line_type
        if trend_line_type is not None:
            trend_line_type_allowed_values = [
                "LINEAR",
                "HARMONIC",
                "POLYNOMIAL",
                "MANN-KENDALL",
                "SEASONAL-KENDALL",
            ]
            if [element.lower() for element in trend_line_type_allowed_values].count(
                trend_line_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "trend_line_type can only be one of the following: "
                    + str(trend_line_type_allowed_values)
                )

            for element in trend_line_type_allowed_values:
                if trend_line_type.upper() == element:
                    trend_line_type_val = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if cycle_unit is not None:
            if "cycle_unit" in self._tbx.choice_list.generate_trend_raster.keys():
                cycle_unit_allowed_values = self._tbx.choice_list.generate_trend_raster[
                    "cycle_unit"
                ]
                if [element.lower() for element in cycle_unit_allowed_values].count(
                    cycle_unit.lower()
                ) <= 0:
                    raise RuntimeError(
                        "cycle_unit can only be one of the following:  "
                        + str(cycle_unit_allowed_values)
                    )
                for element in cycle_unit_allowed_values:
                    if cycle_unit.lower() == element.lower():
                        cycle_unit = element

        if seasonal_period is not None:
            if "seasonal_period" in self._tbx.choice_list.generate_trend_raster.keys():
                seasonal_period_allowed_values = (
                    self._tbx.choice_list.generate_trend_raster["seasonal_period"]
                )
                if [
                    element.lower() for element in seasonal_period_allowed_values
                ].count(seasonal_period.lower()) <= 0:
                    raise RuntimeError(
                        "seasonal_period can only be one of the following:  "
                        + str(seasonal_period_allowed_values)
                    )
                for element in seasonal_period_allowed_values:
                    if seasonal_period.lower() == element.lower():
                        seasonal_period = element

        current_version = None
        if "currentVersion" in self._gis._tools.rasteranalysis.properties.keys():
            current_version = self._gis._tools.rasteranalysis.properties[
                "currentVersion"
            ]

        if (current_version is not None) and current_version <= 10.8:
            gpjob = self._tbx.generate_trend_raster(
                input_multidimensional_raster=input_multidimensional_raster,
                output_name=output_raster,
                dimension=dimension,
                variables=variables,
                trend_line_type=trend_line_type_val,
                frequency=frequency,
                ignore_nodata=ignore_nodata,
                context=context,
                gis=self._gis,
                future=True,
            )
        elif (current_version is not None) and (
            current_version > 10.8 and current_version < 10.9
        ):
            gpjob = self._tbx.generate_trend_raster(
                input_multidimensional_raster=input_multidimensional_raster,
                output_name=output_raster,
                dimension=dimension,
                variables=variables,
                trend_line_type=trend_line_type_val,
                frequency=frequency,
                ignore_nodata=ignore_nodata,
                cycle_length=cycle_length,
                cycle_unit=cycle_unit,
                rmse=rmse,
                r2=r2,
                slope_p_value=slope_p_value,
                context=context,
                gis=self._gis,
                future=True,
            )
        elif (current_version is not None) and current_version >= 10.9:
            gpjob = self._tbx.generate_trend_raster(
                input_multidimensional_raster=input_multidimensional_raster,
                output_name=output_raster,
                dimension=dimension,
                variables=variables,
                trend_line_type=trend_line_type_val,
                frequency=frequency,
                ignore_nodata=ignore_nodata,
                cycle_length=cycle_length,
                cycle_unit=cycle_unit,
                rmse=rmse,
                r2=r2,
                slope_p_value=slope_p_value,
                seasonal_period=seasonal_period,
                context=context,
                gis=self._gis,
                future=True,
            )

        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        else:
            item = None
        if future:
            return RAJob(gpjob, item=item)
        return RAJob(gpjob, item=item).result()

    def predict_using_trend_raster(
        self,
        input_multidimensional_raster=None,
        output_name=None,
        variables=None,
        dimension_definition="BY_VALUE",
        dimension_values=None,
        start=None,
        end=None,
        interval_value=1,
        interval_unit=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        variables: variables (str). Optional parameter.

        dimension_definition: dimensionDefinition (str). Optional parameter.
           Choice list:['BY_VALUE', 'BY_INTERVAL']

        dimension_values: dimensionValues (str). Optional parameter.

        start: start (str). Optional parameter.

        end: end (str). Optional parameter.

        interval_value: intervalValue (float). Optional parameter.

        interval_unit: intervalUnit (str). Optional parameter.
           Choice list:['HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS']

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "PredictUsingTrendRaster"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        dimension_definition_val = dimension_definition
        if dimension_definition is not None:
            dimension_definition_allowed_values = ["BY_VALUE", "BY_INTERVAL"]
            if [
                element.lower() for element in dimension_definition_allowed_values
            ].count(dimension_definition.lower()) <= 0:
                raise RuntimeError(
                    "dimension_definition can only be one of the following: "
                    + str(dimension_definition_allowed_values)
                )

            for element in dimension_definition_allowed_values:
                if dimension_definition.upper() == element:
                    dimension_definition_val = element

        interval_unit_val = interval_unit
        if interval_unit is not None:
            interval_unit_allowed_values = [
                "HOURS",
                "DAYS",
                "DAILY",
                "WEEKS",
                "MONTHS",
                "YEARS",
            ]
            if [element.lower() for element in interval_unit_allowed_values].count(
                interval_unit.lower()
            ) <= 0:
                raise RuntimeError(
                    "interval_unit can only be one of the following: "
                    + str(interval_unit_allowed_values)
                )

            for element in interval_unit_allowed_values:
                if interval_unit.upper() == element:
                    interval_unit_val = element

        values = None
        if dimension_values is not None:
            if isinstance(dimension_values, str):
                if ";" in dimension_values:
                    values = dimension_values.split(";")
                elif "," in dimension_values:
                    values = dimension_values.split(",")
                else:
                    values = dimension_values
            if isinstance(dimension_values, list):
                values = dimension_values

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.predict_using_trend_raster(
            input_multidimensional_raster=input_multidimensional_raster,
            output_name=output_raster,
            variables=variables,
            dimension_definition=dimension_definition_val,
            dimension_values=values,
            start=start,
            end=end,
            interval_value=interval_value,
            interval_unit=interval_unit_val,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        else:
            item = None
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def find_argument_statistics(
        self,
        input_raster=None,
        output_name=None,
        dimension=None,
        dimension_definition="ALL",
        interval_keyword=None,
        variables=None,
        statistics_type="ARGUMENT_MIN",
        min_value=None,
        max_value=None,
        multiple_occurrence_value=None,
        ignore_nodata=True,
        context=None,
        argument_value=None,
        comparison="EQUAL_TO",
        occurrence="FIRST_OCCURRENCE",
        future=False,
        **kwargs,
    ):
        """
        input_raster: inputRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        dimension: dimension (str). Optional parameter.

        dimension_definition: dimensionDefinition (str). Optional parameter.
           Choice list:['ALL', 'INTERVAL_KEYWORD']

        interval_keyword: intervalKeyword (str). Optional parameter.
           Choice list:['HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY',
           'RECURRING_DAILY', 'RECURRING_WEEKLY', 'RECURRING_MONTHLY', 'RECURRING_QUARTERLY']

        variables: variables (str). Optional parameter.

        statistics_type: statisticsType (str). Optional parameter.
           Choice list:['ARGUMENT_MIN', 'ARGUMENT_MAX', 'ARGUMENT_MEDIAN', 'DURATION', 'ARGUMENT_VALUE']

        min_value: minValue (float). Optional parameter.

        max_value: maxValue (float). Optional parameter.

        multiple_occurrence_value: multipleOccurrenceValue (int). Optional parameter.  .

        ignore_nodata: ignoreNodata (bool). Optional parameter.

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

         argument_value: argumentValue (int). Optional parameter. Required when statistics_type is set to 'ARGUMENT_VALUE'.

         comparison: comparison (str). Optional parameter.

         occurrence: occurrence (str). Optional parameter.

        """

        task = "FindArgumentStatistics"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_layer=input_raster)

        dimension_definition_val = dimension_definition
        if dimension_definition is not None:
            dimension_definition_allowed_values = ["ALL", "INTERVAL_KEYWORD"]

            if [
                element.lower() for element in dimension_definition_allowed_values
            ].count(dimension_definition.lower()) <= 0:
                raise RuntimeError(
                    "dimension_definition can only be one of the following: "
                    + str(dimension_definition_allowed_values)
                )

            for element in dimension_definition_allowed_values:
                if dimension_definition.upper() == element:
                    dimension_definition_val = element

        interval_keyword_val = interval_keyword
        if interval_keyword is not None:
            interval_keyword_allowed_values = [
                "HOURLY",
                "DAILY",
                "WEEKLY",
                "MONTHLY",
                "QUARTERLY",
                "YEARLY",
                "RECURRING_DAILY",
                "RECURRING_WEEKLY",
                "RECURRING_MONTHLY",
                "RECURRING_QUARTERLY",
            ]

            if [element.lower() for element in interval_keyword_allowed_values].count(
                interval_keyword.lower()
            ) <= 0:
                raise RuntimeError(
                    "interval_keyword can only be one of the following: "
                    + str(interval_keyword_allowed_values)
                )

            for element in interval_keyword_allowed_values:
                if interval_keyword.upper() == element:
                    interval_keyword_val = element

        statistics_type_val = statistics_type
        if statistics_type is not None:
            statistics_type_allowed_values = [
                "ARGUMENT_MIN",
                "ARGUMENT_MAX",
                "ARGUMENT_MEDIAN",
                "DURATION",
                "ARGUMENT_VALUE",
            ]
            if [element.lower() for element in statistics_type_allowed_values].count(
                statistics_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "statistics_type can only be one of the following: "
                    + str(statistics_type_allowed_values)
                )

            for element in statistics_type_allowed_values:
                if statistics_type.upper() == element:
                    statistics_type_val = element

            if statistics_type.lower() == "argument_value" and argument_value is None:
                raise RuntimeError(
                    "argument_value is required when statistics_type is set to 'ARGUMENT_VALUE'."
                )

        comparison_val = comparison
        if comparison is not None:
            comparison_type_allowed_values = [
                "EQUAL_TO",
                "GREATER_THAN",
                "SMALLER_THAN",
            ]
            if [element.lower() for element in comparison_type_allowed_values].count(
                comparison.lower()
            ) <= 0:
                raise RuntimeError(
                    "comparison can only be one of the following: "
                    + str(comparison_type_allowed_values)
                )

            for element in comparison_type_allowed_values:
                if comparison.upper() == element:
                    comparison_val = element

        occurrence_val = occurrence
        if occurrence is not None:
            occurrence_type_allowed_values = [
                "FIRST_OCCURRENCE",
                "LAST_OCCURRENCE",
            ]
            if [element.lower() for element in occurrence_type_allowed_values].count(
                occurrence.lower()
            ) <= 0:
                raise RuntimeError(
                    "occurrence can only be one of the following: "
                    + str(occurrence_type_allowed_values)
                )

            for element in occurrence_type_allowed_values:
                if occurrence.upper() == element:
                    occurrence_val = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        params = {
            "input_raster": input_raster,
            "output_name": output_raster,
            "dimension": dimension,
            "dimension_definition": dimension_definition_val,
            "interval_keyword": interval_keyword_val,
            "variables": variables,
            "statistics_type": statistics_type_val,
            "min_value": min_value,
            "max_value": max_value,
            "multiple_occurrence_value": multiple_occurrence_value,
            "ignore_nodata": ignore_nodata,
            "context": context,
            "argument_value": argument_value,
            "comparison": comparison_val,
            "occurrence": occurrence_val,
            "gis": self._gis,
        }
        params = _inspect_function_inputs(self._tbx.find_argument_statistics, **params)
        params["future"] = True
        gpjob = self._tbx.find_argument_statistics(**params)

        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service

        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def linear_spectral_unmixing(
        self,
        input_raster=None,
        output_name=None,
        input_spectral_profile=None,
        value_option=[],
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_raster: inputRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        input_spectral_profile: inputSpectralProfile (str). Optional parameter.

        value_option: valueOption (str). Optional parameter.
           Choice list:['SUM_TO_ONE', 'NON_NEGATIVE']

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "LinearSpectralUnmixing"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_layer=input_raster)

        return_list = []
        value_option_allowed_values = ["SUM_TO_ONE", "NON_NEGATIVE"]
        for element in value_option_allowed_values:
            for ele in value_option:
                if ele.upper() == element:
                    return_list.append(element)

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if input_spectral_profile is not None:
            if isinstance(input_spectral_profile, str):
                input_spectral_profile = {"uri": input_spectral_profile}
            elif isinstance(input_spectral_profile, Item):
                input_spectral_profile = {"itemId": input_spectral_profile.itemid}

        gpjob = self._tbx.linear_spectral_unmixing(
            input_raster=input_raster,
            output_name=output_raster,
            input_spectral_profile=input_spectral_profile,
            value_option=return_list,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def subset_multidimensional_raster(
        self,
        input_multidimensional_raster=None,
        output_name=None,
        variables=None,
        dimension_definition="BY_VALUE",
        dimension_ranges=None,
        dimension_values=None,
        dimension=None,
        start_of_first_iteration=None,
        end_of_first_iteration=None,
        iteration_step=None,
        iteration_unit=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        variables: variables (str). Optional parameter.

        dimension_definition: dimensionDefinition (str). Optional parameter.
           Choice list:['ALL', 'BY_VALUE', 'BY_RANGES', 'BY_ITERATION']

        dimension_ranges: dimensionRanges (str). Optional parameter.

        dimension_values: dimensionValues (str). Optional parameter.

        dimension: dimension (str). Optional parameter.

        start_of_first_iteration: recurrenceFrom (str). Optional parameter.

        end_of_first_iteration: recurrenceTo (str). Optional parameter.

        iteration_step: recurrenceInterval (float). Optional parameter.

        iteration_unit: recurrenceUnit (str). Optional parameter.
           Choice list:['HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS']

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "SubsetMultidimensionalRaster"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        dimension_definition_val = dimension_definition
        if dimension_definition is not None:
            dimension_definition_allowed_values = [
                "ALL",
                "BY_VALUE",
                "BY_RANGES",
                "BY_ITERATION",
            ]
            if [
                element.lower() for element in dimension_definition_allowed_values
            ].count(dimension_definition.lower()) <= 0:
                raise RuntimeError(
                    "dimension_definition can only be one of the following: "
                    + str(dimension_definition_allowed_values)
                )

            for element in dimension_definition_allowed_values:
                if dimension_definition.upper() == element:
                    dimension_definition_val = element

        iteration_unit_val = iteration_unit
        if iteration_unit is not None:
            iteration_unit_allowed_values = [
                "HOURS",
                "DAYS",
                "DAILY",
                "WEEKS",
                "MONTHS",
                "YEARS",
            ]
            if [element.lower() for element in iteration_unit_allowed_values].count(
                iteration_unit.lower()
            ) <= 0:
                raise RuntimeError(
                    "iteration_unit can only be one of the following: "
                    + str(iteration_unit_allowed_values)
                )

            for element in iteration_unit_allowed_values:
                if iteration_unit.upper() == element:
                    iteration_unit_val = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.subset_multidimensional_raster(
            input_multidimensional_raster=input_multidimensional_raster,
            output_name=output_raster,
            variables=variables,
            dimension_definition=dimension_definition_val,
            dimension_ranges=dimension_ranges,
            dimension_values=dimension_values,
            dimension=dimension,
            start_of_first_iteration=start_of_first_iteration,
            end_of_first_iteration=end_of_first_iteration,
            iteration_step=iteration_step,
            iteration_unit=iteration_unit_val,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def cost_path_as_polyline(
        self,
        input_destination_raster_or_features=None,
        input_cost_distance_raster=None,
        input_cost_backlink_raster=None,
        output_polyline_name=None,
        path_type=None,
        destination_field=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """

         Parameters

        input_destination_raster_or_features: inputDestinationRasterOrFeatures (str). Required parameter.

        input_cost_distance_raster: inputCostDistanceRaster (str). Required parameter.

        input_cost_backlink_raster: inputCostBacklinkRaster (str). Required parameter.

        output_polyline_name: outputPolylineName (str). Required parameter.

        path_type: pathType (str). Optional parameter.

        destination_field: destinationField (str). Optional parameter.

         context: Context contains additional settings that affect task execution.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

         Returns
         -------
         output_raster : Image layer item
        """
        task = "CostPathAsPolyline"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_destination_raster_or_features, _FEATURE_INPUTS):
            input_destination_raster_or_features = self._feature_input(
                input_destination_raster_or_features
            )
        elif isinstance(input_destination_raster_or_features, Item):
            input_destination_raster_or_features = {
                "itemId": input_destination_raster_or_features.itemid
            }
        else:
            input_destination_raster_or_features = self._layer_input(
                input_destination_raster_or_features
            )

        input_cost_distance_raster = self._layer_input(
            input_layer=input_cost_distance_raster
        )

        input_cost_backlink_raster = self._layer_input(
            input_layer=input_cost_backlink_raster
        )

        path_type_allowed_values = ["BEST_SINGLE", "EACH_CELL", "EACH_ZONE"]
        path_type_val = path_type
        if path_type is not None:
            if [element.lower() for element in path_type_allowed_values].count(
                path_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "path_type can only be one of the following: "
                    + str(path_type_allowed_values)
                )
            for element in path_type_allowed_values:
                if path_type.lower() == element.lower():
                    path_type_val = element

        if output_polyline_name is None:
            output_polyline_service_name = "Output Polyline_" + _id_generator()
            output_polyline_name = output_polyline_service_name.replace(" ", "_")
        else:
            output_polyline_service_name = output_polyline_name.replace(" ", "_")

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        output_polyline_service = self._create_output_feature_service(
            output_name=output_polyline_name,
            output_service_name=output_polyline_service_name,
            task="CostPathAsPolyline",
            folder=folder,
        )
        if folderId is not None:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                }
            )

        gpjob = self._tbx.cost_path_as_polyline(
            input_destination_raster_or_features=input_destination_raster_or_features,
            input_cost_distance_raster=input_cost_distance_raster,
            input_cost_backlink_raster=input_cost_backlink_raster,
            output_polyline_name=output_polyline_name,
            path_type=path_type_val,
            destination_field=destination_field,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def classify_objects_using_deep_learning(
        self,
        input_raster,
        model,
        model_arguments=None,
        input_features=None,
        class_label_field=None,
        process_all_raster_items=None,
        output_feature_class=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Function can be used to output feature service with assigned class label for each feature based on
        information from overlapped imagery data using the designated deep learning model.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_raster                             Required. raster layer that contains objects that needs to be classified.
        ------------------------------------     --------------------------------------------------------------------
        model                                    Required model object.
        ------------------------------------     --------------------------------------------------------------------
        model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.

                                                 eg: {"name1":"value1", "name2": "value2"}
        ------------------------------------     --------------------------------------------------------------------
        input_features                           Optional feature layer.
                                                 The point, line, or polygon input feature layer that identifies the location of each object to be
                                                 classified and labelled. Each row in the input feature layer represents a single object.

                                                 If no input feature layer is specified, the function assumes that each input image contains a single object
                                                 to be classified. If the input image or images use a spatial reference, the output from the function is a
                                                 feature layer, where the extent of each image is used as the bounding geometry for each labelled
                                                 feature layer. If the input image or images are not spatially referenced, the output from the function
                                                 is a table containing the image ID values and the class labels for each image.
        ------------------------------------     --------------------------------------------------------------------
        class_label_field                        Optional str. The name of the field that will contain the classification label in the output feature layer.

                                                 If no field name is specified, a new field called ClassLabel will be generated in the output feature layer.
        ------------------------------------     --------------------------------------------------------------------
        process_all_raster_items                 Optional bool.

                                                 If set to False, all raster items in the mosaic dataset or image service will be mosaicked together and processed. This is the default.

                                                 If set to True, all raster items in the mosaic dataset or image service will be processed as separate images.
        ------------------------------------     --------------------------------------------------------------------
        output_name                              Optional. If not provided, a Feature layer is created by the method and used as the output .
                                                 You can pass in an existing Feature Service Item from your GIS to use that instead.
                                                 Alternatively, you can pass in the name of the output Feature Service that should be created by this method
                                                 to be used as the output for the tool.
                                                 A RuntimeError is raised if a service by that name already exists
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                 Dictionary can contain value for following keys:

                                                 - cellSize - Set the output raster cell size, or resolution

                                                 - extent - Sets the processing extent used by the function

                                                 - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                                 - processorType - Sets the processor type. "CPU" or "GPU"

                                                 Eg: {"processorType" : "CPU"}

                                                 Setting context parameter will override the values set using arcgis.env
                                                 variable for this particular function.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ====================================     ====================================================================

        :return:
            The output feature layer item containing the detected objects

        """
        task = "ClassifyObjectsUsingDeepLearning"
        gis = self._gis

        input_raster = self._layer_input(input_layer=input_raster)

        if input_features is not None:
            input_features = self._layer_input(input_layer=input_features)

        if model is None:
            raise RuntimeError("model cannot be None")
        else:
            model_value = self._set_param(model)

        model_arguments_value = None
        if model_arguments:
            try:
                model_arguments_value = dict(
                    (str(k), str(v)) for k, v in model_arguments.items()
                )
            except:
                model_arguments_value = model_arguments

        if not isinstance(process_all_raster_items, bool):
            raise RuntimeError(
                "process_all_raster_items value should be an instance of bool"
            )

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if output_feature_class is None:
            output_service_name = "ClassifyObjectsUsingDeepLearning_" + _id_generator()
            output_feature_class = output_service_name.replace(" ", "_")
        else:
            output_service_name = output_feature_class.replace(" ", "_")

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]
        output_service = self._create_output_feature_service(
            output_name=output_feature_class,
            output_service_name=output_service_name,
            task="Classify Objects",
            folder=folder,
        )
        if folderId is not None:
            output_feature_class = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_feature_class = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_service_name,
                        "serviceUrl": output_service.url,
                    },
                    "itemProperties": {"itemId": output_service.itemid},
                }
            )

        gpjob = self._tbx.classify_objects_using_deep_learning(
            input_raster=input_raster,
            input_features=input_features,
            output_feature_class=output_feature_class,
            model=model_value,
            model_arguments=model_arguments_value,
            class_label_field=class_label_field,
            process_all_raster_items=process_all_raster_items,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        gpjob._return_item = output_service
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()

    def define_nodata(
        self,
        input_raster,
        nodata,
        query_filter=None,
        num_of_bands=None,
        composite_value=None,
        future=False,
        **kwargs,
    ):
        """
        Parameters
        ----------
        input_raster: inputRaster (str). Required parameter.
        nodata: nodata (str). Required parameter.
        query_filter: queryfilter (str). Optional parameter.
        num_of_bands: numOfBands (int). Optional parameter.
        composite_value: compositeValue (bool). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for
                the task to complete. The default is False, which means wait for results.
        Returns
        -------
        output_raster : Image layer item
        """
        task = "DefineNodata"
        gis = self._gis

        context_param = {}
        context = None
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_raster = self._layer_input(input_raster)

        if not isinstance(composite_value, bool):
            raise RuntimeError("composite_value must be an instance of boolean")

        gpjob = self._tbx.define_nodata(
            input_raster=input_raster,
            nodata=nodata,
            query_filter=query_filter,
            num_of_bands=num_of_bands,
            composite_value=composite_value,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def optimal_path_as_line(
        self,
        input_destination_raster_or_features,
        input_distance_accumulation_raster,
        input_back_direction_raster,
        output_polyline_name,
        destination_field=None,
        path_type=None,
        context=None,
        future=False,
        create_network_paths="DESTINATIONS_TO_SOURCES",
        **kwargs,
    ):
        """
        Parameters
        ----------
        input_destination_raster_or_features: inputDestinationRasterOrFeatures (str). Required parameter.

        input_distance_accumulation_raster: inputDistanceAccumulationRaster (str). Required parameter.

        input_back_direction_raster: inputBackDirectionRaster (str). Required parameter.

        output_polyline_name: outputPolylineName (str). Required parameter.

        destination_field: destinationField (str). Optional parameter.

        path_type: pathType (str). Optional parameter.
          Choice list:BEST_SINGLE,EACH_CELL,EACH_ZONE

        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for
                the task to complete. The default is False, which means wait for results.
        Returns
        -------
        output_raster : Image layer item
        """
        task = "OptimalPathAsLine"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_destination_raster_or_features, _FEATURE_INPUTS):
            input_destination_raster_or_features = self._feature_input(
                input_destination_raster_or_features
            )
        elif isinstance(input_destination_raster_or_features, Item):
            input_destination_raster_or_features = {
                "itemId": input_destination_raster_or_features.itemid
            }
        else:
            input_destination_raster_or_features = self._layer_input(
                input_destination_raster_or_features
            )

        input_distance_accumulation_raster = self._layer_input(
            input_layer=input_distance_accumulation_raster
        )

        input_back_direction_raster = self._layer_input(
            input_layer=input_back_direction_raster
        )

        path_type_allowed_values = ["BEST_SINGLE", "EACH_CELL", "EACH_ZONE"]
        path_type_val = path_type
        if path_type is not None:
            if [element.lower() for element in path_type_allowed_values].count(
                path_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "path_type can only be one of the following: "
                    + str(path_type_allowed_values)
                )
            for element in path_type_allowed_values:
                if path_type.lower() == element.lower():
                    path_type_val = element

        if output_polyline_name is None:
            output_polyline_service_name = "Output Polyline_" + _id_generator()
            output_polyline_name = output_polyline_service_name.replace(" ", "_")
        else:
            output_polyline_service_name = output_polyline_name.replace(" ", "_")

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        output_polyline_service = self._create_output_feature_service(
            output_name=output_polyline_name,
            output_service_name=output_polyline_service_name,
            task="OptimalPathAsLine",
            folder=folder,
        )
        if folderId is not None:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_polyline_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_polyline_service_name,
                        "serviceUrl": output_polyline_service.url,
                    },
                    "itemProperties": {"itemId": output_polyline_service.itemid},
                }
            )

        if create_network_paths is not None:
            if isinstance(create_network_paths, str):
                if create_network_paths.upper() == "NETWORK_PATHS":
                    create_network_paths = True
                elif create_network_paths.upper() == "DESTINATIONS_TO_SOURCES":
                    create_network_paths = False
            if not isinstance(create_network_paths, bool):
                raise RuntimeError(
                    "create_network_paths should be one of the following - NETWORK_PATHS, DESTINATIONS_TO_SOURCES or should be of type bool."
                )

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version < 10.9:
                gpjob = self._tbx.optimal_path_as_line(
                    input_destination_raster_or_features=input_destination_raster_or_features,
                    input_distance_accumulation_raster=input_distance_accumulation_raster,
                    input_back_direction_raster=input_back_direction_raster,
                    output_polyline_name=output_polyline_name,
                    path_type=path_type_val,
                    destination_field=destination_field,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.9:
                gpjob = self._tbx.optimal_path_as_line(
                    input_destination_raster_or_features=input_destination_raster_or_features,
                    input_distance_accumulation_raster=input_distance_accumulation_raster,
                    input_back_direction_raster=input_back_direction_raster,
                    output_polyline_name=output_polyline_name,
                    path_type=path_type_val,
                    destination_field=destination_field,
                    create_network_paths=create_network_paths,
                    context=context,
                    gis=self._gis,
                    future=True,
                )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_polyline_service)
        return RAJob(gpjob, output_polyline_service).result()

    def optimal_region_connections(
        self,
        input_region_raster_or_features,
        input_barrier_raster_or_features=None,
        input_cost_raster=None,
        distance_method="PLANAR",
        connections_within_regions="GENERATE_CONNECTIONS",
        output_optimal_lines_name=None,
        output_neighbor_connections_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Parameters
        ----------
        input_region_raster_or_features: inputRegionsRasterOrFeatures (str). Required parameter.

        output_optimal_lines_name: outputOptimalLinesName (str). Required parameter.

        input_barrier_raster_or_features: inputBarrierData (str). Optional parameter.

        input_cost_raster: inputCostRaster (str). Optional parameter.

        output_neighbor_connections_name: outputNeighborConnectionsName (str). Optional parameter.

        distance_method: distanceMethod (str). Optional parameter.
            Choice list:PLANAR,GEODESIC

        connections_within_regions: connectionsWithinRegions (str). Optional parameter.
            Choice list:GENERATE_CONNECTIONS,NO_CONNECTIONS

        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for
                the task to complete. The default is False, which means wait for results.
        Returns
        -------
        output_raster : Image layer item
        """
        task = "OptimalRegionConnections"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_region_raster_or_features, _FEATURE_INPUTS):
            input_region_raster_or_features = self._feature_input(
                input_region_raster_or_features
            )
        elif isinstance(input_region_raster_or_features, Item):
            input_region_raster_or_features = {
                "itemId": input_region_raster_or_features.itemid
            }
        else:
            input_region_raster_or_features = self._layer_input(
                input_region_raster_or_features
            )

        if input_barrier_raster_or_features is not None:
            if isinstance(input_barrier_raster_or_features, _FEATURE_INPUTS):
                input_barrier_raster_or_features = self._feature_input(
                    input_barrier_raster_or_features
                )
            elif isinstance(input_barrier_raster_or_features, Item):
                input_barrier_raster_or_features = {
                    "itemId": input_barrier_raster_or_features.itemid
                }
            else:
                input_barrier_raster_or_features = self._layer_input(
                    input_barrier_raster_or_features
                )

        if input_cost_raster is not None:
            input_cost_raster = self._layer_input(input_layer=input_cost_raster)

        distance_method_allowed_values = ["PLANAR", "GEODESIC"]
        distance_method_val = distance_method
        if distance_method is not None:
            if [element.lower() for element in distance_method_allowed_values].count(
                distance_method.lower()
            ) <= 0:
                raise RuntimeError(
                    "distance_method can only be one of the following: "
                    + str(distance_method_allowed_values)
                )
            for element in distance_method_allowed_values:
                if distance_method.lower() == element.lower():
                    distance_method_val = element

        connections_within_regions_allowed_values = [
            "GENERATE_CONNECTIONS",
            "NO_CONNECTIONS",
        ]
        connections_within_regions_val = connections_within_regions
        if connections_within_regions is not None:
            if [
                element.lower() for element in connections_within_regions_allowed_values
            ].count(connections_within_regions.lower()) <= 0:
                raise RuntimeError(
                    "connections_within_regions can only be one of the following: "
                    + str(connections_within_regions_allowed_values)
                )
            for element in connections_within_regions_allowed_values:
                if connections_within_regions.lower() == element.lower():
                    connections_within_regions_val = element

        if output_optimal_lines_name is None:
            output_optimal_lines_service_name = "Output Optimal Lines" + _id_generator()
            output_optimal_lines_name = output_optimal_lines_service_name.replace(
                " ", "_"
            )
        else:
            output_optimal_lines_service_name = output_optimal_lines_name.replace(
                " ", "_"
            )

        if output_neighbor_connections_name is None:
            output_neighbor_connections_service_name = (
                "Output Neighbor Connections" + _id_generator()
            )
            output_neighbor_connections_name = (
                output_neighbor_connections_service_name.replace(" ", "_")
            )
        else:
            output_neighbor_connections_service_name = (
                output_neighbor_connections_name.replace(" ", "_")
            )

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        output_optimal_lines_service = self._create_output_feature_service(
            output_name=output_optimal_lines_name,
            output_service_name=output_optimal_lines_service_name,
            task="OptimalRegionConnections",
            folder=folder,
        )
        if folderId is not None:
            output_optimal_lines_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_optimal_lines_service_name,
                        "serviceUrl": output_optimal_lines_service.url,
                    },
                    "itemProperties": {"itemId": output_optimal_lines_service.itemid},
                    "folderId": folderId,
                }
            )
        else:
            output_optimal_lines_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_optimal_lines_service_name,
                        "serviceUrl": output_optimal_lines_service.url,
                    },
                    "itemProperties": {"itemId": output_optimal_lines_service.itemid},
                }
            )

        output_neighbor_connections_service = self._create_output_feature_service(
            output_name=output_neighbor_connections_name,
            output_service_name=output_neighbor_connections_service_name,
            task="OptimalRegionConnections",
            folder=folder,
        )
        if folderId is not None:
            output_neighbor_connections_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_neighbor_connections_service_name,
                        "serviceUrl": output_neighbor_connections_service.url,
                    },
                    "itemProperties": {
                        "itemId": output_neighbor_connections_service.itemid
                    },
                    "folderId": folderId,
                }
            )
        else:
            output_neighbor_connections_name = json.dumps(
                {
                    "serviceProperties": {
                        "name": output_neighbor_connections_service_name,
                        "serviceUrl": output_neighbor_connections_service.url,
                    },
                    "itemProperties": {
                        "itemId": output_neighbor_connections_service.itemid
                    },
                }
            )

        gpjob = self._tbx.optimal_region_connections(
            input_region_raster_or_features=input_region_raster_or_features,
            input_barrier_raster_or_features=input_barrier_raster_or_features,
            input_cost_raster=input_cost_raster,
            distance_method=distance_method_val,
            connections_within_regions=connections_within_regions_val,
            output_optimal_lines_name=output_optimal_lines_name,
            output_neighbor_connections_name=output_neighbor_connections_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(
                gpjob,
                [
                    output_optimal_lines_service,
                    output_neighbor_connections_service,
                ],
            )
        return RAJob(
            gpjob,
            [
                output_optimal_lines_service,
                output_neighbor_connections_service,
            ],
        ).result()

    def distance_accumulation(
        self,
        input_source_raster_or_features,
        output_distance_accumulation_raster_name=None,
        input_barrier_raster_or_features=None,
        input_surface_raster=None,
        input_cost_raster=None,
        input_vertical_raster=None,
        vertical_factor="BINARY 1 -30 30",
        input_horizontal_raster=None,
        horizontal_factor="BINARY 1 45",
        source_initial_accumulation=None,
        source_maximum_accumulation=None,
        source_cost_multiplier=None,
        source_direction=None,
        distance_method="PLANAR",
        output_back_direction_raster_name=None,
        output_source_direction_raster_name=None,
        output_source_location_raster_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Parameters
        ----------
           input_source_raster_or_features: inputSourceRasterOrFeatures (str). Required parameter.

           output_distance_accumulation_raster_name: outputDistanceAccumulationRasterName (str). Required parameter.

           input_barrier_raster_or_features: inputBarrierRasterOrFeatures (str). Optional parameter.

           input_surface_raster: inputSurfaceRaster (str). Optional parameter.

           input_cost_raster: inputCostRaster (str). Optional parameter.

           input_vertical_raster: inputVerticalRaster (str). Optional parameter.

           vertical_factor: verticalFactor (str). Optional parameter.

           input_horizontal_raster: inputHorizontalRaster (str). Optional parameter.

           horizontal_factor: horizontalFactor (str). Optional parameter.

           output_back_direction_raster_name: outputBackDirectionRasterName (str). Optional parameter.

           output_source_direction_raster_name: outputSourceDirectionRasterName (str). Optional parameter.

           output_source_location_raster_name: outputSourceLocationRasterName (str). Optional parameter.

           source_initial_accumulation: sourceInitialAccumulation (str). Optional parameter.

           source_maximum_accumulation: sourceMaximumAccumulation (str). Optional parameter.

           source_cost_multiplier: sourceCostMultiplier (str). Optional parameter.

           source_direction: sourceDirection (str). Optional parameter.

           distance_method: distanceMethod (str). Optional parameter.
              Choice list:PLANAR,GEODESIC

        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for
                the task to complete. The default is False, which means wait for results.
        Returns
        -------
        output_raster : Image layer item
        """

        task = "DistanceAccumulation"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_source_raster_or_features, _FEATURE_INPUTS):
            input_source_raster_or_features = self._feature_input(
                input_source_raster_or_features
            )
        elif isinstance(input_source_raster_or_features, Item):
            input_source_raster_or_features = {
                "itemId": input_source_raster_or_features.itemid
            }
        else:
            input_source_raster_or_features = self._layer_input(
                input_source_raster_or_features
            )

        if input_barrier_raster_or_features:
            if isinstance(input_barrier_raster_or_features, _FEATURE_INPUTS):
                input_barrier_raster_or_features = self._feature_input(
                    input_barrier_raster_or_features
                )
            elif isinstance(input_barrier_raster_or_features, Item):
                input_barrier_raster_or_features = {
                    "itemId": input_source_raster_or_features.itemid
                }
            else:
                input_barrier_raster_or_features = self._layer_input(
                    input_barrier_raster_or_features
                )

        if input_surface_raster is not None:
            input_surface_raster = self._layer_input(input_layer=input_surface_raster)

        if input_cost_raster is not None:
            input_cost_raster = self._layer_input(input_layer=input_cost_raster)

        if input_vertical_raster is not None:
            input_vertical_raster = self._layer_input(input_layer=input_vertical_raster)

        if input_horizontal_raster is not None:
            input_horizontal_raster = self._layer_input(
                input_layer=input_horizontal_raster
            )

        (
            output_distance_accumulation_raster,
            output_distance_accumulation_service,
        ) = self._set_output_raster(
            output_name=output_distance_accumulation_raster_name,
            task=task,
            output_properties=kwargs,
        )
        output_layers = [output_distance_accumulation_service]
        output_source_direction_raster = None
        if output_source_direction_raster_name is not None:
            (
                output_source_direction_raster,
                output_source_direction_service,
            ) = self._set_output_raster(
                output_name=output_source_direction_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(output_source_direction_service)
        output_source_location_raster = None
        if output_source_location_raster_name is not None:
            (
                output_source_location_raster,
                out_allocation_service,
            ) = self._set_output_raster(
                output_name=output_source_location_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(out_allocation_service)
        output_back_direction_raster = None
        if output_back_direction_raster_name is not None:
            (
                output_back_direction_raster,
                out_back_direction_service,
            ) = self._set_output_raster(
                output_name=output_back_direction_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(out_back_direction_service)

        gpjob = self._tbx.distance_accumulation(
            input_source_raster_or_features=input_source_raster_or_features,
            output_distance_accumulation_raster_name=output_distance_accumulation_raster,
            input_barrier_raster_or_features=input_barrier_raster_or_features,
            input_surface_raster=input_surface_raster,
            input_cost_raster=input_cost_raster,
            input_vertical_raster=input_vertical_raster,
            vertical_factor=vertical_factor,
            input_horizontal_raster=input_horizontal_raster,
            horizontal_factor=horizontal_factor,
            source_initial_accumulation=source_initial_accumulation,
            source_maximum_accumulation=source_maximum_accumulation,
            source_cost_multiplier=source_cost_multiplier,
            source_direction=source_direction,
            distance_method=distance_method,
            output_back_direction_raster_name=output_back_direction_raster,
            output_source_direction_raster_name=output_source_direction_raster,
            output_source_location_raster_name=output_source_location_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_layers)

        return RAJob(gpjob, output_layers).result()

    def distance_allocation(
        self,
        input_source_raster_or_features,
        output_distance_allocation_raster_name=None,
        input_barrier_raster_or_features=None,
        input_surface_raster=None,
        input_cost_raster=None,
        input_vertical_raster=None,
        vertical_factor="BINARY 1 -30 30",
        input_horizontal_raster=None,
        horizontal_factor="BINARY 1 45",
        source_initial_accumulation=None,
        source_maximum_accumulation=None,
        source_cost_multiplier=None,
        source_direction=None,
        distance_method="PLANAR",
        output_distance_accumulation_raster_name=None,
        output_back_direction_raster_name=None,
        output_source_direction_raster_name=None,
        output_source_location_raster_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Parameters
        ----------
           input_source_raster_or_features: inputSourceRasterOrFeatures (str). Required parameter.

           output_distance_allocation_raster_name: outputDistanceAccumulationRasterName (str). Required parameter.

           input_barrier_raster_or_features: inputBarrierRasterOrFeatures (str). Optional parameter.

           input_surface_raster: inputSurfaceRaster (str). Optional parameter.

           input_cost_raster: inputCostRaster (str). Optional parameter.

           input_vertical_raster: inputVerticalRaster (str). Optional parameter.

           vertical_factor: verticalFactor (str). Optional parameter.

           input_horizontal_raster: inputHorizontalRaster (str). Optional parameter.

           horizontal_factor: horizontalFactor (str). Optional parameter.

           output_back_direction_raster_name: outputBackDirectionRasterName (str). Optional parameter.

           output_source_direction_raster_name: outputSourceDirectionRasterName (str). Optional parameter.

           output_source_location_raster_name: outputSourceLocationRasterName (str). Optional parameter.

           source_initial_accumulation: sourceInitialAccumulation (str). Optional parameter.

           source_maximum_accumulation: sourceMaximumAccumulation (str). Optional parameter.

           source_cost_multiplier: sourceCostMultiplier (str). Optional parameter.

           source_direction: sourceDirection (str). Optional parameter.

           distance_method: distanceMethod (str). Optional parameter.
              Choice list:PLANAR,GEODESIC

        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for
                the task to complete. The default is False, which means wait for results.
        Returns
        -------
        output_raster : Image layer item
        """

        task = "DistanceAllocation"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_source_raster_or_features, _FEATURE_INPUTS):
            input_source_raster_or_features = self._feature_input(
                input_source_raster_or_features
            )
        elif isinstance(input_source_raster_or_features, Item):
            input_source_raster_or_features = {
                "itemId": input_source_raster_or_features.itemid
            }
        else:
            input_source_raster_or_features = self._layer_input(
                input_source_raster_or_features
            )

        if input_barrier_raster_or_features:
            if isinstance(input_barrier_raster_or_features, _FEATURE_INPUTS):
                input_barrier_raster_or_features = self._feature_input(
                    input_barrier_raster_or_features
                )
            elif isinstance(input_barrier_raster_or_features, Item):
                input_barrier_raster_or_features = {
                    "itemId": input_source_raster_or_features.itemid
                }
            else:
                input_barrier_raster_or_features = self._layer_input(
                    input_barrier_raster_or_features
                )

        if input_surface_raster is not None:
            input_surface_raster = self._layer_input(input_layer=input_surface_raster)

        if input_cost_raster is not None:
            input_cost_raster = self._layer_input(input_layer=input_cost_raster)

        if input_vertical_raster is not None:
            input_vertical_raster = self._layer_input(input_layer=input_vertical_raster)

        if input_horizontal_raster is not None:
            input_horizontal_raster = self._layer_input(
                input_layer=input_horizontal_raster
            )

        (
            output_distance_allocation_raster,
            output_distance_accumulation_service,
        ) = self._set_output_raster(
            output_name=output_distance_allocation_raster_name,
            task=task,
            output_properties=kwargs,
        )
        output_layers = [output_distance_accumulation_service]
        output_distance_accumulation_raster = None
        if output_distance_accumulation_raster_name is not None:
            (
                output_distance_accumulation_raster,
                output_distance_accumulation_service,
            ) = self._set_output_raster(
                output_name=output_distance_accumulation_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(output_distance_accumulation_service)

        output_source_direction_raster = None
        if output_source_direction_raster_name is not None:
            (
                output_source_direction_raster,
                output_source_direction_service,
            ) = self._set_output_raster(
                output_name=output_source_direction_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(output_source_direction_service)

        output_source_location_raster = None
        if output_source_location_raster_name is not None:
            (
                output_source_location_raster,
                out_allocation_service,
            ) = self._set_output_raster(
                output_name=output_source_location_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(out_allocation_service)
        output_back_direction_raster = None
        if output_back_direction_raster_name is not None:
            (
                output_back_direction_raster,
                out_back_direction_service,
            ) = self._set_output_raster(
                output_name=output_back_direction_raster_name,
                task=task,
                output_properties=kwargs,
            )
            output_layers.append(out_back_direction_service)

        gpjob = self._tbx.distance_allocation(
            input_source_raster_or_features=input_source_raster_or_features,
            output_distance_allocation_raster_name=output_distance_allocation_raster,
            input_barrier_raster_or_features=input_barrier_raster_or_features,
            input_surface_raster=input_surface_raster,
            input_cost_raster=input_cost_raster,
            input_vertical_raster=input_vertical_raster,
            vertical_factor=vertical_factor,
            input_horizontal_raster=input_horizontal_raster,
            horizontal_factor=horizontal_factor,
            source_initial_accumulation=source_initial_accumulation,
            source_maximum_accumulation=source_maximum_accumulation,
            source_cost_multiplier=source_cost_multiplier,
            source_direction=source_direction,
            distance_method=distance_method,
            output_distance_accumulation_raster_name=output_distance_accumulation_raster,
            output_back_direction_raster_name=output_back_direction_raster,
            output_source_direction_raster_name=output_source_direction_raster,
            output_source_location_raster_name=output_source_location_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True

        if future:
            return RAJob(gpjob, output_layers)
        return RAJob(gpjob, output_layers).result()

    def analyze_changes_using_ccdc(
        self,
        input_multidimensional_raster,
        bands_for_detecting_change=[],
        bands_for_temporal_masking=[],
        chi_squared_threshold=0.99,
        min_anomaly_observations=6,
        update_frequency=1,
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        bands_for_detecting_change: bandsForDetectingChange (str). Optional parameter.

        bands_for_temporal_masking: bandsForTemporalMasking (str). Optional parameter.

        chi_squared_threshold: chiSquaredThreshold (float). Optional parameter.

        min_anomaly_observations: minAnomalyObservations (int). Optional parameter.

        update_frequency: updateFrequency (float). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "AnalyzeChangesUsingCCDC"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.analyze_changes_using_ccdc(
            input_multidimensional_raster=input_multidimensional_raster,
            bands_for_detecting_change=bands_for_detecting_change,
            bands_for_temporal_masking=bands_for_temporal_masking,
            chi_squared_threshold=chi_squared_threshold,
            min_anomaly_observations=min_anomaly_observations,
            update_frequency=update_frequency,
            output_name=output_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def detect_change_using_change_analysis_raster(
        self,
        input_change_analysis_raster,
        change_type="TIME_OF_LATEST_CHANGES",
        max_number_of_changes=1,
        segment_date="BEGINNING_OF_SEGMENT",
        change_direction="ALL",
        filter_by_year=False,
        min_year=None,
        max_year=None,
        filter_by_duration=False,
        min_duration=None,
        max_duration=None,
        filter_by_magnitude=False,
        min_magnitude=None,
        max_magnitude=None,
        filter_by_start_value=False,
        min_start_value=None,
        max_start_value=None,
        filter_by_end_value=False,
        min_end_value=None,
        max_end_value=None,
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_change_analysis_raster: inputChangeAnalysisRaster (str). Required parameter.

        output_name: outputName (str). Optional parameter.

        change_type: changeType (str). Optional parameter.
           Choice list:TIME_OF_LATEST_CHANGE,TIME_OF_EARLIEST_CHANGE,TIME_OF_LARGEST_CHANGE,NUM_OF_CHANGES

        max_number_of_changes: maxNumberOfChanges (int). Optional parameter.

        segment_date: segmentDate (str). Optional parameter.
           Choice list:BEGINNING_OF_SEGMENT,END_OF_SEGMENT

        change_direction: changeDirection (str). Optional parameter.
           Choice list:ALL,INCREASE,DECREASE

        filter_by_year: filterByYear (bool). Optional parameter.

        min_year: minYear (int). Optional parameter.

        max_year: maxYear (int). Optional parameter.

        filter_by_duration: filterByDuration (bool). Optional parameter.

        min_duration: minDuration (float). Optional parameter.

        max_duration: maxDuration (float). Optional parameter.

        filter_by_magnitude: filterByMagnitude (bool). Optional parameter.

        min_magnitude: minMagnitude (float). Optional parameter.

        max_magnitude: maxMagnitude (float). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "DetectChangeByCCDC"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_change_analysis_raster = self._layer_input(
            input_layer=input_change_analysis_raster
        )

        if change_type is not None:
            change_type_allowed_values = (
                self._tbx.choice_list.detect_change_using_change_analysis_raster[
                    "change_type"
                ]
            )
            if [element.lower() for element in change_type_allowed_values].count(
                change_type.lower()
            ) <= 0:
                raise RuntimeError(
                    "change_type can only be one of the following: "
                    + str(change_type_allowed_values)
                )
            for element in change_type_allowed_values:
                if change_type.lower() == element.lower():
                    change_type = element

        if segment_date is not None:
            if (
                "segment_date"
                in self._tbx.choice_list.detect_change_using_change_analysis_raster.keys()
            ):
                segment_date_allowed_values = (
                    self._tbx.choice_list.detect_change_using_change_analysis_raster[
                        "segment_date"
                    ]
                )
                if [element.lower() for element in segment_date_allowed_values].count(
                    segment_date.lower()
                ) <= 0:
                    raise RuntimeError(
                        "segment_date can only be one of the following:  "
                        + str(segment_date_allowed_values)
                    )
                for element in segment_date_allowed_values:
                    if segment_date.lower() == element.lower():
                        segment_date = element

        if change_direction is not None:
            if (
                "change_direction"
                in self._tbx.choice_list.detect_change_using_change_analysis_raster.keys()
            ):
                change_direction_allowed_values = (
                    self._tbx.choice_list.detect_change_using_change_analysis_raster[
                        "change_direction"
                    ]
                )
                if [
                    element.lower() for element in change_direction_allowed_values
                ].count(change_direction.lower()) <= 0:
                    raise RuntimeError(
                        "change_direction can only be one of the following:  "
                        + str(change_direction_allowed_values)
                    )
                for element in change_direction_allowed_values:
                    if change_direction.lower() == element.lower():
                        change_direction = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version < 10.9:
                gpjob = self._tbx.detect_change_using_change_analysis_raster(
                    input_change_analysis_raster=input_change_analysis_raster,
                    change_type=change_type,
                    max_number_of_changes=max_number_of_changes,
                    output_name=output_raster,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.9:
                gpjob = self._tbx.detect_change_using_change_analysis_raster(
                    input_change_analysis_raster=input_change_analysis_raster,
                    change_type=change_type,
                    max_number_of_changes=max_number_of_changes,
                    segment_date=segment_date,
                    change_direction=change_direction,
                    filter_by_year=filter_by_year,
                    min_year=min_year,
                    max_year=max_year,
                    filter_by_duration=filter_by_duration,
                    min_duration=min_duration,
                    max_duration=max_duration,
                    filter_by_magnitude=filter_by_magnitude,
                    min_magnitude=min_magnitude,
                    max_magnitude=max_magnitude,
                    filter_by_start_value=filter_by_start_value,
                    min_start_value=min_start_value,
                    max_start_value=max_start_value,
                    filter_by_end_value=filter_by_end_value,
                    min_end_value=min_end_value,
                    max_end_value=max_end_value,
                    output_name=output_raster,
                    context=context,
                    gis=self._gis,
                    future=True,
                )

        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def manage_multidimensional_raster(
        self,
        target_multidimensional_raster,
        manage_mode="APPEND_SLICES",
        variables=None,
        input_multidimensional_rasters=None,
        dimension_name=None,
        dimension_value=None,
        dimension_description=None,
        dimension_unit=None,
        future=False,
        **kwargs,
    ):
        """
        target_multidimensional_raster: targetMultidimensionalRaster (str). Required parameter.

        manage_mode: manageMode (str). Optional parameter.
            Choice list:ADD_DIMENSION,APPEND_SLICES,APPEND_VARIABLES,REPLACE_SLICES,DELETE_VARIABLES,REMOVE_DIMENSION

        variables: variables (str). Optional parameter.

        input_multidimensional_rasters: inputMultidimensionalRasters (str). Optional parameter.

        dimension_name: dimensionName (str). Optional parameter.

        dimension_value: dimensionValue (str). Optional parameter.

        dimension_description: dimensionDescription (str). Optional parameter.

        dimension_unit: dimensionUnit (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "ManageMultidimensionalRaster"

        gis = self._gis

        target_multidimensional_raster = self._layer_input(
            input_layer=target_multidimensional_raster
        )

        if input_multidimensional_rasters is not None:
            input_multidimensional_rasters = self._set_multiple_raster_inputs(
                input_multidimensional_rasters
            )

        manage_mode_allowed_values = (
            self._tbx.choice_list.manage_multidimensional_raster["manage_mode"]
        )
        if [element.lower() for element in manage_mode_allowed_values].count(
            manage_mode.lower()
        ) <= 0:
            raise RuntimeError(
                "manage_mode can only be one of the following: "
                + str(manage_mode_allowed_values)
            )
        for element in manage_mode_allowed_values:
            if manage_mode.lower() == element.lower():
                manage_mode = element

        gpjob = self._tbx.manage_multidimensional_raster(
            target_multidimensional_raster=target_multidimensional_raster,
            manage_mode=manage_mode,
            variables=variables,
            input_multidimensional_rasters=input_multidimensional_rasters,
            dimension_name=dimension_name,
            dimension_value=dimension_value,
            dimension_description=dimension_description,
            dimension_unit=dimension_unit,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def sample(
        self,
        in_rasters,
        in_location_data,
        output_name=None,
        resampling_type="NEAREST",
        unique_id_field=None,
        acquisition_definition=None,
        statistics_type="MEAN",
        percentile_value=None,
        buffer_distance=None,
        layout="ROW_WISE",
        generate_feature_class=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        in_rasters: inRasters (str). Required parameter.
        in_location_data: inLocationData (str). Required parameter.
        output_name: outputTableName (str). Required parameter.
        resampling_type: resamplingType (str). Optional parameter.
           Choice list:NEAREST,BILINEAR,CUBIC
        unique_id_field: uniqueIdField (str). Optional parameter.
        acquisition_definition: acquisitionDefinition (str). Optional parameter.
        statistics_type: statisticsType (str). Optional parameter.
           Choice list:MINIMUM,MAXIMUM,MEDIAN,MEAN,SUM,MAJORITY,MINORITY,STD,PERCENTILE
        percentile_value: percentileValue (str). Optional parameter.
        buffer_distance: bufferDistance (str). Optional parameter.
        layout: layout (str). Optional parameter.
           Choice list:ROW_WISE,COLUMN_WISE
        generate_feature_class: generateFeatureClass (bool). Required parameter.
        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.
        """

        task = "Sample"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        in_rasters = self._set_multiple_raster_inputs(in_rasters)

        statistics_type_allowed_values = self._tbx.choice_list.sample["statistics_type"]
        if [element.lower() for element in statistics_type_allowed_values].count(
            statistics_type.lower()
        ) <= 0:
            raise RuntimeError(
                "statistics_type can only be one of the following: "
                + str(statistics_type_allowed_values)
            )
        for element in statistics_type_allowed_values:
            if statistics_type.lower() == element.lower():
                statistics_type = element

        resampling_type_allowed_values = self._tbx.choice_list.sample["resampling_type"]
        if [element.lower() for element in resampling_type_allowed_values].count(
            resampling_type.lower()
        ) <= 0:
            raise RuntimeError(
                "resampling_type can only be one of the following: "
                + str(resampling_type_allowed_values)
            )
        for element in resampling_type_allowed_values:
            if resampling_type.lower() == element.lower():
                resampling_type = element

        layout_allowed_values = self._tbx.choice_list.sample["layout"]
        if [element.lower() for element in layout_allowed_values].count(
            layout.lower()
        ) <= 0:
            raise RuntimeError(
                "layout can only be one of the following: " + str(layout_allowed_values)
            )
        for element in layout_allowed_values:
            if layout.lower() == element.lower():
                layout = element

        if isinstance(in_location_data, _FEATURE_INPUTS):
            in_location_data = self._feature_input(in_location_data)
        elif isinstance(in_location_data, Item):
            in_location_data = {"itemId": in_location_data.itemid}
        else:
            in_location_data = self._layer_input(in_location_data)

        folder = None
        folderId = None

        if output_name is None:
            output_name = str(task) + "_" + _id_generator()

        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
        if folder is not None:
            if isinstance(folder, dict):
                if "id" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                owner = gis.properties.user.username
                folderId = gis._portal.get_folder_id(owner, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, owner)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]
            output_name = json.dumps(
                {
                    "serviceProperties": {"name": output_name},
                    "itemProperties": {"folderId": folderId},
                }
            )
        else:
            output_name = json.dumps({"serviceProperties": {"name": output_name}})

        gpjob = self._tbx.sample(
            in_rasters=in_rasters,
            in_location_data=in_location_data,
            output_name=output_name,
            resampling_type=resampling_type,
            unique_id_field=unique_id_field,
            acquisition_definition=acquisition_definition,
            statistics_type=statistics_type,
            percentile_value=percentile_value,
            buffer_distance=buffer_distance,
            layout=layout,
            generate_feature_class=generate_feature_class,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def compute_accuracyfor_object_detection(
        self,
        detected_features,
        ground_truth_features,
        out_accuracy_table_name=None,
        out_accuracy_report_name=None,
        detected_class_value_field=None,
        ground_truth_class_value_field=None,
        min_iou=None,
        mask_features=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        detected_features: detectedFeatures (str). Required parameter.

        ground_truth_features: groundTruthFeatures (str). Required parameter.

        out_accuracy_table_name: outAccuracyTableName (str). Required parameter.

        out_accuracy_report_name: outAccuracyReportName (str). Optional parameter.

        detected_class_value_field: detectedClassValueField (str). Optional parameter.

        ground_truth_class_value_field: groundTruthClassValuField (str). Optional parameter.

        min_io_u: minIoU (str). Optional parameter.

        mask_features: maskFeatures (str). Optional parameter.

        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.
        """

        task = "ComputeAccuracyforObjectDetection"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        detected_features = self._feature_input(input_layer=detected_features)

        ground_truth_features = self._feature_input(input_layer=ground_truth_features)

        if mask_features is not None:
            mask_features = self._feature_input(input_layer=mask_features)

        folder = None
        folderId = None

        if out_accuracy_table_name is None:
            out_accuracy_table_name = str(task) + "_" + _id_generator()

        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
        if folder is not None:
            if isinstance(folder, dict):
                if "id" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                owner = gis.properties.user.username
                folderId = gis._portal.get_folder_id(owner, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, owner)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]
            out_accuracy_table_name = json.dumps(
                {
                    "serviceProperties": {"name": out_accuracy_table_name},
                    "itemProperties": {"folderId": folderId},
                }
            )
        else:
            out_accuracy_table_name = json.dumps(
                {"serviceProperties": {"name": out_accuracy_table_name}}
            )

        if out_accuracy_report_name is not None:
            if isinstance(out_accuracy_report_name, str):
                if (
                    "/fileShares/" in out_accuracy_report_name
                    or "/rasterStores/" in out_accuracy_report_name
                ):
                    out_accuracy_report_name = {"uri": out_accuracy_report_name}
                else:
                    out_accuracy_report_name = {"name": out_accuracy_report_name}
            elif isinstance(out_accuracy_report_name, arcgis.gis.Item):
                out_accuracy_report_name = {
                    "itemId": out_accuracy_report_name.itemid,
                    "name": out_accuracy_report_name.name,
                }
        gpjob = self._tbx.compute_accuracyfor_object_detection(
            detected_features=detected_features,
            ground_truth_features=ground_truth_features,
            out_accuracy_table_name=out_accuracy_table_name,
            out_accuracy_report_name=out_accuracy_report_name,
            detected_class_value_field=detected_class_value_field,
            ground_truth_class_value_field=ground_truth_class_value_field,
            min_io_u=min_iou,
            mask_features=mask_features,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def merge_multidimensional_rasters(
        self,
        input_multidimensional_rasters,
        resolve_overlap_method="FIRST",
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_rasters: inputMultidimensionalRasters (str). Required parameter.

        output_name: outputName (str). Required parameter.

        resolve_overlap_method: resolveOverlapMethod (str). Optional parameter.
           Choice list:FIRST,LAST,MIN,MAX,MEAN,SUM
        context: context (str). Optional parameter.
        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.
        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.
        """

        task = "MergeMultidimensionalRasters"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_rasters = self._set_multiple_raster_inputs(
            input_multidimensional_rasters
        )
        resolve_overlap_method_allowed_values = (
            self._tbx.choice_list.merge_multidimensional_rasters[
                "resolve_overlap_method"
            ]
        )
        if [element.lower() for element in resolve_overlap_method_allowed_values].count(
            resolve_overlap_method.lower()
        ) <= 0:
            raise RuntimeError(
                "resolve_overlap_method can only be one of the following: "
                + str(resolve_overlap_method_allowed_values)
            )
        for element in resolve_overlap_method_allowed_values:
            if resolve_overlap_method.lower() == element.lower():
                resolve_overlap_method = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.merge_multidimensional_rasters(
            input_multidimensional_rasters=input_multidimensional_rasters,
            resolve_overlap_method=resolve_overlap_method,
            output_name=output_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def analyze_changes_using_landtrendr(
        self,
        input_multidimensional_raster,
        processing_band=None,
        snapping_date="06-30",
        max_num_segments=5,
        vertex_count_overshoot=2,
        spike_threshold=0.9,
        recovery_threshold=0.25,
        prevent_one_year_recovery=True,
        increasing_recovery_trend=True,
        min_num_observations=6,
        best_model_proportion=1.25,
        pvalue_threshold=0.01,
        output_other_bands=False,
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        processing_band: processingBand (str). Optional parameter.

        snapping_date: snappingDate (str). Optional parameter.

        max_num_segments: maxNumSegments (int). Optional parameter.

        vertex_count_overshoot: vertexCountOvershoot (int). Optional parameter.

        spike_threshold: spikeThreshold (float). Optional parameter.

        recovery_threshold: recoveryThreshold (float). Optional parameter.

        prevent_one_year_recovery: preventOneYearRecovery (bool). Optional parameter.

        increasing_recovery_trend: increasingRecoveryTrend (bool). Optional parameter.

        min_num_observations: minNumObservations (int). Optional parameter.

        best_model_proportion: bestModelProportion (float). Optional parameter.

        pvalue_threshold: pvalueThreshold (float). Optional parameter.

        output_other_bands: outputOtherBands (bool). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "AnalyzeChangesUsingLandTrendr"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.analyze_changes_using_land_trendr(
            input_multidimensional_raster=input_multidimensional_raster,
            processing_band=processing_band,
            snapping_date=snapping_date,
            max_num_segments=max_num_segments,
            vertex_count_overshoot=vertex_count_overshoot,
            spike_threshold=spike_threshold,
            recovery_threshold=recovery_threshold,
            prevent_one_year_recovery=prevent_one_year_recovery,
            increasing_recovery_trend=increasing_recovery_trend,
            min_num_observations=min_num_observations,
            best_model_proportion=best_model_proportion,
            pvalue_threshold=pvalue_threshold,
            output_other_bands=output_other_bands,
            output_name=output_raster,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def zonal_statistics_as_table(
        self,
        input_zone_raster_or_features=None,
        input_value_raster=None,
        zone_field=None,
        ignore_nodata=True,
        statistic_type="ALL",
        percentile_values=[90],
        process_as_multidimensional=False,
        percentile_interpolation_type="AUTO_DETECT",
        output_name=None,
        context=None,
        future=False,
        circular_calculation=False,
        circular_wrap_value=360,
        **kwargs,
    ):
        """
        Calculates  the values of a raster within the zones of another dataset and reports the results to a table.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_zone_raster_or_features            Required. The input that defines the zones. Both raster and feature
                                                 can be used for the zone input.
        ------------------------------------     --------------------------------------------------------------------
        input_value_raster                       Required raster. Raster that contains the values on which to summarize a statistic.
        ------------------------------------     --------------------------------------------------------------------
        zone_field                               Required parameter.  The field that defines each zone. It can be an
                                                 integer or a string field of the zone dataset.
        ------------------------------------     --------------------------------------------------------------------
        ignore_nodata                            Optional boolean. Denotes whether NoData values in the value input
                                                 will influence the results of the zone that they fall within.
                                                 true - Within any particular zone, only cells that have a value in
                                                 the input value raster will be used in determining the output value
                                                 for that zone. NoData cells in the value raster will be ignored in
                                                 the statistic calculation. This is the default.

                                                 false - Within any particular zone, if any NoData cells exist in the
                                                 value raster, it is deemed that there is insufficient information to
                                                 perform statistical calculations for all the cells in that zone;
                                                 therefore, the entire zone will receive the NoData value on the output raster.
        ------------------------------------     --------------------------------------------------------------------
        statistic_type                           Optional string.  Choose the statistic to calculate.The available options
                                                 when the value raster is integer are ALL, MEAN, MAJORITY, MAXIMUM, MEDIAN,
                                                 MINIMUM, MINORITY, PERCENTILE, RANGE, STD, SUM, VARIETY,
                                                 MIN_MAX, MEAN_STD, and  MIN_MAX_MEAN.
                                                 If the value raster is float, the options are ALL, MEAN, MAXIMUM, MINIMUM,
                                                 RANGE, STD, and SUM.

                                                 ALL- All of the statistics will be calculated.
                                                 This is the default.

                                                 MEAN-Calculates the average of all cells in the raster layer to be summarized that
                                                 belong to the same zone as the output cell.

                                                 MAJORITY- Determines the value that occurs most often of all cells in the raster
                                                 layer to be summarized that belong to the same zone as the output cell.

                                                 MAXIMUM- Determines the largest value of all cells in the raster layer
                                                 to be summarized that belong to the same zone as the output cell.

                                                 MEDIAN- Determines the median value of all cells in the raster layer
                                                 to be summarized that belong to the same zone as the output cell.

                                                 MINIMUM- Determines the smallest value of all cells in the raster
                                                 layer to be summarized that belong to the same zone as the output cell.

                                                 MINORITY- Determines the value that occurs least often of all cells in
                                                 the raster layer to be summarized that belong to the same zone as the
                                                 output cell.

                                                 PERCENTILE - Calculates a percentile of all cells in the value raster
                                                 that belong to the same zone as the output cell. The 90th percentile is calculated by default.
                                                 You can specify other values (from 0 to 100) using the Percentile Values parameter.

                                                 RANGE- Calculates the difference between the largest and smallest value of all
                                                 cells in the raster layer to be summarized that belong to the same zone
                                                 as the output cell.

                                                 STD - Calculates the standard deviation of all cells in
                                                 the raster layer to be summarized that belong to the same zone as the output cell.

                                                 SUM- Calculates the total value of all cells in the raster layer to be
                                                 summarized that belong to the same zone as the output cell.

                                                 VARIETY- Calculates the number of unique values for all cells in the raster
                                                 layer to be summarized that belong to the same zone as the output cell.

                                                 MIN_MAX - Both the minimum and maximum statistics are calculated.

                                                 MEAN_STD -  Both the mean and standard deviation statistics
                                                 are calculated.

                                                 MIN_MAX_MEAN - The minimum, maximum and mean statistics are calculated.
        ------------------------------------     --------------------------------------------------------------------
        percentile_values                        Optional list of double values.
                                                 The percentile to calculate. The default is 90, for the 90th percentile.
                                                 The values can range from 0 to 100. The 0th percentile is essentially
                                                 equivalent to the Minimum statistic, and the 100th Percentile is equivalent to
                                                 Maximum. A value of 50 will produce essentially the same result as the Median statistic.
                                                 This option is only available if the Statistics Type parameter is set to PERCENTILE or ALL.
        ------------------------------------     --------------------------------------------------------------------
        percentile_value                         Optional int. The percentile to calculate when the
                                                 statistics_type parameter is set to PERCENTILE.
                                                 This value can range from 0 to 100. The default is 90.
        ------------------------------------     --------------------------------------------------------------------
        process_as_multidimensional              Optional bool, Determines how the input rasters will be processed if they
                                                 are multidimensional.
                                                 False - Statistics will be calculated from the current slice of a
                                                 multidimensional image service. This is the default.
                                                 True - Statistics will be calculated for all dimensions (such as time or depth)
                                                 of a multidimensional image service.
        ------------------------------------     --------------------------------------------------------------------
        percentile_interpolation_type            Optional str. Determines the type of percentile interpolation type when the
                                                 number of values from the input value raster to be calculated are even.

                                                    - AUTO_DETECT - If the input value raster has integer pixel type, the
                                                                    NEAREST method is used. If the input value raster
                                                                    has floating point pixel type, then the LINEAR
                                                                    method is used. This is the default.
                                                    - NEAREST - Nearest value to the desired percentile. In this case,
                                                                the output pixel type is same as that of the input value
                                                                raster.
                                                    - LINEAR - Weighted average of two surrounding values from the
                                                                desired percentile. In this case, the output pixel
                                                                type is floating point.
        ------------------------------------     --------------------------------------------------------------------
        output_name                              Optional string. Name of the output feature item or table item to be created.
                                                 If not provided, a random name is generated by the method and used as
                                                 the output name.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS object. If not specified, the currently active connection
                                                 is used.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and
                                                 results will be returned asynchronously.
        ------------------------------------     --------------------------------------------------------------------
        folder                                   Keyword only parameter. Optional str or dict. Creates a folder in the portal, if it does
                                                 not exist, with the given folder name and persists the output in this folder.
                                                 The dictionary returned by the gis.content.create_folder() can also be passed in as input.

                                                 Example:
                                                    {'username': 'user1', 'id': '6a3b77c187514ef7873ba73338cf1af8', 'title': 'trial'}
        ====================================     ====================================================================

        :return: Feature Layer
        """

        task = "ZonalStatisticsAsTable"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_zone_raster_or_features, _FEATURE_INPUTS):
            input_zone_raster_or_features = self._feature_input(
                input_zone_raster_or_features
            )
        elif isinstance(input_zone_raster_or_features, Item):
            input_zone_raster_or_features = {
                "itemId": input_zone_raster_or_features.itemid
            }
        else:
            input_zone_raster_or_features = self._layer_input(
                input_zone_raster_or_features
            )

        input_value_raster = self._layer_input(input_layer=input_value_raster)

        statistic_type_allowed_values = self._tbx.choice_list.zonal_statistics_as_table[
            "statistic_type"
        ]
        if [element.lower() for element in statistic_type_allowed_values].count(
            statistic_type.lower()
        ) <= 0:
            raise RuntimeError(
                "statistic_type can only be one of the following: "
                + str(statistic_type_allowed_values)
            )
        for element in statistic_type_allowed_values:
            if statistic_type.lower() == element.lower():
                statistic_type = element

        percentile_interpolation_type_allowed_values = (
            self._tbx.choice_list.zonal_statistics_as_table[
                "percentile_interpolation_type"
            ]
        )
        if [
            element.lower() for element in percentile_interpolation_type_allowed_values
        ].count(percentile_interpolation_type.lower()) <= 0:
            raise RuntimeError(
                "percentile_interpolation_type can only be one of the following: "
                + str(percentile_interpolation_type_allowed_values)
            )
        for element in percentile_interpolation_type_allowed_values:
            if percentile_interpolation_type.lower() == element.lower():
                percentile_interpolation_type = element

        folder = None
        folderId = None

        if output_name is None:
            output_name = str(task) + "_" + _id_generator()

        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
        if folder is not None:
            if isinstance(folder, dict):
                if "id" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                owner = gis.properties.user.username
                folderId = gis._portal.get_folder_id(owner, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, owner)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]
            output_name = json.dumps(
                {
                    "serviceProperties": {"name": output_name},
                    "itemProperties": {"folderId": folderId},
                }
            )
        else:
            output_name = json.dumps({"serviceProperties": {"name": output_name}})

        current_version = None
        if "currentVersion" in self._gis._tools.rasteranalysis.properties.keys():
            current_version = self._gis._tools.rasteranalysis.properties[
                "currentVersion"
            ]

        if (current_version is not None) and current_version < 11:
            gpjob = self._tbx.zonal_statistics_as_table(
                input_zone_raster_or_features=input_zone_raster_or_features,
                input_value_raster=input_value_raster,
                zone_field=zone_field,
                ignore_nodata=ignore_nodata,
                statistic_type=statistic_type,
                percentile_values=percentile_values,
                process_as_multidimensional=process_as_multidimensional,
                percentile_interpolation_type=percentile_interpolation_type,
                output_table_name=output_name,
                context=context,
                gis=self._gis,
                future=True,
            )
        elif (current_version is not None) and current_version >= 11:
            gpjob = self._tbx.zonal_statistics_as_table(
                input_zone_raster_or_features=input_zone_raster_or_features,
                input_value_raster=input_value_raster,
                zone_field=zone_field,
                ignore_nodata=ignore_nodata,
                statistic_type=statistic_type,
                percentile_values=percentile_values,
                process_as_multidimensional=process_as_multidimensional,
                percentile_interpolation_type=percentile_interpolation_type,
                output_table_name=output_name,
                context=context,
                gis=self._gis,
                future=True,
                circular_calculation=circular_calculation,
                circular_wrap_value=circular_wrap_value,
            )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def compute_change_raster(
        self,
        input_from_raster,
        input_to_raster,
        compute_change_method="DIFFERENCE",
        from_classes=None,
        to_classes=None,
        filter_method="CHANGED_PIXELS_ONLY",
        transition_class_colors="AVERAGE",
        from_classname_field=None,
        to_classname_field=None,
        output_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_from_raster: inputFromRaster (str). Required parameter.

        input_to_raster: inputToRaster (str). Required parameter.

        output_name: outputName (str). Required parameter.

        compute_change_method: computeChangeMethod (str). Optional parameter.
           Choice list:DIFFERENCE,RELATIVE_DIFFERENCE,CATEGORICAL_DIFFERENCE, SPECTRAL_EUCLIDEAN_DISTANCE, SPECTRAL_ANGLE_DIFFERENCE, BAND_WITH_MOST_CHANGE

        from_classes: fromClasses (str). Optional parameter.

        to_classes: toClasses (str). Optional parameter.

        filter_method: filterMethod (str). Optional parameter.
           Choice list:ALL,CHANGED_PIXELS_ONLY,UNCHANGED_PIXELS_ONLY

        transition_class_colors: transitionClassColors (str). Optional parameter.
           Choice list:AVERAGE,FROM_COLOR,TO_COLOR

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "ComputeChangeRaster"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_from_raster = self._layer_input(input_layer=input_from_raster)
        input_to_raster = self._layer_input(input_layer=input_to_raster)

        if compute_change_method is not None:
            compute_change_method_allowed_values = (
                self._tbx.choice_list.compute_change_raster["compute_change_method"]
            )
            if [
                element.lower() for element in compute_change_method_allowed_values
            ].count(compute_change_method.lower()) <= 0:
                raise RuntimeError(
                    "compute_change_method can only be one of the following: "
                    + str(compute_change_method_allowed_values)
                )
            for element in compute_change_method_allowed_values:
                if compute_change_method.lower() == element.lower():
                    compute_change_method = element

        if filter_method is not None:
            filter_method_allowed_values = self._tbx.choice_list.compute_change_raster[
                "filter_method"
            ]
            if [element.lower() for element in filter_method_allowed_values].count(
                filter_method.lower()
            ) <= 0:
                raise RuntimeError(
                    "filter_method can only be one of the following: "
                    + str(filter_method_allowed_values)
                )
            for element in filter_method_allowed_values:
                if filter_method.lower() == element.lower():
                    filter_method = element

        if transition_class_colors is not None:
            transition_class_colors_allowed_values = (
                self._tbx.choice_list.compute_change_raster["transition_class_colors"]
            )
            if [
                element.lower() for element in transition_class_colors_allowed_values
            ].count(transition_class_colors.lower()) <= 0:
                raise RuntimeError(
                    "transition_class_colors can only be one of the following: "
                    + str(transition_class_colors_allowed_values)
                )
            for element in transition_class_colors_allowed_values:
                if transition_class_colors.lower() == element.lower():
                    transition_class_colors = element

        output_raster, output_service = self._set_output_raster(
            output_name=output_name, task=task, output_properties=kwargs
        )

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version < 10.91:
                gpjob = self._tbx.compute_change_raster(
                    input_from_raster=input_from_raster,
                    input_to_raster=input_to_raster,
                    compute_change_method=compute_change_method,
                    from_classes=from_classes,
                    to_classes=to_classes,
                    filter_method=filter_method,
                    transition_class_colors=transition_class_colors,
                    output_name=output_raster,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
            elif (current_version is not None) and current_version >= 10.91:
                gpjob = self._tbx.compute_change_raster(
                    input_from_raster=input_from_raster,
                    input_to_raster=input_to_raster,
                    compute_change_method=compute_change_method,
                    from_classes=from_classes,
                    to_classes=to_classes,
                    filter_method=filter_method,
                    transition_class_colors=transition_class_colors,
                    from_classname_field=from_classname_field,
                    to_classname_field=to_classname_field,
                    output_name=output_raster,
                    context=context,
                    gis=self._gis,
                    future=True,
                )
        gpjob._is_ra = True
        gpjob._item_properties = True
        item = None
        if output_service:
            item = output_service
        if future:
            return RAJob(gpjob, item=item)

        return RAJob(gpjob, item=item).result()

    def train_deep_learning_model(
        self,
        in_folder=None,
        output_name=None,
        model_type=None,
        arguments=None,
        batch_size=2,
        max_epochs=None,
        learning_rate=None,
        backbone_model=None,
        validation_percent=None,
        pretrained_model=None,
        stop_training=True,
        freeze_model=True,
        overwrite_model=False,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Function can be used to train a deep learning model using the output from the
        export_training_data function.
        It generates the deep learning model package (*.dlpk) and adds it to your enterprise portal.
        train_model function performs the training using the Raster Analytics server.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        in_folder                                Required string. This is the input location for the training sample data.
                                                 It can be the path of output location on the file share raster data store or a
                                                 shared file system path.
                                                 The training sample data folder needs to be the output of export_training_data function,
                                                 containing "images" and "labels" folder,
                                                 as well as the JSON model definition file written out together by the tool.
                                                 File share raster store path examples:
                                                   -  /rasterStores/yourRasterStoreFolderName/trainingSampleData
                                                   - /fileShares/yourFileShareFolderName/trainingSampleData
                                                File share path example:
                                                   - \\serverName\deepLearning\trainingSampleData
        ------------------------------------     --------------------------------------------------------------------
        model_type                               Required string. The model type to use for training the deep learning model.
                                                 Possible values: SSD, UNET, FEATURE_CLASSIFIER, PSPNET, RETINANET, MASKRCNN
                                                  - SSD - The Single Shot Detector (SSD) is used for object detection.
                                                  - UNET - U-Net is used for pixel classification.
                                                  - FEATURE_CLASSIFIER - The Feature Classifier is used for object classification.
                                                  - PSPNET - The Pyramid Scene Parsing Network (PSPNET) is used for pixel classification.
                                                  - RETINANET - The RetinaNet is used for object detection.
                                                  - MASKRCNN - The MarkRCNN is used for object detection
        ------------------------------------     --------------------------------------------------------------------
        model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.

                                                 eg: {"name1":"value1", "name2": "value2"}
        ------------------------------------     --------------------------------------------------------------------
        batch_size                               Optional int.
                                                 The number of training samples to be processed for training at one time.
                                                 If the server has a powerful GPU, this number can be increased to 16, 36, 64, and so on.
                                                 Example:
                                                    4
        ------------------------------------     --------------------------------------------------------------------
        max_epochs                               Optional int. The maximum number of epochs that the model should be trained.
                                                 One epoch means the whole training dataset will be passed forward and backward
                                                 through the deep neural network one time.

                                                 Example:
                                                    20
        ------------------------------------     --------------------------------------------------------------------
        learning_rate                            Optional float.
                                                 The rate at which the weights are updated during the training.
                                                 It is a small positive value in the range between 0.0 and 1.0.
                                                 If learning rate is set to 0, it will extract the optimal learning rate
                                                 from the learning curve during the training process.
                                                 Example:
                                                    0.0
        ------------------------------------     --------------------------------------------------------------------
        backbone_model                           Optional string.
                                                 Specifies the preconfigured neural network to be used as an architecture for training the new model.
                                                 Possible values: DENSENET121 , DENSENET161 , DENSENET169 , DENSENET201 , MOBILENET_V2 , MASKRCNN50_FPN ,
                                                                  RESNET18 , RESNET34 , RESNET50 , RESNET101 , RESNET152 , VGG11 , VGG11_BN , VGG13 ,
                                                                  VGG13_BN , VGG16 , VGG16_BN , VGG19 , VGG19_BN
                                                 Example:
                                                    RESNET34
        ------------------------------------     --------------------------------------------------------------------
        validation_percent                       Optional float.
                                                 The percentage (in %) of training sample data that will be used for validating the model.
                                                 Example:
                                                    10
        ------------------------------------     --------------------------------------------------------------------
        pretrained_model                         Optional dlpk portal item.

                                                 The pretrained model to be used for fine tuning the new model.
                                                 It is a deep learning model package (dlpk) portal item.
        ------------------------------------     --------------------------------------------------------------------
        stop_training                            Optional bool.
                                                 Specifies whether early stopping will be implemented.

                                                 True - The model training will stop when the model is no longer improving,
                                                        regardless of the maximum epochs specified. This is the default.
                                                 False - The model training will continue until the maximum epochs is reached.
        ------------------------------------     --------------------------------------------------------------------
        freeze_model                             Optional bool.
                                                 Specifies whether to freeze the backbone layers in the pretrained model,
                                                 so that the weights and biases in the backbone layers remain unchanged.

                                                 True - The predefined weights and biases will not be altered in the backboneModel.
                                                        This is the default.
                                                 False - The weights and biases of the backboneModel may be altered to better
                                                         fit your training samples. This may take more time to process but
                                                         usually could get better results.
        ------------------------------------     --------------------------------------------------------------------
        overwrite_model                          Optional bool.
                                                 Overwrites an existing deep learning model package (.dlpk) portal item with the same name.

                                                 If the output_name parameter uses the file share data store path, this overwriteModel parameter is not applied.

                                                 True - The portal .dlpk item will be overwritten.
                                                 False - The portal .dlpk item will not be overwritten. This is the default.
        ------------------------------------     --------------------------------------------------------------------
        output_name                              Optional. trained deep learning model package can either be added as an item
                                                 to the portal or can be written to a datastore.

                                                 To add as an item, specify the name of the output deep learning model package (item)
                                                 to be created.
                                                 Example:

                                                    "trainedModel"

                                                 In order to write the dlpk to fileshare datastore, specify the datastore path.

                                                 Example -
                                                    "/fileShares/filesharename/folder"
        ------------------------------------     --------------------------------------------------------------------
        context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                 Dictionary can contain value for following keys:
                                                 - cellSize - Set the output raster cell size, or resolution
                                                 - extent - Sets the processing extent used by the function
                                                 - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"
                                                 - processorType - Sets the processor type. "CPU" or "GPU"
                                                 Eg: {"processorType" : "CPU"}
                                                 Setting context parameter will override the values set using arcgis.env
                                                 variable for this particular function.
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ====================================     ====================================================================

        :return:
            Returns the dlpk portal item that has properties for title, type, filename, file, id and folderId.
        """
        task = "TrainDeepLearningModel"
        gis = self._gis

        if isinstance(in_folder, arcgis.gis.Datastore):
            in_folder = in_folder.datapath
        elif isinstance(in_folder, list):
            in_folder = [
                folder.datapath
                if isinstance(folder, arcgis.gis.Datastore)
                else str(folder)
                for folder in in_folder
            ]
            in_folder = ",".join(in_folder)

        if pretrained_model is not None:
            pretrained_model = self._set_param(pretrained_model)

        model_arguments_value = None
        if arguments:
            try:
                model_arguments_value = dict(
                    (str(k), str(v)) for k, v in arguments.items()
                )
            except:
                model_arguments_value = arguments

        folderId = None
        folder = None
        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
            if folder is not None:
                if isinstance(folder, dict):
                    if "id" in folder:
                        folderId = folder["id"]
                        folder = folder["title"]
                else:
                    owner = gis.properties.user.username
                    folderId = gis._portal.get_folder_id(owner, folder)
                if folderId is None:
                    folder_dict = gis.content.create_folder(folder, owner)
                    folder = folder_dict["title"]
                    folderId = folder_dict["id"]

        if output_name is None:
            output_item_name = "TrainDeepLearningModel_" + _id_generator()
            output_name = output_item_name.replace(" ", "_")

        if "/fileShares/" in output_name or "/rasterStores/" in output_name:
            output_name = {"uri": output_name}
        else:
            if folderId is not None:
                output_name = {"name": output_name, "folderId": folderId}
            else:
                output_name = {"name": output_name}

        gpjob = self._tbx.train_deep_learning_model(
            in_folder=in_folder,
            output_name=output_name,
            model_type=model_type,
            arguments=model_arguments_value,
            batch_size=batch_size,
            max_epochs=max_epochs,
            learning_rate=learning_rate,
            backbone_model=backbone_model,
            validation_percent=validation_percent,
            pretrained_model=pretrained_model,
            stop_training=stop_training,
            freeze_model=freeze_model,
            overwrite_model=overwrite_model,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def summarize_categorical_raster(
        self,
        input_categorical_raster,
        dimension=None,
        area_of_interest=None,
        area_of_interest_id_field=None,
        output_summary_table_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Generates a table containing the pixel count for each class, in each slice of an input categorical raster.
        Function available in ArcGIS Image Server 10.9.1 and higher.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_from_raster                        Required ImageryLayer object. The multidimensional, categorical raster to be summarized.
        ------------------------------------     --------------------------------------------------------------------
        dimension                                Optional string. The name of the dimension to use for the summary. If
                                                 there is more than one dimension and no value is specified, all slices
                                                 will be summarized using all combinations of dimension values.
        ------------------------------------     --------------------------------------------------------------------
        area_of_interest                         Optional FeatureLayer. The polygon feature layer containing the area
                                                 or areas of interest to use when calculating the pixel count per category.
                                                 If no area of interest is specified, the entire raster dataset will be
                                                 included in the analysis.
        ------------------------------------     --------------------------------------------------------------------
        area_of_interest_id_field                Optional string/integer. The field in the polygon feature layer that
                                                 defines each area of interest.
        ------------------------------------     --------------------------------------------------------------------
        output_summary_table_name                Optional string. Name of the output feature item or table item to be created
                                                 If not provided, a random name is generated by the method and used as
                                                 the output name.
        ------------------------------------     --------------------------------------------------------------------
        context                                  Context contains additional settings that affect task execution.

                                                 context parameter overwrites values set through arcgis.env parameter

                                                 This function has the following settings:

                                                 - Extent (extent): A bounding box that defines the analysis area.

                                                    Example:
                                                        {"extent": {"xmin": -122.68,
                                                        "ymin": 45.53,
                                                        "xmax": -122.45,
                                                        "ymax": 45.6,
                                                        "spatialReference": {"wkid": 4326}}}

                                                 - Output Spatial Reference (outSR): The output raster will be
                                                    projected into the output spatial reference.

                                                    Example:
                                                        {"outSR": {spatial reference}}

                                                 - Parallel Processing Factor (parallelProcessingFactor): controls
                                                    Raster Processing (CPU) service instances.

                                                    Example:
                                                        Syntax example with a specified number of processing instances:

                                                        {"parallelProcessingFactor": "2"}

                                                        Syntax example with a specified percentage of total
                                                        processing instances:

                                                        {"parallelProcessingFactor": "60%"}
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Keyword only parameter. Optional Boolean. If True, the result will be a GPJob object and
                                                 results will be returned asynchronously.
        ------------------------------------     --------------------------------------------------------------------
        folder                                   Keyword only parameter. Optional str or dict. Creates a folder in the portal, if it does
                                                 not exist, with the given folder name and persists the output in this folder.
                                                 The dictionary returned by the gis.content.create_folder() can also be passed in as input.

                                                 Example:
                                                    {'username': 'user1', 'id': '6a3b77c187514ef7873ba73338cf1af8', 'title': 'trial'}
        ====================================     ====================================================================

        :return: Feature Layer

        """

        task = "SummarizeCategoricalRaster"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_categorical_raster = self._layer_input(
            input_layer=input_categorical_raster
        )

        if area_of_interest is not None:
            area_of_interest = self._feature_input(input_layer=area_of_interest)

        folder = None
        folderId = None

        if output_summary_table_name is None:
            output_summary_table_name = str(task) + "_" + _id_generator()

        if kwargs is not None:
            if "folder" in kwargs:
                folder = kwargs["folder"]
        if folder is not None:
            if isinstance(folder, dict):
                if "id" in folder:
                    folderId = folder["id"]
                    folder = folder["title"]
            else:
                owner = gis.properties.user.username
                folderId = gis._portal.get_folder_id(owner, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, owner)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]
            output_summary_table_name = json.dumps(
                {
                    "serviceProperties": {"name": output_summary_table_name},
                    "itemProperties": {"folderId": folderId},
                }
            )
        else:
            output_summary_table_name = json.dumps(
                {"serviceProperties": {"name": output_summary_table_name}}
            )

        gpjob = self._tbx.summarize_categorical_raster(
            input_categorical_raster=input_categorical_raster,
            dimension=dimension,
            area_of_interest=area_of_interest,
            area_of_interest_id_field=area_of_interest_id_field,
            output_summary_table_name=output_summary_table_name,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def train_random_trees_regression_model(
        self,
        input_rasters,
        input_target_data,
        target_value_field=None,
        target_dimension_field=None,
        raster_dimension=None,
        max_number_of_trees=50,
        max_tree_depth=30,
        max_number_of_samples=100000,
        average_points_per_cell="KEEP_ALL_POINTS",
        percent_samples_for_testing=10,
        output_scatter_plots_name=None,
        output_sample_features_name=None,
        output_importance_table_name=None,
        context=None,
        future=False,
        output_ecd_item_name=None,
        **kwargs,
    ):
        """
        Models the relationship between explanatory variables (independent variables) and a target dataset (dependent variable).
        Function available in ArcGIS Image Server 10.9.1 and higher.

        ====================================     ====================================================================
        **Parameter**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_rasters                            Required ImageryLayer object. The single-band, multidimensional, or
                                                 multiband raster datasets, or mosaic datasets, containing explanatory variables.
        ------------------------------------     --------------------------------------------------------------------
        input_target_data                        Required FeatureLayer or ImageryLayer. The raster or point feature class
                                                 containing the target variable (dependant variable) data.
        ------------------------------------     --------------------------------------------------------------------
        target_value_field                       Optional string. The field name of the information to model in the target
                                                 point feature class or raster dataset.
        ------------------------------------     --------------------------------------------------------------------
        target_dimension                         Optional string. A date field or numeric field in the input point feature
                                                 class that defines the dimension values.
        ------------------------------------     --------------------------------------------------------------------
        raster_dimension                         Optional string. The dimension name of the input multidimensional raster
                                                 (explanatory variables) that links to the dimension in the target data.
        ------------------------------------     --------------------------------------------------------------------
        max_number_of_trees                      Optional integer. The maximum number of trees in the forest. Increasing
                                                 the number of trees will lead to higher accuracy rates, although this
                                                 improvement will level off. The number of trees increases the processing time linearly.
                                                 The default is 50.
        ------------------------------------     --------------------------------------------------------------------
        max_tree_depth                           Optional integer. The maximum depth of each tree in the forest. Depth
                                                 determines the number of rules each tree can create, resulting in a decision.
                                                 Trees will not grow any deeper than this setting.
                                                 The default is 30.
        ------------------------------------     --------------------------------------------------------------------
        max_number_of_samples                    Optional integer. The maximum number of samples that will be used for
                                                 the regression analysis. A value that is less than or equal to 0 means
                                                 that the system will use all the samples from the input target raster
                                                 or point feature class to train the regression model.
                                                 The default value is 10,000.
        ------------------------------------     --------------------------------------------------------------------
        average_points_per_cell                  Optional string. Specifies whether the average will be calculated when
                                                 multiple training points fall into one cell. This parameter is applicable
                                                 only when the input target is a point feature class.
                                                 Options include:
                                                 - "KEEP_ALL_POINTS" — All points will be used when multiple training points fall into a single cell.
                                                                       This is the default.
                                                 - "AVERAGE_POINTS_PER_CELL" —The average value of the training points within a cell will be calculated.
        ------------------------------------     --------------------------------------------------------------------
        output_scatter_plots_name                Optional string. The name for the output scatterplots includes scatterplots
                                                 of training data, test data, and location test data.
        ------------------------------------     --------------------------------------------------------------------
        output_sample_features_name              Optional string. Name of the output feature item a feature class containing
                                                 target values and predicted values for training points, test points, and location test points.
        ------------------------------------     --------------------------------------------------------------------
        percent_samples_for_testing              Optional float. Defines the percentage of test points used for error checking.
                                                 The tool checks for three types of errors: errors on training points,
                                                 errors on test points, and errors on test location points.
                                                 The default is 10.
        ------------------------------------     --------------------------------------------------------------------
        output_importance_table_name             Optional string. Name of the output feature item or table item to be created.
                                                 This name is to create a table containing information describing the
                                                 importance of each explanatory variable used in the model. A larger
                                                 number indicates the corresponding variable is more correlated to the
                                                 predicted variable and will contribute more in prediction. Values range
                                                 between 0 and 1, and the sum of all the values equals 1.
        ------------------------------------     --------------------------------------------------------------------
        context                                  Context contains additional settings that affect task execution.

                                                 context parameter overwrites values set through arcgis.env parameter

                                                 This function has the following settings:

                                                 - Cell size (cellSize) - Set the output raster cell size, or resolution

                                                 - Extent (extent): A bounding box that defines the analysis area.

                                                    Example:
                                                        {"extent": {"xmin": -122.68,
                                                        "ymin": 45.53,
                                                        "xmax": -122.45,
                                                        "ymax": 45.6,
                                                        "spatialReference": {"wkid": 4326}}}

                                                 - Parallel Processing Factor (parallelProcessingFactor): controls
                                                    Raster Processing (CPU) service instances.

                                                    Example:
                                                        Syntax example with a specified number of processing instances:

                                                        {"parallelProcessingFactor": "2"}

                                                        Syntax example with a specified percentage of total
                                                        processing instances:

                                                        {"parallelProcessingFactor": "60%"}
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Keyword only parameter. Optional Boolean. If True, the result will be a GPJob object and
                                                 results will be returned asynchronously.
        ------------------------------------     --------------------------------------------------------------------
        folder                                   Keyword only parameter. Optional str or dict. Creates a folder in the portal, if it does
                                                 not exist, with the given folder name and persists the output in this folder.
                                                 The dictionary returned by the gis.content.create_folder() can also be passed in as input.

                                                 Example:
                                                    {'username': 'user1', 'id': '6a3b77c187514ef7873ba73338cf1af8', 'title': 'trial'}
        ====================================     ====================================================================

        :return: Dictionary

        """

        task = "TrainRandomTreesRegressionModel"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_rasters = self._set_multiple_raster_inputs(input_rasters=input_rasters)

        if isinstance(input_target_data, _FEATURE_INPUTS):
            input_target_data = self._feature_input(input_target_data)
        elif isinstance(input_target_data, Item):
            input_target_data = {"itemId": input_target_data.itemid}
        else:
            input_target_data = self._layer_input(input_target_data)

        if output_scatter_plots_name is not None:
            if isinstance(output_scatter_plots_name, str):
                if (
                    "/fileShares/" in output_scatter_plots_name
                    or "/rasterStores/" in output_scatter_plots_name
                ):
                    output_scatter_plots_name = {"uri": output_scatter_plots_name}
                else:
                    output_scatter_plots_name = {"name": output_scatter_plots_name}

        if output_sample_features_name is not None:
            output_sample_features_name = json.dumps(
                {"serviceProperties": {"name": output_sample_features_name}}
            )

        if output_importance_table_name is not None:
            output_importance_table_name = json.dumps(
                {"serviceProperties": {"name": output_importance_table_name}}
            )

        if output_ecd_item_name is not None:
            if isinstance(output_ecd_item_name, Item):
                output_ecd_item_name = {
                    "name": output_ecd_item_name.name,
                    "itemId": output_ecd_item_name.itemid,
                }
            elif isinstance(output_ecd_item_name, str):
                output_ecd_item_name = json.dumps({"name": output_ecd_item_name})

        if self._current_version is not None:
            current_version = self._current_version
            if (current_version is not None) and current_version >= 11.1:
                gpjob = self._tbx.train_random_trees_regression_model(
                    input_rasters=input_rasters,
                    input_target_data=input_target_data,
                    target_value_field=target_value_field,
                    target_dimension_field=target_dimension_field,
                    raster_dimension=raster_dimension,
                    output_importance_table_name=output_importance_table_name,
                    max_number_of_trees=max_number_of_trees,
                    max_tree_depth=max_tree_depth,
                    max_number_of_samples=max_number_of_samples,
                    average_points_per_cell=average_points_per_cell,
                    output_scatter_plots_name=output_scatter_plots_name,
                    output_sample_features_name=output_sample_features_name,
                    percent_samples_for_testing=percent_samples_for_testing,
                    context=context,
                    output_ecd_item_name=output_ecd_item_name,
                    gis=self._gis,
                    future=True,
                )
            else:
                gpjob = self._tbx.train_random_trees_regression_model(
                    input_rasters=input_rasters,
                    input_target_data=input_target_data,
                    target_value_field=target_value_field,
                    target_dimension_field=target_dimension_field,
                    raster_dimension=raster_dimension,
                    output_importance_table_name=output_importance_table_name,
                    max_number_of_trees=max_number_of_trees,
                    max_tree_depth=max_tree_depth,
                    max_number_of_samples=max_number_of_samples,
                    average_points_per_cell=average_points_per_cell,
                    output_scatter_plots_name=output_scatter_plots_name,
                    output_sample_features_name=output_sample_features_name,
                    percent_samples_for_testing=percent_samples_for_testing,
                    context=context,
                    gis=self._gis,
                    future=True,
                )

        gpjob._is_ra = (True,)
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def export_to_tile_package(
        self,
        input_imagery_layer,
        output_tile_package=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_imagery_layer: inputImageryLayer (str). Required parameter.

        output_tile_package: outputTilePackage (str). Required parameter.

         context: context (str). Optional parameter.

         gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


         future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        task = "ExportToTilePackage"

        gis = self._gis

        if (
            gis._con._product != "AGOL"
            or "rasterAnalytics" not in gis.properties.helperServices
        ):
            raise RuntimeError(
                "Export to tile package functionality is supported only on ArcGIS Online environment that has Raster Analysis capabilties"
            )

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        if isinstance(input_imagery_layer, Item):
            url = "%s/sharing/rest/content/items/%s" % (
                input_imagery_layer._portal.url,
                input_imagery_layer.id,
            )
            token = input_imagery_layer._gis._con._create_token(url)
            if token is not None:
                url = url + "?token=" + token
            input_imagery_layer = {
                "itemId": input_imagery_layer.itemid,
                "url": url,
            }
        elif (isinstance(input_imagery_layer, str)) and (
            "http:" in input_imagery_layer or "https:" in input_imagery_layer
        ):
            input_imagery_layer = {"url": input_imagery_layer}
        else:
            raise RuntimeError("Invalid value for input_imagery_layer parameter")

        if output_tile_package is None:
            output_tile_package = str(task) + "_" + _id_generator()

        gpjob = self._tbx.export_to_tile_package(
            input_imagery_layer=input_imagery_layer,
            output_tile_package=output_tile_package,
            context=context,
            gis=self._gis,
            future=True,
        )
        gpjob._is_ra = True
        if future:
            return RAJob(gpjob)

        return RAJob(gpjob).result()

    def predict_using_regression_model(
        self,
        input_rasters,
        input_regression_definition,
        output_predicted_raster_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_rasters: inputRasters (str). Required parameter.

        input_regression_definition: inputRegressionDefinition (str). Required parameter.

        output_predicted_raster_name: outputPredictedRasterName (str). Optional parmameter.

        context: context (str). Optional parameter.
        """

        task = "PredictUsingRegressionModel"
        gis = self._gis

        if self._gis._is_agol:
            supported_ra_methods = dir(self._tbx)
            if "predict_using_regression_model" not in supported_ra_methods:
                raise RuntimeError(
                    f"{task} is not supported on this ArcGIS Online organization."
                )

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_rasters = self._set_multiple_raster_inputs(input_rasters=input_rasters)

        if isinstance(input_regression_definition, Item):
            input_regression_definition = {"itemId": input_regression_definition.itemid}
        elif isinstance(input_regression_definition, str):
            if (
                "/fileShares/" in input_regression_definition
                or "/rasterStores/" in input_regression_definition
                or "/cloudStores/" in input_regression_definition
            ):
                input_regression_definition = {"uri": input_regression_definition}

        output_predicted_raster_name, output_service = self._set_output_raster(
            output_name=output_predicted_raster_name,
            task=task,
            output_properties=kwargs,
        )

        gpjob = self._tbx.predict_using_regression_model(
            input_rasters=input_rasters,
            input_regression_definition=input_regression_definition,
            output_predicted_raster_name=output_predicted_raster_name,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = (True,)
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def derive_continuous_flow(
        self,
        input_surface_raster,
        input_depressions_data=None,
        input_weight_raster=None,
        flow_direction_type="D8",
        force_flow=False,
        output_flow_accumulation_raster_name=None,
        output_flow_direction_raster_name=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        Generates a raster of accumulated flow into each cell from an input surface raster with no prior sink or depression filling required.
        
        ====================================     ====================================================================
        **Argument**                             **Description**
        ------------------------------------     --------------------------------------------------------------------
        input_surface_raster                     Required The input elevation surface.
        ------------------------------------     --------------------------------------------------------------------
        input_depressions_data                   Optional. A dataset that defines real depressions. The depressions can\
                                                 be defined either through a raster or a feature layer.\
                                                
                                                 If input is a raster, the depression cells must take a valid value, including\
                                                 zero, and the areas that are not depressions must be NoData.
        ------------------------------------     --------------------------------------------------------------------
        input_weight_raster                      Optional. A raster that defines the fraction of flow that contributes\
                                                 to flow accumulation at each cell. The weight is only applied to flow accumulation.
                                                
                                                 If no weight raster is specified, a default weight of 1 will be applied to each cell. 
        ------------------------------------     --------------------------------------------------------------------
        flow_direction_type                      Optional string. Specifies the flow direction type to use.
        
                                                 Choice list: ['D8', 'MFD'] 
        
                                                    - D8 is for the D8 flow direction type. This is the default. 
                                                    - MFD is for the Multi Flow Direction type.
        ------------------------------------     --------------------------------------------------------------------
        force_flow                               Optional string. Specifies if edge cells will always flow outward or follow normal flow rules.
        
                                                 Choice list: ['NORMAL', 'FORCE'] The default value is 'NORMAL'.
        ------------------------------------     --------------------------------------------------------------------
        output_flow_accumulation_raster_name     Optional. If not provided, an Image Service is created by the method and\
                                                 used as the output raster.
                                                
                                                 The output raster representing flow accumulation (number of upstream cells\
                                                 draining to each cell). The output raster is of floating-point type. You can pass\
                                                 in an existing Image Service Item from your GIS to use that instead. 
                                                
                                                 Alternatively, you can pass in the name of the output Image Service that should be\
                                                 created by this method to be used as the output for the tool. A RuntimeError is\
                                                 raised if a service by that name already exists.
        ------------------------------------     --------------------------------------------------------------------
        output_flow_direction_raster_name        Optional string. Name of the flow_direction_raster. This parameter determines\
                                                 whether flow_direction_raster should be generated or not. Set this parameter,\
                                                 in order to generate the flow_direction_raster.
        ------------------------------------     --------------------------------------------------------------------
        context                                  Context contains additional settings that affect task execution.

                                                 context parameter overwrites values set through arcgis.env parameter

                                                 This function has the following settings:

                                                  - Cell size (cellSize) - Set the output raster cell size, or resolution

                                                  - Extent (extent): A bounding box that defines the analysis area.

                                                    Example:
                                                        {"extent: {"xmin": -122.68, 
                                                        "ymin": 45.53,
                                                        "xmax": -122.45,
                                                        "ymax": 45.6,
                                                        "spatialReference": {"wkid": 4326}}}

                                                  - Output Spatial Reference (outSR): The output raster will be
                                                    projected into the output spatial reference.

                                                    Example:
                                                        {"outSR": {spatial reference}}

                                                  - Snap Raster (snapRaster): The output raster will have its
                                                    cells aligned with the specified snap raster.

                                                    Example:
                                                        {'snapRaster': {'url': '<image_service_url>'}}

                                                  - Cell Size (cellSize): The output raster will have the resolution
                                                    specified by cell size.

                                                    Example:
                                                        {'cellSize': 11} or {'cellSize': {'url': <image_service_url>}}  or {'cellSize': 'MaxOfIn'}

                                                  - Parallel Processing Factor (parallelProcessingFactor): controls
                                                    Raster Processing (CPU) service instances.

                                                    Example:
                                                        Syntax example with a specified number of processing instances:

                                                        {"parallelProcessingFactor": "2"}

                                                        Syntax example with a specified percentage of total
                                                        processing instances:

                                                        {"parallelProcessingFactor": "60%"}
        ------------------------------------     --------------------------------------------------------------------
        gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------------------------     --------------------------------------------------------------------
        future                                   Keyword only parameter. Optional Boolean. If True, the result will be a GPJob object and
                                                 results will be returned asynchronously.
        ====================================     ====================================================================     

        """

        task = "DeriveContinuousFlow"

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_surface_raster = self._layer_input(input_layer=input_surface_raster)

        if isinstance(input_depressions_data, _FEATURE_INPUTS):
            input_depressions_data = self._feature_input(
                input_layer=input_depressions_data
            )
        elif isinstance(input_depressions_data, Item):
            input_depressions_data = {"itemId": input_depressions_data.itemid}
        elif input_depressions_data is not None:
            input_depressions_data = self._layer_input(
                input_layer=input_depressions_data
            )

        if input_weight_raster is not None:
            input_weight_raster = self._layer_input(input_layer=input_weight_raster)

        if flow_direction_type is not None:
            flow_direction_type_allowed_values = (
                self._tbx.choice_list.derive_continuous_flow["flow_direction_type"]
            )
            if [
                element.lower() for element in flow_direction_type_allowed_values
            ].count(flow_direction_type.lower()) <= 0:
                raise RuntimeError(
                    "flow_direction_type can only be one of the following: "
                    + str(flow_direction_type_allowed_values)
                )
            for element in flow_direction_type_allowed_values:
                if flow_direction_type.lower() == element.lower():
                    flow_direction_type = element

        if force_flow is not None:
            if isinstance(force_flow, bool):
                force_flow = force_flow
            elif isinstance(force_flow, str):
                if force_flow == "NORMAL":
                    force_flow = False
                elif force_flow == "FORCE":
                    force_flow = True
                else:
                    raise RuntimeError(
                        "force_flow can only be one of the following: ['NORMAL', 'FORCE']"
                    )
            else:
                raise RuntimeError(
                    "Invalid datatype given for force_flow. force_flow can only be one of the following: ['NORMAL', 'FORCE']"
                )

        (
            output_accumulation_raster,
            output_accumulation_service,
        ) = self._set_output_raster(
            output_name=output_flow_accumulation_raster_name,
            task=task,
            output_properties=kwargs,
        )

        output_direction_raster = None
        if output_flow_direction_raster_name is not None:
            (
                output_direction_raster,
                output_direction_service,
            ) = self._set_output_raster(
                output_name=output_flow_direction_raster_name,
                task=task,
                output_properties=kwargs,
            )

        gpjob = self._tbx.derive_continuous_flow(
            input_surface_raster=input_surface_raster,
            output_flow_accumulation_raster_name=output_accumulation_raster,
            input_depressions_data=input_depressions_data,
            input_weight_raster=input_weight_raster,
            output_flow_direction_raster_name=output_direction_raster,
            flow_direction_type=flow_direction_type,
            force_flow=force_flow,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = True
        gpjob._item_properties = True

        if future:
            if output_direction_raster:
                return RAJob(
                    gpjob,
                    item=[
                        output_accumulation_service,
                        output_direction_service,
                    ],
                )
            return RAJob(
                gpjob,
                item=output_accumulation_service,
            )
        if output_direction_raster:
            return RAJob(
                gpjob,
                item=[output_accumulation_service, output_direction_service],
            ).result()
        return RAJob(
            gpjob,
            item=output_accumulation_service,
        ).result()

    def mosaic_image(
        self,
        input_rasters,
        target_raster,
        mosaic_operator="LAST",
        mosaic_colormap_mode="FIRST",
        no_data_value=None,
        context=None,
        gis=None,
        future=False,
    ):
        """
        input_rasters: inputRasters (str). Required parameter.

        target_raster: targetRaster (str). Required parameter.

        mosaic_operator: mosaicOperator (str). Optional parameter.
        Choice list:FIRST,LAST,BLEND,MEAN,MININUM,MAXIMUM,SUM

        mosaic_colormap_mode: mosaicColormapMode (str). Optional parameter.
        Choice list:FIRST,LAST,MATCH,REJECT

        no_data_value: noDataValue (float). Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.


        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.

        """

        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_rasters = self._set_multiple_raster_inputs(input_rasters)

        target_raster = self._layer_input(input_layer=target_raster)

        mosaic_operator_allowed_values = self._tbx.choice_list.mosaic_image[
            "mosaic_operator"
        ]
        mosaic_operator = (
            mosaic_operator.upper()
            if isinstance(mosaic_operator, str)
            else mosaic_operator
        )
        if mosaic_operator not in mosaic_operator_allowed_values:
            raise RuntimeError(
                f"mosaic_operator can only be one of the following: {mosaic_operator_allowed_values}"
            )

        mosaic_colormap_mode_allowed_values = self._tbx.choice_list.mosaic_image[
            "mosaic_colormap_mode"
        ]
        mosaic_colormap_mode = (
            mosaic_colormap_mode.upper()
            if isinstance(mosaic_colormap_mode, str)
            else mosaic_colormap_mode
        )
        if mosaic_colormap_mode not in mosaic_colormap_mode_allowed_values:
            raise RuntimeError(
                f"mosaic_colormap_mode can only be one of the following: {mosaic_colormap_mode_allowed_values}"
            )

        gpjob = self._tbx.mosaic_image(
            input_rasters=input_rasters,
            target_raster=target_raster,
            mosaic_operator=mosaic_operator,
            mosaic_colormap_mode=mosaic_colormap_mode,
            no_data_value=no_data_value,
            context=context,
            gis=gis,
            future=True,
        )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def multidimensional_principal_components(
        self,
        input_multidimensional_raster,
        mode="DIMENSION_REDUCTION",
        dimension=None,
        output_principal_components_name=None,
        output_loadings_name=None,
        output_eigen_values_table_name=None,
        variable=None,
        number_of_principal_components="95%",
        context=None,
        future=False,
        **kwargs,
    ):
        """
        input_multidimensional_raster: inputMultidimensionalRaster (str). Required parameter.

        mode: mode (str). Required parameter.

        dimension: dimension (str). Required parameter.

        output_principal_components_name: outputPrincipalComponents (str). Required parameter.

        output_loadings_name: outputLoadingsName (str). Required parameter.

        output_eigen_values_table_name: outputEigenValuesTableName (str). Optional parameter.

        variable: variable (str). Optional parameter.

        number_of_principal_components: numOfPrincipalComponents (str). Optional parameter.

        context: context (str). Optional parameter.

        future: future (str). Optional parameter.
        """

        task = "MultidimensionalPrincipalComponents"
        gis = self._gis

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        input_multidimensional_raster = self._layer_input(
            input_layer=input_multidimensional_raster
        )

        mode_val = mode
        if mode is not None:
            mode_allowed_values = [
                "DIMENSION_REDUCTION",
                "SPATIAL_REDUCTION",
            ]
            if [element.lower() for element in mode_allowed_values].count(
                mode.lower()
            ) <= 0:
                raise RuntimeError(
                    "mode can only be one of the following: " + str(mode_allowed_values)
                )

            for element in mode_allowed_values:
                if mode.upper() == element:
                    mode_val = element

        if mode_val.lower() == "dimension_reduction":
            (
                output_principal_components_name,
                output_image_service,
            ) = self._set_output_raster(
                output_name=output_principal_components_name,
                task=task,
                output_properties=kwargs,
            )
            output_loadings_name, output_feature_service = self._set_output_feature(
                output_name=output_loadings_name, task=task, output_properties=kwargs
            )
        # mode is "spatial_reduction" here
        else:
            (
                output_principal_components_name,
                output_image_service,
            ) = self._set_output_feature(
                output_name=output_principal_components_name,
                task=task,
                output_properties=kwargs,
            )
            output_loadings_name, output_feature_service = self._set_output_raster(
                output_name=output_loadings_name, task=task, output_properties=kwargs
            )

        if output_eigen_values_table_name is not None:
            (
                output_eigen_values_table_name,
                output_feature_service,
            ) = self._set_output_feature(
                output_name=output_eigen_values_table_name,
                task=task,
                output_properties=kwargs,
            )

        gpjob = self._tbx.multidimensional_principal_components(
            input_multidimensional_raster=input_multidimensional_raster,
            mode=mode_val,
            dimension=dimension,
            output_principal_components_name=output_principal_components_name,
            output_loadings_name=output_loadings_name,
            output_eigen_values_table_name=output_eigen_values_table_name,
            variable=variable,
            number_of_principal_components=number_of_principal_components,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = (True,)
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob)
        return RAJob(gpjob).result()

    def detect_change_using_deep_learning(
        self,
        from_raster,
        to_raster,
        model,
        output_classified_raster=None,
        model_arguments=None,
        context=None,
        future=False,
        **kwargs,
    ):
        """
        from_raster: input ImageryLayer (str). Required parameter.

        to_raster: input ImageryLayer (str). Required parameter.

        model: input model (str). Required parameter.

        output_classified_raster: output ImageryLayer (str). Optional parameter.

        model_arguments: Name-value pairs of arguments and their values that can be customized by the clients. Optional parameter.

        context: context (str). Optional parameter.

        gis: Optional, the GIS on which this tool runs. If not specified, the active GIS is used.

        future: Optional, If True, a future object will be returns and the process will not wait for the task to complete. The default is False, which means wait for results.
        """

        task = "DetectChangeUsingDeepLearning"
        gis = self._gis

        from_raster = self._layer_input(input_layer=from_raster)
        to_raster = self._layer_input(input_layer=to_raster)

        if model is None:
            raise RuntimeError("model cannot be None")
        else:
            model_value = self._set_param(model)

        model_arguments_value = None
        if model_arguments:
            try:
                model_arguments_value = dict(
                    (str(k), str(v)) for k, v in model_arguments.items()
                )
            except:
                model_arguments_value = model_arguments

        context_param = {}
        _set_raster_context(context_param, context)
        if "context" in context_param.keys():
            context = context_param["context"]

        output_raster, output_service = self._set_output_raster(
            output_name=output_classified_raster, task=task, output_properties=kwargs
        )

        gpjob = self._tbx.detect_change_using_deep_learning(
            from_raster=from_raster,
            to_raster=to_raster,
            model_definition=model_value,
            output_classified_raster_name=output_raster,
            model_arguments=model_arguments_value,
            context=context,
            gis=self._gis,
            future=True,
        )

        gpjob._is_ra = True
        gpjob._item_properties = True
        if future:
            return RAJob(gpjob, output_service)
        return RAJob(gpjob, output_service).result()


###########################################################################
class _GeoanalyticsTools(_AsyncService):
    """
    The Geoanalytics Tools from the GIS.
    GeoAnalyticsTools are provided for distributed analysis of large datasets.
    """

    def __init__(self, url, gis):
        """
        Constructs a client to the service given it's url from ArcGIS Online or Portal.
        """
        # super(RasterAnalysisTools, self).__init__(url, gis)
        super(_GeoanalyticsTools, self).__init__(url, gis)

    def _create_output_service(self, output_name, task, store=None):
        ok = self._gis.content.is_service_name_available(output_name, "Feature Service")
        if not ok:
            raise RuntimeError(
                "A Feature Service by this name already exists: " + output_name
            )
        if store is None:
            from arcgis.env import output_datastore

            store = output_datastore or "spatiotemporal"

        createParameters = {
            "currentVersion": 10.2,
            "serviceDescription": "",
            "hasVersionedData": False,
            "supportsDisconnectedEditing": False,
            "hasStaticData": True,
            "maxRecordCount": 2000,
            "supportedQueryFormats": "JSON",
            "capabilities": "Query",
            "description": "",
            "copyrightText": "",
            "allowGeometryUpdates": False,
            "syncEnabled": False,
            "editorTrackingInfo": {
                "enableEditorTracking": False,
                "enableOwnershipAccessControl": False,
                "allowOthersToUpdate": True,
                "allowOthersToDelete": True,
            },
            "xssPreventionInfo": {
                "xssPreventionEnabled": True,
                "xssPreventionRule": "InputOnly",
                "xssInputRule": "rejectInvalid",
            },
            "tables": [],
            "name": output_name,
            "options": {"dataSourceType": store},
        }

        output_service = self._gis.content.create_service(
            output_name,
            create_params=createParameters,
            service_type="featureService",
        )
        description = "Feature Service generated from running the " + task + " tool."
        item_properties = {
            "description": description,
            "tags": "Analysis Result, " + task,
            "snippet": "Analysis Feature Service generated from " + task,
        }
        output_service.update(item_properties)
        return output_service

    def aggregate_points(
        self,
        point_layer,
        output_name,
        distance_interval=None,
        distance_interval_unit=None,
        bin_type="SQUARE",
        polygon_layer=None,
        time_interval=None,
        time_interval_unit=None,
        time_repeat=None,
        time_repeat_unit=None,
        time_reference=None,
        summary_fields=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        point_layer : Required FeatureSet

        distance_interval : Optional float

        distance_interval_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        bin_type : Optional string
            One of the following: ['SQUARE', 'HEXAGON']
        polygon_layer : Optional FeatureSet

        time_interval : Optional int

        time_interval_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_repeat : Optional int

        time_repeat_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_reference : Optional datetime.date

        summary_fields : Optional string

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "AggregatePoints"

        params = {}
        context = {}

        params["pointLayer"] = super()._feature_input(point_layer)
        if distance_interval is not None:
            params["distanceInterval"] = distance_interval
        if distance_interval_unit is not None:
            params["distanceIntervalUnit"] = distance_interval_unit
        if bin_type is not None:
            params["binType"] = bin_type
        if polygon_layer is not None:
            params["polygonLayer"] = super()._feature_input(polygon_layer)
        if time_interval is not None:
            params["timeInterval"] = time_interval
        if time_interval_unit is not None:
            params["timeIntervalUnit"] = time_interval_unit
        if time_repeat is not None:
            params["timeRepeat"] = time_repeat
        if time_repeat_unit is not None:
            params["timeRepeatUnit"] = time_repeat_unit
        if time_reference is not None:
            params["timeReference"] = time_reference
        if summary_fields is not None:
            params["summaryFields"] = summary_fields

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def describe_dataset(
        self,
        in_dataset,
        out_sr=None,
        out_extent=None,
        datastore="GDB",
        context=None,
    ):
        """


        Parameters
        ----------
        in_dataset : Required FeatureSet

        out_sr : Optional string

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']
        context: Optional dict


        Returns
        -------
        output_json : layer (Feature Service item)
        """

        task = "DescribeDataset"

        params = {}

        params["in_dataset"] = in_dataset
        if out_sr is not None:
            params["gax:env:out_sr"] = out_sr
        if out_extent is not None:
            params["gax:env:outExtent"] = out_extent
        if datastore is not None:
            params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = context

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        # if output_name is not None:
        #     #url = job_values['output']['url']
        #     #return FeatureLayer(url, self._gis) #item
        #     item_properties = {
        #         "properties":{
        #             "jobUrl": task_url + '/jobs/' + job_info['jobId'],
        #             "jobType": "GPServer",
        #             "jobId": job_info['jobId'],
        #             "jobStatus": "completed"
        #             }
        #         }
        #     output_service.update(item_properties)
        #     return output_service
        # else:
        #     # Feature Collection
        #     return arcgis.features.FeatureCollection(job_values['output_json'])
        return job_values["output_json"]

    def join_features(
        self,
        target_layer,
        join_layer,
        output_name,
        join_operation="Join one to one",
        join_fields=None,
        summary_fields=None,
        spatial_relationship=None,
        spatial_near_distance=None,
        spatial_near_distance_unit=None,
        temporal_relationship=None,
        temporal_near_distance=None,
        temporal_near_distance_unit=None,
        attribute_relationship=None,
        join_condition=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        target_layer : Required FeatureSet

        join_layer : Required FeatureSet

        join_operation : Required string
            One of the following: ['Join one to one', 'Join one to many']
        join_fields : Optional string

        summary_fields : Optional string

        spatial_relationship : Optional string
            One of the following: ['Equals', 'Intersects', 'Contains', 'Within', 'Crosses', 'Touches', 'Overlaps', 'Near']
        spatial_near_distance : Optional float

        spatial_near_distance_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        temporal_relationship : Optional string
            One of the following: ['Equals', 'Intersects', 'During', 'Contains', 'Finishes', 'FinishedBy', 'Meets',
            'MetBy', 'Overlaps', 'OverlappedBy', 'Starts', 'StartedBy', 'Near', 'NearBefore', 'NearAfter']
        temporal_near_distance : Optional int

        temporal_near_distance_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        attribute_relationship : Optional string

        join_condition : Optional string

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']

        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "JoinFeatures"

        params = {}
        context = {}

        params["targetLayer"] = super()._feature_input(target_layer)
        params["joinLayer"] = super()._feature_input(join_layer)
        params["joinOperation"] = join_operation
        if join_fields is not None:
            params["joinFields"] = join_fields
        if summary_fields is not None:
            params["summaryFields"] = summary_fields
        if spatial_relationship is not None:
            params["spatialRelationship"] = spatial_relationship
        if spatial_near_distance is not None:
            params["spatialNearDistance"] = spatial_near_distance
        if spatial_near_distance_unit is not None:
            params["spatialNearDistanceUnit"] = spatial_near_distance_unit
        if temporal_relationship is not None:
            params["temporalRelationship"] = temporal_relationship
        if temporal_near_distance is not None:
            params["temporalNearDistance"] = temporal_near_distance
        if temporal_near_distance_unit is not None:
            params["temporalNearDistanceUnit"] = temporal_near_distance_unit
        if attribute_relationship is not None:
            params["attributeRelationship"] = attribute_relationship
        if join_condition is not None:
            params["joinCondition"] = join_condition

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
            # if datastore is not None:
            #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def create_buffers(
        self,
        input_layer,
        output_name,
        distance=None,
        distance_unit=None,
        field=None,
        method="PLANAR",
        dissolve_option="NONE",
        dissolve_fields=None,
        summary_fields=None,
        multipart=False,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        distance : Optional float

        distance_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        field : Optional string

        method : Required string
            One of the following: ['GEODESIC', 'PLANAR']
        dissolve_option : Optional string
            One of the following: ['ALL', 'LIST', 'NONE']
        dissolve_fields : Optional string

        summary_fields : Optional string

        multipart : Optional bool

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "CreateBuffers"

        params = {}
        context = {}

        params["inputLayer"] = super()._feature_input(input_layer)
        if distance is not None:
            params["distance"] = distance
        if distance_unit is not None:
            params["distanceUnit"] = distance_unit
        if field is not None:
            params["field"] = field
        params["method"] = method
        if dissolve_option is not None:
            params["dissolveOption"] = dissolve_option
        if dissolve_fields is not None:
            params["dissolveFields"] = dissolve_fields
        if summary_fields is not None:
            params["summaryFields"] = summary_fields
        if multipart is not None:
            params["multipart"] = multipart

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def calculate_density(
        self,
        input_layer,
        bin_size,
        bin_size_unit,
        radius,
        radius_unit,
        output_name,
        fields=None,
        weight="UNIFORM",
        bin_type="SQUARE",
        time_interval=None,
        time_interval_unit=None,
        time_repeat=None,
        time_repeat_unit=None,
        time_reference=None,
        area_units=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        fields : Optional string

        weight : Required string
            One of the following: ['UNIFORM', 'KERNEL']
        bin_size : Required float

        bin_size_unit : Required string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        bin_type : Required string
            One of the following: ['SQUARE', 'HEXAGON']
        time_interval : Optional int

        time_interval_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_repeat : Optional int

        time_repeat_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_reference : Optional datetime.date

        radius : Required float

        radius_unit : Required string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        area_units : Optional string
            One of the following: ['ACRES', 'SQUARE_KILOMETERS', 'SQUARE_INCHES', 'SQUARE_FEET', 'SQUARE_YARDS', 'SQUARE_MAP_UNITS', 'SQUARE_METERS', 'SQUARE_MILES', 'HECTARES']
        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional int

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "CalculateDensity"

        params = {}
        context = {}

        params["inputLayer"] = super()._feature_input(input_layer)
        if fields is not None:
            params["fields"] = fields
        params["weight"] = weight
        params["binSize"] = bin_size
        params["binSizeUnit"] = bin_size_unit
        params["binType"] = bin_type
        if time_interval is not None:
            params["timeInterval"] = time_interval
        if time_interval_unit is not None:
            params["timeIntervalUnit"] = time_interval_unit
        if time_repeat is not None:
            params["timeRepeat"] = time_repeat
        if time_repeat_unit is not None:
            params["timeRepeatUnit"] = time_repeat_unit
        if time_reference is not None:
            params["timeReference"] = time_reference
        params["radius"] = radius
        params["radiusUnit"] = radius_unit
        if area_units is not None:
            params["areaUnits"] = area_units

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def reconstruct_tracks(
        self,
        input_layer,
        track_fields,
        output_name,
        method="PLANAR",
        buffer_field=None,
        summary_fields=None,
        time_split=None,
        time_split_unit=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
        distance_split=None,
        distance_split_unit=None,
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        track_fields : Required string

        method : Required string
            One of the following: ['GEODESIC', 'PLANAR']
        buffer_field : Optional string

        summary_fields : Optional string

        time_split : Optional int

        time_split_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']

        distance_split : Optional string.
         A distance used to split tracks. Any features in the inputLayer that are in the same track
         and are greater than this distance apart will be split into a new track. The units of the
         distance values are supplied by the distance_split_unit parameter.

        distance_split_unit : Optional string.
         The distance unit to be used with the distance value specified in distance_split.
         Values: Meters | Kilometers | Feet | Miles | NauticalMiles | Yards

        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "ReconstructTracks"

        params = {}
        context = {}

        params["inputLayer"] = super()._feature_input(input_layer)
        params["trackFields"] = track_fields
        params["method"] = method
        if buffer_field is not None:
            params["bufferField"] = buffer_field
        if summary_fields is not None:
            params["summaryFields"] = summary_fields
        if time_split is not None:
            params["timeSplit"] = time_split
        if time_split_unit is not None:
            params["timeSplitUnit"] = time_split_unit

        if distance_split is not None:
            params["distanceSplit"] = distance_split

        if distance_split_unit is not None:
            params["distanceSplitUnit"] = distance_split_unit

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
            # if datastore is not None:
            #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def create_space_time_cube(
        self,
        point_layer,
        distance_interval,
        distance_interval_unit,
        time_interval,
        time_interval_unit,
        output_name,
        time_interval_alignment=None,
        reference_time=None,
        summary_fields=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        point_layer : Required FeatureSet

        distance_interval : Required float

        distance_interval_unit : Required string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        time_interval : Required int

        time_interval_unit : Required string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_interval_alignment : Optional string
            One of the following: ['END_TIME', 'START_TIME', 'REFERENCE_TIME']
        reference_time : Optional datetime.date

        summary_fields : Optional string

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output_cube : layer (Feature Service item)
        """

        task = "CreateSpaceTimeCube"

        params = {}
        context = {}

        params["pointLayer"] = super()._feature_input(point_layer)
        params["distanceInterval"] = distance_interval
        params["distanceIntervalUnit"] = distance_interval_unit
        params["timeInterval"] = time_interval
        params["timeIntervalUnit"] = time_interval_unit
        if time_interval_alignment is not None:
            params["timeIntervalAlignment"] = time_interval_alignment
        if reference_time is not None:
            params["referenceTime"] = reference_time
        if summary_fields is not None:
            params["summaryFields"] = summary_fields

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["outputCube"])

    def create_panel_data(
        self,
        in_target_features,
        in_join_features,
        time_interval,
        time_interval_unit,
        time_repeat,
        time_repeat_unit,
        time_reference,
        out_features_name,
        in_summary_stats=None,
        in_spatial_relationship=None,
        in_spatial_distance=None,
        in_spatial_distance_unit=None,
        in_attribute_relationship=None,
        out_sr=None,
        out_extent=None,
        datastore="GDB",
        context=None,
        output_name=None,
    ):
        """


        Parameters
        ----------
        in_target_features : Required FeatureSet

        in_join_features : Required FeatureSet

        in_summary_stats : Optional string

        in_spatial_relationship : Optional string
            One of the following: ['Intersect', 'Contains', 'Within', 'Crosses', 'Touches', 'Overlaps', 'Near']
        in_spatial_distance : Optional float

        in_spatial_distance_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        in_attribute_relationship : Optional string

        time_interval : Required int

        time_interval_unit : Required string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_repeat : Required int

        time_repeat_unit : Required string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_reference : Required datetime.date

        out_features_name : Required string

        out_sr : Optional string

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']
        context: Optional dict


        Returns
        -------
        out_features : layer (Feature Service item)
        """

        task = "CreatePanelData"

        params = {}

        params["in_target_features"] = in_target_features
        params["in_join_features"] = in_join_features
        if in_summary_stats is not None:
            params["in_summary_stats"] = in_summary_stats
        if in_spatial_relationship is not None:
            params["in_spatial_relationship"] = in_spatial_relationship
        if in_spatial_distance is not None:
            params["in_spatial_distance"] = in_spatial_distance
        if in_spatial_distance_unit is not None:
            params["in_spatial_distanceUnit"] = in_spatial_distance_unit
        if in_attribute_relationship is not None:
            params["in_attribute_relationship"] = in_attribute_relationship
        params["timeInterval"] = time_interval
        params["timeIntervalUnit"] = time_interval_unit
        params["timeRepeat"] = time_repeat
        params["timeRepeatUnit"] = time_repeat_unit
        params["timeReference"] = time_reference
        params["out_featuresName"] = out_features_name
        if out_sr is not None:
            params["gax:env:out_sr"] = out_sr
        if out_extent is not None:
            params["gax:env:outExtent"] = out_extent
        if datastore is not None:
            params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = context
        output_service = self._create_output_service(output_name, task)
        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["out_features"])

    def generate_manifest(
        self,
        data_store_item_id,
        update_data_item=False,
        out_sr=None,
        out_extent=None,
        datastore="GDB",
        context=None,
        output_name=None,
    ):
        """


        Parameters
        ----------
        data_store_item_id : Required string

        update_data_item : Optional bool

        out_sr : Optional string

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']
        context: Optional dict


        Returns
        -------
        manifest : layer (Feature Service item)
        """

        task = "GenerateManifest"

        params = {}

        params["dataStoreItemId"] = data_store_item_id
        if update_data_item is not None:
            params["updateDataItem"] = update_data_item
        if out_sr is not None:
            params["gax:env:out_sr"] = out_sr
        if out_extent is not None:
            params["gax:env:outExtent"] = out_extent
        if datastore is not None:
            params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = context
        output_service = self._create_output_service(output_name, task)
        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["manifest"])

    def create_sample(
        self,
        input_layer,
        output_layer_name,
        out_sr=None,
        out_extent=None,
        datastore="GDB",
        context=None,
        output_name=None,
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        output_layer_name : Required string

        out_sr : Optional string

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']
        context: Optional dict


        Returns
        -------
        output_layer : layer (Feature Service item)
        """

        task = "CreateSample"

        params = {}

        params["inputLayer"] = super()._feature_input(input_layer)
        params["outputLayerName"] = output_layer_name
        if out_sr is not None:
            params["gax:env:out_sr"] = out_sr
        if out_extent is not None:
            params["gax:env:outExtent"] = out_extent
        if datastore is not None:
            params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = context
        output_service = self._create_output_service(output_name, task)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["outputLayer"])

    def copy_to_data_store(
        self,
        input_layer,
        output_name,
        out_sr=None,
        out_extent=None,
        datastore="GDB",
        context=None,
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        output_name : Required string

        out_sr : Optional string

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']
        context: Optional dict


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "CopyToDataStore"

        params = {}

        params["inputLayer"] = super()._feature_input(input_layer)

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            params["gax:env:out_sr"] = out_sr
        if out_extent is not None:
            params["gax:env:outExtent"] = out_extent
        if datastore is not None:
            params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = context

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def summarize_attributes(
        self,
        input_layer,
        fields,
        output_name,
        summary_fields=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        input_layer : Required FeatureSet

        fields : Required string

        summary_fields : Optional string

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "SummarizeAttributes"

        params = {}
        context = {}

        params["inputLayer"] = super()._feature_input(input_layer)
        params["fields"] = fields
        if summary_fields is not None:
            params["summaryFields"] = summary_fields

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def summarize_within(
        self,
        summary_layer,
        output_name,
        bin_size=None,
        bin_size_unit=None,
        bin_type="SQUARE",
        sum_within_layer=None,
        time_interval=None,
        time_interval_unit=None,
        time_repeat=None,
        time_repeat_unit=None,
        time_reference=None,
        summary_fields=None,
        proportional_weighting=False,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        summary_layer : Required FeatureSet

        bin_size : Optional float

        bin_size_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        bin_type : Optional string
            One of the following: ['SQUARE', 'HEXAGON']
        sum_within_layer : Optional FeatureSet

        time_interval : Optional int

        time_interval_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_repeat : Optional int

        time_repeat_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_reference : Optional datetime.date

        summary_fields : Optional string

        proportional_weighting : Optional bool

        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "SummarizeWithin"

        params = {}
        context = {}

        params["summaryLayer"] = super()._feature_input(summary_layer)
        if bin_size is not None:
            params["binSize"] = bin_size
        if bin_size_unit is not None:
            params["binSizeUnit"] = bin_size_unit
        if bin_type is not None:
            params["binType"] = bin_type
        if sum_within_layer is not None:
            params["sumWithinLayer"] = super()._feature_input(sum_within_layer)
        if time_interval is not None:
            params["timeInterval"] = time_interval
        if time_interval_unit is not None:
            params["timeIntervalUnit"] = time_interval_unit
        if time_repeat is not None:
            params["timeRepeat"] = time_repeat
        if time_repeat_unit is not None:
            params["timeRepeatUnit"] = time_repeat_unit
        if time_reference is not None:
            params["timeReference"] = time_reference
        if summary_fields is not None:
            params["summaryFields"] = summary_fields
        if proportional_weighting is not None:
            params["proportionalWeighting"] = proportional_weighting

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])

    def find_hot_spots(
        self,
        point_layer,
        bin_size,
        bin_size_unit,
        output_name,
        time_step_interval=None,
        time_step_interval_unit=None,
        time_step_alignment=None,
        referencetime=None,
        neighborhood_distance=None,
        neighborhood_distance_unit=None,
        out_sr=None,
        process_sr=None,
        out_extent=None,
        datastore="GDB",
    ):
        """


        Parameters
        ----------
        point_layer : Required FeatureSet

        bin_size : Required float

        bin_size_unit : Required string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        time_step_interval : Optional int

        time_step_interval_unit : Optional string
            One of the following: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds', 'Milliseconds']
        time_step_alignment : Optional string
            One of the following: ['END_TIME', 'START_TIME', 'REFERENCE_TIME']
        referencetime : Optional datetime.date

        neighborhood_distance : Optional float

        neighborhood_distance_unit : Optional string
            One of the following: ['Feet', 'Yards', 'Miles', 'Meters', 'Kilometers', 'Nautical Miles']
        output_name : Required string

        out_sr : Optional int

        process_sr : Optional int

        out_extent : Optional string

        datastore : Optional string
            One of the following: ['BDS', 'GDB']


        Returns
        -------
        output : layer (Feature Service item)
        """

        task = "FindHotSpots"

        params = {}
        context = {}

        params["pointLayer"] = super()._feature_input(point_layer)
        params["binSize"] = bin_size
        params["binSizeUnit"] = bin_size_unit
        if time_step_interval is not None:
            params["time_step_interval"] = time_step_interval
        if time_step_interval_unit is not None:
            params["time_step_intervalUnit"] = time_step_interval_unit
        if time_step_alignment is not None:
            params["time_step_alignment"] = time_step_alignment
        if referencetime is not None:
            params["reference time"] = referencetime
        if neighborhood_distance is not None:
            params["neighborhoodDistance"] = neighborhood_distance
        if neighborhood_distance_unit is not None:
            params["neighborhoodDistanceUnit"] = neighborhood_distance_unit

        output_service = self._create_output_service(output_name, task)

        params["outputName"] = json.dumps(
            {
                "serviceProperties": {
                    "name": output_name,
                    "serviceUrl": output_service.url,
                },
                "itemProperties": {"itemId": output_service.itemid},
            }
        )
        if out_sr is not None:
            context["outSR"] = {"wkid": int(out_sr)}
        if process_sr is not None:
            context["processSR"] = {"wkid": int(process_sr)}
        if out_extent is not None:
            context["extent"] = out_extent
        # if datastore is not None:
        #     params["gax:env:datastore"] = datastore
        if context is not None:
            params["context"] = json.dumps(context)

        task_url, job_info, job_id = super()._analysis_job(task, params)

        job_info = super()._analysis_job_status(task_url, job_info)
        job_values = super()._analysis_job_results(task_url, job_info, job_id)
        # print(job_values)
        if output_name is not None:
            # url = job_values['output']['url']
            # return FeatureLayer(url, self._gis) #item
            item_properties = {
                "properties": {
                    "jobUrl": task_url + "/jobs/" + job_info["jobId"],
                    "jobType": "GPServer",
                    "jobId": job_info["jobId"],
                    "jobStatus": "completed",
                }
            }
            output_service.update(item_properties)
            return output_service
        else:
            # Feature Collection
            return arcgis.features.FeatureCollection(job_values["output"])


###########################################################################
class _GeometryService(_GISService):
    """
    A geometry service contains utility methods that provide access to
    sophisticated and frequently used geometric operations. An ArcGIS
    Server web site can only expose one geometry service with the static
    name GeometryService.
    """

    def __init__(self, url, gis=None):
        super(_GeometryService, self).__init__(url, gis)

    @classmethod
    def fromitem(cls, item):
        if not item.type == "Geometry Service":
            raise TypeError("item must be a type of Geometry Service, not " + item.type)
        return cls(item.url, item._gis)

    # ----------------------------------------------------------------------
    def areas_and_lengths(
        self,
        polygons,
        lengthUnit,
        areaUnit,
        calculationType,
        sr=4326,
        future=False,
    ):
        """
        The areasAndLengths operation is performed on a geometry service
        resource. This operation calculates areas and perimeter lengths
        for each polygon specified in the input array.

        Inputs:
           polygons - The array of polygons whose areas and lengths are
                      to be computed.
           lengthUnit - The length unit in which the perimeters of
                        polygons will be calculated. If calculationType
                        is planar, then lengthUnit can be any esriUnits
                        constant. If lengthUnit is not specified, the
                        units are derived from sr. If calculationType is
                        not planar, then lengthUnit must be a linear
                        esriUnits constant, such as esriSRUnit_Meter or
                        esriSRUnit_SurveyMile. If lengthUnit is not
                        specified, the units are meters. For a list of
                        valid units, see esriSRUnitType Constants and
                        esriSRUnit2Type Constant.
           areaUnit - The area unit in which areas of polygons will be
                      calculated. If calculationType is planar, then
                      areaUnit can be any esriUnits constant. If
                      areaUnit is not specified, the units are derived
                      from sr. If calculationType is not planar, then
                      areaUnit must be a linear esriUnits constant such
                      as esriSRUnit_Meter or esriSRUnit_SurveyMile. If
                      areaUnit is not specified, then the units are
                      meters. For a list of valid units, see
                      esriSRUnitType Constants and esriSRUnit2Type
                      constant.
                      The list of valid esriAreaUnits constants include,
                      esriSquareInches | esriSquareFeet |
                      esriSquareYards | esriAcres | esriSquareMiles |
                      esriSquareMillimeters | esriSquareCentimeters |
                      esriSquareDecimeters | esriSquareMeters | esriAres
                      | esriHectares | esriSquareKilometers.
           calculationType -  The type defined for the area and length
                              calculation of the input geometries. The
                              type can be one of the following values:
                              planar - Planar measurements use 2D
                                       Euclidean distance to calculate
                                       area and length. Th- should
                                       only be used if the area or
                                       length needs to be calculated in
                                       the given spatial reference.
                                       Otherwise, use preserveShape.
                              geodesic - Use this type if you want to
                                       calculate an area or length using
                                       only the vertices of the polygon
                                       and define the lines between the
                                       points as geodesic segments
                                       independent of the actual shape
                                       of the polygon. A geodesic
                                       segment is the shortest path
                                       between two points on an ellipsoid.
                              preserveShape - This type calculates the
                                       area or length of the geometry on
                                       the surface of the Earth
                                       ellipsoid. The shape of the
                                       geometry in its coordinate system
                                       is preserved.
                              future - boolean. This operation determines if the job
                                       is run asynchronously or not.
        Output:
           JSON as dictionary
        """
        url = self._url + "/areasAndLengths"
        if isinstance(areaUnit, str):
            areaUnit = {"areaUnit": areaUnit}
        params = {
            "f": "json",
            "lengthUnit": lengthUnit,
            "areaUnit": areaUnit,
            "calculationType": calculationType,
            "sr": sr,
        }
        if isinstance(polygons, list) and len(polygons) > 0:
            p = polygons[0]
            if isinstance(p, Polygon):
                if hasattr(p, "spatialReference"):
                    params["sr"] = p.spatialReference
                params["polygons"] = polygons
            elif isinstance(p, dict):
                params["polygons"] = polygons
            del p
        elif isinstance(polygons, dict):
            params["polygons"] = [polygons]
        elif isinstance(polygons, Polygon):
            params["polygons"] = [polygons]
        else:
            return "No polygons provided, please submit a list of polygon geometries"
        executor = concurrent.futures.ThreadPoolExecutor(1)
        futureobj = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )
        executor.shutdown(False)
        job = GeometryJob(
            future=futureobj,
            task_name="areas_and_lengths",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()
            # return self._con.post(path=url, postdata=params, token=self._token)

    # ----------------------------------------------------------------------
    def __geometryListToGeomTemplate(self, geometries):
        """
        converts a list of common.Geometry objects to the geometry
        template value
        Input:
           geometries - list of common.Geometry objects
        Output:
           Dictionary in geometry service template
        """
        template = {"geometryType": None, "geometries": []}
        if isinstance(geometries, list) and len(geometries) > 0:
            for g in geometries:
                if not isinstance(g, Geometry):
                    g = Geometry(g)

                if isinstance(g, Polyline):
                    template["geometryType"] = "esriGeometryPolyline"
                elif isinstance(g, Polygon):
                    template["geometryType"] = "esriGeometryPolygon"
                elif isinstance(g, Point):
                    template["geometryType"] = "esriGeometryPoint"
                elif isinstance(g, MultiPoint):
                    template["geometryType"] = "esriGeometryMultipoint"
                elif isinstance(g, Envelope):
                    template["geometryType"] = "esriGeometryEnvelope"
                else:
                    raise AttributeError("Invalid geometry type")
                template["geometries"].append(g)
                del g
            return template
        return template

    # ----------------------------------------------------------------------
    def __geometryToGeomTemplate(self, geometry):
        """
        Converts a single geometry object to a geometry service geometry
        template value.

        Input:
           geometry - geometry object
        Output:
           python dictionary of geometry template
        """
        template = {"geometryType": None, "geometry": None}

        if not isinstance(geometry, Geometry):
            geometry = Geometry(geometry)
        if isinstance(geometry, Polyline):
            template["geometryType"] = "esriGeometryPolyline"
        elif isinstance(geometry, Polygon):
            template["geometryType"] = "esriGeometryPolygon"
        elif isinstance(geometry, Point):
            template["geometryType"] = "esriGeometryPoint"
        elif isinstance(geometry, MultiPoint):
            template["geometryType"] = "esriGeometryMultipoint"
        elif isinstance(geometry, Envelope):
            template["geometryType"] = "esriGeometryEnvelope"
        else:
            raise AttributeError("Invalid geometry type")
        template["geometry"] = geometry
        return template

    # ----------------------------------------------------------------------
    def __geomToStringArray(self, geometries, returnType="str"):
        """function to convert the geomtries to strings"""
        listGeoms = []
        for g in geometries:
            if not isinstance(g, Geometry):
                g = Geometry(g)
            if isinstance(g, Point):
                listGeoms.append(g)
            elif isinstance(g, Polygon):
                listGeoms.append(g)
            elif isinstance(g, Polyline):
                listGeoms.append({"paths": g["paths"]})
            elif isinstance(g, MultiPoint):
                listGeoms.append({"points": g.get("points", [])})
        if returnType == "str":
            return json.dumps(listGeoms)
        elif returnType == "list":
            return listGeoms
        else:
            return json.dumps(listGeoms)

    # ----------------------------------------------------------------------
    def _process_results(self, results, out_sr=None):
        """processes the result"""
        from arcgis.geometry import SpatialReference

        if isinstance(results, concurrent.futures.Future):
            results = results.result()
            if "error" in results:
                return results
        if isinstance(results, list):
            vals = []
            for result in results:
                if isinstance(result, dict):
                    if (
                        out_sr
                        and not "spatialReference" in result
                        and isinstance(out_sr, int)
                    ):
                        result["spatialReference"] = {"wkid": out_sr}
                    elif (
                        out_sr
                        and not "spatialReference" in result
                        and isinstance(out_sr, str)
                    ):
                        result["spatialReference"] = {"wkt": out_sr}
                    elif (
                        out_sr
                        and not "spatialReference" in result
                        and isinstance(out_sr, (dict, SpatialReference))
                    ):
                        result["spatialReference"] = out_sr
                    vals.append(Geometry(result))
                del result
            return vals
        elif isinstance(results, dict):
            if "geometries" in results:
                return self._process_results(results["geometries"], out_sr=out_sr)
            elif "geometry" in results:
                return Geometry(results["geometry"])
            else:
                return Geometry(results)
        else:
            return results

    # --------------------------------------------------------------------------
    def _execute_by_chunk(self, url, params, number_executors, task_name, sr, future):
        # create chunks and output the results
        chunk = 65000
        # gather all the geometries we will chunk
        if task_name == "cut":
            all_geometries = params["target"]["geometries"]
            geom_type = params["target"]["geometryType"]
            geom_param = "target"
        else:
            all_geometries = (
                params["geometries"]["geometries"]
                if "geometries" in params
                else params["geometry"]["geometries"]
            )
            geom_type = (
                params["geometries"]["geometryType"]
                if "geometries" in params
                else params["geometry"]["geometryType"]
            )
            geom_param = "geometries" if "geometries" in params else "geometry"
        all_results = []
        with concurrent.futures.ThreadPoolExecutor(number_executors) as executor:
            # loop until all chunks reached
            for i in range(0, len(all_geometries), chunk):
                geoms = all_geometries[i : i + chunk]
                params[geom_param] = {
                    "geometryType": geom_type,
                    "geometries": geoms,
                }
                f1 = executor.submit(
                    self._con.post,
                    **{
                        "path": url,
                        "postdata": params,
                        "token": self._token,
                    },
                )
                if number_executors == 2:
                    f2 = executor.submit(
                        self._process_results,
                        **{"results": f1, "out_sr": sr},
                    )
                job = GeometryJob(
                    future=f1 if number_executors == 1 else f2,
                    task_name=task_name,
                    jobid=None,
                    task_url=url,
                    notify=False,
                    gis=self._gis,
                    out_wkid=sr,
                )
                if future:
                    return job
                else:
                    results = job.result()
                    if isinstance(results, dict):
                        all_results.append(results)
                    else:
                        for result in results:
                            all_results.append(result)
        return all_results

    # ----------------------------------------------------------------------
    def auto_complete(self, polygons=None, polylines=None, sr=None, future=False):
        """
        The autoComplete operation simplifies the process of
        constructing new polygons that are adjacent to other polygons.
        It constructs polygons that fill in the gaps between existing
        polygons and a set of polylines.

        Inputs:
           polygons - array of Polygon objects
           polylines - list of Polyline objects
           sr - spatial reference of the input geometries WKID
        """
        url = self._url + "/autoComplete"
        params = {"f": "json"}
        if polygons is None:
            polygons = []
        if polylines is None:
            polylines = []
        if sr is not None:
            params["sr"] = sr
        if isinstance(polygons, list):
            params["polygons"] = polygons
            all_geometries = polygons
            geom_param = "polygons"
        elif isinstance(polygons, Polygon):
            params["polygons"] = [polygons]
            all_geometries = [polygons]
            geom_param = "polygons"
        if isinstance(polylines, Polyline):
            params["polylines"] = [polylines]
            all_geometries = [polylines]
            geom_param = "polylines"
        elif isinstance(polylines, list):
            params["polylines"] = polylines
            all_geometries = polylines
            geom_param = "polylines"
        all_results = []
        chunk = 65000
        with concurrent.futures.ThreadPoolExecutor(2) as executor:
            # loop until all chunks reached
            for i in range(0, len(all_geometries), chunk):
                geoms = all_geometries[i : i + chunk]
                params[geom_param] = geoms
                f1 = executor.submit(
                    self._con.post,
                    **{
                        "path": url,
                        "postdata": params,
                        "token": self._token,
                    },
                )
                f2 = executor.submit(
                    self._process_results, **{"results": f1, "out_sr": sr}
                )
                job = GeometryJob(
                    future=f2,
                    task_name="auto_complete",
                    jobid=None,
                    task_url=url,
                    notify=False,
                    gis=self._gis,
                )
                if future:
                    return job
                else:
                    results = job.result()
                    for result in results:
                        all_results.append(result)
        return all_results

    # ----------------------------------------------------------------------
    def buffer(
        self,
        geometries,
        inSR,
        distances,
        unit,
        outSR=None,
        bufferSR=None,
        unionResults=None,
        geodesic=None,
        future=False,
    ):
        """
        The buffer operation is performed on a geometry service resource
        The result of this operation is buffered polygons at the
        specified distances for the input geometry array. Options are
        available to union buffers and to use geodesic distance.

        Inputs:
          geometries - The array of geometries to be buffered.
          isSR - The well-known ID of the spatial reference or a spatial
           reference JSON object for the input geometries.
          distances - The distances that each of the input geometries is
           buffered.
          unit - The units for calculating each buffer distance. If unit
           is not specified, the units are derived from bufferSR. If
           bufferSR is not specified, the units are derived from inSR.
          outSR - The well-known ID of the spatial reference or a
           spatial reference JSON object for the input geometries.
          bufferSR - The well-known ID of the spatial reference or a
           spatial reference JSON object for the input geometries.
          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. The default is false
          geodesic - Set geodesic to true to buffer the input geometries
           using geodesic distance. Geodesic distance is the shortest
           path between two points along the ellipsoid of the earth. If
           geodesic is set to false, the 2D Euclidean distance is used
           to buffer the input geometries. The default value depends on
           the geometry type, unit and bufferSR.
        """
        url = self._url + "/buffer"
        params = {"f": "json", "inSR": inSR}
        if geodesic is not None:
            params["geodesic"] = geodesic
        if unionResults is not None:
            params["unionResults"] = unionResults

        if isinstance(geometries, list) and len(geometries) > 0:
            g = geometries[0]
            if isinstance(g, Polygon):
                params["geometries"] = {
                    "geometryType": "esriGeometryPolygon",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, Point):
                params["geometries"] = {
                    "geometryType": "esriGeometryPoint",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, Polyline):
                params["geometries"] = {
                    "geometryType": "esriGeometryPolyline",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, dict):
                params["geometries"] = geometries
            else:
                print(
                    "The passed in geometries are in an unsupported format. "
                    "List of dicts or Geometry objects are supported"
                )
                return None
        if isinstance(distances, list):
            distances = [str(d) for d in distances]

            params["distances"] = ",".join(distances)
        else:
            params["distances"] = str(distances)
        params["unit"] = unit
        if bufferSR is not None:
            params["bufferSR"] = bufferSR
        if outSR is not None:
            params["outSR"] = outSR
        sr = outSR or inSR
        return self._execute_by_chunk(url, params, 2, "buffer", sr, future)

    # ----------------------------------------------------------------------
    def convex_hull(self, geometries, sr=None, future=False):
        """
        The convexHull operation is performed on a geometry service
        resource. It returns the convex hull of the input geometry. The
        input geometry can be a point, multipoint, polyline, or polygon.
        The convex hull is typically a polygon but can also be a polyline
        or point in degenerate cases.

        Inputs:
           geometries - The geometries whose convex hull is to be created.
           sr - The well-known ID or a spatial reference JSON object for
                the output geometry.
        """
        url = self._url + "/convexHull"
        params = {"f": "json"}
        if isinstance(geometries, list) and len(geometries) > 0:
            g = geometries[0]
            if sr is not None:
                params["sr"] = sr
            else:
                params["sr"] = g.spatial_reference
            if isinstance(g, Polygon):
                params["geometries"] = {
                    "geometryType": "esriGeometryPolygon",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, Point):
                params["geometries"] = {
                    "geometryType": "esriGeometryPoint",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, Polyline):
                params["geometries"] = {
                    "geometryType": "esriGeometryPolyline",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
            elif isinstance(g, MultiPoint):
                params["geometries"] = {
                    "geometryType": "esriGeometryMultipoint",
                    "geometries": self.__geomToStringArray(geometries, "list"),
                }
        else:
            return None
        return self._execute_by_chunk(url, params, 2, "convex_hull", sr, future)

    # ----------------------------------------------------------------------
    def cut(self, cutter, target, sr=None, future=False):
        """
        The cut operation is performed on a geometry service resource. This
        operation splits the target polyline or polygon where it's crossed
        by the cutter polyline.
        At 10.1 and later, this operation calls simplify on the input
        cutter and target geometries.

        Inputs:
           cutter - The polyline that will be used to divide the target
            into pieces where it crosses the target.The spatial reference
            of the polylines is specified by sr. The structure of the
            polyline is the same as the structure of the JSON polyline
            objects returned by the ArcGIS REST API.
           target - The array of polylines/polygons to be cut. The
            structure of the geometry is the same as the structure of the
            JSON geometry objects returned by the ArcGIS REST API. The
            spatial reference of the target geometry array is specified by
            sr.
           sr - The well-known ID or a spatial reference JSON object for
            the output geometry.
        """
        url = self._url + "/cut"
        params = {"f": "json"}
        if sr is not None:
            params["sr"] = sr
        if isinstance(cutter, (Polyline, dict)):
            params["cutter"] = cutter
        else:
            raise AttributeError("Input must be type Polyline/Dictionary")
        if isinstance(target, list) and len(target) > 0:
            template = {"geometryType": "", "geometries": []}
            for g in target:
                if isinstance(g, Polygon):
                    template["geometryType"] = "esriGeometryPolygon"
                    template["geometries"].append(g)
                if isinstance(g, Polyline):
                    template["geometryType"] = "esriGeometryPolyline"
                    template["geometries"].append(g)
                else:
                    AttributeError(
                        "Invalid geometry in target, entries can only be Polygon or Polyline"
                    )
                del g
            params["target"] = template
        else:
            AttributeError(
                "You must provide at least 1 Polygon/Polyline geometry in a list"
            )
        return self._execute_by_chunk(url, params, 2, "cut", sr, future)

    # ----------------------------------------------------------------------
    def densify(
        self,
        geometries,
        sr,
        maxSegmentLength,
        lengthUnit,
        geodesic=False,
        future=False,
    ):
        """
        The densify operation is performed on a geometry service resource.
        This operation densifies geometries by plotting points between
        existing vertices.

        Inputs:
           geometries - The array of geometries to be densified. The
            structure of each geometry in the array is the same as the
            structure of the JSON geometry objects returned by the ArcGIS
            REST API.
           sr - The well-known ID or a spatial reference JSON object for
            the input polylines. For a list of valid WKID values, see
            Projected coordinate systems and Geographic coordinate systems.
           maxSegmentLength - All segments longer than maxSegmentLength are
            replaced with sequences of lines no longer than
            maxSegmentLength.
           lengthUnit - The length unit of maxSegmentLength. If geodesic is
            set to false, then the units are derived from sr, and
            lengthUnit is ignored. If geodesic is set to true, then
            lengthUnit must be a linear unit. In a case where lengthUnit is
            not specified and sr is a PCS, the units are derived from sr.
            In a case where lengthUnit is not specified and sr is a GCS,
            then the units are meters.
           geodesic - If geodesic is set to true, then geodesic distance is
            used to calculate maxSegmentLength. Geodesic distance is the
            shortest path between two points along the ellipsoid of the
            earth. If geodesic is set to false, then 2D Euclidean distance
            is used to calculate maxSegmentLength. The default is false.
        """
        url = self._url + "/densify"
        template = {"geometryType": None, "geometries": []}
        params = {
            "f": "json",
            "sr": sr,
            "maxSegmentLength": maxSegmentLength,
            "lengthUnit": lengthUnit,
            "geodesic": geodesic,
        }
        if isinstance(geometries, list) and len(geometries) > 0:
            for g in geometries:
                if not isinstance(g, Geometry):
                    g = Geometry(g)
                if isinstance(g, Polyline):
                    template["geometryType"] = "esriGeometryPolyline"
                elif isinstance(g, Polygon):
                    template["geometryType"] = "esriGeometryPolygon"
                else:
                    raise AttributeError("Invalid geometry type")

                template["geometries"].append(g)

        elif isinstance(geometries, dict):
            if not isinstance(geometries, Geometry):
                g = Geometry(geometries)

            if isinstance(g, Polyline):
                template["geometryType"] = "esriGeometryPolyline"
            elif isinstance(g, Polygon):
                template["geometryType"] = "esriGeometryPolygon"
            template["geometries"].append(g)
        params["geometries"] = template

        return self._execute_by_chunk(url, params, 2, "densify", sr, future)

    # ----------------------------------------------------------------------
    def difference(self, geometries, sr, geometry, future=False):
        """
        The difference operation is performed on a geometry service
        resource. This operation constructs the set-theoretic difference
        between each element of an array of geometries and another geometry
        the so-called difference geometry. In other words, let B be the
        difference geometry. For each geometry, A, in the input geometry
        array, it constructs A-B.

        Inputs:
          geometries -  An array of points, multipoints, polylines or
           polygons. The structure of each geometry in the array is the
           same as the structure of the JSON geometry objects returned by
           the ArcGIS REST API.
          geometry - A single geometry of any type and of a dimension equal
           to or greater than the elements of geometries. The structure of
           geometry is the same as the structure of the JSON geometry
           objects returned by the ArcGIS REST API. The use of simple
           syntax is not supported.
          sr - The well-known ID of the spatial reference or a spatial
           reference JSON object for the input geometries.
        """
        url = self._url + "/difference"
        params = {"f": "json", "sr": sr}
        if isinstance(geometries, list) and len(geometries) > 0:
            template = {"geometryType": None, "geometries": []}
            for g in geometries:
                if isinstance(g, Polyline):
                    template["geometryType"] = "esriGeometryPolyline"
                elif isinstance(g, Polygon):
                    template["geometryType"] = "esriGeometryPolygon"
                elif isinstance(g, Point):
                    template["geometryType"] = "esriGeometryPoint"
                elif isinstance(g, Point):
                    template["geometryType"] = "esriGeometryMultipoint"
                else:
                    raise AttributeError("Invalid geometry type")
                template["geometries"].append(g)
                del g
            params["geometries"] = template
        geomTemplate = {"geometryType": None, "geometries": []}
        if isinstance(geometry, Polyline):
            geomTemplate["geometryType"] = "esriGeometryPolyline"
        elif isinstance(geometry, Polygon):
            geomTemplate["geometryType"] = "esriGeometryPolygon"
        elif isinstance(geometry, Point):
            geomTemplate["geometryType"] = "esriGeometryPoint"
        elif isinstance(geometry, Point):
            geomTemplate["geometryType"] = "esriGeometryMultipoint"
        else:
            raise AttributeError("Invalid geometry type")
        geomTemplate["geometry"] = geometry
        params["geometry"] = geomTemplate

        return self._execute_by_chunk(url, params, 2, "difference", sr, future)

    # ----------------------------------------------------------------------
    def distance(
        self,
        sr,
        geometry1,
        geometry2,
        distanceUnit="",
        geodesic=False,
        future=False,
    ):
        """
        The distance operation is performed on a geometry service resource.
        It reports the 2D Euclidean or geodesic distance between the two
        geometries.

        Inputs:
         sr - The well-known ID or a spatial reference JSON object for
          input geometries.
         geometry1 - The geometry from which the distance is to be
          measured. The structure of the geometry is same as the structure
          of the JSON geometry objects returned by the ArcGIS REST API.
         geometry2 - The geometry from which the distance is to be
          measured. The structure of the geometry is same as the structure
          of the JSON geometry objects returned by the ArcGIS REST API.
         distanceUnit - specifies the units for measuring distance between
          the geometry1 and geometry2 geometries.
         geodesic - If geodesic is set to true, then the geodesic distance
          between the geometry1 and geometry2 geometries is returned.
          Geodesic distance is the shortest path between two points along
          the ellipsoid of the earth. If geodesic is set to false or not
          specified, the planar distance is returned. The default value is
          false.
        """
        url = self._url + "/distance"
        params = {
            "f": "json",
            "sr": sr,
            "distanceUnit": distanceUnit,
            "geodesic": geodesic,
        }
        geometry1 = self.__geometryToGeomTemplate(geometry=geometry1)
        geometry2 = self.__geometryToGeomTemplate(geometry=geometry2)
        params["geometry1"] = geometry1
        params["geometry2"] = geometry2
        executor = concurrent.futures.ThreadPoolExecutor(1)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )
        executor.shutdown(False)
        job = GeometryJob(
            future=f1,
            task_name="distance",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def find_transformation(
        self,
        inSR,
        outSR,
        extentOfInterest=None,
        numOfResults=1,
        future=False,
    ):
        """
        The findTransformations operation is performed on a geometry
        service resource. This operation returns a list of applicable
        geographic transformations you should use when projecting
        geometries from the input spatial reference to the output spatial
        reference. The transformations are in JSON format and are returned
        in order of most applicable to least applicable. Recall that a
        geographic transformation is not needed when the input and output
        spatial references have the same underlying geographic coordinate
        systems. In this case, findTransformations returns an empty list.
        Every returned geographic transformation is a forward
        transformation meaning that it can be used as-is to project from
        the input spatial reference to the output spatial reference. In the
        case where a predefined transformation needs to be applied in the
        reverse direction, it is returned as a forward composite
        transformation containing one transformation and a transformForward
        element with a value of false.

        Inputs:
           inSR - The well-known ID (WKID) of the spatial reference or a
             spatial reference JSON object for the input geometries
           outSR - The well-known ID (WKID) of the spatial reference or a
             spatial reference JSON object for the input geometries
           extentOfInterest -  The bounding box of the area of interest
             specified as a JSON envelope. If provided, the extent of
             interest is used to return the most applicable geographic
             transformations for the area. If a spatial reference is not
             included in the JSON envelope, the inSR is used for the
             envelope.
           numOfResults - The number of geographic transformations to
             return. The default value is 1. If numOfResults has a value of
             -1, all applicable transformations are returned.
        """
        params = {"f": "json", "inSR": inSR, "outSR": outSR}
        url = self._url + "/findTransformations"
        if isinstance(numOfResults, int):
            params["numOfResults"] = numOfResults
        if isinstance(extentOfInterest, Envelope):
            params["extentOfInterest"] = extentOfInterest
        executor = concurrent.futures.ThreadPoolExecutor(1)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )

        executor.shutdown(False)
        job = GeometryJob(
            future=f1,
            task_name="find_transformation",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def from_geo_coordinate_string(
        self, sr, strings, conversionType, conversionMode=None, future=False
    ):
        """
        The from_geo_coordinate_string operation is performed on a geometry
        service resource. The operation converts an array of well-known
        strings into xy-coordinates based on the conversion type and
        spatial reference supplied by the user. An optional conversion mode
        parameter is available for some conversion types.

        Inputs:
         sr - The well-known ID of the spatial reference or a spatial
          reference json object.
         strings - An array of strings formatted as specified by
          conversionType.
          Syntax: [<string1>,...,<stringN>]
          Example: ["01N AA 66021 00000","11S NT 00000 62155",
                    "31U BT 94071 65288"]
         conversionType - The conversion type of the input strings.
          Valid conversion types are:
           MGRS - Military Grid Reference System
           USNG - United States National Grid
           UTM - Universal Transverse Mercator
           GeoRef - World Geographic Reference System
           GARS - Global Area Reference System
           DMS - Degree Minute Second
           DDM - Degree Decimal Minute
           DD - Decimal Degree
         conversionMode - Conversion options for MGRS, UTM and GARS
          conversion types.
          Conversion options for MGRS and UTM conversion types.
          Valid conversion modes for MGRS are:
           mgrsDefault - Default. Uses the spheroid from the given spatial
            reference.
           mgrsNewStyle - Treats all spheroids as new, like WGS 1984. The
            180 degree longitude falls into Zone 60.
           mgrsOldStyle - Treats all spheroids as old, like Bessel 1841.
            The 180 degree longitude falls into Zone 60.
           mgrsNewWith180InZone01 - Same as mgrsNewStyle except the 180
            degree longitude falls into Zone 01.
           mgrsOldWith180InZone01 - Same as mgrsOldStyle except the 180
            degree longitude falls into Zone 01.
          Valid conversion modes for UTM are:
           utmDefault - Default. No options.
           utmNorthSouth - Uses north/south latitude indicators instead of
            zone numbers. Non-standard. Default is recommended
        """
        url = self._url + "/fromGeoCoordinateString"
        params = {
            "f": "json",
            "sr": sr,
            "strings": strings,
            "conversionType": conversionType,
        }
        if not conversionMode is None:
            params["conversionMode"] = conversionMode
        executor = concurrent.futures.ThreadPoolExecutor(1)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )
        executor.shutdown(False)
        job = GeometryJob(
            future=f1,
            task_name="from_geo_coordinate_string",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def generalize(self, sr, geometries, maxDeviation, deviationUnit, future=False):
        """
        The generalize operation is performed on a geometry service
        resource. The generalize operation simplifies the input geometries
        using the Douglas-Peucker algorithm with a specified maximum
        deviation distance. The output geometries will contain a subset of
        the original input vertices.

        Inputs:
         sr - The well-known ID or a spatial reference JSON object for the
          input geometries.
         geometries - The array of geometries to be generalized.
         maxDeviation - maxDeviation sets the maximum allowable offset,
          which will determine the degree of simplification. This value
          limits the distance the output geometry can differ from the input
          geometry.
         deviationUnit - A unit for maximum deviation. If a unit is not
          specified, the units are derived from sr.
        """
        url = self._url + "/generalize"
        params = {
            "f": "json",
            "sr": sr,
            "deviationUnit": deviationUnit,
            "maxDeviation": maxDeviation,
        }
        params["geometries"] = self.__geometryListToGeomTemplate(geometries=geometries)
        return self._execute_by_chunk(url, params, 2, "generalize", sr, future)

    # ----------------------------------------------------------------------
    def intersect(self, sr, geometries, geometry, future=False):
        """
        The intersect operation is performed on a geometry service
        resource. This operation constructs the set-theoretic intersection
        between an array of geometries and another geometry. The dimension
        of each resultant geometry is the minimum dimension of the input
        geometry in the geometries array and the other geometry specified
        by the geometry parameter.

        Inputs:
         sr - The well-known ID or a spatial reference JSON object for the
          input geometries.
         geometries - An array of points, multipoints, polylines, or
          polygons. The structure of each geometry in the array is the same
          as the structure of the JSON geometry objects returned by the
          ArcGIS REST API.
         geometry - A single geometry of any type with a dimension equal to
          or greater than the elements of geometries.
        """
        url = self._url + "/intersect"
        params = {
            "f": "json",
            "sr": sr,
            "geometries": self.__geometryListToGeomTemplate(geometries=geometries),
            "geometry": self.__geometryToGeomTemplate(geometry=geometry),
        }
        return self._execute_by_chunk(url, params, 2, "intersect", sr, future)

    # ----------------------------------------------------------------------
    def label_points(self, sr, polygons, future=False):
        """
        The label_points operation is performed on a geometry service
        resource. The labelPoints operation calculates an interior point
        for each polygon specified in the input array. These interior
        points can be used by clients for labeling the polygons.

        Inputs:
         sr - The well-known ID of the spatial reference or a spatial
          reference JSON object for the input polygons.
         polygons - The array of polygons whose label points are to be
          computed. The spatial reference of the polygons is specified by
          sr.
        """
        url = self._url + "/labelPoints"
        params = {
            "f": "json",
            "sr": sr,
            "polygons": self.__geomToStringArray(
                geometries=polygons, returnType="list"
            ),
        }
        all_geometries = self.__geomToStringArray(
            geometries=polygons, returnType="list"
        )
        chunk = 65000
        all_results = []
        with concurrent.futures.ThreadPoolExecutor(1) as executor:
            # loop until all chunks reached
            for i in range(0, len(all_geometries), chunk):
                geoms = all_geometries[i : i + chunk]
                params["polygons"] = geoms
                f1 = executor.submit(
                    self._con.post,
                    **{
                        "path": url,
                        "postdata": params,
                        "token": self._token,
                    },
                )
                job = GeometryJob(
                    future=f1,
                    task_name="label_points",
                    jobid=None,
                    task_url=url,
                    notify=False,
                    gis=self._gis,
                    out_wkid=sr,
                )
                if future:
                    return job
                else:
                    results = job.result()
                    for result in results:
                        all_results.append(result)
        return all_results

    # ----------------------------------------------------------------------
    def lengths(self, sr, polylines, lengthUnit, calculationType, future=False):
        """
        The lengths operation is performed on a geometry service resource.
        This operation calculates the 2D Euclidean or geodesic lengths of
        each polyline specified in the input array.

        Inputs:
         sr - The well-known ID of the spatial reference or a spatial
          reference JSON object for the input polylines.
         polylines - The array of polylines whose lengths are to be
          computed.
         lengthUnit - The unit in which lengths of polylines will be
          calculated. If calculationType is planar, then lengthUnit can be
          any esriUnits constant. If calculationType is planar and
          lengthUnit is not specified, then the units are derived from sr.
          If calculationType is not planar, then lengthUnit must be a
          linear esriUnits constant such as esriSRUnit_Meter or
          esriSRUnit_SurveyMile. If calculationType is not planar and
          lengthUnit is not specified, then the units are meters.
         calculationType - calculationType defines the length calculation
          for the geometry. The type can be one of the following values:
            planar - Planar measurements use 2D Euclidean distance to
             calculate length. This type should only be used if the length
             needs to be calculated in the given spatial reference.
             Otherwise, use preserveShape.
            geodesic - Use this type if you want to calculate a length
             using only the vertices of the polygon and define the lines
             between the vertices as geodesic segments independent of the
             actual shape of the polyline. A geodesic segment is the
             shortest path between two points on an earth ellipsoid.
            preserveShape - This type calculates the length of the geometry
             on the surface of the earth ellipsoid. The shape of the
             geometry in its coordinate system is preserved.
        """
        allowedCalcTypes = ["planar", "geodesic", "preserveShape"]
        if calculationType not in allowedCalcTypes:
            raise AttributeError("Invalid calculation Type")
        url = self._url + "/lengths"
        params = {
            "f": "json",
            "sr": sr,
            "polylines": self.__geomToStringArray(
                geometries=polylines, returnType="list"
            ),
            "lengthUnit": lengthUnit,
            "calculationType": calculationType,
        }
        all_geometries = self.__geomToStringArray(
            geometries=polylines, returnType="list"
        )
        chunk = 65000
        all_results = []
        with concurrent.futures.ThreadPoolExecutor(1) as executor:
            # loop until all chunks reached
            for i in range(0, len(all_geometries), chunk):
                geoms = all_geometries[i : i + chunk]
                params["polylines"] = geoms
                f1 = executor.submit(
                    self._con.post,
                    **{
                        "path": url,
                        "postdata": params,
                        "token": self._token,
                    },
                )
                job = GeometryJob(
                    future=f1,
                    task_name="lengths",
                    jobid=None,
                    task_url=url,
                    notify=False,
                    gis=self._gis,
                )
                if future:
                    return job
                else:
                    results = job.result()
                    for result in results:
                        all_results.append(result)
        return all_results

    # ----------------------------------------------------------------------
    def offset(
        self,
        geometries,
        offsetDistance,
        offsetUnit,
        offsetHow="esriGeometryOffsetRounded",
        bevelRatio=10,
        simplifyResult=False,
        sr=None,
        future=False,
    ):
        """
        The offset operation is performed on a geometry service resource.
        This operation constructs geometries that are offset from the
        given input geometries. If the offset parameter is positive, the
        constructed offset will be on the right side of the geometry. Left
        side offsets are constructed with negative parameters. Tracing the
        geometry from its first vertex to the last will give you a
        direction along the geometry. It is to the right and left
        perspective of this direction that the positive and negative
        parameters will dictate where the offset is constructed. In these
        terms, it is simple to infer where the offset of even horizontal
        geometries will be constructed.

        Inputs:
         geometries -  The array of geometries to be offset.
         offsetDistance - Specifies the distance for constructing an offset
          based on the input geometries. If the offsetDistance parameter is
          positive, the constructed offset will be on the right side of the
          curve. Left-side offsets are constructed with negative values.
         offsetUnit - A unit for offset distance. If a unit is not
          specified, the units are derived from sr.
         offsetHow - The offsetHow parameter determines how outer corners
          between segments are handled. The three options are as follows:
           esriGeometryOffsetRounded - Rounds the corner between extended
            offsets.
           esriGeometryOffsetBevelled - Squares off the corner after a
            given ratio distance.
           esriGeometryOffsetMitered - Attempts to allow extended offsets
            to naturally intersect, but if that intersection occurs too far
            from the corner, the corner is eventually bevelled off at a
            fixed distance.
         bevelRatio - bevelRatio is multiplied by the offset distance, and
          the result determines how far a mitered offset intersection can
          be located before it is bevelled. When mitered is specified,
          bevelRatio is ignored and 10 is used internally. When bevelled is
          specified, 1.1 will be used if bevelRatio is not specified.
          bevelRatio is ignored for rounded offset.
         simplifyResult - if simplifyResult is set to true, then self
          intersecting loops will be removed from the result offset
          geometries. The default is false.
         sr - The well-known ID or a spatial reference JSON object for the
          input geometries.
        """
        allowedHow = [
            "esriGeometryOffsetRounded",
            "esriGeometryOffsetBevelled",
            "esriGeometryOffsetMitered",
        ]
        if offsetHow not in allowedHow:
            raise AttributeError("Invalid Offset How value")
        url = self._url + "/offset"
        params = {
            "f": "json",
            "sr": sr,
            "geometries": self.__geometryListToGeomTemplate(geometries=geometries),
            "offsetDistance": offsetDistance,
            "offsetUnit": offsetUnit,
            "offsetHow": offsetHow,
            "bevelRatio": bevelRatio,
            "simplifyResult": json.dumps(simplifyResult),
        }
        return self._execute_by_chunk(url, params, 2, "offset", sr, future)

    # ----------------------------------------------------------------------
    def project(
        self,
        geometries,
        inSR,
        outSR,
        transformation="",
        transformFoward=False,
        future=False,
    ):
        """
        The project operation is performed on a geometry service resource.
        This operation projects an array of input geometries from the input
        spatial reference to the output spatial reference.

        Inputs:
         geometries - The array of geometries to be projected.
         inSR - The well-known ID (WKID) of the spatial reference or a
          spatial reference JSON object for the input geometries.
         outSR - The well-known ID (WKID) of the spatial reference or a
          spatial reference JSON object for the input geometries.
         transformation - The WKID or a JSON object specifying the
          geographic transformation (also known as datum transformation) to
          be applied to the projected geometries. Note that a
          transformation is needed only if the output spatial reference
          contains a different geographic coordinate system than the input
          spatial reference.
         transformForward - A Boolean value indicating whether or not to
          transform forward. The forward or reverse direction of
          transformation is implied in the name of the transformation. If
          transformation is specified, a value for the transformForward
          parameter must also be specified. The default value is false.
        """
        url = self._url + "/project"
        params = {
            "f": "json",
            "inSR": inSR,
            "geometries": self.__geometryListToGeomTemplate(geometries=geometries),
            "outSR": outSR,
            "transformation": transformation,
            "transformFoward": transformFoward,
        }
        results = self._execute_by_chunk(url, params, 2, "project", outSR, future)
        return results

    # ----------------------------------------------------------------------
    def relation(
        self,
        geometries1,
        geometries2,
        sr,
        relation="esriGeometryRelationIntersection",
        relationParam="",
        future=False,
    ):
        """
        The relation operation is performed on a geometry service resource.
        This operation determines the pairs of geometries from the input
        geometry arrays that participate in the specified spatial relation.
        Both arrays are assumed to be in the spatial reference specified by
        sr, which is a required parameter. Geometry types cannot be mixed
        within an array. The relations are evaluated in 2D. In other words,
        z coordinates are not used.

        Inputs:
         geometries1 - The first array of geometries used to compute the
          relations.
         geometries2 -The second array of geometries used to compute the
         relations.
         sr - The well-known ID of the spatial reference or a spatial
          reference JSON object for the input geometries.
         relation - The spatial relationship to be tested between the two
          input geometry arrays.
          Values: esriGeometryRelationCross | esriGeometryRelationDisjoint |
          esriGeometryRelationIn | esriGeometryRelationInteriorIntersection |
          esriGeometryRelationIntersection | esriGeometryRelationLineCoincidence |
          esriGeometryRelationLineTouch | esriGeometryRelationOverlap |
          esriGeometryRelationPointTouch | esriGeometryRelationTouch |
          esriGeometryRelationWithin | esriGeometryRelationRelation
         relationParam - The Shape Comparison Language string to be
          evaluated.
        """
        relationType = [
            "esriGeometryRelationCross",
            "esriGeometryRelationDisjoint",
            "esriGeometryRelationIn",
            "esriGeometryRelationInteriorIntersection",
            "esriGeometryRelationIntersection",
            "esriGeometryRelationLineCoincidence",
            "esriGeometryRelationLineTouch",
            "esriGeometryRelationOverlap",
            "esriGeometryRelationPointTouch",
            "esriGeometryRelationTouch",
            "esriGeometryRelationWithin",
            "esriGeometryRelationRelation",
        ]
        if relation not in relationType:
            raise AttributeError("Invalid relation type")
        url = self._url + "/relation"
        params = {
            "f": "json",
            "sr": sr,
            "geometries1": self.__geometryListToGeomTemplate(geometries=geometries1),
            "geometries2": self.__geometryListToGeomTemplate(geometries=geometries2),
            "relation": relation,
            "relationParam": relationParam,
        }
        executor = concurrent.futures.ThreadPoolExecutor(1)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )

        executor.shutdown(False)
        job = GeometryJob(
            future=f1,
            task_name="relation",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def reshape(self, sr, target, reshaper, future=False):
        """
        The reshape operation is performed on a geometry service resource.
        It reshapes a polyline or polygon feature by constructing a
        polyline over the feature. The feature takes the shape of the
        reshaper polyline from the first place the reshaper intersects the
        feature to the last.

        Input:
         sr - The well-known ID of the spatial reference or a spatial
          reference JSON object for the input geometries.
         target -  The polyline or polygon to be reshaped.
         reshaper - The single-part polyline that does the reshaping.
        """
        url = self._url + "/reshape"
        params = {
            "f": "json",
            "sr": sr,
            "target": self.__geometryToGeomTemplate(geometry=target),
        }
        if isinstance(reshaper, Polyline):
            params["reshaper"] = reshaper
        elif isinstance(reshaper, dict):
            params["reshaper"] = reshaper
        else:
            raise AttributeError("Invalid reshaper object, must be Polyline")
        executor = concurrent.futures.ThreadPoolExecutor(2)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )
        f2 = executor.submit(self._process_results, **{"results": f1, "out_sr": sr})
        executor.shutdown(False)
        job = GeometryJob(
            future=f2,
            task_name="reshape",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
            out_wkid=sr,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def simplify(self, sr, geometries, future=False):
        """
        The simplify operation is performed on a geometry service resource.
        Simplify permanently alters the input geometry so that the geometry
        becomes topologically consistent. This resource applies the ArcGIS
        simplify operation to each geometry in the input array.

        Inputs:
        sr - The well-known ID of the spatial reference or a spatial
          reference JSON object for the input geometries.
        geometries - The array of geometries to be simplified.
        """
        url = self._url + "/simplify"
        params = {
            "f": "json",
            "sr": sr,
            "geometries": self.__geometryListToGeomTemplate(geometries=geometries),
        }
        return self._execute_by_chunk(url, params, 2, "simplify", sr, future)

    # ----------------------------------------------------------------------
    def to_geo_coordinate_string(
        self,
        sr,
        coordinates,
        conversionType,
        conversionMode="mgrsDefault",
        numOfDigits=None,
        rounding=True,
        addSpaces=True,
        future=False,
    ):
        """
        The toGeoCoordinateString operation is performed on a geometry
        service resource. The operation converts an array of
        xy-coordinates into well-known strings based on the conversion type
        and spatial reference supplied by the user. Optional parameters are
        available for some conversion types. Note that if an optional
        parameter is not applicable for a particular conversion type, but a
        value is supplied for that parameter, the value will be ignored.

        Inputs:
          sr -  The well-known ID of the spatial reference or a spatial
           reference json object.
          coordinates - An array of xy-coordinates in JSON format to be
           converted. Syntax: [[x1,y2],...[xN,yN]]
          conversionType - The conversion type of the input strings.
           Allowed Values:
            MGRS - Military Grid Reference System
            USNG - United States National Grid
            UTM - Universal Transverse Mercator
            GeoRef - World Geographic Reference System
            GARS - Global Area Reference System
            DMS - Degree Minute Second
            DDM - Degree Decimal Minute
            DD - Decimal Degree
          conversionMode - Conversion options for MGRS and UTM conversion
           types.
           Valid conversion modes for MGRS are:
            mgrsDefault - Default. Uses the spheroid from the given spatial
             reference.
            mgrsNewStyle - Treats all spheroids as new, like WGS 1984. The
             180 degree longitude falls into Zone 60.
            mgrsOldStyle - Treats all spheroids as old, like Bessel 1841.
             The 180 degree longitude falls into Zone 60.
            mgrsNewWith180InZone01 - Same as mgrsNewStyle except the 180
             degree longitude falls into Zone 01.
            mgrsOldWith180InZone01 - Same as mgrsOldStyle except the 180
             degree longitude falls into Zone 01.
           Valid conversion modes for UTM are:
            utmDefault - Default. No options.
            utmNorthSouth - Uses north/south latitude indicators instead of
             zone numbers. Non-standard. Default is recommended.
          numOfDigits - The number of digits to output for each of the
           numerical portions in the string. The default value for
           numOfDigits varies depending on conversionType.
          rounding - If true, then numeric portions of the string are
           rounded to the nearest whole magnitude as specified by
           numOfDigits. Otherwise, numeric portions of the string are
           truncated. The rounding parameter applies only to conversion
           types MGRS, USNG and GeoRef. The default value is true.
          addSpaces - If true, then spaces are added between components of
           the string. The addSpaces parameter applies only to conversion
           types MGRS, USNG and UTM. The default value for MGRS is false,
           while the default value for both USNG and UTM is true.
        """
        params = {
            "f": "json",
            "sr": sr,
            "coordinates": coordinates,
            "conversionType": conversionType,
        }
        url = self._url + "/toGeoCoordinateString"
        if not conversionMode is None:
            params["conversionMode"] = conversionMode
        if isinstance(numOfDigits, int):
            params["numOfDigits"] = numOfDigits
        if isinstance(rounding, int):
            params["rounding"] = rounding
        if isinstance(addSpaces, bool):
            params["addSpaces"] = addSpaces
        executor = concurrent.futures.ThreadPoolExecutor(1)
        f1 = executor.submit(
            self._con.post,
            **{"path": url, "postdata": params, "token": self._token},
        )

        executor.shutdown(False)
        job = GeometryJob(
            future=f1,
            task_name="to_geo_coordinate_string",
            jobid=None,
            task_url=url,
            notify=False,
            gis=self._gis,
        )
        if future:
            return job
        else:
            return job.result()

    # ----------------------------------------------------------------------
    def trim_extend(self, sr, polylines, trimExtendTo, extendHow=0, future=False):
        """
        The trim_extend operation is performed on a geometry service
        resource. This operation trims or extends each polyline specified
        in the input array, using the user-specified guide polylines. When
        trimming features, the part to the left of the oriented cutting
        line is preserved in the output, and the other part is discarded.
        An empty polyline is added to the output array if the corresponding
        input polyline is neither cut nor extended.

        Inputs:
         sr - The well-known ID of the spatial reference or a spatial
           reference json object.
         polylines - An array of polylines to be trimmed or extended.
         trimExtendTo - A polyline that is used as a guide for trimming or
          extending input polylines.
         extendHow - A flag that is used along with the trimExtend
          operation.
          0 - By default, an extension considers both ends of a path. The
           old ends remain, and new points are added to the extended ends.
           The new points have attributes that are extrapolated from
           adjacent existing segments.
          1 - If an extension is performed at an end, relocate the end
           point to the new position instead of leaving the old point and
           adding a new point at the new position.
          2 - If an extension is performed at an end, do not extrapolate
           the end-segment's attributes for the new point. Instead, make
           its attributes the same as the current end. Incompatible with
           esriNoAttributes.
          4 - If an extension is performed at an end, do not extrapolate
           the end-segment's attributes for the new point. Instead, make
           its attributes empty. Incompatible with esriKeepAttributes.
          8 - Do not extend the 'from' end of any path.
          16 - Do not extend the 'to' end of any path.
        """
        allowedHow = [0, 1, 2, 4, 8, 16]
        if extendHow not in allowedHow:
            raise AttributeError("Invalid extend How value.")
        url = self._url + "/trimExtend"
        params = {
            "f": "json",
            "sr": sr,
            "polylines": self.__geomToStringArray(
                geometries=polylines, returnType="list"
            ),
            "extendHow": extendHow,
            "trimExtendTo": trimExtendTo,
        }
        all_geometries = self.__geomToStringArray(
            geometries=polylines, returnType="list"
        )
        chunk = 65000
        all_results = []
        with concurrent.futures.ThreadPoolExecutor(2) as executor:
            # loop until all chunks reached
            for i in range(0, len(all_geometries), chunk):
                geoms = all_geometries[i : i + chunk]
                params["polylines"] = geoms
                f1 = executor.submit(
                    self._con.post,
                    **{
                        "path": url,
                        "postdata": params,
                        "token": self._token,
                    },
                )
                f2 = executor.submit(
                    self._process_results, **{"results": f1, "out_sr": sr}
                )
                job = GeometryJob(
                    future=f2,
                    task_name="trim_extend",
                    jobid=None,
                    task_url=url,
                    notify=False,
                    gis=self._gis,
                    out_wkid=sr,
                )
                if future:
                    return job
                else:
                    results = job.result()
                    for result in results:
                        all_results.append(result)
        return all_results

    # ----------------------------------------------------------------------
    def union(self, sr, geometries, future=False):
        """
        The union operation is performed on a geometry service resource.
        This operation constructs the set-theoretic union of the geometries
        in the input array. All inputs must be of the same type.

        Inputs:
        sr - The well-known ID of the spatial reference or a spatial
         reference json object.
        geometries - The array of geometries to be unioned.
        """
        url = self._url + "/union"
        params = {
            "f": "json",
            "sr": sr,
            "geometries": self.__geometryListToGeomTemplate(geometries=geometries),
        }
        return self._execute_by_chunk(url, params, 2, "union", sr, future)


class AGSSystemTools:
    """
    ArcGIS Server has System folder that have utility geop,rocessing tools to work
    with multiple aspects of the system.
    """

    _properties = None
    _gptbxs = None
    _gpserver = None
    _gpcatalogs = None

    def __init__(self, gis: "GIS", verbose=False):
        self._gis = gis
        self._verbose = verbose
        self.properties = {}

    # ----------------------------------------------------------------------
    @property
    def _tbx(self):
        """gets the toolbox"""
        if self._gptbxs is None:
            self._gptbxs = {}
            for catalog in self._catalogs:
                try:
                    self._gptbxs[catalog._url.lower()] = catalog.get(
                        "PublishingTools", "System"
                    )
                except:
                    ...
        return self._gptbxs

    # ----------------------------------------------------------------------
    @property
    def _servers(self):
        if self._gpserver is None:
            self._gpserver = self._gis.admin.servers.list()
        return self._gpserver

    # ---------------------------------------------------------------------
    @property
    def _catalogs(self):
        if self._gpcatalogs is None:
            self._gpcatalogs = [server.content for server in self._servers]
        return self._gpcatalogs

    # ---------------------------------------------------------------------
    def refresh_service(
        self,
        layer: ImageryLayer | Raster,
        options: str | None = None,
        future: bool = True,
    ) -> bool:
        """
        Refresh Service is a new task in the existing out-of-the-box
        PublishingTools geoprocessing service used by the service publisher
        to refresh a GIS service to reflect back-end data changes.

        At 10.3, only image services are supported by this tool. Valid
        input image services must have been configured as
        hasLiveData:true through ArcGIS Server Manager Manager.

        =====================     ====================================================================
        **Parameter**             **Description**
        ---------------------     --------------------------------------------------------------------
        layer                     Required ImageryLayer or Raster. The layer to refresh on the server.
        ---------------------     --------------------------------------------------------------------
        options                   Optional string. Additional options to refresh the service.
        ---------------------     --------------------------------------------------------------------
        future                    Optional Boolean. If True, the operation is completed in an asynchronous fashion, else, synchronous fashion.
        =====================     ====================================================================

        :returns: string

        """

        if isinstance(layer, ImageryLayer):
            base_service_url: str = (
                layer._url.split("/services/")[0].lower() + "/services"
            )
            path: str = layer._url.split("/services/")[-1]
            service_type: str = os.path.basename(path)
            service_name: str = os.path.basename(os.path.dirname(path))
            folder: str = ""
            if path.startswith(service_name) == False:
                folder = path.split(f"/{service_name}/")[0]

        elif isinstance(layer, Raster) and isinstance(
            layer._engine_obj, _ImageServerRaster
        ):
            path: str = layer._engine_obj._url.split("/services/")[-1]
            base_service_url: str = (
                layer._engine_obj._url.split("/services/")[0].lower() + "/services"
            )
            service_type: str = os.path.basename(path)
            service_name: str = os.path.basename(os.path.dirname(path))
            folder: str = ""
            if path.startswith(service_name) == False:
                folder = path.split(f"/{service_name}/")[0]
        else:
            raise ValueError(
                "Input must be a service based `Raster` class or `ImageryLayer`"
            )
        tbxs = self._tbx
        if base_service_url in tbxs:
            tbx = tbxs[base_service_url]
        else:
            raise ValueError(
                "The service is not stored on a system where it can be refreshed"
            )

        job = tbx.refresh_service(
            service_name=service_name,
            service_type=service_type,
            service_folder=folder,
            refresh_options=options,
            gis=self._gis,
            future=True,
        )
        if future:
            return job
        else:
            return job.result()


###########################################################################
class _Tools(object):
    """
    Collection of GIS tools. This class holds references to the helper services and tools available
    in the GIS. This class is not created by users directly.
    An instance of this class, called 'tools', is available as a property of the GIS object.
    Users access the GIS tools, such as the geocoders through
    the gis.tools object
    """

    # spatial analysis tools, geoanalytics, rasteranalysis tools, etc through the gis.tools object
    def __init__(self, gis):
        self._gis = gis
        self._geocoders = None
        self._geometry = None
        self._analysis = None
        self._raster_analysis = None
        self._geoanalytics = None
        self._orthomapping = None
        self._packaging = None
        self._symbolservice = None
        self._systemservice = None

    @lru_cache(maxsize=255)
    def _validate_url(self, url):
        res = self._gis._private_service_url(url)
        if "privateServiceUrl" in res:
            return res["privateServiceUrl"]
        else:
            return res["serviceUrl"]
        return url

    @property
    @lru_cache(maxsize=255)
    def system_service(self) -> AGSSystemTools:
        """
        the portal's AGS System Tools provides access to refresh_service
        """
        if self._systemservice is None:
            self._systemservice = AGSSystemTools(gis=self._gis)
        return self._systemservice

    @property
    @lru_cache(maxsize=255)
    def symbol_service(self):
        """the portal's symbol service if available and configured"""
        if self._symbolservice is not None:
            return self._symbolservice
        try:
            if self._gis._is_hosted_nb_home:
                svcurl = self._validate_url(
                    self._gis.properties["helperServices"]["symbols"]["url"]
                )
            else:
                svcurl = self._gis.properties["helperServices"]["symbols"]["url"]
            from arcgis.mapping._types import SymbolService

            self._symbolservice = SymbolService(svcurl, self._gis)
            return self._symbolservice
        except KeyError:
            return None

    @property
    @lru_cache(maxsize=255)
    def geocoders(self):
        """the geocoders, if available and configured"""
        if self._geocoders is not None:
            return self._geocoders
        self._geocoders = []
        try:
            geocode_services = self._gis.properties["helperServices"]["geocode"]
            for geocode_service in geocode_services:
                try:
                    if self._gis._is_hosted_nb_home:
                        url = self._validate_url(geocode_service["url"])
                        self._geocoders.append(Geocoder(url, self._gis))
                    else:
                        self._geocoders.append(
                            Geocoder(geocode_service["url"], self._gis)
                        )
                except RuntimeError as re:
                    _log.warning("Unable to use Geocoder at " + geocode_service["url"])
                    _log.warning(str(re))
        except KeyError:
            pass
        return self._geocoders

    @property
    @lru_cache(maxsize=255)
    def geometry(self):
        """the portal's geometry  tools, if available and configured"""
        if self._geometry is not None:
            return self._geometry
        try:
            if self._gis._is_hosted_nb_home:
                svcurl = self._validate_url(
                    self._gis.properties["helperServices"]["geometry"]["url"]
                )
            else:
                svcurl = self._gis.properties["helperServices"]["geometry"]["url"]
            self._geometry = _GeometryService(svcurl, self._gis)
            return self._geometry
        except KeyError:
            return None

    @property
    @lru_cache(maxsize=255)
    def rasteranalysis(self):
        """the portal's raster analysis tools, if available and configured"""
        if self._raster_analysis is not None:
            return self._raster_analysis
        try:
            try:
                if self._gis._is_hosted_nb_home:
                    svcurl = self._validate_url(
                        self._gis.properties["helperServices"]["rasterAnalytics"]["url"]
                    )
                else:
                    svcurl = self._gis.properties["helperServices"]["rasterAnalytics"][
                        "url"
                    ]
            except:
                print("This GIS does not support raster analysis.")
                return None

            self._raster_analysis = _RasterAnalysisTools(svcurl, self._gis)
            return self._raster_analysis
        except KeyError:
            return None

    @property
    @lru_cache(maxsize=255)
    def geoanalytics(self):
        """the portal's bigdata analytics tools, if available and configured"""
        if self._geoanalytics is not None:
            return self._geoanalytics
        try:
            try:
                svcurl = self._gis.properties["helperServices"]["geoanalytics"]["url"]
                if self._gis._is_hosted_nb_home:
                    svcurl = self._validate_url(svcurl)
            except:
                print("This GIS does not support geoanalytics.")
                return None

            self._geoanalytics = _GeoanalyticsTools(svcurl, self._gis)
            return self._geoanalytics
        except KeyError:
            return None

    @property
    @lru_cache(maxsize=255)
    def featureanalysis(self):
        """the portal's spatial analysis tools, if available and configured"""
        if self._analysis is not None:
            return self._analysis
        try:
            try:
                svcurl = self._gis.properties["helperServices"]["analysis"]["url"]
                if self._gis._is_hosted_nb_home:
                    svcurl = self._validate_url(svcurl)
            except:
                if self._gis._con.token is None:
                    print("You need to be signed in to use spatial analysis.")
                else:
                    print("This GIS does not support spatial analysis.")
                return None

            self._analysis = _FeatureAnalysisTools(svcurl, self._gis)
            return self._analysis
        except KeyError:
            return None

    @property
    @lru_cache(maxsize=255)
    def packaging(self):
        """The Portal's Packaging Tools"""
        if self._packaging is not None:
            return self._packaging
        try:
            svcurl = self._gis.properties["helperServices"]["packaging"]["url"]
            if self._gis._is_hosted_nb_home:
                svcurl = self._validate_url(svcurl)
            self._packaging = _PackagingTools(url=svcurl, gis=self._gis)
            return self._packaging
        except:
            if self._gis._con.token is None:
                print("You need to be signed in to use Packaging Tools.")
                return
            else:
                print("This GIS does not support Packaging Tools.")
                return
        return None

    @property
    @lru_cache(maxsize=255)
    def orthomapping(self):
        """the portal's Ortho-Mapping tools, if available and configured"""
        if self._analysis is not None:
            return self._analysis
        try:
            try:
                svcurl = self._gis.properties.helperServices["orthoMapping"]["url"]
                if self._gis._is_hosted_nb_home:
                    svcurl = self._validate_url(svcurl)
            except:
                if self._gis._con.token is None:
                    raise RuntimeError(
                        "You need to be signed in to use Ortho Mapping Tools."
                    )
                else:
                    raise RuntimeError("This GIS does not support Ortho Mapping Tools.")
                return None

            self._orthomapping = _OrthoMappingTools(svcurl, self._gis)
            return self._orthomapping
        except KeyError:
            return None
