Доброго времени суток, %habrauser%!
Недавно встала задача, хранить в базе данные GPS с дальнейшей возможностью применения различных геометрических функций mysql. Управление координатами должно осуществляться из sonata-admin. Что из этого получилось можно прочитать под катом.
Первое, чем пришлось озаботиться это найти подходящее расширение для доктрины. Потратив некоторое время на гугление нашёл библиотеку doctrine2-mysql-spatial(в форках можно найти версии с включённым в состав pgsql). Всё в ней хорошо, но вот поддержки функций ST_(mysql 5.6 и выше) нет. Не долго думая сделал форк, добавил нужные функции и соорудил пакет для композера. На установке и настройке библиотеки подробно останавливаться не буду, всё банально и есть в файле установки.
Второе это настройка сонаты. Путём некоторых манипуляций с применением доли магии удалось реализовать вывод и сохранение данных для соответствующей сущности.
В классе сущности нам понадобится одно фэйковое поле для пре/пост обработки координат, геттер и сеттер. В админсокм классе перегрузить методы create и update. Итак, начнём.
Координаты будем хранить в поле типа polygon. Примерное описание сущности выглядит так:
После генерации открываем получившийся класс сущности и добавляем туда:
Далее открываем админский класс, который отвечает за вывод нужной сущности в сонате. Идём в метод configureFormFields(FormMapper $formMapper) и добавляем наше поле, которое будет отвечать за работу с координатами:
Также перегружаем методы базового класса:
Результат выглядит примерно так:
В репозиторном классе можно использовать следующий запрос:
Всем спасибо за внимание. Текст и код подготовлены совместно с хабраюзером scooty
Недавно встала задача, хранить в базе данные GPS с дальнейшей возможностью применения различных геометрических функций mysql. Управление координатами должно осуществляться из sonata-admin. Что из этого получилось можно прочитать под катом.
Первое, чем пришлось озаботиться это найти подходящее расширение для доктрины. Потратив некоторое время на гугление нашёл библиотеку doctrine2-mysql-spatial(в форках можно найти версии с включённым в состав pgsql). Всё в ней хорошо, но вот поддержки функций ST_(mysql 5.6 и выше) нет. Не долго думая сделал форк, добавил нужные функции и соорудил пакет для композера. На установке и настройке библиотеки подробно останавливаться не буду, всё банально и есть в файле установки.
Второе это настройка сонаты. Путём некоторых манипуляций с применением доли магии удалось реализовать вывод и сохранение данных для соответствующей сущности.
В классе сущности нам понадобится одно фэйковое поле для пре/пост обработки координат, геттер и сеттер. В админсокм классе перегрузить методы create и update. Итак, начнём.
Координаты будем хранить в поле типа polygon. Примерное описание сущности выглядит так:
показать код
#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
После генерации открываем получившийся класс сущности и добавляем туда:
показать код
//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;
}
Далее открываем админский класс, который отвечает за вывод нужной сущности в сонате. Идём в метод configureFormFields(FormMapper $formMapper) и добавляем наше поле, которое будет отвечать за работу с координатами:
показать код
$formMapper
....
->add('__area','sonata_type_native_collection',[
'options'=>['label'=>'Точка(GpsX;GpsY)'],
'allow_add'=>true,
'allow_delete'=>true,
'label' => 'Полигон координат'
])
....
Также перегружаем методы базового класса:
показать код
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];
}
}
Результат выглядит примерно так:
показать картинку
В репозиторном классе можно использовать следующий запрос:
показать код
$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();
Всем спасибо за внимание. Текст и код подготовлены совместно с хабраюзером scooty