From 71bdf4de48d58c22aaee3449495fe14d80d51b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20Ign=C3=A1cz?= Date: Tue, 21 Jan 2025 17:12:31 +0100 Subject: [PATCH] feat: geohash generate --- README.md | 71 ++++++++++++++++++++---- composer.json | 8 +-- src/Domain/GeohashCalculator.php | 95 ++++++++++++++++++++++++++++++++ src/Example.php | 13 ----- src/Geohash.php | 41 ++++++++++++++ tests/ExampleTest.php | 15 ----- tests/GeohashTest.php | 41 ++++++++++++++ 7 files changed, 241 insertions(+), 43 deletions(-) create mode 100644 src/Domain/GeohashCalculator.php delete mode 100644 src/Example.php create mode 100644 src/Geohash.php delete mode 100644 tests/ExampleTest.php create mode 100644 tests/GeohashTest.php diff --git a/README.md b/README.md index cd12580..3c6b4d3 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,74 @@ -# php-skeleton +# php-geohash -๐ŸŽ‰ This **Skeleton PHP** package created for your new package idea +๐ŸŒ **Geohash** calculation in PHP -

-

- Tests passed - Total Downloads - Latest Version +

+

+ Tests passed + Total Downloads + Latest Version

+The **PHP Geohash Generator** is a simple yet powerful tool that converts geographic coordinates (latitude and longitude) into a compact and highly useful **geohash** string. Perfect for efficiently storing, searching, and managing spatial data in your applications! ๐Ÿš€ + +--- + +## What is a Geohash? ๐Ÿค” + +A geohash is an algorithm that divides the Earth into progressively smaller "boxes" and encodes the coordinates as a text string. + +- **In short:** A geohash is a single string representation of a specific location on Earth. +- **Benefits:** + - Compact and human-readable format. + - Easy to compare: if two locations are close, their geohashes share the same prefix. + - Ideal for fast searches and spatial grouping. + +--- + +## Why use this library? ๐ŸŽฏ + +- **Easy to use**: Simply provide latitude and longitude, and get a geohash in return. +- **Precision-focused**: The algorithm supports up to 12-character geohashes for highly accurate encoding. +- **Perfect for location-based applications**: Whether storing spatial data, searching for locations, or displaying maps, this tool gets the job done efficiently. + +--- + ------ -> **Requires [PHP 8.3+](https://php.net/releases/)** +## Installation & Usage ๐Ÿ› ๏ธ -โšก๏ธ Create your package using [Composer](https://getcomposer.org): +> **Requires [PHP 8.3+](https://php.net/releases/)** ```bash -composer create-project igzard/php-skeleton --prefer-source PackageName +composer require igzard/php-geohash +``` + +```php +use Igzard\Geohash\Geohash; + +// Generate a geohash for a specific location with latitude and longitude coordinates. +echo Geohash::generate(47.497913, 19.040236); // input: Budapest coordinates | output: u2mw1q8xmssz + +// Generate a geohash with custom precision (default is 12), latitude interval, and longitude interval. +echo Geohash::generate(47.497913, 19.040236, [ + 'precision' => 10, + 'latitude_interval' => [ + 'min' => -70, + 'max' => 10, + ], + 'longitude_interval' => [ + 'min' => -80, + 'max' => 170, + ], +]); ``` +## Contributing ๐Ÿค + +Thank you for considering contributing to the **Geohash PHP** library! ๐ŸŽ‰ +Here is some tooling to help you get started: + โœ… Run **Code quality** check: ```bash make code-quality @@ -45,4 +94,4 @@ make phpstan make composer-install ``` -**Skeleton PHP** was created by **[Gergely Ignรกcz](https://x.com/igz4rd)** under the **[MIT license](https://opensource.org/licenses/MIT)**. \ No newline at end of file +**Geohash PHP** was created by **[Gergely Ignรกcz](https://x.com/igz4rd)** under the **[MIT license](https://opensource.org/licenses/MIT)**. \ No newline at end of file diff --git a/composer.json b/composer.json index 2dfba8c..a54c47d 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,12 @@ { - "name": "igzard/php-skeleton", - "description": "๐ŸŽ‰ This skeleton PHP package created for your new package idea", + "name": "igzard/php-geohash", + "description": "Geohash calculation in PHP", "type": "library", - "keywords": ["php", "skeleton", "package"], + "keywords": ["php", "geohash", "package"], "license": "MIT", "autoload": { "psr-4": { - "Igzard\\PhpSkeleton\\": "src/" + "Igzard\\Geohash\\": "src/" } }, "authors": [ diff --git a/src/Domain/GeohashCalculator.php b/src/Domain/GeohashCalculator.php new file mode 100644 index 0000000..9d319c2 --- /dev/null +++ b/src/Domain/GeohashCalculator.php @@ -0,0 +1,95 @@ +latitudeInterval = [$from, $to]; + + return $this; + } + + public function withLongitudeInterval(float $from, float $to): self + { + $this->longitudeInterval = [$from, $to]; + + return $this; + } + + public function withPrecision(int $precision): self + { + $this->precision = $precision; + + return $this; + } + + /** + * Calculate geohash from latitude and longitude with precision. + * + * @param float $latitude Latitude + * @param float $longitude Longitude + * + * @return string Geohash + */ + public function calculate(float $latitude, float $longitude): string + { + $result = ''; + + $isEven = true; + $bit = 0; + $ch = 0; + + while (\strlen($result) < $this->precision) { + if ($isEven) { + $this->refineInterval($this->longitudeInterval, $longitude, $ch, $bit); + } else { + $this->refineInterval($this->latitudeInterval, $latitude, $ch, $bit); + } + + $isEven = !$isEven; + + if ($bit < 4) { + ++$bit; + } else { + $result .= self::BASE32[$ch]; + $bit = 0; + $ch = 0; + } + } + + return $result; + } + + /** + * Refine interval. + * + * @param array $interval Interval + * @param float $value Value + * @param int $ch Ch + * @param int $bit Bit + */ + private function refineInterval(array &$interval, float $value, int &$ch, int $bit): void + { + $mid = ($interval[0] + $interval[1]) / 2; + + if ($value > $mid) { + $ch |= (1 << (4 - $bit)); + $interval[0] = $mid; + } else { + $interval[1] = $mid; + } + } +} diff --git a/src/Example.php b/src/Example.php deleted file mode 100644 index fe18207..0000000 --- a/src/Example.php +++ /dev/null @@ -1,13 +0,0 @@ - 12, 'longitude_interval' => [-180.0, 180.0], 'latitude_interval' => [-90.0, 90.0]] + * + * @return string Geohash + */ + public static function generate(float $latitude, float $longitude, array $config = []): string + { + $precision = $config['precision'] ?? 12; + $longitudeInterval = $config['longitude_interval'] ?? [-180.0, 180.0]; + $latitudeInterval = $config['latitude_interval'] ?? [-90.0, 90.0]; + + return (new GeohashCalculator()) + ->withLongitudeInterval($longitudeInterval[0], $longitudeInterval[1]) + ->withLatitudeInterval($latitudeInterval[0], $latitudeInterval[1]) + ->withPrecision($precision) + ->calculate($latitude, $longitude); + } +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 33ecc83..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,15 +0,0 @@ -assertEquals('World', $example->hello()); - } -} \ No newline at end of file diff --git a/tests/GeohashTest.php b/tests/GeohashTest.php new file mode 100644 index 0000000..5b75c54 --- /dev/null +++ b/tests/GeohashTest.php @@ -0,0 +1,41 @@ +assertEquals( + $expected, + Geohash::generate($latitude, $longitude) + ); + } + + public function locationDataProvider(): array + { + return [ + 'case 1# Budapest' => [ + 'latitude' => 47.497913, + 'longitude' => 19.040236, + 'expected' => 'u2mw1q8xmssz', + ], + 'case 2# New York' => [ + 'longitude' => 40.730610, + 'latitude' => -73.935242, + 'expected' => 'dr5rtwccpbpb', + ], + 'case 2# Moscow' => [ + 'longitude' => 55.751244, + 'latitude' => 37.618423, + 'expected' => 'ucfv0j0ysc1k', + ], + ]; + } +} \ No newline at end of file