davetimmins/ArcGIS.PCL

Add ability to Query without knowing the geometry type

SergeyBarskiy opened this issue · 4 comments

Often times as we run various rules, we need to get the geometry for sole reason to pass it to another query. At that time we do not know the geometry type nor do we care what it is. Would it be possible to have a Query method that does not specify type of geometry?

For now I created a workaround via custom serializer as a proof that the feature is doable. I pasted the class below

`using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using ArcGIS.ServiceModel;
using ArcGIS.ServiceModel.Common;
using ArcGIS.ServiceModel.Operation;
using Newtonsoft.Json.Linq;

namespace ArcGisIntegration.Services
{
public class ArcGisSerializer : ISerializer
{
private static ISerializer _serializer = null;

    public static void Init(Newtonsoft.Json.JsonSerializerSettings settings = null)
    {
        _serializer = new ArcGisSerializer(settings);
        SerializerFactory.Get = (() => _serializer ?? new ArcGisSerializer(settings));
    }

    private readonly Newtonsoft.Json.JsonSerializerSettings _settings;

    public ArcGisSerializer(Newtonsoft.Json.JsonSerializerSettings settings = null)
    {
        _settings = settings ?? new Newtonsoft.Json.JsonSerializerSettings
        {
            NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
            MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore,
            StringEscapeHandling = Newtonsoft.Json.StringEscapeHandling.EscapeHtml,
            TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None
        };
    }

    public Dictionary<string, string> AsDictionary<T>(T objectToConvert) where T : CommonParameters
    {
        var stringValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectToConvert, _settings);

        var jobject = Newtonsoft.Json.Linq.JObject.Parse(stringValue);
        var dict = new Dictionary<string, string>();
        foreach (var item in jobject)
        {
            dict.Add(item.Key, item.Value.ToString());
        }
        return dict;
    }

    public T AsPortalResponse<T>(string dataToConvert) where T : IPortalResponse
    {
        // got generic geometry in the API
        if (typeof(T) == typeof(QueryResponse<IGeometry>))
        {
            var preliminaryData = JObject.Parse(dataToConvert);
            var type = preliminaryData.GetValue("geometryType").Value<string>();
            switch (type)
            {
                case "esriGeometryEnvelope":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Extent>>(dataToConvert));
                case "esriGeometryMultipoint":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<MultiPoint>>(dataToConvert));
                case "esriGeometryPoint":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Point>>(dataToConvert));
                case "esriGeometryPolygon":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Polygon>>(dataToConvert));
                case "esriGeometryPolyline":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Polyline>>(dataToConvert));
                default:
                    throw new ArgumentException(
                        "AsPortalResponse - cannot support IGeometry for value returned type of " + type);
            }
        }
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(dataToConvert, _settings);
    }

    private IPortalResponse CreateResponce<TResponce>(QueryResponse<TResponce> responce)
        where TResponce : IGeometry
    {
        var features = new List<Feature<IGeometry>>();
        responce.Features.ToList().ForEach(one => features.Add(new Feature<IGeometry>
        {
            Attributes = one.Attributes,
            Geometry = one.Geometry
        }));
        var result = new QueryResponse<IGeometry>
        {
            DisplayFieldName = responce.DisplayFieldName,
            Error = responce.Error,
            ExceededTransferLimit = responce.ExceededTransferLimit,
            Features = features,
            FieldAliases = responce.FieldAliases,
            Fields = responce.Fields,
            GeometryTypeString = responce.GeometryTypeString,
            GlobalIdFieldName = responce.GlobalIdFieldName,
            Links = responce.Links,
            ObjectIdFieldName = responce.ObjectIdFieldName,
            SpatialReference = responce.SpatialReference

        };
        return result;
    }
}

}`

For now you will be best to just customise the serializer for your app (the source code package is best for this ArcGIS.PCL.JsonDotNetSerializer.Source) and create a new Query method in a class that extends PortalGateway(Base). If I was to remove the generic constraint I think it would be for every operation and then manage it via the serializer which I may do down the line but that would be a bigger rework. It's also worth mentioning that you have the option of using the ServiceStack serializer (and I had planned on supporting others), so that would need to be modified to work this way too.

