Search code examples
.netelasticsearchnestelasticsearch-net

Nest (Geo)Location's Longitude is always 0?


I've been digging deeper into using NEST for a .Net based project that shall utilize ElasticSearch but what keeps or kept puzzling me is that GeoDistance queries never returned any results.

When debugging into the responses for a simple "*" query and looking at the .Documents of the search result, all document instances have a Longitude value of 0.0 - Latitude however is the correct one.

This is a bare-bones ES server as fresh as it gets (download and run), nothing (re-)configured.. same for one hosted at FacetFlow.

As for versions, they are 1.4.3 for Elasticsearch.Net and also NEST, ElasticSearch itself is version 1.4.4.

Is there anything I am missing here or more precisely - what am I missing here?

The sample code looks like this (the GeoLocation class used below is the Nest.GeoLocation one):

using System;
using System.Linq;
using Nest;

namespace NestPlayground
{
    public class Post
    {
        public Guid Id { get; set; }
        public string User { get; set; }
        public DateTime CreatedAt { get; set; }
        public string Message { get; set; }
        public GeoLocation Location { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var indexName = "sampleindex";

            var uri = new Uri("<elasticsearch url>");
            var settings = new ConnectionSettings(uri).SetDefaultIndex(indexName);
            var client = new ElasticClient(settings);

            client.DeleteIndex(indexName);

            var post = new Post
            {
                Id = Guid.NewGuid(),
                User = "Some User",
                CreatedAt = DateTime.UtcNow,
                Message = "Some Sample Message",
                Location = new GeoLocation(37.809860, -122.476995)
            };

            client.Index(post);
            client.Refresh();

            // Execute a search using the connection from above.

            var result = client.Search<Post>(s => s
                               .Index(indexName)
                               .Query(queryDescriptor => queryDescriptor.QueryString(queryStringQueryDescriptor => queryStringQueryDescriptor.Query("*")))
                               //.Filter(filterDescriptor => filterDescriptor.GeoDistance(post1 => post1.Location, geoDistanceFilterDescriptor => geoDistanceFilterDescriptor
                               //    .Distance(50, GeoUnit.Kilometers)
                               //    .Location(Lat: 37.802774, Lon: -122.4478561)
                               //    .Optimize(GeoOptimizeBBox.Indexed)))
                               );

            // this DOES return the just created/indexed document, but its .Longitude / result.Documents.First().Location.Longtitude property is always '0'?!
        }
    }
}

Solution

  • 1. Looks like the GeoLocation type is out of date. Even the NEST tests use a CustomGeoLocation class.

    So your Post class should look like:

    public class Post
    {
        public Guid Id { get; set; }
        public string User { get; set; }
        public DateTime CreatedAt { get; set; }
        public string Message { get; set; }
        [ElasticProperty(Type = FieldType.GeoPoint)]
        public Location Location { get; set; }
    }
    
    public class Location
    {
        public Location(double lat, double lon)
        {
            Lat = lat;
            Lon = lon;
        }
    
        public double Lat { get; set; }
        public double Lon { get; set; }
    }
    

    2. Documentation for Geo Distance Filter says:

    The filter requires the geo_point type to be set on the relevant field.

    this is why I set Location type to FieldType.GeoPoint.

    Remember to create mapping for your index.

    client.CreateIndex(
        descriptor =>
            descriptor.Index(indexName)
                .AddMapping<Post>(
                    m => m.Properties(p => p
                        .GeoPoint(mappingDescriptor => mappingDescriptor.Name(f => f.Location).IndexLatLon()))));
    

    I turned on lat_lon because you wanted to use GeoOptimizeBBox.Indexed in your GeoDistanceFilter.

    ES mapping for your index:

    {
        "sampleindex" : {
            "mappings" : {
                "post" : {
                    "properties" : {
                        "createdAt" : {
                            "type" : "date",
                            "format" : "dateOptionalTime"
                        },
                        "id" : {
                            "type" : "string"
                        },
                        "location" : {
                            "type" : "geo_point",
                            "lat_lon" : true
                        },
                        "message" : {
                            "type" : "string"
                        },
                        "user" : {
                            "type" : "string"
                        }
                    }
                }
            }
        }
    }
    

    3. Now this query finally works

    var result = client.Search<Post>(s => s
        .Index(indexName)
        .Query(
            queryDescriptor => queryDescriptor.QueryString(queryStringQueryDescriptor => queryStringQueryDescriptor.Query("*")))
        .Filter(
            filterDescriptor =>
                filterDescriptor.GeoDistance(post1 => post1.Location, geoDistanceFilterDescriptor => geoDistanceFilterDescriptor
                    .Distance(500, GeoUnit.Kilometers)
                    .Location(37.802774, -122.4478561)
                    .Optimize(GeoOptimizeBBox.Indexed)))
        );
    

    Hope this helps :)