src/Entity/Route.php line 35

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use ApiPlatform\Core\Annotation\ApiFilter;
  4. use ApiPlatform\Core\Annotation\ApiResource;
  5. use ApiPlatform\Core\Annotation\ApiSubresource;
  6. use App\DBAL\Types\DeliveryStatusType;
  7. use App\DBAL\Types\RouteStatusType;
  8. use App\Handler\ResponseCollection;
  9. use Doctrine\Common\Collections\ArrayCollection;
  10. use Doctrine\Common\Collections\Collection;
  11. use Doctrine\Common\Collections\Criteria;
  12. use Doctrine\ORM\Event\LifecycleEventArgs;
  13. use Doctrine\ORM\Event\PreUpdateEventArgs;
  14. use Doctrine\ORM\Mapping as ORM;
  15. use Symfony\Component\Serializer\Annotation\Groups;
  16. use Symfony\Component\Serializer\Annotation\MaxDepth;
  17. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
  18. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
  19. use App\Traits\TestRoute;
  20. use Ramsey\Uuid\Uuid;
  21. /**
  22.  * @ApiResource(
  23.  *     normalizationContext={"groups"={"layout:read"}, "enable_max_depth"=true},
  24.  *     attributes={"order"={"startDate", "routeDays.dayIndex"}}
  25.  * )
  26.  * @ApiFilter(SearchFilter::class, properties={"status"})
  27.  * @ApiFilter(OrderFilter::class, properties={"startDate", "title", "status"})
  28.  */
  29. #[ORM\HasLifecycleCallbacks]
  30. #[ORM\Entity(repositoryClass'App\Repository\RouteRepository')]
  31. class Route
  32. {
  33.     #[Groups(groups: ['layout:read''route:list'])]
  34.     #[ORM\Id]
  35.     #[ORM\Column(type'guid'uniquetrue)]
  36.     private $id;
  37.     #[Groups(groups: ['layout:read''route:list'])]
  38.     #[ORM\ManyToOne(targetEntity'App\Entity\Vehicle'inversedBy'routes')]
  39.     private $vehicle;
  40.     #[Groups(groups: ['layout:read'])]
  41.     #[ORM\Column(type'integer')]
  42.     private $duration 0;
  43.     #[Groups(groups: ['layout:read''route:list'])]
  44.     #[ORM\Column(type'date'nullabletrue)]
  45.     private $startDate;
  46.     #[Groups(groups: ['layout:read'])]
  47.     #[ORM\Column(type'json')]
  48.     private $polygon = [];
  49.     #[ORM\OneToMany(targetEntity'App\Entity\RouteElement'mappedBy'route'cascade: ['persist'])]
  50.     private $routeElements
  51.     #[Groups(groups: ['layout:read'])]
  52.     #[ORM\ManyToOne(targetEntity'App\Entity\Location')]
  53.     #[ORM\JoinColumn(nullablefalse)]
  54.     private $sourceLocation;
  55.     #[Groups(groups: ['layout:read'])]
  56.     #[ORM\ManyToOne(targetEntity'App\Entity\Location')]
  57.     #[ORM\JoinColumn(nullablefalse)]
  58.     private $destinationLocation;
  59.     #[Groups(groups: ['layout:read'])]
  60.     #[ORM\Column(type'string'nullabletrue)]
  61.     private $destinationLocationId;
  62.     #[Groups(groups: ['layout:read'])]
  63.     #[ORM\Column(type'string'nullabletrue)]
  64.     private $sourceLocationId;
  65.     #[Groups(groups: ['layout:read''route:list'])]
  66.     #[MaxDepth(2)]
  67.     #[ORM\OneToMany(targetEntity'App\Entity\RouteDay'mappedBy'route'cascade: ['persist'])]
  68.     #[ORM\OrderBy(value: ['dayIndex' => 'ASC'])]
  69.     private $routeDays;
  70.     #[Groups(groups: ['layout:read''route:list'])]
  71.     #[ORM\Column(type'string'length64)]
  72.     private $title;
  73.     #[Groups(groups: ['layout:read''route:list'])]
  74.     #[ORM\Column(type'RouteStatusType')]
  75.     private $status 'Open';
  76.     #[ORM\OneToMany(targetEntity'App\Entity\Protocol'mappedBy'route'orphanRemovaltrue)]
  77.     private $protocols;
  78.     private $isDeleted false;
  79.     private $batchProcessing false;
  80.     #[ORM\PostPersist]
  81.     public function createDays(LifecycleEventArgs $args)
  82.     {
  83.         /*
  84.         $em = $args->getEntityManager();
  85.         
  86.         if($this->routeDays->count() == 0) {
  87.             $startDate = $this->startDate->format('U');
  88.             $endDate = $this->endDate->format('U');
  89.             $currentDate = $startDate;
  90.             $dayIndex = 1;
  91.             do {
  92.                 $newDay = new RouteDay();
  93.                 $newDay->setRoute($this);
  94.                 if($dayIndex == 1) {
  95.                     $newDay->setStartLocation($this->getSourceLocation());
  96.                 }
  97.         
  98.                 $newDay->setDayIndex($dayIndex++);
  99.                 $newDay->setDate(new \DateTime(date('Y-m-d', $currentDate)));
  100.         
  101.                 $em->persist($newDay);
  102.         
  103.                 $currentDate += 86400;
  104.         
  105.         //                $newDay
  106.             } while($currentDate <= $endDate);
  107.         
  108.             $em->flush();
  109.         }
  110.         */
  111.     }
  112.     public function refresh()
  113.     {
  114.         // Do not refresh during batch processing
  115.         if ($this->isBatchProcessing()) return;
  116.         foreach ($this->getRouteDays() as $day) {
  117.             $elements $day->getRouteElements();
  118.             foreach ($elements as $element) {
  119.                 $element->refreshTravelTime();
  120.             }
  121.             $day->refreshStartTimes();
  122.             $day->refreshFinalRoute();
  123.             $day->refreshTotals();
  124.         }
  125.         $this->updateWeights();
  126.     }
  127.     #[ORM\PreUpdate]
  128.     public function preUpdate(PreUpdateEventArgs $args)
  129.     {
  130.         $changeSet $args->getEntityChangeSet();
  131.         $em $args->getEntityManager();
  132.         if (
  133.             isset($changeSet['startDate'])
  134.         ) {
  135.             $routeDays $this->getRouteDays();
  136.             $currentDate = clone $this->getStartDate();
  137.             $dailyInterval = new \DateInterval('P1D');
  138.             if ($currentDate !== null) {
  139.                 foreach ($routeDays as $routeDay) {
  140.                     $routeDay->setDate(new \DateTime($currentDate->format('Y-m-d')));
  141.                     $currentDate $currentDate->add($dailyInterval);
  142.                     $em->persist($routeDay);
  143.                 }
  144.             }
  145.         }
  146.     }
  147.     #[ORM\PrePersist]
  148.     public function processPolygon()
  149.     {
  150.         if (is_string($this->polygon)) {
  151.             $this->polygon json_decode($this->polygontrue);
  152.         }
  153.         if (empty($this->title)) {
  154.             $this->title 'Route ' date('Y-m-d');
  155.         }
  156.     }
  157.     public function __construct()
  158.     {
  159.         $this->routeElements = new ArrayCollection();
  160.         $this->routeDays = new ArrayCollection();
  161.         $this->protocols = new ArrayCollection();
  162.         $this->id Uuid::uuid4();
  163.     }
  164.     public function getId(): ?string
  165.     {
  166.         return $this->id;
  167.     }
  168.     public function getVehicle(): ?Vehicle
  169.     {
  170.         return $this->vehicle;
  171.     }
  172.     public function setVehicle(?Vehicle $vehicle): self
  173.     {
  174.         $this->vehicle $vehicle;
  175.         return $this;
  176.     }
  177.     public function getDuration(): ?int
  178.     {
  179.         return $this->duration;
  180.     }
  181.     public function setDuration(int $duration): self
  182.     {
  183.         $this->duration $duration;
  184.         return $this;
  185.     }
  186.     public function getStartDate(): ?\DateTimeInterface
  187.     {
  188.         return $this->startDate;
  189.     }
  190.     public function setStartDate(?\DateTimeInterface $startDate): self
  191.     {
  192.         $this->startDate $startDate;
  193.         return $this;
  194.     }
  195.     public function getEndDate(): ?\DateTimeInterface
  196.     {
  197.         return $this->getRouteDays()->last()->getDate();
  198.     }
  199.     public function getPolygon(): ?array
  200.     {
  201.         return $this->polygon;
  202.     }
  203.     public function setPolygon(?array $polygon): self
  204.     {
  205.         $this->polygon $polygon;
  206.         return $this;
  207.     }
  208.     /**
  209.      * @return Collection|RouteElement[]
  210.      */
  211.     public function getRouteElements(): Collection
  212.     {
  213.         return $this->routeElements;
  214.     }
  215.     public function addRouteElement(RouteElement $routeElement): self
  216.     {
  217.         if (!$this->routeElements->contains($routeElement)) {
  218.             $this->routeElements[] = $routeElement;
  219.             $routeElement->setRoute($this);
  220.         }
  221.         return $this;
  222.     }
  223.     public function removeRouteElement(RouteElement $routeElement): self
  224.     {
  225.         if ($this->routeElements->contains($routeElement)) {
  226.             $this->routeElements->removeElement($routeElement);
  227.             // set the owning side to null (unless already changed)
  228.             if ($routeElement->getRoute() === $this) {
  229.                 $routeElement->setRoute(null);
  230.             }
  231.         }
  232.         return $this;
  233.     }
  234.     #[ORM\PreRemove]
  235.     public function preRemove()
  236.     {
  237.         $this->isDeleted true;
  238.     }
  239.     public function setIsDeleted($value)
  240.     {
  241.         $this->isDeleted = ($value == true);
  242.     }
  243.     public function isDeleted()
  244.     {
  245.         return $this->isDeleted;
  246.     }
  247.     public function getSourceLocation(): ?Location
  248.     {
  249.         return $this->sourceLocation;
  250.     }
  251.     public function setSourceLocation(?Location $sourceLocation): self
  252.     {
  253.         $this->sourceLocation $sourceLocation;
  254.         return $this;
  255.     }
  256.     public function getDestinationLocation(): ?Location
  257.     {
  258.         return $this->destinationLocation;
  259.     }
  260.     public function setDestinationLocation(?Location $destinationLocation): self
  261.     {
  262.         $this->destinationLocation $destinationLocation;
  263.         return $this;
  264.     }
  265.     public function updateWeights()
  266.     {
  267.         $days $this->getRouteDays();
  268.         $weight 0;
  269.         foreach ($days as $day) {
  270.             if (
  271.                 $day->getStartLocation() !== null &&
  272.                 $day->getStartLocation()->isWarehouse()
  273.             ) {
  274.                 $loadingWeight $this->calcLoadingWeight(
  275.                     $day->getStartLocation(),
  276.                     $day->getDate()
  277.                 );
  278.                 $weight += $loadingWeight;
  279.             }
  280.             $day->setStartWeight(
  281.                 $weight
  282.             );
  283.             $routeElements $day->getRouteElements();
  284.             // Resorting is required to get correct position text
  285.             $iterator $routeElements->getIterator();
  286.             $iterator->uasort(function ($a$b) {
  287.                 return ($a->getSequence() < $b->getSequence()) ? -1;
  288.             });
  289.             /** @var RouteElement[] $routeElements */
  290.             $routeElements = new ArrayCollection(iterator_to_array($iterator));
  291.             foreach ($routeElements as $routeElement) {
  292.                 $routeElement->setTravelWeight($weight);
  293.                 $startWeight $weight;
  294.                 if ($routeElement->isDelivery()) {
  295.                     $weight -= $routeElement->getDeliveryWeights();
  296.                 } elseif ($routeElement->isLocation()) {
  297.                     $loadingWeight $this->calcLoadingWeight(
  298.                         $routeElement->getLocation(),
  299.                         $routeElement->getFullArrivalDateTime()
  300.                     );
  301.                     $routeElement->setLoadingWeight($loadingWeight);
  302.                     $weight += $loadingWeight;
  303.                 }
  304.                 $routeElement->setWeightChange($weight $startWeight);
  305.                 $routeElement->setFinalWeight($weight);
  306.             }
  307.             $day->setEndWeight($weight);
  308.         }
  309.         $this->updateRoutePositions();
  310.     }
  311.     public function updateRoutePositions()
  312.     {
  313.         $routePosition 0;
  314.         $days $this->getRouteDays();
  315.         foreach ($days as $day) {
  316.             $routeElements $day->getRouteElements();
  317.             // Resorting is required to get correct position text
  318.             $iterator $routeElements->getIterator();
  319.             $iterator->uasort(function ($a$b) {
  320.                 return ($a->getSequence() < $b->getSequence()) ? -1;
  321.             });
  322.             /** @var RouteElement[] $routeElements */
  323.             $routeElements = new ArrayCollection(iterator_to_array($iterator));
  324.             foreach ($routeElements as $routeElement) {
  325.                 if ($this->getStatus() != RouteStatusType::OPEN && !$routeElement->hasRoutePosition()) {
  326.                     // Because fixedRoutePosition is a textfield, it is 1-based.
  327.                     $routeElement->setFixedRoutePosition($this->getRouteElements()->count() + 1);
  328.                 }
  329.                 $fixedPosition $routeElement->getFixedRoutePosition();
  330.                 $routeElement->setRoutePosition($routePosition);
  331.                 if (empty($fixedPosition)) {
  332.                     $routePosition++;
  333.                 }
  334.                 // } else {
  335.                 // $routeElement->setRoutePosition($fixedPosition);
  336.                 // }
  337.             }
  338.         }
  339.     }
  340.     public function calcLoadingWeight(Location $location\DateTimeInterface $since)
  341.     {
  342.         $weight 0;
  343.         $days $this->getRouteDays();
  344.         //        echo '- START -'.PHP_EOL;
  345.         foreach ($days as $day) {
  346.             if (
  347.                 $day->getStartLocation() !== null &&
  348.                 $day->getStartLocation()->getId() === $location->getId() &&
  349.                 $day->getDate()->format('Y-m-d') > $since->format('Y-m-d')
  350.             ) {
  351.                 // Current Day have same location, then the requested location
  352.                 // will be loaded later
  353.                 return $weight;
  354.             }
  355.             $routeElements $day->getRouteElements();
  356.             foreach ($routeElements as $routeElement) {
  357.                 if (
  358.                     $routeElement->getFullArrivalDateTime() < $since
  359.                 ) {
  360.                     continue;
  361.                 }
  362.                 if (
  363.                     $routeElement->isLocation() &&
  364.                     $routeElement->getFullArrivalDateTime() > $since &&
  365.                     $routeElement->getLocation()->getId() === $location->getId()
  366.                 ) {
  367.                     return $weight;
  368.                 }
  369.                 if ($routeElement->isLocation()) {
  370.                     continue;
  371.                 }
  372.                 if (
  373.                     $routeElement->isDelivery() &&
  374.                     $routeElement->getDelivery()->getSourceAddress() &&
  375.                     $routeElement->getDelivery()->getSourceAddress()->getId() === $location->getAddress()->getId()
  376.                 ) {
  377.                     $weight += $routeElement->getDeliveryWeights();
  378.                 }
  379.             }
  380.         }
  381.         return $weight;
  382.     }
  383.     /**
  384.      * @return mixed
  385.      */
  386.     #[Groups(groups: ['layout:read''route:list'])]
  387.     public function getRouteEfficency() : array
  388.     {
  389.         $travelTime 0;
  390.         $deliveryMinutes 0;
  391.         foreach ($this->getRouteDays() as $day) {
  392.             foreach ($day->getRouteElements()  as $routeElements) {
  393.                 $travelTime += $routeElements->getTravelTime();
  394.                 $deliveryMinutes += $routeElements->getDeliveryDuration();
  395.             }
  396.             $travelTime += $day->getFinalTravelTime();
  397.         }
  398.         return [
  399.             'delivery_time' => round($deliveryMinutes2),
  400.             'travel_time' => round($travelTime2),
  401.         ];
  402.     }
  403.     /**
  404.      * @return Collection|RouteDay[]
  405.      */
  406.     public function getRouteDays(): Collection
  407.     {
  408.         return $this->routeDays;
  409.     }
  410.     /**
  411.      * @throws \Exception
  412.      */
  413.     public function getRouteDayByIndex(int $dayIndex): RouteDay
  414.     {
  415.         foreach ($this->getRouteDays() as $day) {
  416.             if ($day->getDayIndex() == $dayIndex) {
  417.                 return $day;
  418.             }
  419.         }
  420.         throw new \Exception('Requested dayIndex ' $dayIndex ' not found');
  421.     }
  422.     public function addRouteDay(RouteDay $routeDay): self
  423.     {
  424.         if (!$this->routeDays->contains($routeDay)) {
  425.             $this->routeDays[] = $routeDay;
  426.             $routeDay->setRoute($this);
  427.         }
  428.         return $this;
  429.     }
  430.     public function removeRouteDay(RouteDay $routeDay): self
  431.     {
  432.         if ($this->routeDays->contains($routeDay)) {
  433.             $this->routeDays->removeElement($routeDay);
  434.             // set the owning side to null (unless already changed)
  435.             if ($routeDay->getRoute() === $this) {
  436.                 $routeDay->setRoute(null);
  437.             }
  438.         }
  439.         return $this;
  440.     }
  441.     public function getTitle(): ?string
  442.     {
  443.         return $this->title;
  444.     }
  445.     public function setTitle(string $title): self
  446.     {
  447.         $this->title $title;
  448.         return $this;
  449.     }
  450.     public function getStatus()
  451.     {
  452.         return $this->status;
  453.     }
  454.     public function setStatus($status): self
  455.     {
  456.         $this->status $status;
  457.         $routeElements $this->getRouteElements();
  458.         if ($status == RouteStatusType::OPEN) {
  459.             $deliveryCollectionStatus DeliveryStatusType::OPEN;
  460.         } elseif ($status == RouteStatusType::ACTIVE) {
  461.             $deliveryCollectionStatus DeliveryStatusType::PLANNED;
  462.         } elseif ($status == RouteStatusType::PLANNED) {
  463.             $deliveryCollectionStatus DeliveryStatusType::PLANNED;
  464.         } elseif ($status == RouteStatusType::ARCHIVED) {
  465.             $deliveryCollectionStatus DeliveryStatusType::DONE;
  466.         }
  467.         foreach ($routeElements as $routeElement) {
  468.             if ($routeElement->isDelivery()) {
  469.                 $collection $routeElement->getCollection();
  470.                 if (empty($collection)) {
  471.                     throw new \Exception('RouteElement is Delivery, but without Collection: ' $routeElement->getId());
  472.                 }
  473.                 $routeElement->getCollection()->setStatus($deliveryCollectionStatus);
  474.             }
  475.             if ($status == RouteStatusType::PLANNED) {
  476.                 $fixedPosition $routeElement->getFixedRoutePosition();
  477.                 if(empty($fixedPosition)) {
  478.                     $routeElement->setFixedRoutePosition($routeElement->getRoutePosition() + 1);
  479.                 }
  480.             }
  481.         }
  482.         return $this;
  483.     }
  484.     /**
  485.      * @return Collection|Protocol[]
  486.      */
  487.     public function getProtocols(): Collection
  488.     {
  489.         return $this->protocols;
  490.     }
  491.     public function addProtocol(Protocol $protocol): self
  492.     {
  493.         if (!$this->protocols->contains($protocol)) {
  494.             $this->protocols[] = $protocol;
  495.             $protocol->setRoute($this);
  496.         }
  497.         return $this;
  498.     }
  499.     public function removeProtocol(Protocol $protocol): self
  500.     {
  501.         if ($this->protocols->contains($protocol)) {
  502.             $this->protocols->removeElement($protocol);
  503.             // set the owning side to null (unless already changed)
  504.             if ($protocol->getRoute() === $this) {
  505.                 $protocol->setRoute(null);
  506.             }
  507.         }
  508.         return $this;
  509.     }
  510.     public function getRouteImage($width 800$height 1024): string
  511.     {
  512.         $mapOptions = [
  513.             'width' => $width,
  514.             'height' => $height,
  515.             'legs' => [],
  516.             'marker' => [],
  517.         ];
  518.         /**
  519.          * @var RouteDay[] $routeDays
  520.          */
  521.         $routeDays $this->getRouteDays();
  522.         $placed = [];
  523.         foreach ($routeDays as $routeDay) {
  524.             $startLocation $routeDay->getStartLocation();
  525.             if (!empty($startLocation)) {
  526.                 $mapOptions['marker'][] = [
  527.                     'coords' => [(float)$startLocation->getAddress()->getLongitude(), (float)$startLocation->getAddress()->getLatitude()],
  528.                     // 'text' => substr(trim(str_replace('DB24', '', $startLocation->getAddress()->getCompany())), 0, 1),
  529.                     'icon' => 'store',
  530.                 ];
  531.             }
  532.             /**
  533.              * @var RouteElement[] $routeElements
  534.              */
  535.             $routeElements $routeDay->getRouteElements();
  536.             foreach ($routeElements as $routeElement) {
  537.                 if($routeElement->isPause()) continue;
  538.                 // Check if this is a virtual element (end location polyline only)
  539.                 $extra $routeElement->getExtra();
  540.                 $isVirtual = isset($extra['isVirtual']) && $extra['isVirtual'] === true;
  541.                 $routing $routeElement->getRouting();
  542.                 // Only add marker if element has a destination address (skip virtual elements)
  543.                 if (!$isVirtual && $routeElement->getDestinationAddress() !== null) {
  544.                     if (!isset($placed[$routeElement->getDestinationAddress()->getId()])) {
  545.                         $mapOptions['marker'][] = [
  546.                             'coords' => [(float)$routeElement->getDestinationAddress()->getLongitude(), (float)$routeElement->getDestinationAddress()->getLatitude()],
  547.                             'text' => ''.$routeElement->getFinalRoutePosition(),
  548.                             'icon' => 'grey',
  549.                         ];
  550.                         $placed[$routeElement->getDestinationAddress()->getId()] = true;
  551.                     }
  552.                 }
  553.                 // Add polyline for all elements (including virtual ones)
  554.                 $polyline = [];
  555.                 if(!empty($routing['polyline'])) {
  556.                     foreach ($routing['polyline'] as $single) {
  557.                         $polyline[] = [$single['x'], $single['y']];
  558.                     }
  559.                     $mapOptions['legs'][] = [
  560.                         'color' => '#51789b99',
  561.                         'width' => 8,
  562.                         'polyline' => $polyline,
  563.                     ];
  564.                 }
  565.             }
  566.             if($routeDay->getFinalRouting() !== null) {
  567.                 $routeDay->refreshFinalRoute();
  568.                 $finalRouting $routeDay->getFinalRouting();
  569.                 if (!empty($finalRouting['polyline'])) {
  570.                     $polyline = [];
  571.                     foreach ($finalRouting['polyline'] as $single) {
  572.                         $polyline[] = [$single['x'], $single['y']];
  573.                     }
  574.                     $mapOptions['legs'][] = [
  575.                         'color' => '#51789b99',
  576.                         'width' => 8,
  577.                         'polyline' => $polyline,
  578.                     ];
  579.                 }
  580.             }
  581.         }
  582.         $logFile __DIR__ '/../../var/log/map_debug.log';
  583.         $ch curl_init();
  584.         //curl_setopt($ch, CURLOPT_URL, 'http://localhost:3001/');
  585.         curl_setopt($chCURLOPT_URL'https://static-maps.redoo.cloud/');
  586.         curl_setopt($chCURLOPT_POST1);
  587.         curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
  588.         curl_setopt($chCURLOPT_HTTPHEADER, array('Content-Type:application/json'));
  589.         $payload json_encode($mapOptions);
  590.         curl_setopt($chCURLOPT_POSTFIELDS$payload);
  591.         file_put_contents($logFile"\n" date('Y-m-d H:i:s') . ' [MAP] Sending map request for route ' $this->getId() . "\n"FILE_APPEND);
  592.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] - Markers: ' count($mapOptions['marker']) . ', Legs: ' count($mapOptions['legs']) . "\n"FILE_APPEND);
  593.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] - Payload: ' $payload "\n"FILE_APPEND);
  594.         $response curl_exec($ch);
  595.         $httpCode curl_getinfo($chCURLINFO_HTTP_CODE);
  596.         $error curl_error($ch);
  597.         curl_close($ch);
  598.         if ($error) {
  599.             file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] ERROR: ' $error "\n"FILE_APPEND);
  600.             return '';
  601.         }
  602.         if ($httpCode !== 200) {
  603.             file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] ERROR: HTTP ' $httpCode ': ' substr($response0500) . "\n"FILE_APPEND);
  604.             return '';
  605.         }
  606.         if (empty($response)) {
  607.             file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] ERROR: Empty response' "\n"FILE_APPEND);
  608.             return '';
  609.         }
  610.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] SUCCESS: Response length ' strlen($response) . ' bytes' "\n"FILE_APPEND);
  611.         // Save raw PNG for debugging
  612.         $debugPngPath __DIR__ '/../../var/log/map_' $this->getId() . '.png';
  613.         file_put_contents($debugPngPath$response);
  614.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] Saved PNG to: ' $debugPngPath "\n"FILE_APPEND);
  615.         // Verify it's a valid PNG
  616.         $imageInfo getimagesizefromstring($response);
  617.         if ($imageInfo === false) {
  618.             file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] ERROR: Response is not a valid image!' "\n"FILE_APPEND);
  619.             return '';
  620.         }
  621.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] Image verified: ' $imageInfo[0] . 'x' $imageInfo[1] . ' ' $imageInfo['mime'] . "\n"FILE_APPEND);
  622.         $dataUri 'data:image/png;base64,' base64_encode($response);
  623.         file_put_contents($logFiledate('Y-m-d H:i:s') . ' [MAP] Data URI length: ' strlen($dataUri) . ' chars' "\n"FILE_APPEND);
  624.         return $dataUri;
  625.     }
  626.     public function getTotalTravelDistance($formatted false)
  627.     {
  628.         $totalTravelDistance 0;
  629.         /**
  630.          * @var RouteDay[] $routeDays
  631.          */
  632.         $routeDays $this->getRouteDays();
  633.         foreach ($routeDays as $routeDay) {
  634.             /**
  635.              * @var RouteElement[] $routeElements
  636.              */
  637.             $routeElements $routeDay->getRouteElements();
  638.             foreach ($routeElements as $routeElement) {
  639.                 $totalTravelDistance += $routeElement->getTravelDistance();
  640.             }
  641.             $totalTravelDistance += $routeDay->getFinalTravelDistance();
  642.         }
  643.         if ($formatted == false) {
  644.             return $totalTravelDistance;
  645.         }
  646.         return number_format($totalTravelDistance2',''.');
  647.     }
  648.     public function getTotalTravelTime($formatted false)
  649.     {
  650.         $totalValue 0;
  651.         /**
  652.          * @var RouteDay[] $routeDays
  653.          */
  654.         $routeDays $this->getRouteDays();
  655.         foreach ($routeDays as $routeDay) {
  656.             /**
  657.              * @var RouteElement[] $routeElements
  658.              */
  659.             $routeElements $routeDay->getRouteElements();
  660.             foreach ($routeElements as $routeElement) {
  661.                 $totalValue += $routeElement->getTravelTime();
  662.             }
  663.         }
  664.         if ($formatted === false) {
  665.             return $totalValue;
  666.         }
  667.         $hours floor($totalValue 60);
  668.         $minutes $totalValue - ($hours 60);
  669.         return str_pad($hours2'0'STR_PAD_LEFT) . ':' str_pad($minutes2'0'STR_PAD_LEFT) . ' h';
  670.     }
  671.     public function getTotalDistanceCosts($formatted false)
  672.     {
  673.         $totalValue 0;
  674.         /**
  675.          * @var RouteDay[] $routeDays
  676.          */
  677.         $routeDays $this->getRouteDays();
  678.         foreach ($routeDays as $routeDay) {
  679.             /**
  680.              * @var RouteElement[] $routeElements
  681.              */
  682.             $routeElements $routeDay->getRouteElements();
  683.             foreach ($routeElements as $routeElement) {
  684.                 $routing $routeElement->getRouting();
  685.                 if (!empty($routing['tollcost'])) {
  686.                     $totalValue += $routing['distancecost'];
  687.                 }
  688.             }
  689.         }
  690.         if ($formatted == false) {
  691.             return $totalValue;
  692.         }
  693.         return number_format($totalValue2',''.');
  694.     }
  695.     public function getTotalTollCosts($formatted false)
  696.     {
  697.         $totalValue 0;
  698.         /**
  699.          * @var RouteDay[] $routeDays
  700.          */
  701.         $routeDays $this->getRouteDays();
  702.         foreach ($routeDays as $routeDay) {
  703.             /**
  704.              * @var RouteElement[] $routeElements
  705.              */
  706.             $routeElements $routeDay->getRouteElements();
  707.             foreach ($routeElements as $routeElement) {
  708.                 $routing $routeElement->getRouting();
  709.                 
  710.                 if(!empty($routing['tollcost'])) {
  711.                     $totalValue += $routing['tollcost'];
  712.                 }
  713.             }
  714.         }
  715.         if ($formatted == false) {
  716.             return $totalValue;
  717.         }
  718.         return number_format($totalValue2',''.');
  719.     }
  720.     public function getTotalCosts($formatted false)
  721.     {
  722.         $totalValue $this->getTotalTollCosts() + $this->getTotalDistanceCosts();
  723.         if ($formatted == false) {
  724.             return $totalValue;
  725.         }
  726.         return number_format($totalValue2',''.');
  727.     }
  728.     /**
  729.      * When Batch Processing is enabled, no route refresh is done on route
  730.      *
  731.      * @return void
  732.      */
  733.     public function enableBatchProcessing()
  734.     {
  735.         $this->batchProcessing true;
  736.     }
  737.     public function disableBatchProcessing()
  738.     {
  739.         $this->batchProcessing false;
  740.         $this->refresh();
  741.     }
  742.     public function isBatchProcessing()
  743.     {
  744.         return $this->batchProcessing;
  745.     }
  746. }