Covivo/mobicoop

View on GitHub
api/src/Geography/Entity/Direction.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

/**
 * Copyright (c) 2018, MOBICOOP. All rights reserved.
 * This project is dual licensed under AGPL and proprietary licence.
 ***************************
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <gnu.org/licenses>.
 ***************************
 *    Licence MOBICOOP described in the file
 *    LICENSE
 */

namespace App\Geography\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Carpool\Entity\Criteria;
use App\Geography\Service\GeoTools;
use CrEOF\Spatial\PHP\Types\Geometry\LineString;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use CrEOF\Spatial\PHP\Types\Geometry\Polygon;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * A direction : the route to follow between 2 or more addresses using an individual transport mode.
 *
 * @author Sylvain Briat <sylvain.briat@mobicoop.org>
 *
 * @ORM\Entity
 * @ORM\Table(indexes={@ORM\Index(name="IDX_DIRTER", columns={"distance", "duration", "bbox_min_lon", "bbox_min_lat", "bbox_max_lon", "bbox_max_lat"})})
 * @ORM\HasLifecycleCallbacks
 * @ApiResource(
 *      attributes={
 *          "normalization_context"={"groups"={"read"}, "enable_max_depth"="true"},
 *          "denormalization_context"={"groups"={"write"}}
 *      },
 *      collectionOperations={
 *          "search"={
 *              "method"="GET",
 *              "path"="/directions/search",
 *              "normalization_context"={"groups"={"read"}},
 *              "swagger_context" = {
 *                  "tags"={"Geography"},
 *                  "parameters" = {
 *                      {
 *                          "name" = "points",
 *                          "in" = "query",
 *                          "required" = "true",
 *                          "type" = "array",
 *                          "format" = "float",
 *                          "description" = "The points in the form points[x][longitude]&points[x][latitude]"
 *                      }
 *                  }
 *              }
 *          }
 *      },
 *      itemOperations={
 *          "get"={
 *              "swagger_context" = {
 *                  "tags"={"Geography"}
 *              }
 *          }
 *      }
 * )
 */
class Direction
{
    public const DEFAULT_ID = 999999999999;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     * @Groups("read")
     */
    private $id;

    /**
     * @var int the total distance of the direction in meter
     * @ORM\Column(type="integer")
     * @Groups({"read","results","write","mass","thread"})
     */
    private $distance;

    /**
     * @var int the total duration of the direction in seconds
     * @ORM\Column(type="integer")
     * @Groups({"read","results","write","mass","thread"})
     */
    private $duration;

    /**
     * @var int the total ascend of the direction in meter
     * @ORM\Column(type="integer", nullable=true)
     * @Groups({"read","write","mass"})
     */
    private $ascend;

    /**
     * @var int the total descend of the direction in meter
     * @ORM\Column(type="integer", nullable=true)
     * @Groups({"read","write","mass"})
     */
    private $descend;

    /**
     * @var float the minimum longitude of the bounding box of the direction
     * @ORM\Column(type="decimal", precision=10, scale=6, nullable=true)
     * @Groups({"read","write"})
     */
    private $bboxMinLon;

    /**
     * @var float the minimum latitude of the bounding box of the direction
     * @ORM\Column(type="decimal", precision=10, scale=6, nullable=true)
     * @Groups({"read","write"})
     */
    private $bboxMinLat;

    /**
     * @var float the maximum longitude of the bounding box of the direction
     * @ORM\Column(type="decimal", precision=10, scale=6, nullable=true)
     * @Groups({"read","write"})
     */
    private $bboxMaxLon;

    /**
     * @var float the maximum latitude of the bounding box of the direction
     * @ORM\Column(type="decimal", precision=10, scale=6, nullable=true)
     * @Groups({"read","write"})
     */
    private $bboxMaxLat;

    /**
     * @var string the geoJson bounding box of the direction
     * @ORM\Column(type="polygon", nullable=true)
     * @Groups({"read","write"})
     */
    private $geoJsonBbox;

    /**
     * @var null|int the initial bearing of the direction in degrees
     * @ORM\Column(type="integer",nullable=true)
     * @Groups({"read","write"})
     */
    private $bearing;

    /**
     * @var null|string the textual encoded detail of the direction
     * @Groups({"read","write"})
     */
    private $detail;

