Files
Location-Tracker/LocationTrackerApp/Services/LocationService.cs
Carlos Gutierrez fecd0ce968 Integration and add heat map demo
- Implemented OpenStreetMap using WebView with Leaflet.js
- Added OpenStreetMapView component with interactive map functionality
- Created heat map visualization with color-coded intensity
- Added 30 dummy location points around San Francisco Bay Area
- Implemented location tracking with real-time pin placement
- Added comprehensive UI with two-row button layout
- Features: Start/Stop tracking, Center map, Demo heat map, Clear demo, Reset map
- Added location count display and confirmation dialogs
- Updated project structure and documentation
- All functionality tested and working on Android emulator
2025-10-12 21:42:25 -04:00

317 lines
9.9 KiB
C#

using Microsoft.Extensions.Logging;
using LocationTrackerApp.Models;
using LocationTrackerApp.Data;
namespace LocationTrackerApp.Services;
/// <summary>
/// Service for tracking user location and storing data in SQLite database
/// </summary>
public class LocationService : ILocationService, IDisposable
{
private readonly LocationDbContext _dbContext;
private readonly ILogger<LocationService> _logger;
private CancellationTokenSource? _cancellationTokenSource;
private bool _isTracking;
private string? _currentSessionId;
private LocationData? _lastKnownLocation;
/// <summary>
/// Event fired when a new location is received
/// </summary>
public event EventHandler<LocationData>? LocationChanged;
/// <summary>
/// Event fired when location tracking status changes
/// </summary>
public event EventHandler<bool>? TrackingStatusChanged;
/// <summary>
/// Gets the current location tracking status
/// </summary>
public bool IsTracking => _isTracking;
/// <summary>
/// Gets the current session ID
/// </summary>
public string? CurrentSessionId => _currentSessionId;
/// <summary>
/// Initializes a new instance of LocationService
/// </summary>
/// <param name="dbContext">Database context for storing location data</param>
/// <param name="logger">Logger for recording service events</param>
public LocationService(LocationDbContext dbContext, ILogger<LocationService> logger)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Starts location tracking
/// </summary>
/// <param name="sessionId">Optional session ID for grouping location data</param>
/// <returns>Task representing the async operation</returns>
public async Task StartTrackingAsync(string? sessionId = null)
{
try
{
if (_isTracking)
{
_logger.LogWarning("Location tracking is already active");
return;
}
// Check permissions first
if (!await HasLocationPermissionsAsync())
{
var granted = await RequestLocationPermissionsAsync();
if (!granted)
{
_logger.LogError("Location permissions not granted");
throw new UnauthorizedAccessException("Location permissions are required for tracking");
}
}
_currentSessionId = sessionId ?? Guid.NewGuid().ToString();
_cancellationTokenSource = new CancellationTokenSource();
_logger.LogInformation("Starting location tracking with session ID: {SessionId}", _currentSessionId);
// Start location tracking
var request = new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Best,
Timeout = TimeSpan.FromSeconds(10)
};
// Start continuous location updates
_ = Task.Run(async () => await TrackLocationContinuouslyAsync(_cancellationTokenSource.Token));
_isTracking = true;
OnTrackingStatusChanged(true);
_logger.LogInformation("Location tracking started successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to start location tracking");
throw;
}
}
/// <summary>
/// Stops location tracking
/// </summary>
/// <returns>Task representing the async operation</returns>
public async Task StopTrackingAsync()
{
try
{
if (!_isTracking)
{
_logger.LogWarning("Location tracking is not active");
return;
}
_logger.LogInformation("Stopping location tracking");
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_isTracking = false;
_currentSessionId = null;
OnTrackingStatusChanged(false);
_logger.LogInformation("Location tracking stopped successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to stop location tracking");
throw;
}
}
/// <summary>
/// Gets the current location
/// </summary>
/// <returns>Current location data or null if not available</returns>
public async Task<LocationData?> GetCurrentLocationAsync()
{
try
{
if (!await HasLocationPermissionsAsync())
{
_logger.LogWarning("Location permissions not available");
return null;
}
var request = new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Best,
Timeout = TimeSpan.FromSeconds(10)
};
var location = await Geolocation.GetLocationAsync(request);
if (location == null)
{
_logger.LogWarning("Unable to get current location");
return null;
}
var locationData = new LocationData
{
Latitude = location.Latitude,
Longitude = location.Longitude,
Accuracy = location.Accuracy ?? 0,
Altitude = location.Altitude,
Speed = location.Speed,
Timestamp = DateTime.UtcNow,
SessionId = _currentSessionId
};
_lastKnownLocation = locationData;
return locationData;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get current location");
return null;
}
}
/// <summary>
/// Requests location permissions from the user
/// </summary>
/// <returns>True if permissions are granted, false otherwise</returns>
public async Task<bool> RequestLocationPermissionsAsync()
{
try
{
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
var granted = status == PermissionStatus.Granted;
_logger.LogInformation("Location permission request result: {Status}", status);
return granted;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to request location permissions");
return false;
}
}
/// <summary>
/// Checks if location permissions are granted
/// </summary>
/// <returns>True if permissions are granted, false otherwise</returns>
public async Task<bool> HasLocationPermissionsAsync()
{
try
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
return status == PermissionStatus.Granted;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to check location permissions");
return false;
}
}
/// <summary>
/// Continuously tracks location updates
/// </summary>
/// <param name="cancellationToken">Cancellation token for stopping the tracking</param>
private async Task TrackLocationContinuouslyAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested && _isTracking)
{
var locationData = await GetCurrentLocationAsync();
if (locationData != null)
{
// Save to database
await SaveLocationDataAsync(locationData);
// Notify subscribers
OnLocationChanged(locationData);
}
// Wait before next update (adjust interval as needed)
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Location tracking cancelled");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in continuous location tracking");
}
}
/// <summary>
/// Saves location data to the database
/// </summary>
/// <param name="locationData">Location data to save</param>
private async Task SaveLocationDataAsync(LocationData locationData)
{
try
{
_dbContext.LocationData.Add(locationData);
await _dbContext.SaveChangesAsync();
_logger.LogDebug("Location data saved: Lat={Latitude}, Lng={Longitude}, Time={Timestamp}",
locationData.Latitude, locationData.Longitude, locationData.Timestamp);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save location data to database");
}
}
/// <summary>
/// Raises the LocationChanged event
/// </summary>
/// <param name="locationData">The location data that changed</param>
protected virtual void OnLocationChanged(LocationData locationData)
{
LocationChanged?.Invoke(this, locationData);
}
/// <summary>
/// Raises the TrackingStatusChanged event
/// </summary>
/// <param name="isTracking">Current tracking status</param>
protected virtual void OnTrackingStatusChanged(bool isTracking)
{
TrackingStatusChanged?.Invoke(this, isTracking);
}
/// <summary>
/// Disposes the service and stops tracking
/// </summary>
public void Dispose()
{
try
{
if (_isTracking)
{
StopTrackingAsync().Wait();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing LocationService");
}
finally
{
_cancellationTokenSource?.Dispose();
}
}
}