[Решено] Ближайшие точки (ноды) по координатам

Главные вкладки

Аватар пользователя mozh mozh 4 января 2016 в 12:08

Имеются ноды с полями широты и долготы. При открытии конкретной ноды, хотелось бы показать ближайшие точки (ноды) отобрав их по широте и долготе.
Есть мысли рассчитать некую дистанцию от нулевой широты и долготы до текущей ноды. Записать значение ноды в новое поле и на основе этой дистанции брать соседние. Например текущее значение дистанции 100 для ноды (значение просто с головы) и делать выборку нод от 90 до 110 по дистанции.
Вопрос, по какой формуле считать это самое значение дистанции? или может есть другие варианты

Комментарии

Аватар пользователя mozh mozh 4 января 2016 в 12:20

Спасибо, вечером попробую.
а пока нашел вот такую штуку http://sunnyblik.livejournal.com/29267.html и там есть запрос SELECT * FROM `metro` WHERE `x` BETWEEN '0' AND '6' AND `y` BETWEEN '0' AND '6', есть мысли использовать его если ничего не выйдет

Аватар пользователя mozh mozh 4 января 2016 в 13:05

есть одна проблемка, ноды уже заполнены (150 тыс), но вот для координат не использовалось Geolocation Field

Аватар пользователя mozh mozh 4 января 2016 в 13:55

обычные поля текстовые или float... все сделано отметки текущей точки на карте есть, да вот понадобилось соседние выводить((
буду думать как перегнать их в гео филд

Аватар пользователя dgastudio dgastudio 4 января 2016 в 15:50
<?php
  $query 
= new EntityFieldQuery();
  
$result $query->entityCondition('entity_type''node')
    ->
entityCondition('bundle''mycontenttype')
    ->
execute();
  
$nodes node_load_multiple(array_keys($result['node']));
foreach(
$nodes as $node){
 
$old_data $node->field_old_geo['und'][0]['value'];
 
$geo explode(',',$old_date);
 
$node->field_coordinates[LANGUAGE_NONE][0] = geofield_compute_values(
          array(
              
'lat' => $geo[0], 
              
'lon' => $geo[1],
      ), 
GEOFIELD_INPUT_LAT_LON);
}
node_save($node);
?>

как то так

Аватар пользователя mozh mozh 4 января 2016 в 15:52

ок, понял. Спасибо, вечером будут испытания)
только нужно будет разбить думаю на более мелкие операции вот это node_load_multiple, 150 тыс многовато за один раз, как бы не загнулся скрипт

Аватар пользователя Orion76 Orion76 4 января 2016 в 21:24

mozh wrote:

150 тыс многовато за один раз, как бы не загнулся скрипт

hook_action_info и модуль VBO спасут отца русской демократии..

https://api.drupal.org/api/drupal/modules!system!system.api.php/function...
https://www.drupal.org/node/2052067

Объявить в хуке функцию, реализующую action.
Вставить в нее, немного адаптировав, приведенный выше код..
Сделать вьюс для выборки нужных материалов.
Добавить к нему поле VBO и выбрать созданный ранее action.
и запустить action на выполнение.

Аватар пользователя mozh mozh 9 января 2016 в 22:34

В общем на "говнокодил" по другому...)
Сделал выборку 10 значений больше текущих координат с сортировкой по широте и долготе:

<?php$query = db_select('node','n');
                $query->join('field_data_field__coordinates_d','d','d.entity_id = n.nid');
                $query->join('field_data_field__coordinates_s','s','s.entity_id = n.nid');
                $query->fields('n',array('nid'))
                       ->fields('d',array('field__coordinates_d_value'))
                       ->fields('s',array('field__coordinates_s_value'))
                       ->condition('s.field__coordinates_s_value', array($current_s), '>=')
                       ->condition('d.field__coordinates_d_value', array($current_d), '>=')
                       ->condition('n.nid', array($current_nid), '<>')
                       ->range(0, 10)
                       ->orderBy('d.field__coordinates_d_value', 'ASC')
                       ->orderBy('s.field__coordinates_s_value', 'ASC');
                $results_p = $query->execute()->fetchAll();?>

Далее выбрал 10 значений меньше текущих координат:

