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
This commit is contained in:
316
LocationTrackerApp/Services/LocationService.cs
Normal file
316
LocationTrackerApp/Services/LocationService.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user