Skip to content

Commit

Permalink
Merge branch 'feature/pigeon-distance' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
arthaud-proust committed Oct 26, 2023
2 parents e2ad589 + f08c049 commit a5d4580
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 27 deletions.
72 changes: 72 additions & 0 deletions app/Computers/FlightDistanceComputer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Computers;

use App\Exceptions\Computers\FlightDistanceComputer\MissingFromCoordinateException;
use App\Exceptions\Computers\FlightDistanceComputer\MissingToCoordinateException;

class FlightDistanceComputer
{
protected const KM_IN_MILE = 1.609344;
protected const METERS_IN_KM = 1_000;
protected const MINUTES_IN_DEGREE = 60;
protected const STATUTE_MILES_IN_NAUTICAL_MILE = 1.1515;

protected ?float $fromLat = null;
protected ?float $fromLng = null;

protected ?float $toLat = null;
protected ?float $toLng = null;

public function __construct()
{
}

public function from(float $lat, float $lng): self
{
$this->fromLat = $lat;
$this->fromLng = $lng;

return $this;
}

public function to(float $lat, float $lng): self
{
$this->toLat = $lat;
$this->toLng = $lng;

return $this;
}

protected function checkMissingCoordinates(): void
{
if ($this->fromLng === null || $this->fromLat === null) {
throw new MissingFromCoordinateException();
}

if ($this->toLng === null || $this->toLat === null) {
throw new MissingToCoordinateException();
}
}

public function getDistanceInMiles(): float
{
$this->checkMissingCoordinates();

if (($this->fromLat === $this->toLat) && ($this->fromLng === $this->toLng)) {
return 0;
}

$theta = $this->fromLng - $this->toLng;
$dist = sin(deg2rad($this->fromLat)) * sin(deg2rad($this->toLat)) + cos(deg2rad($this->fromLat)) * cos(deg2rad($this->toLat)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);

return $dist * self::MINUTES_IN_DEGREE * self::STATUTE_MILES_IN_NAUTICAL_MILE;
}

public function getDistanceInKm(): float
{
return $this->getDistanceInMiles() * self::KM_IN_MILE;
}
}
50 changes: 50 additions & 0 deletions app/Computers/FlightDurationComputer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace App\Computers;

use App\Exceptions\Computers\FlightDurationComputer\MissingDistanceException;

class FlightDurationComputer
{
protected const METERS_IN_KM = 1_000;
protected const MINUTES_IN_HOUR = 60;

protected const EARTH_KM_CIRCUMFERENCE = 40_070;

protected const MAX_METER_DISTANCE_ON_EARTH = self::EARTH_KM_CIRCUMFERENCE * self::METERS_IN_KM / 2;
protected const MIN_METER_DISTANCE_ON_EARTH = 0;

protected const MIN_DURATION_IN_MINUTES = 10;
protected const MAX_DURATION_IN_MINUTES = 4 * self::MINUTES_IN_HOUR;

protected ?float $distanceInMeter = null;

public function __construct()
{
}

public function forKmDistance($kmDistance): self
{
$this->distanceInMeter = $kmDistance * self::METERS_IN_KM;

return $this;
}

protected function checkMissingDistance(): void
{
if ($this->distanceInMeter === null) {
throw new MissingDistanceException();
}
}

public function getDurationInMinutes(): float
{
$this->checkMissingDistance();

$b = self::MIN_DURATION_IN_MINUTES;
$a = (self::MAX_DURATION_IN_MINUTES - self::MIN_DURATION_IN_MINUTES) / (self::MAX_METER_DISTANCE_ON_EARTH - self::MIN_METER_DISTANCE_ON_EARTH);
$x = $this->distanceInMeter;

return $a * $x + $b;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Exceptions\Computers\FlightDistanceComputer;

use Exception;
use Throwable;

class MissingFromCoordinateException extends Exception
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct("From coordinate are required to compute distance", $code, $previous);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Exceptions\Computers\FlightDistanceComputer;

use Exception;
use Throwable;

class MissingToCoordinateException extends Exception
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct("To coordinate are required to compute distance", $code, $previous);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Exceptions\Computers\FlightDurationComputer;

use Exception;
use Throwable;

class MissingDistanceException extends Exception
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct("Distance is required to compute duration", $code, $previous);
}
}
22 changes: 20 additions & 2 deletions app/Http/Controllers/Pigeon/PigeonGetNewsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@

namespace App\Http\Controllers\Pigeon;

use App\Computers\FlightDistanceComputer;
use App\Computers\FlightDurationComputer;
use App\Http\Controllers\Controller;
use App\Http\Requests\Pigeon\GetNewsRequest;
use App\Models\News;
use Illuminate\Http\RedirectResponse;

