vkr / geolocation-bundle
为Symfony2/3提供的快速获取地理位置数据的最近点的包
Requires
- php: >=5.6
- doctrine/doctrine-bundle: ~1.4
- doctrine/orm: >=2.2.3
- symfony/symfony: ~2.8|~3.0
Requires (Dev)
- phpunit/phpunit: >=5.4
This package is auto-updated.
Last update: 2024-09-20 23:39:04 UTC
README
此包是为了解决我遇到的一个相当棘手的问题而创建的 - 在大量数据中查找与点N最近的点。有一个相当复杂的公式可以帮助我们做到这一点,然而,在数据库查询中使用它是一个非常糟糕的想法 - 你会杀死你的数据库。
此包通过创建一个两步算法来解决此问题,首先执行一个简单的数据库查询,查找任何可能最近的点,然后使用脚本应用公式并在这些点中选择一个“赢家”。
此包包含三个服务,您可以使用它们分别进行步骤#1、步骤#2以及与Doctrine通信。该包依赖于Symfony和Doctrine,但是,如果您不希望激活第三个服务,则实际上不需要使用Doctrine。
安装
此处没有要安装的内容,只需在AppKernel.php
中激活该包即可。
使用
检索边界框
首先,我们需要创建一个边界框,这是一个土地的矩形块,然后找到所有位于该框内的点。
我假设您的所有地理空间数据都使用纬度和经度的度数。
要创建边界框,我们需要知道正方形的边长。由于我们所有的数据都是以度为单位,我们将使用度作为该测量的单位。然而,经度的度对应于不同的距离,这取决于纬度。因此,我们将使用不可变的纬度度来测量该框。一度的纬度等于111.375公里。
以下是围绕给定纬度和经度的点创建框的方法。
$boundingBoxFinder = $this->get('vkr_geolocation.bounding_box_finder');
$lat = 40;
$lng = -100;
$allowance = 0.5;
$boundingBox = $this->boundingBoxFinder->setBoundingBox($lat, $lng, $allowance);
这里有一个边长大约55公里(半度纬度)的方形,中心有一个点(40,-100)。
所需的容许范围取决于您的数据源中点的密集程度。例如,如果您在都市区搜索邮编,您不会希望使用超过0.1的容许范围。
如果您位于国际日期变更线(经度+180/-180)附近,该服务会考虑这一点,并且仍然会给出一个完美的方形框。
如果您不希望使用BoundingBoxFinder
服务,您可以自己创建BoundingBox
对象,根据以下规则
lat
属性由一个包含min
和max
值的数组组成;lng
属性由一个包含一个或多个包含min
和max
值的数组的数组组成。
示例
$latPair = [
'min' => 30,
'max' => 35,
];
$lngPairs = [
[
'min' => '-100',
'max' => '-95',
],
];
$boundingBox = new VKR\GeolocationBundle\Entity\Perishable\BoundingBox($latPair, $lngPairs);
查询数据库
有了边界框后,我们需要将其内容解析为DQL查询。这就是doctrine_querier
服务的作用。但是,您需要定义一个数据源实体来使用它。实体必须符合VKR\GeolocationBundle\Interfaces\GeolocatableEntityInterface
,它定义了getLat()
和getLng()
方法。该服务将收集所有位于您的边界框内的点。
$doctrineQuerier = $this->get('vkr_geolocation.doctrine_querier');
$result = $doctrineQuerier->getRecords($boundingBox, YourEntity::class);
$result
的内容与您通常从Doctrine中的getResult()
得到的内容没有区别。
有时您可能希望获取边界框中遇到的非唯一字段的全部值。例如,您有一个包含邮编的数据库,但您只对城市感兴趣。如果是这种情况,您可以使用getDistinctRecords()
$result = $doctrineQuerier->getDistinctRecords($boundingBox, YourEntity::class, 'city');
您将得到一个城市名称的零索引数组。
计算最近点
最后,我们可以测试查询结果中哪个点是最接近的一个。
$calculator = $this->get('vkr_geolocation.nearest_point_calculator');
$index = $calculator->findNearestPoint($lat, $lng, $result);
$nearestPointCoords = [
'lat' => $result[$index]->getLat(),
'lng' => $result[$index]->getLng(),
];
这就完成了。
可选:定义实体管理器
如果你使用具有多个实体管理器的设置,你可以在 config.yml
中定义一个可选参数。假设你的管理器被命名为 "my",那么在 config.yml
中添加以下内容:
vkr_geolocation:
entity_manager_service: "doctrine.orm.my_entity_manager"
这样,doctrine_querier
服务将使用指定的实体管理器查询数据库。
API
VKR\GeolocationBundle\Entity\Perishable\BoundingBox BoundingBoxFinder::setBoundingBox(float $lat, float $lng, float $allowanceLat)
void DoctrineQuerier::__construct(Doctrine\ORM\EntityManager $em)
array|null DoctrineQuerier::getRecords(VKR\GeolocationBundle\Entity\Perishable\BoundingBox $boundingBox, string $entityClassName)
array DoctrineQuerier::getDistinctRecords(VKR\GeolocationBundle\Entity\Perishable\BoundingBox $boundingBox, string $entityClassName, string $fieldName)
int NearestPointCalculator::findNearestPoint(float $lat, float $lng, array $valueList)
第三个参数必须是包含 VKR\GeolocationBundle\Interfaces\GeolocatableEntityInterface
对象的零索引数组。