Skip to content

Instantly share code, notes, and snippets.

@dherges
Last active August 29, 2015 14:01
Show Gist options
  • Save dherges/b17f94ab4daada3c1358 to your computer and use it in GitHub Desktop.
Save dherges/b17f94ab4daada3c1358 to your computer and use it in GitHub Desktop.
GeoQuery
/*
* Copyright 2014 David Herges <david@spektrakel.de>
* Licensed under MIT (http://opensource.org/licenses/MIT)
*/
define('M_NEGATIVE_PI_2', ((double) 0.0 - M_PI_2));
define('M_NEGATIVE_PI', ((double) 0.0 - M_PI));
define('M_2PI', ((double) 2.0 * M_PI));
/**
* A Query to perform geolocation searches. Should be easy to adopt to other Frameworks than TYPO3
*
* <p>
* All you need is (latitude, longitude, perimeter) -> (southEast, northWest) boundary?
* Have a look at {@link #toRectangularBounds($latitude, $longitude, $perimeter)}!
*
* <p>
* Lots of credits go to Jan Matuschek.
*
* @link http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
*/
class GeoQuery extends \TYPO3\CMS\Extbase\Persistence\Generic\Query {
const PROPERTY_NAME_LATITUDE = "latitude";
const PROPERTY_NAME_LONGITUDE = "longitude";
/**
* Radius of the Earth's surface in meters
*/
const EARTH_RADIUS_METERS = 6371010;
const MIN_LAT = M_NEGATIVE_PI_2; // Math.toRadians(-90d); // -PI/2
const MAX_LAT = M_PI_2; // Math.toRadians(90d); // PI/2
const MIN_LON = M_NEGATIVE_PI; // Math.toRadians(-180d); // -PI
const MAX_LON = M_PI; // Math.toRadians(180d); // PI
/**
* Returns a constraint that filters entries whose geolocation is within the rectangular bounds of a plane surface
* area around the center point (latitude, longitude) and within radius (perimeter) in meters.
*
* @param $latitude Latitude in degrees; floating-point value
* @param $longitude Longitude in degrees; floating-point value
* @param $perimeter Perimeter; integer value in meters
*
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface
*/
public function around($latitude, $longitude, $perimeter) {
// (lat, long, perimeter) to rectangular bounds
$bounds = $this->toRectangularBounds($latitude, $longitude, $perimeter);
// where condition: (lat >= minLat) && (lat <= maxLat) && (lon >= minLon) && (lon <= maxLon)
return $this->logicalAnd(
$this->greaterThanOrEqual(self::PROPERTY_NAME_LATITUDE, $bounds->minLat),
$this->lessThanOrEqual(self::PROPERTY_NAME_LATITUDE, $bounds->maxLat),
$this->greaterThanOrEqual(self::PROPERTY_NAME_LONGITUDE, $bounds->minLon),
$this->lessThanOrEqual(self::PROPERTY_NAME_LONGITUDE, $bounds->maxLon)
);
}
/**
* Courtesy of http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
*
* @param $latitude
* @param $longitude
* @param $perimeter
*
* @return \stdClass With properties 'minLat', 'maxLat', 'minLon', 'maxLon'
*/
private function toRectangularBounds($latitude, $longitude, $perimeter) {
// degrees to radians
$radLat = deg2rad($latitude);
$radLon = deg2rad($longitude);
$radDist = $perimeter / self::EARTH_RADIUS_METERS;
// initial values for minLat, maxLat, minLon, maxLon
$minLat = $radLat - $radDist;
$maxLat = $radLat + $radDist;
$minLon = (double) 0.0;
$maxLon = (double) 0.0;
if (($minLat > self::MIN_LAT) && ($maxLat < self::MAX_LAT)) {
$deltaLon = asin(sin($radDist) / cos($radLat));
$minLon = $radLon - $deltaLon;
if ($minLon < self::MIN_LON) {
$minLon += M_2PI;
}
$maxLon = $radLon + $deltaLon;
if ($maxLon > self::MAX_LON) {
$maxLon -= M_2PI;
}
} else {
// a pole is within the distance
$minLat = max($minLat, self::MIN_LAT);
$maxLat = min($maxLat, self::MAX_LAT);
$minLon = self::MIN_LON;
$maxLon = self::MAX_LON;
}
$bounds = new \stdClass();
$bounds->minLat = rad2deg($minLat);
$bounds->maxLat = rad2deg($maxLat);
$bounds->minLon = rad2deg($minLon);
$bounds->maxLon = rad2deg($maxLon);
return $bounds;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment