public async void QueryHandHoles100MeterRadiusFromJ1_ShouldReturn4HandHandholes()
        {
            // Setup
            var stops     = new RouteNodeKindEnum[] { RouteNodeKindEnum.CentralOfficeSmall };
            var interests = new RouteNodeKindEnum[] { RouteNodeKindEnum.HandHole };

            var nearestNodeQuery = new FindNearestRouteNodes(TestRouteNetwork.J_1, 10, 100, stops, interests);

            // Act
            var nearestNodeQueryResult = await _queryDispatcher.HandleAsync <FindNearestRouteNodes, Result <FindNearestRouteNodesResult> >(nearestNodeQuery);

            // Assert
            nearestNodeQueryResult.IsSuccess.Should().BeTrue();

            // We should get 4 hand holes back
            nearestNodeQueryResult.Value.RouteNetworkTraces.Count().Should().Be(4);
            nearestNodeQueryResult.Value.RouteNetworkElements.Count().Should().Be(4);
            nearestNodeQueryResult.Value.RouteNetworkElements.Count(r => r.RouteNodeInfo.Kind == RouteNodeKindEnum.HandHole).Should().Be(4);

            // Check trace for handhole HH_10
            var handHole10Trace = nearestNodeQueryResult.Value.RouteNetworkTraces[TestRouteNetwork.HH_10];

            handHole10Trace.Name.Should().Be("HH-10");
            handHole10Trace.RouteNetworkSegmentIds.Length.Should().Be(3);
            handHole10Trace.RouteNetworkSegmentGeometries.Length.Should().Be(3);
            handHole10Trace.RouteNetworkSegmentIds[0].Should().Be(TestRouteNetwork.S13);
            handHole10Trace.RouteNetworkSegmentIds[1].Should().Be(TestRouteNetwork.S5);
            handHole10Trace.RouteNetworkSegmentIds[2].Should().Be(TestRouteNetwork.S6);
            handHole10Trace.Distance.Should().BeInRange(94, 96);
        }
        public async void QueryHandHoles30MeterRadiusFromJ1_ShouldReturn3HandHandholes()
        {
            // Setup
            var stops     = new RouteNodeKindEnum[] { RouteNodeKindEnum.CentralOfficeSmall };
            var interests = new RouteNodeKindEnum[] { RouteNodeKindEnum.HandHole };

            var nearestNodeQuery = new FindNearestRouteNodes(TestRouteNetwork.J_1, 10, 30, stops, interests);

            // Act
            var nearestNodeQueryResult = await _queryDispatcher.HandleAsync <FindNearestRouteNodes, Result <FindNearestRouteNodesResult> >(nearestNodeQuery);

            // Assert
            nearestNodeQueryResult.IsSuccess.Should().BeTrue();

            // We should get 4 hand holes back
            nearestNodeQueryResult.Value.RouteNetworkTraces.Count().Should().Be(3);
            nearestNodeQueryResult.Value.RouteNetworkElements.Count().Should().Be(3);
        }
        public Task <Result <FindNearestRouteNodesResult> > HandleAsync(FindNearestRouteNodes query)
        {
            Stopwatch sw = new();

            sw.Start();

            var getRouteNetworkElementsResult = _routeNodeRepository.GetRouteElements(new RouteNetworkElementIdList()
            {
                query.SourceRouteNodeId
            });

            // Here we return a error result, because we're dealing with invalid route network ids provided by the client
            if (getRouteNetworkElementsResult.IsFailed || getRouteNetworkElementsResult.Value.Count != 1)
            {
                return(Task.FromResult(
                           Result.Fail <FindNearestRouteNodesResult>(new FindNearestRouteNodesError(FindNearestRouteNodesErrorCodes.INVALID_QUERY_ARGUMENT_ERROR_LOOKING_UP_SPECIFIED_ROUTE_NETWORK_ELEMENT_BY_ID, $"Error looking up route network node with id: {query.SourceRouteNodeId}")).
                           WithError(getRouteNetworkElementsResult.Errors.First())
                           ));
            }

            var sourceRouteNode = getRouteNetworkElementsResult.Value.First() as RouteNode;

            if (sourceRouteNode == null)
            {
                return(Task.FromResult(
                           Result.Fail <FindNearestRouteNodesResult>(new FindNearestRouteNodesError(FindNearestRouteNodesErrorCodes.INVALID_QUERY_ARGUMENT_ERROR_LOOKING_UP_SPECIFIED_ROUTE_NETWORK_ELEMENT_BY_ID, $"Error looking up route network node. Got { getRouteNetworkElementsResult.Value.First().GetType().Name} querying element with id: {query.SourceRouteNodeId}")).
                           WithError(getRouteNetworkElementsResult.Errors.First())
                           ));
            }

            var sourceRouteNodePoint = GetPoint(sourceRouteNode.Coordinates);

            long version      = _routeNetworkState.GetLatestCommitedVersion();
            var  stopHash     = query.NodeKindStops.ToHashSet();
            var  interestHash = query.NodeKindOfInterests.ToHashSet();

            // Fetch objects from route network graph to query on
            var routeNetworkSubset = sourceRouteNode.UndirectionalDFS <RouteNode, RouteSegment>(
                version: version,
                nodeCriteria: n => (n.RouteNodeInfo == null || n.RouteNodeInfo.Kind == null || !stopHash.Contains(n.RouteNodeInfo.Kind.Value)) && GetPoint(n.Coordinates).Distance(sourceRouteNodePoint) < query.SearchRadiusMeters,
                includeElementsWhereCriteriaIsFalse: true
                );

            // Find nodes to check/trace shortest path
            var nodeCandidates  = GetAllNodeCandidates(sourceRouteNode, interestHash, routeNetworkSubset);
            var graphForTracing = GetGraphForTracing(version, nodeCandidates, routeNetworkSubset);
            var nodesOfInterest = GetNodesOfInterest(nodeCandidates, interestHash).ToList();

            ConcurrentBag <NearestRouteNodeTraceResult> nodeTraceResults = new();

            int nShortestPathTraces = 0;

            ParallelOptions po = new ParallelOptions();

            po.MaxDegreeOfParallelism = Environment.ProcessorCount;


            foreach (var nodeToTrace in nodesOfInterest)
            {
                var shortestPathTrace = ShortestPath(nodeToTrace.Node, sourceRouteNode.Id, graphForTracing);
                nodeTraceResults.Add(shortestPathTrace);
                nShortestPathTraces++;

                if (NumberOfShortestPathTracesWithinDistance(nodeTraceResults, nodeToTrace.BirdDistanceToSource) >= query.MaxHits)
                {
                    break;
                }
            }

            var nodeTraceResultOrdered = nodeTraceResults.OrderBy(n => n.Distance).ToList();

            sw.Stop();
            _logger.LogInformation($"{nShortestPathTraces} shortets path trace(s) processed in {sw.ElapsedMilliseconds} milliseconds finding the {query.MaxHits} nearest nodes to source node with id: {sourceRouteNode.Id}");

            List <NearestRouteNodeTraceResult> tracesToReturn            = new();
            List <IRouteNetworkElement>        routeNodeElementsToReturn = new();

            for (int i = 0; i < query.MaxHits && i < nodeTraceResultOrdered.Count; i++)
            {
                // Add trace
                var traceToAdd = nodeTraceResultOrdered[i];

                tracesToReturn.Add(traceToAdd);

                var routeElementToAdd = _routeNodeRepository.NetworkState.GetRouteNetworkElement(traceToAdd.DestNodeId);

                if (routeElementToAdd != null)
                {
                    routeNodeElementsToReturn.Add(routeElementToAdd);
                }
            }

            var result = new FindNearestRouteNodesResult(
                sourceRouteNodeId: sourceRouteNode.Id,
                routeNetworkElements: GetRouteNetworkDetailsQueryHandler.MapRouteElementDomainObjectsToQueryObjects(query.RouteNetworkElementFilter, routeNodeElementsToReturn),
                routeNetworkTraces: tracesToReturn
                );

            return(Task.FromResult(
                       Result.Ok <FindNearestRouteNodesResult>(
                           result
                           )
                       ));
        }
        public RouteNetworkServiceQueries(ILogger <RouteNetworkServiceQueries> logger, IQueryDispatcher queryDispatcher)
        {
            Description = "GraphQL API for querying data owned by route nodes and route segments";

            Field <RouteNetworkElementType>(
                "routeElement",
                arguments: new QueryArguments(new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "Id"
            }),
                resolve: context =>
            {
                if (!Guid.TryParse(context.GetArgument <string>("id"), out Guid routeNodeId))
                {
                    context.Errors.Add(new ExecutionError("Wrong value for guid"));
                    return(null);
                }

                var routeNodeQuery = new GetRouteNetworkDetails(new RouteNetworkElementIdList()
                {
                    routeNodeId
                })
                {
                    RelatedInterestFilter = RelatedInterestFilterOptions.ReferencesFromRouteElementOnly
                };

                var queryResult = queryDispatcher.HandleAsync <GetRouteNetworkDetails, Result <GetRouteNetworkDetailsResult> >(routeNodeQuery).Result;

                if (queryResult.IsFailed)
                {
                    context.Errors.Add(new ExecutionError(queryResult.Errors.First().Message));
                    return(null);
                }

                return(queryResult.Value.RouteNetworkElements[routeNodeId]);
            }
                );


            Field <ListGraphType <RouteNetworkTraceType> >(
                "nearestNeighborNodes",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "sourceRouteNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <RouteNodeKindEnumType> > > {
                Name = "stops"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <RouteNodeKindEnumType> > > {
                Name = "interests"
            },
                    new QueryArgument <NonNullGraphType <IntGraphType> > {
                Name = "maxHits"
            },
                    new QueryArgument <NonNullGraphType <IntGraphType> > {
                Name = "maxBirdFlyDistanceMeters"
            }
                    ),
                resolve: context =>
            {
                Guid routeNodeId = context.GetArgument <Guid>("sourceRouteNodeId");
                List <RouteNodeKindEnum> stops     = context.GetArgument <List <RouteNodeKindEnum> >("stops");
                List <RouteNodeKindEnum> interests = context.GetArgument <List <RouteNodeKindEnum> >("interests");
                int maxHits = context.GetArgument <int>("maxHits");
                int maxBirdFlyDistanceMeters = context.GetArgument <int>("maxBirdFlyDistanceMeters");


                var nearestNodeQuery = new FindNearestRouteNodes(routeNodeId, maxHits, maxBirdFlyDistanceMeters, stops.ToArray(), interests.ToArray());

                var nearestNodeQueryResult = queryDispatcher.HandleAsync <FindNearestRouteNodes, Result <FindNearestRouteNodesResult> >(nearestNodeQuery).Result;

                if (nearestNodeQueryResult.IsFailed)
                {
                    context.Errors.Add(new ExecutionError(nearestNodeQueryResult.Errors.First().Message));
                    return(null);
                }

                return(nearestNodeQueryResult.Value.RouteNetworkTraces);
            }
                );
        }