class PigeonGetNewsController extends Controller
{
public function __invoke(GetNewsRequest $request, News $news): RedirectResponse
public function __invoke(
GetNewsRequest $request,
News $news,
FlightDistanceComputer $distanceComputer,
FlightDurationComputer $durationComputer): RedirectResponse
{
$userLat = $request->input('lat');
$userLng = $request->input('lng');

$kmDistance = $distanceComputer
->from($userLat, $userLng)
->to($news->lat, $news->lng)
->getDistanceInKm();

$timeInMinutes = $durationComputer
->forKmDistance($kmDistance)
->getDurationInMinutes();

$request->user()->pigeon->news()->attach($news, [
'arrival_date' => now()->addMinutes(config('pigeon.flight_minutes')),
'arrival_date' => now()->addMinutes($timeInMinutes),
]);

return redirect()->route('news.index');
Expand Down
3 changes: 2 additions & 1 deletion app/Http/Requests/Pigeon/GetNewsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public function authorize(): bool
public function rules(): array
{
return [
//
'lat' => 'numeric|required',
'lng' => 'numeric|required',
];
}
}
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@heroicons/vue": "^2.0.18",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/components": "^10.5.0",
"@vueuse/core": "^10.5.0",
"ol": "^8.1.0"
}
Expand Down
66 changes: 42 additions & 24 deletions resources/js/Pages/News/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {Feature} from "ol";
import {Point} from "ol/geom";
import {fromLonLat} from "ol/proj";
import {computed, onMounted, onUnmounted, ref} from "vue";
import {useElementSize, useParentElement} from "@vueuse/core";
import {UseGeolocation} from "@vueuse/components";
import {useElementSize, useGeolocation, useParentElement} from "@vueuse/core";
import PigeonPerch from "@/Components/Pigeon/PigeonPerch.vue";
import Modal from '@/Components/Modal.vue';
import PrimaryButton from "@/Components/Primitives/PrimaryButton.vue";
Expand Down Expand Up @@ -56,7 +57,7 @@ function handleClickFeature(feature: Feature): void {
const arrivedNews = props.pigeon.news.find((pigeonNews) => pigeonNews.id === newsId);
if (arrivedNews?.message?.is_arrived || newsUserId === props.pigeon.user_id ) {
if (arrivedNews?.message?.is_arrived || newsUserId === props.pigeon.user_id) {
router.visit(route('news.show', newsId));
return;
}
Expand All @@ -79,7 +80,14 @@ function handleClickFeature(feature: Feature): void {
const newsToGet = ref<NewsData | null>(null);
const pigeonSentModalOpened = ref<boolean>(false);
const legendModalOpened = ref<boolean>(false);
const form = useForm({});
const form = useForm<{
lat: number | null,
lng: number | null,
}>({
lat: null,
lng: null,
});
const {coords, locatedAt, error} = useGeolocation()
function confirmGetNews(news: NewsData) {
newsToGet.value = news;
Expand All @@ -90,6 +98,9 @@ function getNews() {
return;
}
form.lng = coords.value.longitude;
form.lat = coords.value.latitude;
form.post(route('pigeon.get-news', newsToGet.value?.id), {
preserveScroll: true,
onSuccess: () => {
Expand All @@ -116,9 +127,9 @@ function handlePerchClick() {
}
}
function getLegend(){
function getLegend() {
legendModalOpened.value = true
}
</script>
Expand All @@ -129,7 +140,7 @@ function getLegend(){
</Head>

<AuthenticatedLayout>
<button class="absolute m-3 z-50 h-9 w-9 bg-pink border flex justify-center content-center rounded-full" @click="getLegend" >
<button class="absolute m-3 z-50 h-9 w-9 bg-pink border flex justify-center content-center rounded-full" @click="getLegend">
<span class="not-sr-only text-button p-1">?</span>
<span class="sr-only">Voir la légende de la carte</span>
</button>
Expand All @@ -148,24 +159,31 @@ function getLegend(){
</div>

<Modal :show="!!newsToGet" @close="closeModal">
<form @submit.prevent="getNews" class="p-6">
<H3 class="mb-4">
Je dois aller chercher cette information ?
</H3>

<div class="flex flex-col gap-2">
<PrimaryButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Oui, celle là !
</PrimaryButton>

<QuaternaryButton type="button" @click="closeModal">
Non, annuler
</QuaternaryButton>
</div>
</form>
<UseGeolocation v-slot="{ coords: { latitude, longitude } }">
<form @submit.prevent="getNews" class="p-6 space-y-2">
<H3>
Je dois aller chercher cette information ?
</H3>

<p v-if="form.errors.lat || form.errors.lng" class="text-red">
Votre position est requise pour que je puisse partir.<br>
Autorisez la géolocalisation pour que je sache où vous retrouver !
</p>

<div class="flex flex-col gap-2">
<PrimaryButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Oui, celle là !
</PrimaryButton>

<QuaternaryButton type="button" @click="closeModal">
Non, annuler
</QuaternaryButton>
</div>
</form>
</UseGeolocation>
</Modal>

<PigeonSentModal :show="pigeonSentModalOpened" @close="pigeonSentModalOpened=false" />
Expand Down
Loading

0 comments on commit a5d4580

Please sign in to comment.