    /**
     * @var string The geoJson linestring detail of the direction.
     *             Note : the detail should be a MULTIPOINT, but we can't use it as it's not supported by the version of doctrine2 spatial package for mysql 5.7 (?)
     *             Todo : try to create a multipoint custom type for doctrine 2 spatial ?
     * @Groups({"read","write"})
     */
    private $geoJsonDetail;

    /**
     * @var string The simplified geoJson linestring detail of the direction.
     *             Created using the Ramer-Douglas-Peucker algorithm.
     * @ORM\Column(type="linestring", nullable=true)
     * @Groups({"read","write"})
     */
    private $geoJsonSimplified;

    /**
     * @var string the textual encoded snapped waypoints of the direction
     * @Groups({"read","write"})
     */
    private $snapped;

    /**
     * @var string the encoding format of the detail
     * @ORM\Column(type="string", length=45)
     * @Groups({"read","write"})
     */
    private $format;

    /**
     * @var null|int the CO2 emission for this direction
     * @Groups({"read","mass"})
     */
    private $co2;

    /**
     * @var null|array The decoded points (from detail) of the direction as Address objects.
     *                 Can be used to draw the path on a map.
     * @Groups("read")
     */
    private $points;

    /**
     * @var null|array The decoded points (from detail) of the direction as latitude/longitude array.
     *                 Can be used to draw the path on a map.
     * @Groups("read")
     */
    private $directPoints;

    /**
     * @var null|array The decoded snapped waypoints of the direction.
     *                 The snapped waypoints are the mandatory waypoints of the direction.
     *                 These points can slightly differ from the original waypoints as they are given by the router.
     *                 /!\ different than Waypoint entity /!\
     */
    private $snappedWaypoints;

    /**
     * @var null|int[] the duration from the start to the each snapped waypoint
     */
    private $durations;

    /**
     * @var \DateTimeInterface creation date
     *
     * @ORM\Column(type="datetime", nullable=true)
     * @Groups({"read"})
     */
    private $createdDate;

    /**
     * @var \DateTimeInterface updated date
     *
     * @ORM\Column(type="datetime", nullable=true)
     * @Groups({"read"})
     */
    private $updatedDate;

    /**
     * @var null|ArrayCollection the territories of this direction
     *
     * @ORM\ManyToMany(targetEntity="\App\Geography\Entity\Territory")
     */
    private $territories;

    /**
     * @var bool Save the geoJson with the direction.
     *           Used to avoid slow insert/updates for realtime operations.
     */
    private $saveGeoJson;

    /**
     * @var bool Set the possibility to update the detail directly, instead of using an external system.
     *           Used for dynamic carpool where we can construct a direction from scratch (adding points on the fly).
     */
    private $detailUpdatable;

    /**
     * @var ArrayCollection the criterias as driver related to the direction
     * @ORM\OneToMany(targetEntity="\App\Carpool\Entity\Criteria", mappedBy="directionDriver")
     */
    private $criteriaDrivers;

    /**
     * @var ArrayCollection the criterias as passenger related to the direction
     * @ORM\OneToMany(targetEntity="\App\Carpool\Entity\Criteria", mappedBy="directionPassenger")
     */
    private $criteriaPassengers;

