Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/grails/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP/MySQL中的地理搜索(距离)(性能)_Php_Mysql_Performance_Distance_Gis - Fatal编程技术网

PHP/MySQL中的地理搜索(距离)(性能)

PHP/MySQL中的地理搜索(距离)(性能),php,mysql,performance,distance,gis,Php,Mysql,Performance,Distance,Gis,我有一个MySQL表(MyISAM),其中包含大约200k个我从中选择的lat/long对条目,基于另一个lat/long对的对距离(大圆公式)。(例如,在50.281852和2.504883周围10km半径范围内的所有入口) 我的问题是这个查询大约需要0,28秒。只为那20万个条目运行(每天都会继续获得更多)。而0,28秒。正常情况下,这个查询运行得非常频繁,因为它为我的web应用程序的主要功能提供了动力,而且常常是更大查询的一部分 有没有办法加快速度?显然,MySQL每次都必须运行所有200

我有一个MySQL表(MyISAM),其中包含大约200k个我从中选择的lat/long对条目,基于另一个lat/long对的对距离(大圆公式)。(例如,在50.281852和2.504883周围10km半径范围内的所有入口)

我的问题是这个查询大约需要0,28秒。只为那20万个条目运行(每天都会继续获得更多)。而0,28秒。正常情况下,这个查询运行得非常频繁,因为它为我的web应用程序的主要功能提供了动力,而且常常是更大查询的一部分

有没有办法加快速度?显然,MySQL每次都必须运行所有200k条目的数据,并对每个条目执行大循环公式。我在Stack Overflow上读到了一些关于geohashing、R树之类的东西,但我认为这不是我想要的方式。部分原因是我从来都不是一个数学迷,但主要原因是我认为这个问题已经由一个比我更聪明的人在图书馆/分馆/等处解决了。这个图书馆/分馆已经过广泛的测试,并定期更新

MySQL似乎有一个空间扩展,但它不提供距离函数。我应该看看另一个数据库,把这个坐标对放进去吗?PostgreSQL似乎有一个相当成熟的空间扩展。你知道这件事吗?或者PostgreSQL会仅仅使用大圈公式来获取某个区域内的所有条目吗

是否有专门的独立产品或mysql扩展已经满足了我的需求

或者是否有一个PHP库可以用来进行计算?使用APC,我可以很容易地将lat-long对放入内存(这200k个条目大约需要5MB),然后在PHP内部运行查询。然而,这种方法的问题是,我会有一个类似SELECT的MySQL查询。。从…起其中id在(id1,id2,…)中表示所有结果,这些结果最多可以有几千个。MySQL处理此类查询的能力如何?然后(因为这是一个数字运算任务),在PHP中这样做是否足够快

还有其他想法吗?我应该/不应该做什么

为了完整起见,这里是示例查询,去掉了任何不相关的部分(如我所说,通常这是我连接多个表的更大查询的一部分):

