Недавно был использован бизнес-сценарий для поиска людей поблизости, поэтому я проверил соответствующую информацию и сделал техническое резюме различных способов и конкретных реализаций использования PHP для достижения связанных функций.Я приветствую ваши комментарии и исправления.Приступим к точка:
LBS (услуги на основе определения местоположения)
Поиск людей поблизости имеет более широкий собственный термин, называемый LBS (Location-Based Service).LBS относится к получению информации о местоположении пользователей мобильных терминалов через сеть радиосвязи операторов мобильной связи или внешние методы позиционирования.При поддержке платформы это это бизнес с добавленной стоимостью, который предоставляет соответствующие услуги пользователям. Таким образом, местоположение пользователя должно быть получено в первую очередь.Местоположение пользователя может быть получено с помощью GPS, базовой станции оператора, WIFI и т. д. Как правило, клиент получает координаты широты и долготы местоположения пользователя и загружает их на сервер приложений. Сервер приложений сохраняет координаты пользователя, а клиент При получении данных о находящихся поблизости людях сервер приложений обращается к базе данных для проверки и сортировки с учетом географического положения запрашивающего и определенных условий (расстояние, пол, время активности, и т.д.).
Как найти расстояние между двумя точками по широте и долготе?
Все мы знаем, что координаты двух точек в плоских координатах можно вычислить с помощью формулы расстояния в плоских координатах, но широта и долгота — это сферическая система координат, которая использует сферическую поверхность трехмерного пространства для определения пространства на плоскости. Предполагая, что Земля является идеальной сферой, формула расчета сферического расстояния выглядит следующим образом:
Эта статья рекомендуется для тех, кто интересуется конкретным процессом вывода:Рассчитать расстояние между двумя точками на земле на основе широты и долготы - математическая формула и вывод
Код функции 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. предложения Конкретный план реализации должен быть пересмотрен в соответствии с конкретным бизнесом.