
Implementing work with coordinates in sonata-admin
Good day,% habrauser%!
Recently, the task arose to store GPS data in the database with the further possibility of using various mysql geometric functions. Coordinates should be managed from sonata-admin. What came of this can be read under the cut.
The first thing I had to worry about was finding a suitable extension for the doctrine. Having spent some time googling, I found the doctrine2-mysql-spatial library (in the forks you can find versions with pgsql included). Everything is good in it, but there is no support for the ST_ functions (mysql 5.6 and higher). Without hesitation, he made a fork , added the necessary functions and built a package for the composer. I won’t dwell on installing and configuring the library, everything is corny and is in the fileinstallation .
The second is the tuning of the sonata. By some manipulations using a fraction of magic, it was possible to realize the output and storage of data for the corresponding entity.
In the entity class, we need one fake field for pre / post processing of coordinates, getter and setter. In the admin class, overload the create and update methods. So, let's begin.
The coordinates will be stored in a field of type polygon. A sample description of the entity looks like this:
After generation, open the resulting entity class and add there:
Next, open the admin class, which is responsible for the conclusion of the desired entity in the sonata. We go to the configureFormFields method (FormMapper $ formMapper) and add our field, which will be responsible for working with coordinates:
We also overload the base class methods:
The result looks something like this:
In the repository class, you can use the following query:
Thank you all for your attention. Text and code prepared in conjunction with scooty
Recently, the task arose to store GPS data in the database with the further possibility of using various mysql geometric functions. Coordinates should be managed from sonata-admin. What came of this can be read under the cut.
The first thing I had to worry about was finding a suitable extension for the doctrine. Having spent some time googling, I found the doctrine2-mysql-spatial library (in the forks you can find versions with pgsql included). Everything is good in it, but there is no support for the ST_ functions (mysql 5.6 and higher). Without hesitation, he made a fork , added the necessary functions and built a package for the composer. I won’t dwell on installing and configuring the library, everything is corny and is in the fileinstallation .
The second is the tuning of the sonata. By some manipulations using a fraction of magic, it was possible to realize the output and storage of data for the corresponding entity.
In the entity class, we need one fake field for pre / post processing of coordinates, getter and setter. In the admin class, overload the create and update methods. So, let's begin.
The coordinates will be stored in a field of type polygon. A sample description of the entity looks like this:
show code
#Bundle\Resources\config\doctrine\Location.orm.yml
Location:
type: entity
table: Location
repositoryClass: LocationRepository
id:
id:
type: integer
nullable: false
generator:
strategy: AUTO
fields:
title:
type: string
length: 500
nullable: false
area:
type: polygon
nullable: false
After generation, open the resulting entity class and add there:
show code
//Bundle\Entity\Location.php
private $__area;
const COORDS_SEPARATOR = '; ';
const POLYGON_SEPARATOR = '[]';
public function get_Area($raw = false)
{
if ($raw)
{
return $this->__area;
}
$result = array();
if (is_null($this->getArea()))
{
return $result;
}
$rings = $this->getArea()->getRings();
$count_rings = count($rings) -1;
foreach ($rings as $key => $line)
{
foreach ($line->getPoints() as $point)
{
$result[] = $point->getX() . self::COORDS_SEPARATOR . $point->getY();
}
if($count_rings != $key)
{
$result[] = Task::POLYGON_SEPARATOR;
}
}
return $result;
}
public function set_Area($__area) {
$this->__area = $__area;
return $this;
}
Next, open the admin class, which is responsible for the conclusion of the desired entity in the sonata. We go to the configureFormFields method (FormMapper $ formMapper) and add our field, which will be responsible for working with coordinates:
show code
$formMapper
....
->add('__area','sonata_type_native_collection',[
'options'=>['label'=>'Точка(GpsX;GpsY)'],
'allow_add'=>true,
'allow_delete'=>true,
'label' => 'Полигон координат'
])
....
We also overload the base class methods:
show code
public function update($object)
{
$object = $this->prepareTask($object);
return parent::update($object);
}
public function create($object)
{
$object = $this->prepareTask($object);
return parent::create($object);
}
protected function prepareTask($object)
{
$res = array();
if (count($object->get_Area(true)))
{
$flb = $this->getConfigurationPool()->getContainer()->get('session')->getFlashBag();
$i = 0;
foreach ($object->get_Area(true) as $point)
{
if((string) $point === Task::POLYGON_SEPARATOR)
{
if(count($res[$i]) > 2)
{
$this->fillLastPoint($res[$i]);
}
$i++;
continue;
}
if (!preg_match('/[\d]+[.]{0,1}[\d]{0,}' . preg_quote(Task::COORDS_SEPARATOR) . '[\d]+[.]{0,1}[\d]{0,}/', (string) $point))
{
$flb->add(
'danger', 'Не верный формат точки ' . (string) $point . '. Должны быть два целых или дробных числа(разделитель .) через ";пробел"'
);
return $object;
}
list($x, $y) = explode(Task::COORDS_SEPARATOR, $point);
$res[$i][] = new Point($x, $y);
}
foreach ($res as $key => $ring)
{
if(count($ring) < 3)
{
$flb->add(
'danger', "Полигон координат №" . $key + 1 . " не был изменен, т.к. для построение полигона необходимо минимум 3 точки, вы указали лишь " . count($ring) . '.'
);
unset($res[$key]);
continue;
}
}
if(count($res))
{
end($res);
$key = key($res);
$this->fillLastPoint($res[$key]);
$object->setArea(new Polygon($res));
}
}
return $object;
}
private function fillLastPoint(&$arr)
{
if ($arr[0]->getX() !== end($arr)->getX() || $arr[0]->getY() !== end($arr)->getY())
{
$arr[] = $arr[0];
}
}
The result looks something like this:
show picture

In the repository class, you can use the following query:
show code
$query = $this->getEntityManager()
->createQuery(
'SELECT
t
FROM
Bundle:Location t
where
ST_CONTAINS(t.area, Point(:x,:y)) = 1'
)
->setParameter('x', $x)
->setParameter('y', $y)
;
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'CrEOF\Spatial\ORM\Query\GeometryWalker');
$result = $query->getResult();
Thank you all for your attention. Text and code prepared in conjunction with scooty