Another way of doing this is to query the service first using DescribeLayer https://github.com/davetimmins/ArcGIS.PCL/blob/master/src/ArcGIS.ServiceModel/PortalGateway.cs#L150 and use the returned geometry type, adds another http call but you then get the service metadata.

Makes sense. I will go the route of custom serializer, technically the one posted above already works. I had to fix a couple of things in it. I was just thinking that adding methods without generic constraint would be helpful. Not removing existing, but just add new methods with the same signatures. If you are querying by attributes without including geometry, then the requirement to specify geometry type looks even stranger. I can understand the amount work. Thanks for the suggestion. I am posting final version of my serializer in case anyone else wants it. I had to jump through some hoops casting to IGeometry. Might be easier if the generic parameter was declared as "<out T>" everywhere, but this is pretty minor.

Thanks again.

using System;
using System.Collections.Generic;
using System.Linq;
using ArcGIS.ServiceModel;
using ArcGIS.ServiceModel.Common;
using ArcGIS.ServiceModel.Operation;
using Newtonsoft.Json.Linq;

namespace ArcGisIntegration.Services
{
public class ArcGisSerializer : ISerializer
{

    private readonly Newtonsoft.Json.JsonSerializerSettings _settings;

    public ArcGisSerializer(Newtonsoft.Json.JsonSerializerSettings settings = null)
    {
        _settings = settings ?? new Newtonsoft.Json.JsonSerializerSettings
        {
            NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
            MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore,
            StringEscapeHandling = Newtonsoft.Json.StringEscapeHandling.EscapeHtml,
            TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None
        };
    }

    public Dictionary<string, string> AsDictionary<T>(T objectToConvert) where T : CommonParameters
    {
        var stringValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectToConvert, _settings);

        var jobject = JObject.Parse(stringValue);
        var dict = new Dictionary<string, string>();
        foreach (var item in jobject)
        {
            dict.Add(item.Key, item.Value.ToString());
        }
        return dict;
    }

    public T AsPortalResponse<T>(string dataToConvert) where T : IPortalResponse
    {
        // got generic geometry in the API
        if (typeof(T) == typeof(QueryResponse<IGeometry>))
        {
            var preliminaryData = JObject.Parse(dataToConvert);
            var type = preliminaryData.GetValue("geometryType")?.Value<string>();
            if (string.IsNullOrEmpty(type))
            {
                return (T)CreateResponce(AsPortalResponse<QueryResponse<Point>>(dataToConvert));
            }
            switch (type)
            {
                case "esriGeometryEnvelope":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Extent>>(dataToConvert));
                case "esriGeometryMultipoint":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<MultiPoint>>(dataToConvert));
                case "esriGeometryPoint":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Point>>(dataToConvert));
                case "esriGeometryPolygon":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Polygon>>(dataToConvert));
                case "esriGeometryPolyline":
                    return (T)CreateResponce(AsPortalResponse<QueryResponse<Polyline>>(dataToConvert));
                default:
                    throw new ArgumentException(
                        "AsPortalResponse - cannot support IGeometry for value returned type of " + type);
            }
        }
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(dataToConvert, _settings);
    }

    private IPortalResponse CreateResponce<TResponce>(QueryResponse<TResponce> responce)
        where TResponce : IGeometry
    {
        var features = new List<Feature<IGeometry>>();
        if (responce.Features != null)
        {
            responce.Features.ToList().ForEach(one => features.Add(new Feature<IGeometry>
            {
                Attributes = one.Attributes,
                Geometry = one.Geometry
            }));
        }
        var result = new QueryResponse<IGeometry>
        {
            DisplayFieldName = responce.DisplayFieldName,
            Error = responce.Error,
            ExceededTransferLimit = responce.ExceededTransferLimit,
            Features = features,
            FieldAliases = responce.FieldAliases,
            Fields = responce.Fields,
            GeometryTypeString = responce.GeometryTypeString,
            GlobalIdFieldName = responce.GlobalIdFieldName,
            Links = responce.Links,
            ObjectIdFieldName = responce.ObjectIdFieldName,
            SpatialReference = responce.SpatialReference

        };
        return result;
    }
}

}

Archiving this repo, you can re=open at https://github.com/davetimmins/Anywhere.ArcGIS if you want to