    public function __construct()
    {
        $this->id = self::DEFAULT_ID;
        $this->territories = new ArrayCollection();
        $this->criteriaDrivers = new ArrayCollection();
        $this->criteriaPassengers = new ArrayCollection();
        $this->saveGeoJson = true;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setId(int $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getDistance(): int
    {
        return $this->distance;
    }

    public function setDistance(int $distance): self
    {
        $this->distance = $distance;

        return $this;
    }

    public function getDuration(): int
    {
        return $this->duration;
    }

    public function setDuration(int $duration): self
    {
        $this->duration = $duration;

        return $this;
    }

    public function getAscend(): ?int
    {
        return $this->ascend;
    }

    public function setAscend(?int $ascend): self
    {
        $this->ascend = $ascend;

        return $this;
    }

    public function getDescend(): ?int
    {
        return $this->descend;
    }

    public function setDescend(?int $descend): self
    {
        $this->descend = $descend;

        return $this;
    }

    public function getBboxMinLon(): ?float
    {
        return $this->bboxMinLon;
    }

    public function setBboxMinLon(?float $bboxMinLon): self
    {
        $this->bboxMinLon = $bboxMinLon;

        return $this;
    }

    public function getBboxMinLat(): ?float
    {
        return $this->bboxMinLat;
    }

    public function setBboxMinLat(?float $bboxMinLat)
    {
        $this->bboxMinLat = $bboxMinLat;

        return $this;
    }

    public function getBboxMaxLon(): ?float
    {
        return $this->bboxMaxLon;
    }

    public function setBboxMaxLon(?float $bboxMaxLon): self
    {
        $this->bboxMaxLon = $bboxMaxLon;

        return $this;
    }

    public function getBboxMaxLat(): ?float
    {
        return $this->bboxMaxLat;
    }

    public function setBboxMaxLat(?float $bboxMaxLat): self
    {
        $this->bboxMaxLat = $bboxMaxLat;

        return $this;
    }

    public function getGeoJsonBbox()
    {
        return $this->geoJsonBbox;
    }

    public function setGeoJsonBbox($geoJsonBbox): self
    {
        $this->geoJsonBbox = $geoJsonBbox;

        return $this;
    }

    public function getBearing(): ?int
    {
        return $this->bearing;
    }

    public function setBearing(?int $bearing): self
    {
        $this->bearing = $bearing;

        return $this;
    }

    public function getDetail(): ?string
    {
        return $this->detail;
    }

    public function setDetail(string $detail): self
    {
        $this->detail = $detail;

        return $this;
    }

    public function getGeoJsonDetail()
    {
        return $this->geoJsonDetail;
    }

    public function setGeoJsonDetail($geoJsonDetail): self
    {
        $this->geoJsonDetail = $geoJsonDetail;

        return $this;
    }

    public function getGeoJsonSimplified()
    {
        return $this->geoJsonSimplified;
    }

    public function setGeoJsonSimplified($geoJsonSimplified): self
    {
        $this->geoJsonSimplified = $geoJsonSimplified;

        return $this;
    }

    public function getSnapped(): ?string
    {
        return $this->snapped;
    }

    public function setSnapped(?string $snapped): self
    {
        $this->snapped = $snapped;

        return $this;
    }

    public function getFormat(): string
    {
        return $this->format;
    }

    public function setFormat(string $format): self
    {
        $this->format = $format;

        return $this;
    }

    public function getCo2(): ?int
    {
        return $this->co2;
    }

    public function setCo2(?int $co2): self
    {
        $this->co2 = $co2;

        return $this;
    }

    public function getPoints(): ?array
    {
        return $this->points;
    }

    public function setPoints(array $points): self
    {
        $this->points = $points;

        return $this;
    }

    public function getDirectPoints(): ?array
    {
        return $this->directPoints;
    }

    public function setDirectPoints(array $directPoints): self
    {
        $this->directPoints = $directPoints;

        return $this;
    }

    public function getSnappedWaypoints(): ?array
    {
        return $this->snappedWaypoints;
    }

    public function setSnappedWaypoints(array $snappedWaypoints): self
    {
        $this->snappedWaypoints = $snappedWaypoints;

        return $this;
    }

    public function getDurations(): ?array
    {
        return $this->durations;
    }

    public function setDurations(array $durations): self
    {
        $this->durations = $durations;

        return $this;
    }

    public function getCreatedDate(): ?\DateTimeInterface
    {
        return $this->createdDate;
    }

    public function setCreatedDate(\DateTimeInterface $createdDate): self
    {
        $this->createdDate = $createdDate;

        return $this;
    }

    public function getUpdatedDate(): ?\DateTimeInterface
    {
        return $this->updatedDate;
    }

    public function setUpdatedDate(\DateTimeInterface $updatedDate): self
    {
        $this->updatedDate = $updatedDate;

        return $this;
    }

    public function hasSaveGeoJson(): ?bool
    {
        return $this->saveGeoJson;
    }

    public function setSaveGeoJson(bool $saveGeoJson): self
    {
        $this->saveGeoJson = $saveGeoJson;

        return $this;
    }

    public function getTerritories()
    {
        return $this->territories->getValues();
    }

    public function addTerritory(Territory $territory): self
    {
        if (!$this->territories->contains($territory)) {
            $this->territories[] = $territory;
        }

        return $this;
    }

    public function removeTerritory(Territory $territory): self
    {
        if ($this->territories->contains($territory)) {
            $this->territories->removeElement($territory);
        }

        return $this;
    }

    public function removeTerritories(): self
    {
        $this->territories->clear();

        return $this;
    }

    public function isDetailUpdatable(): ?bool
    {
        return $this->detailUpdatable;
    }

    public function setDetailUpdatable(bool $detailUpdatable): self
    {
        $this->detailUpdatable = $detailUpdatable;

        return $this;
    }

    public function getCriteriaDrivers(bool $getValues = true)
    {
        if ($getValues) {
            return $this->criteriaDrivers->getValues();
        }

        return $this->criteriaDrivers;
    }

    public function addCriteriaDriver(Criteria $criteriaDriver): self
    {
        if (!$this->criteriaDrivers->contains($criteriaDriver)) {
            $this->criteriaDrivers[] = $criteriaDriver;
        }

        return $this;
    }

    public function removeCriteriaDriver(Criteria $criteriaDriver): self
    {
        if ($this->criteriaDrivers->contains($criteriaDriver)) {
            $this->criteriaDrivers->removeElement($criteriaDriver);
        }

        return $this;
    }

    public function getCriteriaPassengers(bool $getValues = true)
    {
        if ($getValues) {
            return $this->criteriaPassengers->getValues();
        }

        return $this->criteriaPassengers;
    }

    public function addCriteriaPassenger(Criteria $criteriaPassenger): self
    {
        if (!$this->criteriaPassengers->contains($criteriaPassenger)) {
            $this->criteriaPassengers[] = $criteriaPassenger;
        }

        return $this;
    }

    public function removeCriteriaPassenger(Criteria $criteriaPassenger): self
    {
        if ($this->criteriaPassengers->contains($criteriaPassenger)) {
            $this->criteriaPassengers->removeElement($criteriaPassenger);
        }

        return $this;
    }

    // DOCTRINE EVENTS

    /**
     * Creation date.
     *
     * @ORM\PrePersist
     */
    public function setAutoCreatedDate()
    {
        $this->setCreatedDate(new \DateTime());
    }

    /**
     * Update date.
     *
     * @ORM\PreUpdate
     */
    public function setAutoUpdatedDate()
    {
        $this->setUpdatedDate(new \DateTime());
    }

    /**
     * GeoJson representation of the bounding box.
     *
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function setAutoGeoJsonBbox()
    {
        if (!is_null($this->getGeoJsonBbox())) {
            return;
        }
        if (!is_null($this->getBboxMinLon()) && !is_null($this->getBboxMinLat()) && !is_null($this->getBboxMaxLon()) && !is_null($this->getBboxMaxLat())) {
            $this->setGeoJsonBbox(new Polygon([[
                [$this->getBboxMinLon(), $this->getBboxMinLat()],
                [$this->getBboxMinLon(), $this->getBboxMaxLat()],
                [$this->getBboxMaxLon(), $this->getBboxMaxLat()],
                [$this->getBboxMaxLon(), $this->getBboxMinLat()],
                [$this->getBboxMinLon(), $this->getBboxMinLat()],
            ]]));
        }
    }

    /**
     * GeoJson representation of the detail.
     *
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function setAutoGeoJsonDetail()
    {
        if (!$this->hasSaveGeoJson()) {
            return;
        }
        if (!is_null($this->getGeoJsonDetail()) && !$this->isDetailUpdatable()) {
            // if (!$this->isDetailUpdatable()) {
            return;
        }
        if (!is_null($this->getPoints())) {
            // $arrayPoints = [];
            // foreach ($this->getPoints() as $address) {
            //     $arrayPoints[] = new Point($address->getLongitude(), $address->getLatitude());
            // }
            // $this->setGeoJsonDetail(new LineString($arrayPoints));
            $arrayPoints = [];
            $geoTools = new GeoTools();
            $simplifiedPoints = $geoTools->getSimplifiedPoints($this->getPoints());
            // var_dump($this->getPoints());
            // var_dump($simplifiedPoints);exit;
            foreach ($simplifiedPoints as $point) {
                $arrayPoints[] = new Point($point[0], $point[1]);
            }
            $this->setGeoJsonSimplified(new LineString($arrayPoints));
        }
    }
}