Attender / geolocation_module.py
chualinwei3's picture
Upload 30 files
f742815 verified
Raw
History Blame Contribute Delete
7.52 kB
"""
Geolocation Verification Module for Attendr
Implements KR Rule #2: Geolocation Verification
∀x[(Student(x) ∧ IsWithinAllowedArea(x)) → VerifiedLocation(x)]
"""
from utils import haversine_distance, validate_coordinates
from models import Classroom, db
from config import Config
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class GeolocationModule:
"""Handles all geolocation verification operations"""
def __init__(self, default_radius=None):
"""
Initialize geolocation module
Args:
default_radius: Default radius in meters for geofencing
"""
self.default_radius = default_radius or Config.GEOLOCATION_RADIUS_METERS
logger.info(f"Geolocation Module initialized with default radius={self.default_radius}m")
def get_classroom_location(self, classroom_id):
"""
Get classroom GPS coordinates and radius
Args:
classroom_id: ID of the classroom
Returns:
tuple: (latitude, longitude, radius) or (None, None, None) if not found
"""
try:
classroom = Classroom.query.get(classroom_id)
if not classroom:
logger.warning(f"Classroom {classroom_id} not found")
return None, None, None
radius = classroom.radius_meters or self.default_radius
logger.info(f"Classroom {classroom.name}: lat={classroom.latitude}, lon={classroom.longitude}, radius={radius}m")
return classroom.latitude, classroom.longitude, radius
except Exception as e:
logger.error(f"Error fetching classroom location: {str(e)}")
return None, None, None
def calculate_distance(self, lat1, lon1, lat2, lon2):
"""
Calculate distance between two GPS coordinates
Args:
lat1, lon1: First coordinate
lat2, lon2: Second coordinate
Returns:
Distance in meters
"""
try:
distance = haversine_distance(lat1, lon1, lat2, lon2)
logger.info(f"Distance calculated: {distance:.2f} meters")
return distance
except Exception as e:
logger.error(f"Distance calculation error: {str(e)}")
return None
def is_within_allowed_area(self, student_lat, student_lon, classroom_id):
"""
Check if student is within allowed classroom area
Implements: IsWithinAllowedArea(x)
Args:
student_lat: Student's latitude
student_lon: Student's longitude
classroom_id: ID of the classroom
Returns:
tuple: (is_within: bool, distance: float, error_message: str or None)
"""
try:
# Validate student coordinates
valid, error = validate_coordinates(student_lat, student_lon)
if not valid:
logger.warning(f"Invalid student coordinates: {error}")
return False, None, error
# Get classroom location
class_lat, class_lon, radius = self.get_classroom_location(classroom_id)
if class_lat is None:
return False, None, "Classroom location not found"
# Calculate distance
distance = self.calculate_distance(
float(student_lat),
float(student_lon),
class_lat,
class_lon
)
if distance is None:
return False, None, "Failed to calculate distance"
# Check if within radius
is_within = distance <= radius
logger.info(f"Location verification: within_area={is_within}, distance={distance:.2f}m, allowed_radius={radius}m")
if is_within:
return True, distance, None
else:
return False, distance, f"You are {distance:.0f}m away from the classroom (max allowed: {radius}m)"
except Exception as e:
logger.error(f"Location verification error: {str(e)}")
return False, None, f"Location verification failed: {str(e)}"
def verify_location(self, student_lat, student_lon, classroom_id):
"""
Complete location verification process
Implements: Student(x) ∧ IsWithinAllowedArea(x) → VerifiedLocation(x)
Args:
student_lat: Student's latitude
student_lon: Student's longitude
classroom_id: ID of the classroom
Returns:
dict: Verification result with status, distance, and message
"""
is_within, distance, error = self.is_within_allowed_area(
student_lat,
student_lon,
classroom_id
)
result = {
'verified': is_within,
'distance': distance,
'message': None,
'error': error
}
if is_within:
result['message'] = f"Location verified! You are {distance:.0f}m from the classroom."
elif error:
result['message'] = error
return result
def get_all_classrooms(self):
"""
Get all available classrooms
Returns:
List of classroom dictionaries
"""
try:
classrooms = Classroom.query.all()
return [classroom.to_dict() for classroom in classrooms]
except Exception as e:
logger.error(f"Error fetching classrooms: {str(e)}")
return []
def add_classroom(self, name, building, latitude, longitude, radius_meters=None):
"""
Add a new classroom to the system
Args:
name: Classroom name
building: Building name
latitude: GPS latitude
longitude: GPS longitude
radius_meters: Geofencing radius (optional)
Returns:
tuple: (success, classroom_dict or error_message)
"""
try:
# Validate coordinates
valid, error = validate_coordinates(latitude, longitude)
if not valid:
return False, error
# Create classroom
classroom = Classroom(
name=name,
building=building,
latitude=float(latitude),
longitude=float(longitude),
radius_meters=radius_meters or self.default_radius
)
db.session.add(classroom)
db.session.commit()
logger.info(f"Classroom added: {name} in {building}")
return True, classroom.to_dict()
except Exception as e:
db.session.rollback()
logger.error(f"Error adding classroom: {str(e)}")
return False, f"Failed to add classroom: {str(e)}"
# Singleton instance
geolocation_module = GeolocationModule()