resq/src/Service/HubSelector.php
2024-06-26 10:02:04 +08:00

224 lines
7.7 KiB
PHP

<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Service\HubDistributor;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Ramcar\HubCriteria;
class HubSelector
{
protected $container;
protected $em;
protected $im;
protected $hub_distributor;
protected $hub_filter_logger;
protected $trans;
protected $rt;
public function __construct(ContainerInterface $container, EntityManagerInterface $em, InventoryManager $im,
HubDistributor $hub_distributor, HubFilterLogger $hub_filter_logger,
TranslatorInterface $trans, RisingTideGateway $rt)
{
$this->container = $container;
$this->em = $em;
$this->im = $im;
$this->hub_distributor = $hub_distributor;
$this->hub_filter_logger = $hub_filter_logger;
$this->trans = $trans;
$this->rt = $rt;
}
// TODO: move this to env or something so we can enable and disable filters without code changes
protected function getActiveFilters(): array
{
return [
'App\Service\HubFilter\Filters\DateAndTimeHubFilter',
'App\Service\HubFilter\Filters\JoTypeHubFilter',
'App\Service\HubFilter\Filters\PaymentMethodHubFilter',
'App\Service\HubFilter\Filters\RiderAvailabilityHubFilter',
'App\Service\HubFilter\Filters\InventoryHubFilter',
'App\Service\HubFilter\Filters\RoundRobinHubFilter',
'App\Service\HubFilter\Filters\MaxResultsHubFilter',
];
}
public function find(HubCriteria $criteria)
{
$point = $criteria->getPoint();
$limit_results = $criteria->getLimitResults();
$limit_distance = $criteria->getLimitDistance();
$jo_type = $criteria->getJoType();
$flag_inventory_check = $criteria->hasInventoryCheck();
$flag_riders_check = $criteria->hasRidersCheck();
$items = $criteria->getItems();
$date_time = $criteria->getDateTime();
$payment_method = $criteria->getPaymentMethod();
$flag_emergency = $criteria->isEmergency();
$flag_round_robin = $criteria->isRoundRobin();
$jo_id = $criteria->getJobOrderId();
$customer_id = $criteria->getCustomerId();
$customer_class = $criteria->getCustomerClass();
// needed for JORejection records and SMS notifs
$order_date = $criteria->getOrderDate();
$service_type = $criteria->getServiceType();
// error_log('payment methods ' . $payment_method);
// error_log('distance limit ' . $limit_distance);
// error_log('emergency flag ' . $flag_emergency);
// get all the hubs within distance
$filtered_hubs = $this->getClosestHubs($point, $limit_distance, $jo_id, $customer_id);
// build param list
$params = [
'date_time' => $date_time,
'flag_inventory_check' => $flag_inventory_check,
'customer_class' => $customer_class,
'jo_type' => $jo_type,
'order_date' => $order_date,
'service_type' => $service_type,
'items' => $items,
'flag_emergency' => $flag_emergency,
'limit_results' => $limit_results,
'payment_method' => $payment_method,
'flag_riders_check' => $flag_riders_check,
'flag_round_robin' => $flag_round_robin,
];
// loop through all enabled filters
foreach ($this->getActiveFilters() as $hub_filter) {
// no hubs left to filter
if (empty($filtered_hubs)) {
break;
}
$f = $this->container->get($hub_filter);
// check if supported area is exempted from this filter
if ($this->isExemptedByArea($f->getID(), $point)) {
continue;
}
$f->setJOID($jo_id);
$f->setCustomerID($customer_id);
// get requested params only
$req_params = array_intersect_key($params, array_flip($f->getRequestedParams()));
// filter hub list
$filtered_hubs = $f->filter($filtered_hubs, $req_params);
// error_log($f->getID() . ' hubs ' . json_encode($filtered_hubs));
}
// error_log('final hub list ' . json_encode($filtered_hubs));
return $filtered_hubs;
}
protected function getClosestHubs(Point $point, $limit_distance, $jo_id, $customer_id)
{
// get closest hubs based on st_distance function from db
$query_string = 'SELECT h, st_distance(h.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Hub h WHERE h.status_open = true ORDER BY dist';
$query = $this->em->createQuery($query_string)
->setParameter('lat', $point->getLatitude())
->setParameter('lng', $point->getLongitude());
// error_log($query->getSql());
$result = $query->getResult();
$hubs = [];
$hubs_data = [];
foreach ($result as $row)
{
$hubs[] = $row[0];
// get coordinates of hub
$hub_coordinates = $row[0]->getCoordinates();
$cust_lat = $point->getLatitude();
$cust_lng = $point->getLongitude();
$hub_lat = $hub_coordinates->getLatitude();
$hub_lng = $hub_coordinates->getLongitude();
// get distance in kilometers from customer point to hub point
$dist = $this->distance($cust_lat, $cust_lng, $hub_lat, $hub_lng);
if ($dist < $limit_distance)
{
$hubs_data[] = [
'hub' => $row[0],
'db_distance' => $row['dist'],
'distance' => $dist,
'duration' => 0,
'jo_count' => 0,
'inventory' => 0,
];
}
else
{
$this->hub_filter_logger->logFilteredHub($row[0], 'not_within_distance', $jo_id, $customer_id);
}
}
return $hubs_data;
}
// convert db distance to kilometers
protected function distance($lat1, $lon1, $lat2, $lon2)
{
if (($lat1 == $lat2) && ($lon1 == $lon2))
return 0;
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
return round(($miles * 1.609344), 1);
}
protected function isExemptedByArea(string $filter_id, Point $coordinates): bool
{
$long = $coordinates->getLongitude();
$lat = $coordinates->getLatitude();
// get supported area given a set of coordinates
$query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true');
$area = $query->setParameter('long', $long)
->setParameter('lat', $lat)
->setMaxResults(1)
->getOneOrNullResult();
if ($area !== null) {
// get all exceptions
$exceptions = $area->getHubFilterExceptions();
if (isset($exceptions[$filter_id])) {
error_log("FILTER " . $filter_id . " DISABLED FOR AREA: " . $area->getName());
// disable this filter for this area
return true;
}
}
// filter is in place
return false;
}
}