/// <summary>
 /// Method determines is GP feature object violated.
 /// </summary>
 /// <param name="feature">GP feature.</param>
 /// <returns>True if object violated, otherwise - false.</returns>
 private static bool _IsObjectViolated(GPFeature feature)
 {
     int vc;
     return _GetViolatedConstraint(feature, out vc);
 }
 /// <summary>
 /// Create line which contain of two points with equal coordinates and set it as 
 /// items geometry.
 /// </summary>
 /// <param name="item">GPFeature wich geometry must be set.</param>
 /// <param name="point">Point which will be used for polyline.</param>
 private static void _FillGeometry(GPFeature item, double[] point)
 {
     item.Geometry = new GeometryHolder
     {
         Value = new GPPolyline
         {
             Paths = new[] { new[] { point, point } },
         },
     };
 }
 /// <summary>
 /// Method gets violated constraint from GP feature attributes.
 /// </summary>
 /// <param name="feature">GP feature.</param>
 /// <param name="constraint">Violated constraint as output parameter.</param>
 /// <returns>True if any violated constraint was found, otherwise - false.</returns>
 private static bool _GetViolatedConstraint(GPFeature feature, out int constraint)
 {
     return feature.Attributes.TryGet<int>(NAAttribute.VIOLATED_CONSTRAINTS, out constraint);
 }
        /// <summary>
        /// Method creates stop from depot.
        /// </summary>
        /// <param name="depotVisitFeature">Depot GP feature.</param>
        /// <param name="route">Route.</param>
        /// <param name="renewalsByID">Renewals collection associated with its Id.</param>
        /// <returns>Stop data.</returns>
        private StopData _CreateStopFromDepot(
            GPFeature depotVisitFeature,
            Route route,
            IDictionary<Guid, GPFeature> renewalsByID)
        {
            Debug.Assert(depotVisitFeature != null);
            Debug.Assert(route != null);
            Debug.Assert(renewalsByID != null);

            StopData stop = new StopData();

            // Distance.
            stop.Distance = depotVisitFeature.Attributes.Get<double>(NAAttribute.FROM_PREV_DISTANCE);

            // Sequence number.
            stop.SequenceNumber = depotVisitFeature.Attributes.Get<int>(NAAttribute.SEQUENCE);

            // Wait time.
            stop.WaitTime = depotVisitFeature.Attributes.Get<double>(NAAttribute.WAIT_TIME, 0.0);

            // Travel time.
            stop.TravelTime =
                depotVisitFeature.Attributes.Get<double>(NAAttribute.FROM_PREV_TRAVEL_TIME, 0.0);

            // Arrive time.
            stop.ArriveTime = _Get(depotVisitFeature.Attributes, NAAttribute.ARRIVE_TIME);

            // Associated object.
            Guid assocObjectId =
                _AttrToObjectId(NAAttribute.NAME, depotVisitFeature.Attributes);

            Location loc = _GetRouteLocation(route, assocObjectId);
            if (loc == null)
            {
                string message = Properties.Messages.Error_InvalidGPFeatureMapping;
                throw new RouteException(message); // exception
            }

            stop.AssociatedObject = loc;

            var renewalFeature = default(GPFeature);
            if (renewalsByID.TryGetValue(assocObjectId, out renewalFeature))
            {
                stop.TimeAtStop = renewalFeature.Attributes.Get<double>(
                    NAAttribute.SERVICE_TIME,
                    0.0);
            }

            var plannedDate = route.Schedule.PlannedDate.Value;
            var timeWindow = loc.TimeWindow.ToDateTime(plannedDate);
            stop.TimeWindowStart1 = timeWindow.Item1;
            stop.TimeWindowEnd1 = timeWindow.Item2;

            // Curb approach.
            stop.NACurbApproach = CurbApproachConverter.ToNACurbApproach(
                _settings.GetDepotCurbApproach());

            // Route id.
            stop.RouteId = route.Id;

            // Geometry.
            if (loc.GeoLocation != null)
            {
                stop.Geometry = GPObjectHelper.PointToGPPoint(loc.GeoLocation.Value);
            }

            // Stop type.
            stop.StopType = StopType.Location;

            return stop;
        }
        /// <summary>
        /// Method creates stop from order GP feature.
        /// </summary>
        /// <param name="feature">Order GP feature</param>
        /// <param name="route">Route.</param>
        /// <returns>Stop data.</returns>
        private StopData _CreateStopFromOrder(GPFeature feature, Route route)
        {
            var stop = new StopData();

            // distance
            stop.Distance = feature.Attributes.Get<double>(NAAttribute.FROM_PREV_DISTANCE);

            // sequence number
            stop.SequenceNumber = feature.Attributes.Get<int>(NAAttribute.SEQUENCE);

            // wait time
            stop.WaitTime = feature.Attributes.Get<double>(NAAttribute.WAIT_TIME, 0.0);

            // service time
            stop.TimeAtStop = feature.Attributes.Get<double>(NAAttribute.SERVICE_TIME, 0.0);

            // travel time
            stop.TravelTime =
                feature.Attributes.Get<double>(NAAttribute.FROM_PREV_TRAVEL_TIME, 0.0);

            // arrive time
            var arriveTime = _Get(feature.Attributes, NAAttribute.ARRIVE_TIME);

            // set stop arrive time = real arrive time + wait time
            stop.ArriveTime = arriveTime.AddMinutes(stop.WaitTime);

            // TW1
            stop.TimeWindowStart1 = _TryGet(feature.Attributes, NAAttribute.TW_START1);
            stop.TimeWindowEnd1 = _TryGet(feature.Attributes, NAAttribute.TW_END1);

            // TW2
            stop.TimeWindowStart2 = _TryGet(feature.Attributes, NAAttribute.TW_START2);
            stop.TimeWindowEnd2 = _TryGet(feature.Attributes, NAAttribute.TW_END2);

            // curbapproach
            stop.NACurbApproach =
                (NACurbApproachType)feature.Attributes.Get<int>(NAAttribute.ArriveCurbApproach);

            // associated object
            Guid assocObjectId = _AttrToObjectId(NAAttribute.NAME, feature.Attributes);

            Order order = _project.Orders.SearchById(assocObjectId);
            if (order == null)
            {
                string message = Properties.Messages.Error_InvalidGPFeatureMapping;
                throw new RouteException(message); // exception
            }

            stop.AssociatedObject = order;

            stop.TimeAtStop = order.ServiceTime;

            var plannedDate = route.Schedule.PlannedDate.Value;
            var timeWindow = order.TimeWindow.ToDateTime(plannedDate);
            stop.TimeWindowStart1 = timeWindow.Item1;
            stop.TimeWindowEnd1 = timeWindow.Item2;

            timeWindow = order.TimeWindow2.ToDateTime(plannedDate);
            stop.TimeWindowStart2 = timeWindow.Item1;
            stop.TimeWindowEnd2 = timeWindow.Item2;

            // route id
            stop.RouteId = _AttrToObjectId(NAAttribute.ROUTE_NAME, feature.Attributes);

            // geometry
            if (order.GeoLocation != null)
            {
                stop.Geometry = GPObjectHelper.PointToGPPoint(order.GeoLocation.Value);
            }

            // stop type
            stop.StopType = StopType.Order;

            return stop;
        }
        /// <summary>
        /// Method route result.
        /// </summary>
        /// <param name="feature">GP feature.</param>
        /// <param name="route">Route.</param>
        /// <param name="stops">Stop data collection.</param>
        /// <returns>Route result.</returns>
        private RouteResult _CreateRouteResult(GPFeature feature,
                                               Route route,
                                               IList<StopData> stops)
        {
            RouteResult result = new RouteResult();

            // cost
            result.Cost = feature.Attributes.Get<double>(NAAttribute.TOTAL_COST, 0.0);

            // start time
            result.StartTime = _TryGet(feature.Attributes, NAAttribute.START_TIME);

            // end time
            result.EndTime = _TryGet(feature.Attributes, NAAttribute.END_TIME);

            // total time
            result.TotalTime = feature.Attributes.Get<double>(NAAttribute.TOTAL_TIME, 0.0);

            // total distance
            result.TotalDistance = feature.Attributes.Get<double>(NAAttribute.TOTAL_DISTANCE, 0.0);

            // total travel time
            result.TravelTime = feature.Attributes.Get<double>(NAAttribute.TOTAL_TRAVEL_TIME, 0.0);

            // total violation time
            result.ViolationTime =
                feature.Attributes.Get<double>(NAAttribute.TOTAL_VIOLATION_TIME, 0.0);

            // total wait time
            result.WaitTime = feature.Attributes.Get<double>(NAAttribute.TOTAL_WAIT_TIME, 0.0);

            // overtime
            double overtime = result.TotalTime - route.Driver.TimeBeforeOT;
            if (overtime > 0)
                result.Overtime = overtime;

            // capacities
            result.Capacities = _CreateCapacities(stops);

            // path
            // currently we use directions to get route geometry

            // route
            result.Route = route;

            // set stops
            var resStops = new List<StopData>();

            // When a break(s) has preassigned sequence, it could present alone in output
            // stops collection, so in this case there will be no stops at all.
            // Checks in stop sequence present at least one order
            if (stops.Any(stop => stop.StopType == StopType.Order))
                resStops.AddRange(stops);

            result.Stops = resStops;

            return result;
        }
        /// <summary>
        /// Method creates stop from break GP feature.
        /// </summary>
        /// <param name="feature">Break GP feature</param>
        /// <param name="route">Route.</param>
        /// <param name="routeBreak">Break from route.</param>
        /// <param name="stop">Stop data to fill.</param>
        /// <returns>True - if successfully created, otherwise - False.</returns>
        private bool _CreateStopFromBreak(GPFeature feature, Route route,
            Break routeBreak, out StopData stop)
        {
            Debug.Assert(feature != null);
            Debug.Assert(route != null);
            Debug.Assert(routeBreak != null);

            // Check sequence number.
            int sequenceNumber = 0;

            if (!feature.Attributes.TryGet<int>(NAAttribute.SEQUENCE, out sequenceNumber)
                // Sequence always starts from 1. Otherwise: this is empty break.
                || sequenceNumber == 0)
            {
                stop = null;
                return false;
            }

            stop = new StopData();
            stop.SequenceNumber = sequenceNumber;

            // Distance.
            stop.Distance = feature.Attributes.Get<double>(NAAttribute.FROM_PREV_DISTANCE);

            // Wait time.
            stop.WaitTime = feature.Attributes.Get<double>(NAAttribute.WAIT_TIME, 0.0);

            // Service time.
            stop.TimeAtStop = routeBreak.Duration;

            // Travel time.
            stop.TravelTime =
                feature.Attributes.Get<double>(NAAttribute.FROM_PREV_TRAVEL_TIME, 0.0);

            // Time Windows for break stop.
            var breakTimeWindow = _GetBreakTimeWindow(routeBreak);
            var plannedDate = route.Schedule.PlannedDate.Value;

            var timeWindow = breakTimeWindow.ToDateTime(plannedDate);
            stop.TimeWindowStart1 = timeWindow.Item1;
            stop.TimeWindowEnd1 = timeWindow.Item2;

            // Arrive time.
            var arriveTime = _TryGet(feature.Attributes, NAAttribute.ARRIVE_TIME);

            if (!arriveTime.HasValue)
            {
                arriveTime = stop.TimeWindowStart1;
            }

            arriveTime = arriveTime.AddMinutes(stop.WaitTime);

            stop.ArriveTime = arriveTime.Value;

            // Route id.
            stop.RouteId = _AttrToObjectId(NAAttribute.ROUTE_NAME, feature.Attributes);

            // Geometry:
            // There is no geometry for breaks.

            // Curb approach.
            stop.NACurbApproach = NACurbApproachType.esriNAEitherSideOfVehicle;

            // Stop type.
            stop.StopType = StopType.Lunch;

            // Break stop does not have an associated object.
            stop.AssociatedObject = null;

            return true;
        }
        /// <summary>
        /// Method converts network parameters.into GPRecordSet.
        /// </summary>
        /// <returns>Network parameters GPRecordSet.</returns>
        private GPRecordSet _ConvertNetworkParams()
        {
            List<GPFeature> features = new List<GPFeature>();
            foreach (NetworkAttribute attr in _context.NetworkDescription.NetworkAttributes)
            {
                foreach (NetworkAttributeParameter param in attr.Parameters)
                {
                    object value = null;
                    if (_context.SolverSettings.GetNetworkAttributeParameterValue(
                        attr.Name,
                        param.Name,
                        out value))
                    {
                        // Skip null value overrides, let the service to use defaults.
                        if (value != null)
                        {
                            GPFeature feature = new GPFeature();
                            feature.Attributes = new AttrDictionary();
                            feature.Attributes.Add(NAAttribute.NETWORK_ATTR_NAME, attr.Name);
                            feature.Attributes.Add(NAAttribute.NETWORK_ATTR_PARAM_NAME, param.Name);
                            feature.Attributes.Add(NAAttribute.NETWORK_ATTR_PARAM_VALUE, value);
                            features.Add(feature);
                        }
                    }
                }
            }

            GPRecordSet rs = null;
            if (features.Count > 0)
            {
                rs = new GPRecordSet();
                rs.Features = features.ToArray();
            }

            return rs;
        }
        /// <summary>
        /// Method converts order into GPFeature.
        /// </summary>
        /// <param name="unassignedOrder">Unassigned order to convert.</param>
        /// <param name="assignedOrder">Assigned order to convert.</param>
        /// <returns>Orders GPFeature.</returns>
        /// <exception cref="RouteException">If unassigned order is not geocoded.</exception>
        private GPFeature _ConvertOrder(Order unassignedOrder, AssignedOrder assignedOrder)
        {
            Debug.Assert(unassignedOrder != null);

            GPFeature feature = new GPFeature();

            // Geometry.
            IGeocodable gc = unassignedOrder as IGeocodable;
            Debug.Assert(gc != null);

            if (!gc.IsGeocoded)
            {
                throw new RouteException(String.Format(
                    Properties.Messages.Error_OrderIsUngeocoded, unassignedOrder.Id));
            }

            Debug.Assert(gc.GeoLocation != null);
            feature.Geometry = new GeometryHolder();
            feature.Geometry.Value = GPObjectHelper.PointToGPPoint((Point)gc.GeoLocation);

            // Attributes.
            AttrDictionary attrs = new AttrDictionary();

            // Name.
            attrs.Add(NAAttribute.NAME, unassignedOrder.Id.ToString());

            // Curb approach.
            attrs.Add(NAAttribute.CURB_APPROACH,
                (int)CurbApproachConverter.ToNACurbApproach(
                _context.SolverSettings.GetOrderCurbApproach()));

            // Service time.
            attrs.Add(NAAttribute.SERVICE_TIME, unassignedOrder.ServiceTime);

            // Time windows.
            TimeWindow timeWindow1 = unassignedOrder.TimeWindow;
            TimeWindow timeWindow2 = unassignedOrder.TimeWindow2;

            _SetTimeWindowsAttributes(attrs, ref timeWindow1, ref timeWindow2);

            // Max Violation Time 1.
            attrs.Add(NAAttribute.MAX_VIOLATION_TIME1,
                timeWindow1.IsWideOpen ? (double?)null : unassignedOrder.MaxViolationTime);

            // Max Violation Time 2.
            attrs.Add(NAAttribute.MAX_VIOLATION_TIME2,
                timeWindow2.IsWideOpen ? (double?)null : unassignedOrder.MaxViolationTime);

            // PickUp or DropOff quantities.
            string capacities = _FormatCapacities(unassignedOrder.Capacities);

            if (unassignedOrder.Type == OrderType.Delivery)
            {
                attrs.Add(NAAttribute.DELIVERY, capacities);
                attrs.Add(NAAttribute.PICKUP, null);
            }
            else
            {
                attrs.Add(NAAttribute.PICKUP, capacities);
                attrs.Add(NAAttribute.DELIVERY, null);
            }

            // Revenue.
            attrs.Add(NAAttribute.REVENUE,
                unassignedOrder.Priority == OrderPriority.Normal ? 0 : (long)_orderRevenue);

            // Specialties.
            List<Guid> specIds = GetOrderSpecIds(unassignedOrder);
            if (specIds.Count > 0)
                attrs.Add(NAAttribute.SPECIALTY_NAMES, _FormatSpecList(specIds));

            if (assignedOrder != null)
                SetOrderAssignment(attrs, assignedOrder);
            else
                SetOrderAssignment(attrs, unassignedOrder);

            // Status.
            attrs.Add(NAAttribute.STATUS, (int)NAObjectStatus.esriNAObjectStatusOK);

            feature.Attributes = attrs;

            return feature;
        }
        /// <summary>
        /// Method creates GPFeature for seed point.
        /// </summary>
        /// <param name="route">Route to get information.</param>
        /// <param name="pt">Point location of seed point.</param>
        /// <returns>Seed point GPFeature.</returns>
        private static GPFeature _CreateSeedPointFeature(Route route, Point? pt)
        {
            Debug.Assert(route != null);
            Debug.Assert(pt != null);

            GPFeature feature = new GPFeature();

            feature.Attributes = new AttrDictionary();
            feature.Attributes.Add(NAAttribute.ROUTE_NAME, route.Id.ToString());
            feature.Attributes.Add(NAAttribute.SEED_POINT_TYPE,
                (int)NARouteSeedPointType.esriNARouteSeedPointStatic);

            Point seedPoint = (Point)pt;

            feature.Geometry = new GeometryHolder();
            feature.Geometry.Value = GPObjectHelper.PointToGPPoint(seedPoint);

            return feature;
        }
        /// <summary>
        /// Method converts location into GPFeature.
        /// </summary>
        /// <param name="loc">Location to convert.</param>
        /// <returns>Locations GPFeature.</returns>
        /// <exception cref="RouteException">If location is not geocoded.</exception>
        private GPFeature _ConvertDepot(Location loc)
        {
            Debug.Assert(loc != null);

            GPFeature feature = new GPFeature();

            // Geometry.
            feature.Geometry = new GeometryHolder();
            feature.Geometry.Value = GPObjectHelper.PointToGPPoint(_GetLocationPoint(loc));

            // Attributes.
            AttrDictionary attrs = new AttrDictionary();

            // Name.
            attrs.Add(NAAttribute.NAME, loc.Id.ToString());

            // Curb approach.
            attrs.Add(NAAttribute.CURB_APPROACH,
                (int)CurbApproachConverter.ToNACurbApproach(
                _context.SolverSettings.GetDepotCurbApproach()));

            // Set Time Windows attributes.
            TimeWindow timeWindow1 = loc.TimeWindow;
            TimeWindow timeWindow2 = loc.TimeWindow2;

            _SetTimeWindowsAttributes(attrs, ref timeWindow1, ref timeWindow2);

            feature.Attributes = attrs;

            return feature;
        }
        /// <summary>
        /// Method converts zones and seed points from route to GPFeatures and fill collections 
        /// in parameters.
        /// </summary>
        /// <param name="route">Route.</param>
        /// <param name="type">Zone type.</param>
        /// <param name="zoneFeatures">Zones GPFeatures collection to fill.</param>
        /// <param name="seedPointFeatures">Seed points GPFeatures collection to fill.</param>
        private void _ConvertRouteZones(Route route, RouteZoneType type,
            List<GPFeature> zoneFeatures, List<GPFeature> seedPointFeatures)
        {
            Debug.Assert(type != RouteZoneType.None);

            if (_HasValidZones(route))
            {
                if (type == RouteZoneType.Point)
                {
                    seedPointFeatures.Add(_CreateSeedPointFeature(route,
                        _GetZonesCentroid(route.Zones)));
                }
                else if (type == RouteZoneType.Polygon)
                {
                    GPFeature feature = new GPFeature();

                    feature.Attributes = new AttrDictionary();
                    feature.Attributes.Add(NAAttribute.ROUTE_NAME, route.Id.ToString());
                    feature.Attributes.Add(NAAttribute.IS_HARD_ZONE, route.HardZones);

                    // Geometry.
                    feature.Geometry = new GeometryHolder();
                    feature.Geometry.Value = GPObjectHelper.PolygonToGPPolygon(
                        _GetZonesPolygon(route.Zones));

                    zoneFeatures.Add(feature);
                }
                else
                    Debug.Assert(false);
            }
            else
            {
                // Zones count = 0 and some another routes contain point then
                // just set midpoint between the start and end location.
                if (type == RouteZoneType.Point)
                {
                    seedPointFeatures.Add(_CreateSeedPointFeature(route,
                        _GetRouteLocationsMidpoint(route)));
                }
            }
        }
        /// <summary>
        /// Method converts route into GPFeature.
        /// </summary>
        /// <param name="route">Route.</param>
        /// <returns>Routes GPFeature.</returns>
        /// <exception cref="RouteException">If Fuel Economy in routes is 0.0.</exception>
        private GPFeature _ConvertRoute(Route route)
        {
            Debug.Assert(route != null);

            // Attributes.
            AttrDictionary attrs = new AttrDictionary();

            // Name.
            attrs.Add(NAAttribute.NAME, route.Id.ToString());

            // Start depot.
            attrs.Add(NAAttribute.START_DEPOT_NAME,
                route.StartLocation == null ? null : route.StartLocation.Id.ToString());

            // End depot.
            attrs.Add(NAAttribute.END_DEPOT_NAME,
                route.EndLocation == null ? null : route.EndLocation.Id.ToString());

            // Depot service times.
            attrs.Add(NAAttribute.START_DEPOT_SERVICE_TIME,
                route.StartLocation == null ? null : (object)route.TimeAtStart);
            attrs.Add(NAAttribute.END_DEPOT_SERVICE_TIME,
                route.EndLocation == null ? null : (object)route.TimeAtEnd);

            // Grace period.
            _SetTimeWindowAttribute(route.StartTimeWindow,
                NAAttribute.EARLIEST_START_TIME,
                NAAttribute.LATEST_START_TIME,
                attrs);

            // Capacities.
            attrs.Add(NAAttribute.CAPACITIES,
                _FormatCapacities(route.Vehicle.Capacities));

            // Costs.
            attrs.Add(NAAttribute.FIXED_COST, route.Driver.FixedCost + route.Vehicle.FixedCost);
            attrs.Add(NAAttribute.COST_PER_UNIT_TIME, route.Driver.PerHourSalary / 60);
            attrs.Add(NAAttribute.COST_PER_UNIT_OVERTIME, route.Driver.PerHourOTSalary / 60);
            attrs.Add(NAAttribute.OVERTIME_START_TIME, route.Driver.TimeBeforeOT);

            if (route.Vehicle.FuelEconomy == 0.0)
                throw new RouteException(Properties.Messages.Error_InvalidFuelConsumptionValue);

            attrs.Add(NAAttribute.COST_PER_UNIT_DISTANCE,
                route.Vehicle.FuelType.Price / route.Vehicle.FuelEconomy);

            // Order constraints.
            attrs.Add(NAAttribute.MAX_ORDERS, route.MaxOrders);

            // Max drive duration.
            attrs.Add(NAAttribute.MAX_TOTAL_TRAVEL_TIME,
                route.MaxTravelDuration == 0.0 ? null : (object)route.MaxTravelDuration);

            // Max total duration.
            attrs.Add(NAAttribute.MAX_TOTAL_TIME,
                route.MaxTotalDuration == 0.0 ? null : (object)route.MaxTotalDuration);

            // Max drive distance.
            attrs.Add(NAAttribute.MAX_TOTAL_DISTANCE,
                route.MaxTravelDistance == 0.0 ? null : (object)route.MaxTravelDistance);

            // Arrive and depart delay.
            attrs.Add(NAAttribute.ARRIVE_DEPART_DELAY,
                _context.SolverSettings.ArriveDepartDelay);

            // Specialties.
            List<Guid> specIds = GetRouteSpecIds(route);
            if (specIds.Count > 0)
                attrs.Add(NAAttribute.SPECIALTY_NAMES, _FormatSpecList(specIds));

            // assignment rule.
            attrs.Add(NAAttribute.ASSIGNMENT_RULE,
                (int)NARouteAssignmentRule.esriNARouteIncludeInSolve);

            // TODO: dynamic point zones.

            GPFeature feature = new GPFeature();
            feature.Attributes = attrs;

            return feature;
        }
        /// <summary>
        /// Method converts renewal location from route into GPFeature.
        /// </summary>
        /// <param name="loc">Location to convert.</param>
        /// <param name="route">Route on which location assigned.</param>
        /// <returns>Renewals GPFeature.</returns>
        private GPFeature _ConvertRenewal(Location loc, Route route)
        {
            Debug.Assert(loc != null);
            Debug.Assert(route != null);

            GPFeature feature = new GPFeature();

            // Attributes.
            AttrDictionary attrs = new AttrDictionary();

            // Route name.
            attrs.Add(NAAttribute.ROUTE_NAME, route.Id.ToString());
            // Depot name.
            attrs.Add(NAAttribute.DEPOT_NAME, loc.Id.ToString());
            // Service time.
            attrs.Add(NAAttribute.SERVICE_TIME, route.TimeAtRenewal);

            // Sequences: currently sequences are not used.

            feature.Attributes = attrs;

            return feature;
        }
        /// <summary>
        /// Method converts pickup and delivery orders into GPFeature.
        /// </summary>
        /// <param name="pickupOrder">Order.</param>
        /// <param name="deliveryOrder">Order.</param>
        /// <returns>OrderPair GPFeature.</returns>
        private GPFeature _ConvertOrderPair(Order pickupOrder, Order deliveryOrder)
        {
            Debug.Assert(pickupOrder != null && deliveryOrder != null);

            GPFeature feature = new GPFeature();

            // Attributes.
            AttrDictionary attrs = new AttrDictionary();

            // Pickup Name.
            attrs.Add(NAAttribute.FIRST_ORDER_NAME, pickupOrder.Id.ToString());

            // Delivery Name.
            attrs.Add(NAAttribute.SECOND_ORDER_NAME, deliveryOrder.Id.ToString());

            // not using MaxTransitTime

            feature.Attributes = attrs;

            return feature;
        }
        ///////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Builds stop features.
        /// </summary>
        /// <param name="stops">Stops.</param>
        /// <returns>Created route stops recordset for stops.</returns>
        private RouteStopsRecordSet _BuildStopFeatures(IList<StopData> stops)
        {
            Debug.Assert(stops != null);

            // Sort stops respecting sequence.
            var sortedStops = new List<StopData>(stops);
            SolveHelper.SortBySequence(sortedStops);

            Debug.Assert(_context != null);

            SolveHelper.ConsiderArrivalDelayInStops(
                _context.SolverSettings.ArriveDepartDelay, sortedStops);

            // Format impedance attribute name.
            string impedanceAttrName = null;
            if (!string.IsNullOrEmpty(_context.NetworkDescription.ImpedanceAttributeName))
            {
                impedanceAttrName = string.Format(IMPEDANCE_ATTR_FORMAT,
                    _context.NetworkDescription.ImpedanceAttributeName);
            }

            var features = new List<GPFeature>();
            for (int index = 0; index < sortedStops.Count; ++index)
            {
                var feature = new GPFeature();

                // Attributes.
                feature.Attributes = new AttrDictionary();

                StopData sd = sortedStops[index];

                Guid objectId = Guid.Empty;
                if (sd.AssociatedObject != null)
                    objectId = sd.AssociatedObject.Id;

                feature.Attributes.Add(NAAttribute.NAME, objectId.ToString());
                feature.Attributes.Add(NAAttribute.ROUTE_NAME, sd.RouteId.ToString());

                // Effective time window.
                DateTime? twStart = null;
                DateTime? twEnd = null;
                _GetEffectiveTW(sd, out twStart, out twEnd); // NOTE: ignore result
                feature.Attributes.Add(NAAttribute.TW_START, _FormatStopTime(twStart));
                feature.Attributes.Add(NAAttribute.TW_END, _FormatStopTime(twEnd));

                // Service time.
                if (impedanceAttrName != null)
                    feature.Attributes.Add(impedanceAttrName, sd.TimeAtStop);

                var geometry = new GeometryHolder();
                geometry.Value = sd.Geometry;

                if (sd.StopType == StopType.Lunch)
                {
                    var actualStop = SolveHelper.GetActualLunchStop(sortedStops, index);
                    geometry.Value = actualStop.Geometry;
                }

                // Set curb approach.
                var curbApproach = CurbApproachConverter.ToNACurbApproach(
                    _context.SolverSettings.GetOrderCurbApproach());
                if (sd.StopType == StopType.Location)
                {
                    curbApproach = CurbApproachConverter.ToNACurbApproach(
                        _context.SolverSettings.GetDepotCurbApproach());
                }

                feature.Attributes.Add(NAAttribute.CURB_APPROACH, (int)curbApproach);
                feature.Geometry = geometry;

                features.Add(feature);
            }

            var rs = new RouteStopsRecordSet();
            rs.Features = features.ToArray();

            // TODO: will be changed later when support custom AddLocations tool
            rs.DoNotLocateOnRestrictedElements = _context.SolverSettings.ExcludeRestrictedStreets;

            return rs;
        }