试试这个代码。我无法测试它,但如果你一步一步地耐心等待它会起作用。如果您有错误或遇到问题,请给我们反馈。可能是,sql 语句中LIMIT 的参数在第一次尝试时不起作用。告诉我们。另外,我使用了以下代码来进行 Haversine 公式和矩形计算:Reverse Geocoding with it loaded into MySQL database?,因为它与您的几乎相同。
一些建议:
- 尝试转到 OOP。
- 始终应用异常处理并激活错误报告和
处理。我只抛出
Exception 给了你一个大致的看法。
通常你应该学习如何扔和处理SPL
(标准 PHP 库)类型。
- 始终使用prepared statements(我知道你已经这样做了;-)。
- 始终阅读 PHP Manual 中的 PHP 函数返回的内容,以便您可以正确应用句柄情况(异常、布尔值等)。
资源:
祝你好运!
具有 PDO 准备语句和异常处理的Haversine 公式
index.php(主页):
<?php
require_once 'configs.php';
require_once 'functions.php';
require_once 'geolocationFunctions.php';
// Activate error reporting (only on development).
activateErrorReporting();
try {
// Create db connection.
$connection = createConnection(
MYSQL_HOST
, MYSQL_DATABASE
, MYSQL_USERNAME
, MYSQL_PASSWORD
, MYSQL_PORT
, MYSQL_CHARSET
);
$cardsInRadius = getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100);
// For testing purposes.
printData($cardsInRadius, TRUE);
closeConnection($connection);
} catch (PDOException $pdoException) {
// On development.
printData($pdoException, TRUE);
// On production.
// echo $pdoException->getMessage();
exit();
} catch (Exception $exception) {
// On development.
printData($exception, TRUE);
// On production.
// echo $exception->getMessage();
exit();
}
geolocationFunctions.php(包含在主页中):
<?php
/*
* ---------------------
* Geolocation functions
* ---------------------
*/
/**
* Search the for a keyword and return data located within given radius.
*
* @param PDO $connection Connection instance.
* @param string $word Keyword to lookup.
* @param double $longitude Longitude value to lookup.
* @param double $latitude Latitude value to lookup.
* @param integer $radius Distance radius (in miles) having lat/long as center point.
* @param integer $limit [optional] Number of records to return.
* @throws Exception
*/
function getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100) {
/*
* Create a rectangle in which the circle with the given radius will be defined.
* >> 1° of latitude ~= 69 miles
* >> 1° of longitude ~= cos(latitude) * 69
*/
$rectLong1 = $longitude - $radius / abs(cos(deg2rad($latitude)) * 69);
$rectLong2 = $longitude + $radius / abs(cos(deg2rad($latitude)) * 69);
$rectLat1 = $latitude - ($radius / 69);
$rectLat2 = $latitude + ($radius / 69);
// Approximate the circle inside the rectangle.
$distance = sprintf('3956 * 2 * ASIN(SQRT(POWER(SIN((%s - latitudeLocateDB) * pi()/180 / 2), 2) + COS(%s * pi()/180) * COS(latitudeLocateDB * pi()/180) * POWER(SIN((%s - longitudeLocateDB) * pi()/180 / 2), 2) ))'
, $latitude
, $latitude
, $longitude
);
// Sql statement.
$sql = sprintf('SELECT
*,
%s AS distance
FROM carddbtable
WHERE
(
businessNameDB = :businessNameDB
OR lastNameDB = :lastNameDB
OR firstKeywordDB = :firstKeywordDB
OR secondKeywordDB = :secondKeywordDB
OR thirdKeywordDB = :thirdKeywordDB
OR fourthKeywordDB = :fourthKeywordDB
OR fithKeywordDB = :fithKeywordDB
)
AND longitudeLocateDB BETWEEN :rectLong1 AND :rectLong2
AND latitudeLocateDB BETWEEN :rectLat1 AND :rectLat2
HAVING distance < :distance
ORDER BY distance
LIMIT :limit'
, $distance
);
// Prepare and check sql statement (returns PDO statement).
$statement = $connection->prepare($sql);
if (!$statement) {
throw new Exception('The SQL statement can not be prepared!');
}
// Bind values to sql statement parameters.
$statement->bindValue(':businessNameDB', $word, getInputParameterDataType($word));
$statement->bindValue(':lastNameDB', $word, getInputParameterDataType($word));
$statement->bindValue(':firstKeywordDB', $word, getInputParameterDataType($word));
$statement->bindValue(':secondKeywordDB', $word, getInputParameterDataType($word));
$statement->bindValue(':thirdKeywordDB', $word, getInputParameterDataType($word));
$statement->bindValue(':fourthKeywordDB', $word, getInputParameterDataType($word));
$statement->bindValue(':fithKeywordDB', $word, getInputParameterDataType($word));
$statement->bindValue(':rectLong1', $rectLong1, getInputParameterDataType($rectLong1));
$statement->bindValue(':rectLong2', $rectLong2, getInputParameterDataType($rectLong2));
$statement->bindValue(':rectLat1', $rectLat1, getInputParameterDataType($rectLat1));
$statement->bindValue(':rectLat2', $rectLat2, getInputParameterDataType($rectLat2));
$statement->bindValue(':distance', $radius, getInputParameterDataType($radius));
$statement->bindValue(':limit', $limit, getInputParameterDataType($limit));
// Execute and check PDO statement.
if (!$statement->execute()) {
throw new Exception('The PDO statement can not be executed!');
}
// Fetch person details.
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
if (!$fetchedData) {
throw new Exception('Fetching data failed!');
}
return $fetchedData;
}
configs.php(包含在主页中):
<?php
/*
* ----------------
* Database configs
* ----------------
*/
define('MYSQL_HOST', '...');
define('MYSQL_PORT', '3306');
define('MYSQL_DATABASE', '...');
define('MYSQL_CHARSET', 'utf8');
define('MYSQL_USERNAME', '...');
define('MYSQL_PASSWORD', '...');
functions.php(包含在主页中):
<?php
/*
* ---------------------
* Data access functions
* ---------------------
*/
/**
* Create a new db connection.
*
* @param string $host Host.
* @param string $dbname Database name.
* @param string $username Username.
* @param string $password Password.
* @param string $port [optional] Port.
* @param array $charset [optional] Character set.
* @param array $options [optional] Driver options.
* @return PDO Db connection.
*/
function createConnection($host, $dbname, $username, $password, $port = '3306', $charset = 'utf8', $options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => true,
)) {
$dsn = getDsn($host, $dbname, $port, $charset);
$connection = new PDO($dsn, $username, $password);
foreach ($options as $key => $value) {
$connection->setAttribute($key, $value);
}
return $connection;
}
/**
* Create a mysql DSN string.
*
* @param string $host Host.
* @param string $dbname Database name.
* @param string $port [optional] Port.
* @param array $charset [optional] Character set.
* @return string DSN string.
*/
function getDsn($host, $dbname, $port = '3306', $charset = 'utf8') {
$dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s'
, $host
, $port
, $dbname
, $charset
);
return $dsn;
}
/**
* Close a db connection.
*
* @param PDO $connection Db connection.
* @return void
*/
function closeConnection($connection) {
$connection = NULL;
}
/**
* Get the data type of a binding value.
*
* @param mixed $value Binding value.
* @return mixed Data type of the binding value.
*/
function getInputParameterDataType($value) {
$dataType = PDO::PARAM_STR;
if (is_int($value)) {
$dataType = PDO::PARAM_INT;
} elseif (is_bool($value)) {
$dataType = PDO::PARAM_BOOL;
}
return $dataType;
}
/*
* ---------------
* Print functions
* ---------------
*/
/**
* Print data on screen.
*
* @param mixed $data Data to print.
* @param bool $preformatted Print preformatted if TRUE, print normal otherwise.
* @return void
*/
function printData($data, $preformatted = FALSE) {
if ($preformatted) {
echo '<pre>' . print_r($data, true) . '</pre>';
} else {
echo $data;
}
}
/*
* -------------------------
* Error reporting functions
* -------------------------
*/
/**
* Toggle error reporting.
*
* @param integer $level Error level.
* @param bool $display_errors Display errors if TRUE, hide them otherwise.
* @return void
*/
function activateErrorReporting($level = E_ALL, $display_errors = TRUE) {
error_reporting($level);
ini_set('display_errors', ($display_errors ? 1 : 0));
}
编辑 1:针对 MySQL 注入的附加措施:
像这样申请qoute():
function getCardsInRadius($connection, ...) {
$longitude = $connection->quote($longitude);
$latitude = $connection->quote($latitude);
//...
}
编辑 2:解决错误的近似值:
在您的原始代码中,您使用了cos(radians($latitude))*69:
...
between ($longitude-$miles/cos(radians($latitude))*69)
and ($longitude+$miles/cos(radians($latitude))*69)
...
在我的代码中,我使用了abs(cos(deg2rad($latitude)) * 69)。我记得我是故意选择这个的:
$rectLong1 = $longitude - $radius / abs(cos(deg2rad($latitude)) * 69);
$rectLong2 = $longitude + $radius / abs(cos(deg2rad($latitude)) * 69);
看来这可能是问题所在。因此,首先,将deg2rad 替换为radians。那么它应该是:
$rectLong1 = $longitude - $radius / abs(cos(radians($latitude)) * 69);
$rectLong2 = $longitude + $radius / abs(cos(radians($latitude)) * 69);
如果还是不行,就删除abs。那么它应该是:
$rectLong1 = $longitude - $radius / (cos(radians($latitude)) * 69);
$rectLong2 = $longitude + $radius / (cos(radians($latitude)) * 69);
注意括号的位置。
编辑 3 - 可变测量单位:
应用可变测量单位(公里、英里等)作为函数参数并相应地更改地球半径。如何将$measurementUnit 注入函数是您的职责。是的,地球半径几乎是 3959 英里。
function getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100, $measurementUnit = 'miles') {
//...
switch ($measurementUnit) {
case 'miles':
$earthRadius = 3959;
break;
case 'km':
$earthRadius = 6371;
break;
default: // miles
$earthRadius = 3959;
break;
}
$distance = sprintf('%s * 2 * ...'
, $earthRadius
, $latitude
, $latitude
, $longitude
);
//...
}