选择id,
6371*acos(sin(弧度(52.4042924))*sin(弧度(纬度))+cos(弧度(50.281852))*cos(弧度(纬度))*cos(弧度(纬度))作为dst
来自geoloc

有了dst你可以试试四键。这是一个空间索引,可以减少维度。它将地图细分为平铺,但您可以使用它来存储点。您可以下载我的php类hilbert curve@phpclasses.org。它还包括一条z曲线和一条摩尔曲线。重要的是要知道它使用墨卡托投影。您可以查找Bing地图平铺。它解释了如何使用四键。您需要x、y坐标和z(缩放或深度)值。然后它会给你一个四键。

如果你从不同的角度来处理这个问题呢

直线上的10 km为:

  • 纬度等于~1'(分钟)
  • 在经度上等于~6'(分钟)
  • 以此为基础,进行一些快速计算,并在查询中添加到
    WHERE
    子句中,删除通过添加缓冲区(假设长度为1'lat&6'而创建的“框”之外的任何位置

    使用此图像:

  • 您正在搜索的GPS位置(34°12'34.0“,-85°1'1.0”)[34.20944444,-85.01694444]

  • 您可以找到最小/最大纬度/经度

    2a。最小纬度-34.19277778,-85.0169444444

    2b。最小经度-34.209444,-85.116944444

    2c。最大纬度-34.22611111,-85.016944444

    2d。最大经度-34.209444,-84.91694444

  • 使用每个方向的最小值和最大值运行查询

    SELECT *
    
    FROM geoloc
    
    WHERE
    
    lat >= 34.1927777 AND
    
    lat <= 34.2261111 AND
    
    long >= -85.1169444 AND
    
    long <= -84.9169444;
    
    更新


    我忘了提到,我的距离函数将返回以英尺为单位的距离。

    计算一个边界框,以在SQL查询的WHERE子句中选择行的子集,这样您只对该行子集执行昂贵的距离计算,而不是对表中的整个200k记录执行距离计算。本文介绍了该方法(使用PHP代码示例)。然后,您可以在查询中包含针对该子集的Haversine计算,以计算实际距离,并在HAVING子句中考虑该点

    边界框有助于提高性能,因为它意味着您只需对数据的一小部分进行昂贵的距离计算。这实际上与Patrick建议的方法相同,但Movable Type链接对该方法有大量的解释,以及可以用来构建边界框和SQL查询的PHP代码

    编辑

    如果你认为哈弗辛不够准确,那么还有文森蒂公式

    //  Vincenty formula to calculate great circle distance between 2 locations expressed as Lat/Long in KM
    
    function VincentyDistance($lat1,$lat2,$lon1,$lon2){
        $a = 6378137 - 21 * sin($lat1);
        $b = 6356752.3142;
        $f = 1/298.257223563;
    
        $p1_lat = $lat1/57.29577951;
        $p2_lat = $lat2/57.29577951;
        $p1_lon = $lon1/57.29577951;
        $p2_lon = $lon2/57.29577951;
    
        $L = $p2_lon - $p1_lon;
    
        $U1 = atan((1-$f) * tan($p1_lat));
        $U2 = atan((1-$f) * tan($p2_lat));
    
        $sinU1 = sin($U1);
        $cosU1 = cos($U1);
        $sinU2 = sin($U2);
        $cosU2 = cos($U2);
    
        $lambda = $L;
        $lambdaP = 2*M_PI;
        $iterLimit = 20;
    
        while(abs($lambda-$lambdaP) > 1e-12 && $iterLimit>0) {
            $sinLambda = sin($lambda);
            $cosLambda = cos($lambda);
            $sinSigma = sqrt(($cosU2*$sinLambda) * ($cosU2*$sinLambda) + ($cosU1*$sinU2-$sinU1*$cosU2*$cosLambda) * ($cosU1*$sinU2-$sinU1*$cosU2*$cosLambda));
    
            //if ($sinSigma==0){return 0;}  // co-incident points
            $cosSigma = $sinU1*$sinU2 + $cosU1*$cosU2*$cosLambda;
            $sigma = atan2($sinSigma, $cosSigma);
            $alpha = asin($cosU1 * $cosU2 * $sinLambda / $sinSigma);
            $cosSqAlpha = cos($alpha) * cos($alpha);
            $cos2SigmaM = $cosSigma - 2*$sinU1*$sinU2/$cosSqAlpha;
            $C = $f/16*$cosSqAlpha*(4+$f*(4-3*$cosSqAlpha));
            $lambdaP = $lambda;
            $lambda = $L + (1-$C) * $f * sin($alpha) * ($sigma + $C*$sinSigma*($cos2SigmaM+$C*$cosSigma*(-1+2*$cos2SigmaM*$cos2SigmaM)));
        }
    
        $uSq = $cosSqAlpha*($a*$a-$b*$b)/($b*$b);
        $A = 1 + $uSq/16384*(4096+$uSq*(-768+$uSq*(320-175*$uSq)));
        $B = $uSq/1024 * (256+$uSq*(-128+$uSq*(74-47*$uSq)));
    
        $deltaSigma = $B*$sinSigma*($cos2SigmaM+$B/4*($cosSigma*(-1+2*$cos2SigmaM*$cos2SigmaM)- $B/6*$cos2SigmaM*(-3+4*$sinSigma*$sinSigma)*(-3+4*$cos2SigmaM*$cos2SigmaM)));
    
        $s = $b*$A*($sigma-$deltaSigma);
        return $s/1000;
    }
    
    
    echo VincentyDistance($lat1,$lat2,$lon1,$lon2);
    

    到目前为止,我所做的正是上面所描述的@Mark。我想这是一个适合小型站点的可行解决方案,但对我的案例来说并不那么好(200k个条目位于以给定点为中心的约100x100平方公里的方框内。我使用了Mark的相同技巧,但性能太差。5个用户/秒查询附近的lat/lon点几个小时,查询开始需要10-15秒;这是在我调整my.cnf中的mySQL设置后发生的。不要使用e。)我想想想当全世界有200万参赛作品时会发生什么

    那么,现在是进行第2步的时候了:。 它应该通过在一列上使用一个索引(hilbert_数)来解决(lat,lon)列上的B树索引浪费(onrange扫描,仅使用了B树索引的一部分)的问题。hilbert_数是基于hilbert曲线上点的lat/lon坐标计算的数字

    但是第二次公关
    //  Vincenty formula to calculate great circle distance between 2 locations expressed as Lat/Long in KM
    
    function VincentyDistance($lat1,$lat2,$lon1,$lon2){
        $a = 6378137 - 21 * sin($lat1);
        $b = 6356752.3142;
        $f = 1/298.257223563;
    
        $p1_lat = $lat1/57.29577951;
        $p2_lat = $lat2/57.29577951;
        $p1_lon = $lon1/57.29577951;
        $p2_lon = $lon2/57.29577951;
    
        $L = $p2_lon - $p1_lon;
    
        $U1 = atan((1-$f) * tan($p1_lat));
        $U2 = atan((1-$f) * tan($p2_lat));
    
        $sinU1 = sin($U1);
        $cosU1 = cos($U1);
        $sinU2 = sin($U2);
        $cosU2 = cos($U2);
    
        $lambda = $L;
        $lambdaP = 2*M_PI;
        $iterLimit = 20;
    
        while(abs($lambda-$lambdaP) > 1e-12 && $iterLimit>0) {
            $sinLambda = sin($lambda);
            $cosLambda = cos($lambda);
            $sinSigma = sqrt(($cosU2*$sinLambda) * ($cosU2*$sinLambda) + ($cosU1*$sinU2-$sinU1*$cosU2*$cosLambda) * ($cosU1*$sinU2-$sinU1*$cosU2*$cosLambda));
    
            //if ($sinSigma==0){return 0;}  // co-incident points
            $cosSigma = $sinU1*$sinU2 + $cosU1*$cosU2*$cosLambda;
            $sigma = atan2($sinSigma, $cosSigma);
            $alpha = asin($cosU1 * $cosU2 * $sinLambda / $sinSigma);
            $cosSqAlpha = cos($alpha) * cos($alpha);
            $cos2SigmaM = $cosSigma - 2*$sinU1*$sinU2/$cosSqAlpha;
            $C = $f/16*$cosSqAlpha*(4+$f*(4-3*$cosSqAlpha));
            $lambdaP = $lambda;
            $lambda = $L + (1-$C) * $f * sin($alpha) * ($sigma + $C*$sinSigma*($cos2SigmaM+$C*$cosSigma*(-1+2*$cos2SigmaM*$cos2SigmaM)));
        }
    
        $uSq = $cosSqAlpha*($a*$a-$b*$b)/($b*$b);
        $A = 1 + $uSq/16384*(4096+$uSq*(-768+$uSq*(320-175*$uSq)));
        $B = $uSq/1024 * (256+$uSq*(-128+$uSq*(74-47*$uSq)));
    
        $deltaSigma = $B*$sinSigma*($cos2SigmaM+$B/4*($cosSigma*(-1+2*$cos2SigmaM*$cos2SigmaM)- $B/6*$cos2SigmaM*(-3+4*$sinSigma*$sinSigma)*(-3+4*$cos2SigmaM*$cos2SigmaM)));
    
        $s = $b*$A*($sigma-$deltaSigma);
        return $s/1000;
    }
    
    
    echo VincentyDistance($lat1,$lat2,$lon1,$lon2);