public ActionResult <IEnumerable <TrackResult> > GetTracks([FromQuery] TracksByBoundingBox request)
        {
            // Due to a bug in the CosmosDb client, we need to specify the invariant-culture here.
            // The cosmos-db query will be created using the current culture settings.
            // When querying on a double, the decimal point could otherwise be a comma instead of a point
            // which would lead to syntax-errors.
            // (see https://github.com/Azure/azure-cosmos-dotnet-v2/issues/651 )
            System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            var searchKeys = CalculateSearchKeys(request);

            // First coordinate in the boundingbox-polygon should be the upper left corner.
            // From there, add the points counter-clockwise and finish by repeating the first location again.
            // Points must appear counter-clockwise so that st_within will know that we're interested in
            // the area inside the polygon.

            var bbox = new Polygon(
                new[]
            {
                new Microsoft.Azure.Documents.Spatial.Position(request.MinLongitude, request.MaxLatitude),
                new Microsoft.Azure.Documents.Spatial.Position(request.MinLongitude, request.MinLatitude),
                new Microsoft.Azure.Documents.Spatial.Position(request.MaxLongitude, request.MinLatitude),
                new Microsoft.Azure.Documents.Spatial.Position(request.MaxLongitude, request.MaxLatitude),
                new Microsoft.Azure.Documents.Spatial.Position(request.MinLongitude, request.MaxLatitude),
            });

            var query = DbClient.CreateDocumentQuery <VesselGeoPosition>(TracksGeoCollectionUri, new FeedOptions()
            {
                EnableCrossPartitionQuery = true
            })
                        .Where(p => searchKeys.Contains(p.GeoHash_Date) &&
                               p.Position.Within(bbox) &&
                               p.Timestamp >= request.StartDate &&
                               p.Timestamp <= request.EndDate);

            var searchResults = query.ToList();

            var groupedByVesselId = new Dictionary <long, List <VesselGeoPosition> >();

            foreach (var kvp in searchResults)
            {
                if (groupedByVesselId.ContainsKey(kvp.ObjectId) == false)
                {
                    groupedByVesselId.Add(kvp.ObjectId, new List <VesselGeoPosition>());
                }

                groupedByVesselId[kvp.ObjectId].Add(kvp);
            }

            return(Ok(groupedByVesselId.Select(kvp => new TrackResult
            {
                ObjectId = kvp.Key,
                Positions = kvp.Value.Select(p => new Position()
                {
                    Location = p.Position, Timestamp = p.Timestamp
                })
            }).ToArray()));
        }
        private static IEnumerable <string> CalculateSearchKeys(TracksByBoundingBox request)
        {
            var geohashes = NGeoHash.GeoHash.Bboxes(request.MinLatitude, request.MinLongitude, request.MaxLatitude, request.MaxLongitude, 3);

            var numberOfDays = request.EndDate - request.StartDate;

            for (int i = 0; i <= numberOfDays.Days; i++)
            {
                foreach (var geohash in geohashes)
                {
                    yield return($"{geohash}_{request.StartDate.AddDays(i):yyyyMMdd}");
                }
            }
        }