Compare commits

...
8 Commits
4 changed files with 108 additions and 9 deletions
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Achievement;
use App\Models\AchievementCategory;
use App\Models\Aircraft;
use App\Models\Airport;
use App\Models\Alliance;
@@ -105,8 +106,14 @@ class UserProfileController extends Controller
$achievements = Achievement::with(['category', 'difficulty'])
->orderBy('id', 'asc')
->get()
->groupBy(fn(Achievement $a) => $a->category->name)
->map(fn($group) => $group->sortBy('sort_order')->values());
->groupBy(fn(Achievement $a) => $a->category->internal_name)
->sortBy(function ($group, $internalName) {
$index = array_search($internalName, AchievementCategory::ORDER);
return $index === false ? PHP_INT_MAX : $index;
})
->mapWithKeys(fn($group) => [
$group->first()->category->name => $group->sortBy('sort_order')->values(),
]);
$userAchievements = $user->achievements()
->with('achievement')
+10
View File
@@ -16,6 +16,16 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/
class AchievementCategory extends Model
{
const array ORDER = [
'number_of_flights',
'distance',
'general_flying',
'countries_and_continents',
'aircraft',
'airlines_and_alliances',
'fun_challenges',
];
protected $fillable = [
'internal_name',
'name',
@@ -0,0 +1,57 @@
<?php
use App\Models\Achievement;
use App\Models\AchievementCategory;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$newCategory = AchievementCategory::create([
'internal_name' => 'number_of_flights',
'name' => 'Number of Flights',
'description' => 'Achievements for reaching flight count milestones.',
]);
$achievementsToUpdate = [
'general_flying.first_flight',
'general_flying.10_flights',
'general_flying.500_flights',
'general_flying.50_flights',
'general_flying.100_flights',
'general_flying.1000_flights',
];
foreach ($achievementsToUpdate as $internalName) {
$achievement = Achievement::where('internal_name', $internalName)->first();
if (!$achievement) {
continue; // or throw, depending how strict you want this
}
$achievement->update([
'achievement_category_id' => $newCategory->id,
'internal_name' => str_replace('general_flying.', 'number_of_flights.', $internalName),
]);
}
}
public function down(): void
{
$oldCategory = AchievementCategory::where('internal_name', 'general_flying')->first();
if ($oldCategory) {
Achievement::where('internal_name', 'like', 'number_of_flights.%')
->get()
->each(function (Achievement $achievement) use ($oldCategory) {
$achievement->update([
'achievement_category_id' => $oldCategory->id,
'internal_name' => str_replace('number_of_flights.', 'general_flying.', $achievement->internal_name),
]);
});
}
AchievementCategory::where('internal_name', 'number_of_flights')->delete();
}
};
@@ -370,10 +370,34 @@ export default defineComponent({
link.click()
}
function loopPoints(origin: LngLat, radiusDeg = 0.2, steps = 48): LngLat[] {
const [lng, lat] = origin
// Widen the longitude radius to compensate for mercator distortion at this latitude
const lngRadius = radiusDeg / Math.max(Math.cos(lat * Math.PI / 180), 0.15)
// Center the loop above the origin so the airport point sits at the bottom of the loop
const centerLat = lat + radiusDeg
const points: LngLat[] = []
for (let i = 0; i <= steps; i++) {
const theta = (i / steps) * Math.PI * 2
points.push([
lng + Math.sin(theta) * lngRadius,
centerLat - radiusDeg * Math.cos(theta),
])
}
return points // first and last point === origin, at the bottom of the loop
}
const getArc = (flight: Flight): LngLat[] => {
const key = routeKey(flight.departure_airport, flight.arrival_airport)
if (!arcCache.has(key)) {
arcCache.set(key, greatCirclePoints(
const isSameAirport = flight.departure_airport.id === flight.arrival_airport.id
arcCache.set(key, isSameAirport
? loopPoints([flight.departure_airport.longitude_deg, flight.departure_airport.latitude_deg])
: greatCirclePoints(
[flight.departure_airport.longitude_deg, flight.departure_airport.latitude_deg],
[flight.arrival_airport.longitude_deg, flight.arrival_airport.latitude_deg],
))
@@ -666,8 +690,10 @@ export default defineComponent({
? { top: 20, bottom: 20, left: 55, right: 55 }
: { top: 60, bottom: 60, left: 60, right: 60 }
const lngs = props.flights.flatMap(f => [f.departure_airport.longitude_deg, f.arrival_airport.longitude_deg])
const lats = props.flights.flatMap(f => [f.departure_airport.latitude_deg, f.arrival_airport.latitude_deg])
// Use full arc/loop geometry, not just endpoints, so loops aren't clipped
const allPoints = props.flights.flatMap(getArc)
const lngs = allPoints.map(p => p[0])
const lats = allPoints.map(p => p[1])
const minLat = Math.min(...lats)
const maxLat = Math.max(...lats)
@@ -689,7 +715,6 @@ export default defineComponent({
}
map!.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding, duration: 0 })
}
// ── Map init ──────────────────────────────────────────────────────────
const initMap = (): void => {