Найдите людей поблизости с помощью PHP

задняя часть PHP

Недавно был использован бизнес-сценарий для поиска людей поблизости, поэтому я проверил соответствующую информацию и сделал техническое резюме различных способов и конкретных реализаций использования PHP для достижения связанных функций.Я приветствую ваши комментарии и исправления.Приступим к точка:

LBS (услуги на основе определения местоположения)

Поиск людей поблизости имеет более широкий собственный термин, называемый LBS (Location-Based Service).LBS относится к получению информации о местоположении пользователей мобильных терминалов через сеть радиосвязи операторов мобильной связи или внешние методы позиционирования.При поддержке платформы это это бизнес с добавленной стоимостью, который предоставляет соответствующие услуги пользователям. Таким образом, местоположение пользователя должно быть получено в первую очередь.Местоположение пользователя может быть получено с помощью GPS, базовой станции оператора, WIFI и т. д. Как правило, клиент получает координаты широты и долготы местоположения пользователя и загружает их на сервер приложений. Сервер приложений сохраняет координаты пользователя, а клиент При получении данных о находящихся поблизости людях сервер приложений обращается к базе данных для проверки и сортировки с учетом географического положения запрашивающего и определенных условий (расстояние, пол, время активности, и т.д.).

Как найти расстояние между двумя точками по широте и долготе?

Все мы знаем, что координаты двух точек в плоских координатах можно вычислить с помощью формулы расстояния в плоских координатах, но широта и долгота — это сферическая система координат, которая использует сферическую поверхность трехмерного пространства для определения пространства на плоскости. Предполагая, что Земля является идеальной сферой, формула расчета сферического расстояния выглядит следующим образом:

d(x1,y1,x2,y2)=r*arccos(sin(x1)*sin(x2)+cos(x1)*cos(x2)*cos(y1-y2))

Эта статья рекомендуется для тех, кто интересуется конкретным процессом вывода:Рассчитать расстояние между двумя точками на земле на основе широты и долготы - математическая формула и вывод

Код функции PHP выглядит следующим образом:

/**
     * 根据两点间的经纬度计算距离
     * @param $lat1
     * @param $lng1
     * @param $lat2
     * @param $lng2
     * @return float
     */
    public static function getDistance($lat1, $lng1, $lat2, $lng2){
        $earthRadius = 6367000; //approximate radius of earth in meters
        $lat1 = ($lat1 * pi() ) / 180;
        $lng1 = ($lng1 * pi() ) / 180;
        $lat2 = ($lat2 * pi() ) / 180;
        $lng2 = ($lng2 * pi() ) / 180;
        $calcLongitude = $lng2 - $lng1;
        $calcLatitude = $lat2 - $lat1;
        $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
        $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
        $calculatedDistance = $earthRadius * $stepTwo;
        return round($calculatedDistance);
    }

Код MySQL выглядит следующим образом:

SELECT  
  id, (  
    3959 * acos (  
      cos ( radians(78.3232) )  
      * cos( radians( lat ) )  
      * cos( radians( lng ) - radians(65.3234) )  
      + sin ( radians(78.3232) )  
      * sin( radians( lat ) )  
    )  
  ) AS distance  
FROM markers  
HAVING distance < 30  
ORDER BY distance  
LIMIT 0 , 20;   

В дополнение к вышесказанному, полученному путем вычисления формулы сферического расстояния, мы можем использовать некоторые сервисы баз данных, такие как Redis и MongoDB:

Redis 3.2 предоставляет функцию географического местоположения GEO, которая может не только получить расстояние между двумя местоположениями, но и получить набор местоположений с географической информацией в пределах указанного диапазона местоположений.Документация по командам Redis

1. Увеличьте географическое положение

GEOADD key longitude latitude member [longitude latitude member ...]

2. Получите географическое положение

GEOPOS key member [member ...]

3. Получите расстояние между двумя географическими точками

GEODIST key member1 member2 [unit]

4. Получите коллекцию географической информации о местоположении указанной широты и долготы.

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

5. Получите набор географических сведений о указанном члене.

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

MongoDB создала геопространственные индексы специально для такого рода запросов. Индексы 2d и 2dsphere для плоскости и сферы соответственно.Документы MongoDB

