public void StartTiming(bool startOnMovement = true) { if (geolocator == null) { throw new InvalidOperationException("LapTimer must be initialized before calling StartTiming."); } if (isTiming) { StopTiming(); } lapNumber = 1; lapStartElapsedTime = TimeSpan.Zero; headingNormalisation = new List <double>(); lapCoordinates = new List <LapDataPointViewModel>(); currentSector = generateSectors ? null : sectorCoordinates.MinBy(s => s.SectorNumber); accelerometer = Accelerometer.GetDefault(); if (accelerometer != null) { accelerometer.ReadingChanged += accelerometer_ReadingChanged; } geolocator.PositionChanged += geolocator_PositionChanged; if (!startOnMovement) { stopwatch.Start(); isTiming = true; if (TimingStarted != null) { TimingStarted(this, null); } } isFirstReading = true; }
private void mapCircuit_Hold(object sender, System.Windows.Input.GestureEventArgs e) { if (!App.ViewModel.Track.IsLocal) { return; } var mapPosition = e.GetPosition(mapCircuit); var mapLayer = new MapLayer(); var geoCoordinate = mapCircuit.ConvertViewportPointToGeoCoordinate(mapPosition); lastSectorNumber++; double heading = (double)App.ViewModel.DegreesData.SelectedItem; var geographyPoint = GeographyPoint.Create(geoCoordinate.Latitude, geoCoordinate.Longitude, null, heading); var lineCoordinates = geographyPoint.ConvertToLine(0.02); var newSector = new TrackSectorViewModel { StartLatitude = lineCoordinates[0].Latitude, StartLongitude = lineCoordinates[0].Longitude, EndLatitude = lineCoordinates[0].Latitude, EndLongitude = lineCoordinates[0].Longitude, Heading = heading, IsFinishLine = false, SectorNumber = lastSectorNumber }; App.ViewModel.Track.Sectors.Add(newSector); App.ViewModel.Track.SelectedSector = newSector; NavigationService.Navigate(new Uri("/EditSectorPage.xaml", System.UriKind.Relative)); }
public static bool IsCompletedByLapDataPoints(this TrackSectorViewModel sector, LapDataPointViewModel startPoint, LapDataPointViewModel endPoint) { var sectorLineStartPoint = GeoUtmConverter.ToUTM(sector.StartLatitude, sector.StartLongitude); var sectorLineEndPoint = GeoUtmConverter.ToUTM(sector.EndLatitude, sector.EndLongitude); var dataLineStartPoint = GeoUtmConverter.ToUTM(startPoint.Latitude.Value, startPoint.Longitude.Value); var dataLineEndPoint = GeoUtmConverter.ToUTM(endPoint.Latitude.Value, endPoint.Longitude.Value); return(IsIntersect(sectorLineStartPoint, sectorLineEndPoint, dataLineStartPoint, dataLineEndPoint)); }
public static TrackSector AsModel(this TrackSectorViewModel sectorCoordinateViewModel) { return(new TrackSector { id = sectorCoordinateViewModel.Id, EndLatitude = sectorCoordinateViewModel.EndLatitude, EndLongitude = sectorCoordinateViewModel.EndLongitude, Heading = sectorCoordinateViewModel.Heading, IsFinishLine = sectorCoordinateViewModel.IsFinishLine, Number = sectorCoordinateViewModel.SectorNumber, StartLatitude = sectorCoordinateViewModel.StartLatitude, StartLongitude = sectorCoordinateViewModel.StartLongitude }); }
private void mapCircuit_Tap(object sender, System.Windows.Input.GestureEventArgs e) { if (!App.ViewModel.Track.IsLocal) { return; } double smallestDistance = double.MaxValue; TrackSectorViewModel selectedSector = null; var mapPosition = e.GetPosition(mapCircuit); var geoCoordinate = mapCircuit.ConvertViewportPointToGeoCoordinate(mapPosition); var goegraphyPoint = GeographyPoint.Create(geoCoordinate.Latitude, geoCoordinate.Longitude, null, geoCoordinate.Course); foreach (var sector in App.ViewModel.Track.Sectors) { if (double.IsNaN(sector.StartLatitude) || double.IsNaN(sector.StartLongitude) || double.IsNaN(sector.EndLatitude) || double.IsNaN(sector.EndLongitude)) { continue; } var sectorLineStartPoint = GeographyPoint.Create(sector.StartLatitude, sector.StartLongitude); var sectorLineEndPoint = GeographyPoint.Create(sector.EndLatitude, sector.EndLongitude); var midpoint = GeoUtmConverter.Midpoint(sectorLineStartPoint, sectorLineEndPoint, sector.Heading); double distance = midpoint.Distance(goegraphyPoint); if (distance < 0.5 && distance < smallestDistance) { smallestDistance = distance; selectedSector = sector; } } if (selectedSector == null) { return; } App.ViewModel.Track.SelectedSector = selectedSector; NavigationService.Navigate(new Uri("/EditSectorPage.xaml", System.UriKind.Relative)); }
private bool PositionCompletesSector(LapDataPointViewModel currentPosition, LapDataPointViewModel previousPosition, TrackSectorViewModel sector) { if (previousPosition == null) { return(false); } if (currentPosition.Heading.HasValue && !HeadingIsWithinRange(sector.Heading, currentPosition.Heading.Value, 170d)) { return(false); } return(sector.IsCompletedByLapDataPoints(previousPosition, currentPosition)); }
private void EndLap(LapDataPointViewModel lapEndDataPoint, int lapEndDataPointIndex, int currentSectorNumber) { if (!lapCoordinates.Any(lc => lc.Latitude.HasValue && lc.Longitude.HasValue && lc.Heading.HasValue)) { return; } var lapStartTime = lapStartElapsedTime; var lapTime = lapStartElapsedTime = lapEndDataPoint.ElapsedTime; lapEndDataPoint.IsEndOfLap = true; int currentLapEndCoordinate = lapEndDataPointIndex; var allCoordinates = lapCoordinates.ToList(); var currentLapCoordinates = allCoordinates.Skip(previousLapEndCoordinateIndex + 1).Take(currentLapEndCoordinate - previousLapEndCoordinateIndex).ToList(); if (!currentLapCoordinates.Any()) { return; } var lap = new LapViewModel { StartElapsedTime = lapStartTime, EndElapsedTime = lapTime, MaximumSpeed = currentLapCoordinates.Max(c => c.Speed) ?? 0, Timestamp = currentLapCoordinates.First().Timestamp, SectorNumber = currentSectorNumber, LapNumber = lapNumber, IsComplete = true }; previousLapEndCoordinateIndex = currentLapEndCoordinate; if (generateSectors) { if (currentLapCoordinates.Count() < 3) { return; } lap.DataPoints = new ObservableCollection <LapDataPointViewModel>(currentLapCoordinates); var sectors = currentLapCoordinates.ToTrackSectors(); if (sectors.Count < 3) { return; } currentSector = sectors[1]; sectorCoordinates = sectors.Select(s => s.Value); lastSectorNumber = 3; } else { // Some accelerometer readings in this lap but received before currentSector changed will have the last SectorNumber // so change them to be the first sector foreach (var coordinate in currentLapCoordinates) { if (coordinate.SectorNumber == 1) { break; } coordinate.SectorNumber = 1; } lap.DataPoints = new ObservableCollection <LapDataPointViewModel>(currentLapCoordinates); } lapNumber++; if (LapComplete != null) { LapComplete(this, new LapEventArgs(lap)); } }
private void geolocator_PositionChanged(IGeolocator sender, PositionChangedEventArgs args) { if (!isTiming && isFirstReading) { // Speed threshold for starting timing - 4m/s #if DEBUG if (!args.Position.Speed.HasValue || double.IsNaN(args.Position.Speed.Value) || args.Position.Speed.Value < 0.5d) { return; } #else if (!args.Position.Speed.HasValue || double.IsNaN(args.Position.Speed.Value) || args.Position.Speed.Value < 4d) { return; } #endif isFirstReading = false; stopwatch.Start(); isTiming = true; if (TimingStarted != null) { TimingStarted(this, null); } } var elapsedTime = stopwatch.Elapsed; double?speedInUserUnits = args.Position.Speed.HasValue && !double.IsNaN(args.Position.Speed.Value) ? isMetricUnits ? args.Position.Speed * Constants.KMH_TO_METRES_PER_SECOND : args.Position.Speed * Constants.KMH_TO_METRES_PER_SECOND / Constants.MILES_TO_KILOMETRES : null; var newGeographicLapDataPoint = new LapDataPointViewModel { ElapsedTime = elapsedTime, Altitude = args.Position.Altitude.HasValue && !double.IsNaN(args.Position.Altitude.Value) ? args.Position.Altitude : null, Heading = args.Position.Heading.HasValue && !double.IsNaN(args.Position.Heading.Value) ? args.Position.Heading : null, Latitude = args.Position.Latitude, Longitude = args.Position.Longitude, Speed = speedInUserUnits, SectorNumber = currentSector != null ? currentSector.SectorNumber : 1, Timestamp = args.Position.Timestamp.ToLocalTime() }; lapCoordinates.Add(newGeographicLapDataPoint); // Assuming this adding to the end of the list for performance int possibleLapEndGeographicLapDataPointIndex = lastGeographicLapDataPointIndex; lastGeographicLapDataPointIndex = lapCoordinates.Count - 1; bool skipCurrentCoordinate = false; if (generateSectors && currentSector == null) { skipCurrentCoordinate = true; if (newGeographicLapDataPoint.Heading.HasValue) { double?lastHeading = headingNormalisation.Any() ? headingNormalisation.Last() : (double?)null; headingNormalisation.Add(newGeographicLapDataPoint.Heading.Value); if (lastHeading.HasValue && HeadingIsWithinRange(lastHeading.Value, newGeographicLapDataPoint.Heading.Value, 30d)) { currentSector = newGeographicLapDataPoint.ToTrackSector(1, true); } } } if (skipCurrentCoordinate) { return; } int currentSectorNumber = currentSector.SectorNumber; bool sectorCompleted = PositionCompletesSector(newGeographicLapDataPoint, lastGeographicLapDataPoint, currentSector); var possibleSectorEndCoordinate = lastGeographicLapDataPoint; lastGeographicLapDataPoint = newGeographicLapDataPoint; bool lapComplete = false; if (sectorCompleted) { lapComplete = currentSector.IsFinishLine; int nextSectorNumber = currentSectorNumber + 1; if (nextSectorNumber > lastSectorNumber) { nextSectorNumber = firstSectorNumber; } // Fix the sector number for the current point, i.e. Move it over into the next sector and possibleSectorEndCoordinate is the end of the last sector newGeographicLapDataPoint.SectorNumber = nextSectorNumber; if (sectorCoordinates.Any()) { currentSector = sectorCoordinates.Single(s => s.SectorNumber == nextSectorNumber); } else { lapComplete = true; } if (!lapComplete) { EndSector(possibleSectorEndCoordinate, currentSectorNumber); } } if (lapComplete) { EndLap(possibleSectorEndCoordinate, possibleLapEndGeographicLapDataPointIndex, currentSectorNumber); } }
protected override void OnNavigatedTo(NavigationEventArgs e) { string navigationParameter; if (!NavigationContext.QueryString.TryGetValue(AppConstants.NAVIGATIONPARAMETER_TYPE, out navigationParameter)) { navigationParameter = null; } if (App.ViewModel.Settings.IsTrial) { adRotatorControl.Visibility = System.Windows.Visibility.Visible; adDefaultControl.Visibility = System.Windows.Visibility.Visible; } else { adRotatorControl.Visibility = System.Windows.Visibility.Collapsed; adDefaultControl.Visibility = System.Windows.Visibility.Collapsed; } var lapViewModel = App.ViewModel.Track.History.SelectedSession.SelectedLap; string lapMinimumLengthBlurb = string.Format("* {0} {1} {2}", AppResources.Text_Blurb_OfficialLength, Math.Round(App.ViewModel.Track.Length * Constants.APP_MINIMUMLAPLENGTH_FACTOR, 3), App.ViewModel.Settings.IsMetricUnits ? AppResources.Text_Unit_Kilometres : AppResources.Text_Unit_Miles); var lapDetailViewModel = new LapDetailViewModel { Lap = lapViewModel, SpeedUnitText = App.ViewModel.SpeedUnitText, LengthUnitText = App.ViewModel.LengthUnitText, ProjectedLength = lapViewModel.ProjectedLength(), SessionWeather = App.ViewModel.Track.History.SelectedSession.Weather, GpsDeviceName = App.ViewModel.Track.History.SelectedSession.GpsDeviceName, LapMinimumLengthBlurb = lapMinimumLengthBlurb, IsOfficial = null, LapBelongsToCurrentUser = true, IsLeaderboardTime = string.Equals(navigationParameter, AppConstants.NAVIGATIONPARAMETER_VALUE_LEADERBOARD) }; var bestLapViewModel = lapViewModel as BestLapViewModel; if (bestLapViewModel != null) { lapDetailViewModel.UserDisplayName = bestLapViewModel.UserDisplayName; lapDetailViewModel.GpsDeviceName = bestLapViewModel.GpsDeviceName; lapDetailViewModel.IsOfficial = !bestLapViewModel.IsUnofficial; lapDetailViewModel.LapBelongsToCurrentUser = App.ViewModel.IsAuthenticated && bestLapViewModel.UserName == App.MobileService.CurrentUser.UserId; } DataContext = lapDetailViewModel; var sectorSplitsAhead = new Dictionary <int, bool>(); foreach (var sector in App.ViewModel.Track.Sectors) { var sectorSplitDataPoint = lapDetailViewModel.Lap.DataPoints.Where(dp => dp.Latitude.HasValue && dp.Longitude.HasValue).SkipWhile(dp => dp.SectorNumber != sector.SectorNumber).TakeWhile(dp => dp.SectorNumber == sector.SectorNumber).LastOrDefault(); if (!sector.IsFinishLine && sectorSplitDataPoint == null) { sectorSplitsAhead.Add(sector.SectorNumber, false); continue; } var sectorSplit = sector.IsFinishLine ? lapDetailViewModel.Lap.LapTime : sectorSplitDataPoint.ElapsedTime - lapDetailViewModel.Lap.StartElapsedTime; TimeSpan bestSplit; if (sector.IsFinishLine) { bestSplit = App.ViewModel.Track.BestLap != null && App.ViewModel.Track.BestLap.DataPoints != null ? App.ViewModel.Track.BestLap.LapTime : sectorSplit; } else { var bestSplitDataPoint = App.ViewModel.Track.BestLap != null && App.ViewModel.Track.BestLap.DataPoints != null ? App.ViewModel.Track.BestLap.DataPoints.Where(dp => dp.Latitude.HasValue && dp.Longitude.HasValue).SkipWhile(dp => dp.SectorNumber != sector.SectorNumber).TakeWhile(dp => dp.SectorNumber == sector.SectorNumber).LastOrDefault() : null; bestSplit = bestSplitDataPoint != null ? bestSplitDataPoint.ElapsedTime - App.ViewModel.Track.BestLap.StartElapsedTime : sectorSplit; } var splitStatus = sectorSplit - bestSplit; lapDetailViewModel.SectorSplits.Add(Tuple.Create(sector.SectorNumber, sectorSplit, splitStatus)); sectorSplitsAhead.Add(sector.SectorNumber, splitStatus <= TimeSpan.Zero); } mapCircuit.Center = new GeoCoordinate(App.ViewModel.Track.Latitude, App.ViewModel.Track.Longitude); mapCircuit.SetZoomLevelForTrack(App.ViewModel.Track.LengthInMetres()); TrackSectorViewModel finishLineSector = null; foreach (var sector in App.ViewModel.Track.Sectors) { if (sector.IsFinishLine) { finishLineSector = sector; } var sectorLine = new MapPolyline(); sectorLine.StrokeColor = sector.IsFinishLine ? Colors.Orange : Colors.Black; sectorLine.Path.Add(new GeoCoordinate(sector.StartLatitude, sector.StartLongitude)); sectorLine.Path.Add(new GeoCoordinate(sector.EndLatitude, sector.EndLongitude)); mapCircuit.MapElements.Add(sectorLine); } LapDataPointViewModel previousGeographicDataPoint = App.ViewModel.Track.History.SelectedSession.SelectedLap.DataPoints.FirstOrDefault(dp => dp.Latitude != null && dp.Longitude != null); if (previousGeographicDataPoint == null) { return; } var finishLineMidPoint = finishLineSector == null ? null : GeoUtmConverter.Midpoint(GeographyPoint.Create(finishLineSector.StartLatitude, finishLineSector.StartLongitude), GeographyPoint.Create(finishLineSector.EndLatitude, finishLineSector.EndLongitude), finishLineSector.Heading); if (finishLineMidPoint != null && App.ViewModel.Track.History.SelectedSession.SelectedLap.IsComplete) { AddCoordinatesAsPathLineToMap(previousGeographicDataPoint.SectorNumber, sectorSplitsAhead, finishLineMidPoint, previousGeographicDataPoint.AsGeographyPoint()); } LapDataPointViewModel previousAccelerometerDataPoint = null; LapDataPointViewModel maxSpeedDataPoint = null; double?minSpeed = null; var decelerationSpeeds = new List <Tuple <double?, TimeSpan> >(); var mapLineAheadColor = new SolidColorBrush(System.Windows.Media.Colors.Green); var mapLineBehindColor = new SolidColorBrush(System.Windows.Media.Colors.Red); foreach (var dataPoint in App.ViewModel.Track.History.SelectedSession.SelectedLap.DataPoints) { if (!dataPoint.Latitude.HasValue || !dataPoint.Longitude.HasValue) { if (dataPoint.AccelerationX.HasValue || dataPoint.AccelerationY.HasValue || dataPoint.AccelerationZ.HasValue) { previousAccelerometerDataPoint = dataPoint; } continue; } if (maxSpeedDataPoint == null || dataPoint.Speed > maxSpeedDataPoint.Speed) { maxSpeedDataPoint = dataPoint; } if (!minSpeed.HasValue || minSpeed < dataPoint.Speed) { if (decelerationSpeeds.Count > 1 && decelerationSpeeds.Sum(ds => ds.Item2.TotalSeconds) > 0.5) { // Create a pushpin for maximum speed before braking if (maxSpeedDataPoint != null) { string pushpinContent = string.Format("{0}{1}", Math.Round(maxSpeedDataPoint.Speed.Value).ToString(CultureInfo.CurrentCulture), App.ViewModel.Settings.IsMetricUnits ? AppResources.Text_Unit_MetricSpeed : AppResources.Text_Unit_ImperialSpeed); var pushpinData = Tuple.Create(new GeoCoordinate(maxSpeedDataPoint.Latitude.Value, maxSpeedDataPoint.Longitude.Value), pushpinContent); apexDataLayer.Add(pushpinData); maxSpeedDataPoint = null; } double decelerationTime = decelerationSpeeds.Sum(ds => ds.Item2.TotalSeconds); double?totalSpeedLost = decelerationSpeeds.First().Item1 - decelerationSpeeds.Last().Item1; if (totalSpeedLost.HasValue && (totalSpeedLost.Value / decelerationTime) > 3) { // Create a pushpin for minimum speed through corner and the nearest g-force reading (if available) string pushpinContent; if (previousAccelerometerDataPoint != null) { pushpinContent = string.Format("{0}{1} | {2}{3}", Math.Round(minSpeed.Value).ToString(CultureInfo.CurrentCulture), App.ViewModel.Settings.IsMetricUnits ? AppResources.Text_Unit_MetricSpeed : AppResources.Text_Unit_ImperialSpeed, ComputeRelativeGforce(previousAccelerometerDataPoint, App.ViewModel.Track.History.SelectedSession.DeviceOrientation), AppResources.Text_Unit_GForce); } else { pushpinContent = string.Format("{0}{1}", Math.Round(minSpeed.Value).ToString(CultureInfo.CurrentCulture), App.ViewModel.Settings.IsMetricUnits ? AppResources.Text_Unit_MetricSpeed : AppResources.Text_Unit_ImperialSpeed); } var pushpinData = Tuple.Create(new GeoCoordinate(previousGeographicDataPoint.Latitude.Value, previousGeographicDataPoint.Longitude.Value), pushpinContent); apexDataLayer.Add(pushpinData); } } decelerationSpeeds.Clear(); minSpeed = dataPoint.Speed; } else { minSpeed = dataPoint.Speed; decelerationSpeeds.Add(Tuple.Create(minSpeed, dataPoint.ElapsedTime - previousGeographicDataPoint.ElapsedTime)); } AddCoordinatesAsPathLineToMap(dataPoint.SectorNumber, sectorSplitsAhead, previousGeographicDataPoint.AsGeographyPoint(), dataPoint.AsGeographyPoint()); previousGeographicDataPoint = dataPoint; } if (finishLineMidPoint != null && App.ViewModel.Track.History.SelectedSession.SelectedLap.IsComplete) { AddCoordinatesAsPathLineToMap(previousGeographicDataPoint.SectorNumber, sectorSplitsAhead, previousGeographicDataPoint.AsGeographyPoint(), finishLineMidPoint); } base.OnNavigatedTo(e); }