added flight mechanics

This commit is contained in:
2025-09-29 22:46:23 -05:00
parent 8f8c3f232e
commit 2bd64cb748
9 changed files with 102 additions and 95 deletions

View File

@@ -6,4 +6,20 @@ admin.site.register(Carrier)
admin.site.register(AircraftBase) admin.site.register(AircraftBase)
admin.site.register(Equipment) admin.site.register(Equipment)
admin.site.register(Aerodrome) admin.site.register(Aerodrome)
admin.site.register(Flight)
@admin.register(Flight)
class FlightAdmin(admin.ModelAdmin): # type: ignore[type-arg]
list_display = (
"carrier",
"flight_number_display",
"origin",
"destination",
"departure_time",
"arrival_time",
"status",
)
@admin.display(description="Flight")
def flight_number_display(self, obj):
return f"{obj.carrier.icao}{obj.flight_number}"

View File

@@ -1,5 +1,6 @@
# Generated by Django 5.2.6 on 2025-09-30 02:02 # Generated by Django 5.2.6 on 2025-09-30 03:22
import django.core.validators
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@@ -20,7 +21,9 @@ class Migration(migrations.Migration):
('iata', models.CharField(blank=True, max_length=3, null=True, unique=True)), ('iata', models.CharField(blank=True, max_length=3, null=True, unique=True)),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('city', models.CharField(max_length=100)), ('city', models.CharField(max_length=100)),
('country', models.CharField(max_length=100)), ('country', models.CharField(max_length=3)),
('latitude', models.FloatField()),
('longitude', models.FloatField()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@@ -41,7 +44,7 @@ class Migration(migrations.Migration):
('icao', models.CharField(max_length=3, unique=True)), ('icao', models.CharField(max_length=3, unique=True)),
('iata', models.CharField(max_length=2, unique=True)), ('iata', models.CharField(max_length=2, unique=True)),
('name', models.CharField(max_length=100, unique=True)), ('name', models.CharField(max_length=100, unique=True)),
('country', models.CharField(max_length=30)), ('country', models.CharField(max_length=3)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@@ -53,4 +56,19 @@ class Migration(migrations.Migration):
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fleet', to='simulator.carrier')), ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fleet', to='simulator.carrier')),
], ],
), ),
migrations.CreateModel(
name='Flight',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('flight_number', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)])),
('departure_time', models.DateTimeField()),
('carrier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flights', to='simulator.carrier')),
('destination', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arrivals', to='simulator.aerodrome')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flights', to='simulator.equipment')),
('origin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='departures', to='simulator.aerodrome')),
],
options={
'constraints': [models.UniqueConstraint(fields=('carrier', 'flight_number'), name='unique_flight_per_carrier')],
},
),
] ]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.6 on 2025-09-30 02:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('simulator', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='aerodrome',
name='country',
field=models.CharField(max_length=2),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.6 on 2025-09-30 02:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('simulator', '0002_alter_aerodrome_country'),
]
operations = [
migrations.AlterField(
model_name='aerodrome',
name='country',
field=models.CharField(max_length=3),
),
]

View File

@@ -1,35 +0,0 @@
# Generated by Django 5.2.6 on 2025-09-30 03:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('simulator', '0003_alter_aerodrome_country'),
]
operations = [
migrations.AlterField(
model_name='carrier',
name='country',
field=models.CharField(max_length=3),
),
migrations.CreateModel(
name='Flight',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('flight_number', models.PositiveIntegerField(max_length=4)),
('departure_time', models.DateTimeField()),
('arrival_time', models.DateTimeField()),
('carrier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flights', to='simulator.carrier')),
('destination', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arrivals', to='simulator.aerodrome')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flights', to='simulator.equipment')),
('origin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='departures', to='simulator.aerodrome')),
],
options={
'constraints': [models.UniqueConstraint(fields=('carrier', 'flight_number'), name='unique_flight_per_carrier')],
},
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.2.6 on 2025-09-30 03:03
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('simulator', '0004_alter_carrier_country_flight'),
]
operations = [
migrations.AlterField(
model_name='flight',
name='flight_number',
field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
]

View File

@@ -7,6 +7,8 @@ class Aerodrome(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
city = models.CharField(max_length=100) city = models.CharField(max_length=100)
country = models.CharField(max_length=3) country = models.CharField(max_length=3)
latitude = models.FloatField()
longitude = models.FloatField()
def __str__(self): def __str__(self):
return f"{self.icao} - {self.city}" return f"{self.icao} - {self.city}"

View File

@@ -3,6 +3,10 @@ from .carrier import Carrier
from .aircraft import Equipment from .aircraft import Equipment
from .aerodrome import Aerodrome from .aerodrome import Aerodrome
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from simulator.utils import haversine_nm
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.utils import timezone
class Flight(models.Model): class Flight(models.Model):
@@ -23,7 +27,6 @@ class Flight(models.Model):
validators=[MinValueValidator(1), MaxValueValidator(9999)] validators=[MinValueValidator(1), MaxValueValidator(9999)]
) )
departure_time = models.DateTimeField() departure_time = models.DateTimeField()
arrival_time = models.DateTimeField()
class Meta: class Meta:
constraints = [ constraints = [
@@ -34,3 +37,44 @@ class Flight(models.Model):
def __str__(self): def __str__(self):
return f"{self.carrier.icao}{self.flight_number} {self.origin.icao} > {self.destination.icao}" return f"{self.carrier.icao}{self.flight_number} {self.origin.icao} > {self.destination.icao}"
@property
def distance_nm(self) -> float:
return haversine_nm(
self.origin.latitude,
self.origin.longitude,
self.destination.latitude,
self.destination.longitude,
)
@property
def duration(self) -> timedelta:
speed = self.equipment.model.cruise_speed_kt # knots = nm/hr
hours = self.distance_nm / speed
return timedelta(hours=hours)
@property
def arrival_time(self):
return self.departure_time + self.duration
def clean(self):
overlapping = Flight.objects.filter(
equipment=self.equipment,
departure_time__lt=self.arrival_time,
arrival_time__gt=self.departure_time,
).exclude(pk=self.pk)
if overlapping.exists():
raise ValidationError(
f"{self.equipment} is already assigned to another flight in this timeframe."
)
@property
def status(self):
now = timezone.now()
if now < self.departure_time:
return "Scheduled"
elif self.departure_time <= now < self.arrival_time:
return "In-Flight"
else:
return "Arrived"

17
simulator/utils.py Normal file
View File

@@ -0,0 +1,17 @@
import math
def haversine_nm(lat1, lon1, lat2, lon2):
# Earth radius in nautical miles
R = 3440.065
phi1, phi2 = math.radians(lat1), math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lon2 - lon1)
a = (
math.sin(dphi / 2) ** 2
+ math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2) ** 2
)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c