1. Добавьте данные

db.location.insert( {uin : 1 , loc : { lon : 50 , lat : 50 } } )

2. Создайте индекс

db.location.ensureIndex( { loc : "2d" } )

3. Найдите ближайшие точки

db.location.find( { loc :{ $near : [50, 50] } )

4. Максимальное расстояние и предельное количество баров

db.location.find( { loc : { $near : [50, 50] , $maxDistance : 5 } } ).limit(20)

5. Используйте geoNear, чтобы вернуть расстояние каждой точки от точки запроса в результате запроса.

db.runCommand( { geoNear : "location" , near : [ 50 , 50 ], num : 10, query : { type : "museum" } } )

6. Используйте geoNear с условиями запроса и количеством возвращаемых элементов.GeoNear не поддерживает функцию лимита пейджинга и параметров пропуска в поисковом запросе с помощью команды runCommand.

db.runCommand( { geoNear : "location" , near : [ 50 , 50 ], num : 10, query : { uin : 1 } })

Несколько способов PHP и конкретная реализация

1. На основе MySql

Метод добавления участника:

public function geoAdd($uin, $lon, $lat)
{
    $pdo = $this->getPdo();
    $sql = 'INSERT INTO `markers`(`uin`, `lon`, `lat`) VALUES (?, ?, ?)';
    $stmt = $pdo->prepare($sql);
    return $stmt->execute(array($uin, $lon, $lat));
}

Запрос людей поблизости (поддержка условий запроса и разбиения на страницы):

public function geoNearFind($lon, $lat, $maxDistance = 0, $where = array(), $page = 0)
{
    $pdo = $this->getPdo();
    $sql = "SELECT  
              id, (  
                3959 * acos (  
                  cos ( radians(:lat) )  
                  * cos( radians( lat ) )  
                  * cos( radians( lon ) - radians(:lon) )  
                  + sin ( radians(:lat) )  
                  * sin( radians( lat ) )  
                )  
              ) AS distance  
            FROM markers";

    $input[':lat'] = $lat;
    $input[':lon'] = $lon;

    if ($where) {
        $sqlWhere = ' WHERE ';
        foreach ($where as $key => $value) {
            $sqlWhere .= "`{$key}` = :{$key} ,";
            $input[":{$key}"] = $value;
        }
        $sql .= rtrim($sqlWhere, ',');
    }

    if ($maxDistance) {
        $sqlHaving = " HAVING distance < :maxDistance";
        $sql .= $sqlHaving;
        $input[':maxDistance'] = $maxDistance;
    }

    $sql .= ' ORDER BY distance';

    if ($page) {
        $page > 1 ? $offset = ($page - 1) * $this->pageCount : $offset = 0;
        $sqlLimit = " LIMIT {$offset} , {$this->pageCount}";
        $sql .= $sqlLimit;
    }

    $stmt = $pdo->prepare($sql);
    $stmt->execute($input);
    $list = $stmt->fetchAll(PDO::FETCH_ASSOC);

    return $list;
}

2. На основе Redis (выше 3.2)

PHP можно установить с помощью Redisredisрасширение или установить через композиторpredisБиблиотека классов, в этой статье для реализации используется расширение Redis.

Метод добавления участника:

public function geoAdd($uin, $lon, $lat)
{
    $redis = $this->getRedis();
    $redis->geoAdd('markers', $lon, $lat, $uin);
    return true;
}

Запрос людей поблизости (условия запроса и пагинация не поддерживаются):

public function geoNearFind($uin, $maxDistance = 0, $unit = 'km')
{
    $redis = $this->getRedis();
    $options = ['WITHDIST']; //显示距离
    $list = $redis->geoRadiusByMember('markers', $uin, $maxDistance, $unit, $options);
    return $list;
}

3. На основе MongoDB

PHP использует расширения MongoDB сmongo(Документацияmongodb( Документация), методы написания двух очень разные.Чтобы выбрать хорошее расширение, вам нужно просмотреть соответствующий документ.Поскольку расширение mongodb является новой версией, в этой статье выбирается расширение mongodb.

Предположим, мы создаем библиотеку БД и коллекцию местоположений.

Установить индекс:

db.getCollection('location').ensureIndex({"uin":1},{"unique":true}) 
db.getCollection('location').ensureIndex({loc:"2d"})
#若查询位置附带查询,可以将常查询条件添加至组合索引
#db.getCollection('location').ensureIndex({loc:"2d",uin:1})

Метод добавления участника:

public function geoAdd($uin, $lon, $lat)
{
    $document = array(
        'uin' => $uin,
        'loc' => array(
            'lon' =>  $lon,
            'lat' =>  $lat,
        ),
    );

    $bulk = new MongoDB\Driver\BulkWrite;
    $bulk->update(
        ['uin' => $uin],
        $document,
        [ 'upsert' => true]
    );
    //出现noreply 可以改成确认式写入
    $manager = $this->getMongoManager();
    $writeConcern = new MongoDB\Driver\WriteConcern(1, 100);
    //$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 100);
    $result = $manager->executeBulkWrite('db.location', $bulk, $writeConcern);

    if ($result->getWriteErrors()) {
        return false;
    }
    return true;
}  

Запрос людей поблизости (в возвращаемых результатах нет расстояния, поддержка условий запроса, поддержка пейджинга)

public function geoNearFind($lon, $lat, $maxDistance = 0, $where = array(), $page = 0)
{
    $filter = array(
        'loc' => array(
            '$near' => array($lon, $lat),
        ),
    );
    if ($maxDistance) {
        $filter['loc']['$maxDistance'] = $maxDistance;
    }
    if ($where) {
        $filter = array_merge($filter, $where);
    }
    $options = array();
    if ($page) {
        $page > 1 ? $skip = ($page - 1) * $this->pageCount : $skip = 0;
        $options = [
            'limit' => $this->pageCount,
            'skip' => $skip
        ];
    }

    $query = new MongoDB\Driver\Query($filter, $options);
    $manager = $this->getMongoManager();
    $cursor = $manager->executeQuery('db.location', $query);
    $list = $cursor->toArray();
    return $list;
}

Запрос людей поблизости (возврат результатов с расстоянием, поддержка условий запроса, оплата возвращенного количества, не поддержка пейджинга):

public function geoNearFindReturnDistance($lon, $lat, $maxDistance = 0, $where = array(), $num = 0)
{
    $params = array(
        'geoNear' => "location",
        'near' => array($lon, $lat),
        'spherical' => true, // spherical设为false(默认),dis的单位与坐标的单位保持一致,spherical设为true,dis的单位是弧度
        'distanceMultiplier' => 6371, // 计算成公里,坐标单位distanceMultiplier: 111。 弧度单位 distanceMultiplier: 6371
    );

    if ($maxDistance) {
        $params['maxDistance'] = $maxDistance;
    }
    if ($num) {
        $params['num'] = $num;
    }
    if ($where) {
        $params['query'] = $where;
    }

    $command = new MongoDB\Driver\Command($params);
    $manager = $this->getMongoManager();
    $cursor = $manager->executeCommand('db', $command);
    $response = (array) $cursor->toArray()[0];
    $list = $response['results'];
    return $list;
}

Меры предосторожности:

1. Выберите хорошее расширение, написание расширений mongo и mongodb сильно отличается

2. Если при записи данных появляется noreply, проверьте уровень подтверждения записи

3. Данные, запрашиваемые с помощью find, должны вычислять расстояние сами по себе, а запрос с использованием geoNear не поддерживает пейджинг.

4. Расстояние, запрашиваемое с помощью geoNear, необходимо преобразовать в км с использованием параметров сферического и DistanceMultiplier.

Вышеприведенную демонстрацию можно нажать здесь:demo

Суммировать

Выше представлены три способа реализации функции опроса людей поблизости.Каждый метод имеет свои применимые сценарии.Например,строк данных мало,таких как запрос расстояния между пользователем и несколькими городами.Достаточно использовать Mysql. При необходимости можно использовать Redis для быстрого ответа в режиме реального времени и расстояния в пределах нормального диапазона поиска, но если объем данных большой и есть несколько условий фильтрации по атрибутам, удобнее использовать mongo. предложения Конкретный план реализации должен быть пересмотрен в соответствии с конкретным бизнесом.