vkr/geolocation-bundle

为Symfony2/3提供的快速获取地理位置数据的最近点的包

安装: 149

依赖者: 0

建议者: 0

安全: 0

星标: 3

关注者: 2

分支: 0

开放问题: 2

类型:symfony-bundle

1.1.1 2017-04-08 12:53 UTC

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属性由一个包含minmax值的数组组成;
  • lng属性由一个包含一个或多个包含minmax值的数组的数组组成。

示例

$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 对象的零索引数组。