<?php$query = db_select('node','n');
                $query->join('field_data_field__coordinates_d','d','d.entity_id = n.nid');
                $query->join('field_data_field__coordinates_s','s','s.entity_id = n.nid');
                $query->fields('n',array('nid'))
                       ->fields('d',array('field__coordinates_d_value'))
                       ->fields('s',array('field__coordinates_s_value'))
                       ->condition('s.field__coordinates_s_value', array($current_s), '<=')
                       ->condition('d.field__coordinates_d_value', array($current_d), '<=')
                       ->condition('n.nid', array($current_nid), '<>')
                       ->range(0, 10)
                       ->orderBy('d.field__coordinates_d_value', 'DESC')
                       ->orderBy('s.field__coordinates_s_value', 'DESC');
                $results_m = $query->execute()->fetchAll();?>

После объединил массивы вместе:
<?php$results = array_merge($results_p, $results_m);?>
И начал считать дистанцию между текущей точкой и 20-ти выбранных:

<?phpforeach($results as $result){
                        $dist = round(distance($current_s, $current_d, $result->field__coordinates_s_value, $result->field__coordinates_d_value), 2);
                        //dpm($dist);
                        if(isset($dist)){
                            $distance_a[$result->nid]['nid'] = $result->nid;
                            $distance_a[$result->nid]['dist'] = $dist;
                            $title = 'Title';
                            $distance_a[$result->nid]['title'] = $title;
                        }
                        //
                    }?>

Функция distance выглядит так (возвращает расстояние между точками в км):

<?phpfunction distance($lat1, $lon1, $lat2, $lon2) {
    $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);
    return $km = $dist * 60 * 1.1515 * 1.609344;//без умножения на 1.609344 - будут мили
}?>

В итоге получили массив нод с id и расстоянием (дистанциями) до текущей точки
Далее нужно отсортировать все это по возрастанию дистанции и запихать в вывод foreach
Сортируем пользовательской функцией по ключу двумерного массива dist

<?php//for sort http://php.net/manual/ru/function.array-multisort.php
function array_orderby(){
    $args = func_get_args();
    $data = array_shift($args);
    foreach ($args as $n => $field) {
        if (is_string($field)) {
            $tmp = array();
            foreach ($data as $key => $row)
                $tmp[$key] = $row[$field];
            $args[$n] = $tmp;
            }
    }
    $args[] = &$data;
    call_user_func_array('array_multisort', $args);
    return array_pop($args);
}?>

Вот так вызываем: $distance_a = array_orderby($distance_a, 'dist', SORT_ASC);
Ну и в конце выводим 10 значений из 20:

<?php$count = 1;
                    foreach($distance_a as $distance){
                        $block['content'] .= '<li>'.l($distance['title'].' ('.$distance['dist'].'km)', 'node/'.$distance['nid']).'</li>';
                        if($count >= 10) break;
                        else $count++;
                    }?>

Для большей точности можно выбирать из БД больше значений, а не по 10 больше и меньше текущих координат
+ получаем дистанцию для вывода до ближайших точек
Сейчас думаю по поводу кеша для запросов, ну и выслушаю комменты по коду.
Модуль указанный выше не стал использовать, вышло бы дольше, да и не понимал бы я как это работает
Результат:
Резульатт

Аватар пользователя mozh mozh 10 января 2016 в 0:26

Добавил кеширование т.к. 5-6 джоинов в запросе
Первая загрузка Executed 288 queries in 4297.82 ms. Queries exceeding 5 ms are highlighted. Page execution time was 4668.82 ms.
из кеша Executed 188 queries in 14.22 ms. Queries exceeding 5 ms are highlighted. Page execution time was 313.67 ms.

<?php$cache = cache_get('proximity_location_plus_'.$current_nid, 'cache');
                $results_p = array();
                if (!empty($cache->data)) {
                    $results_p = $cache->data;
                }else{
                //здесь 1-й код из предыдущего моего коммента
                cache_set('proximity_location_plus_'.$current_nid, $results_p, 'cache', CACHE_PERMANENT);
                }?>

и для второго запроса аналогично.
Заодно и с кешированием разобрался наконец-то
UPD тест показал, что стандартный кеш отлично кеширует все запросы страницы и код выше не нужен