Skip to content

Commit

Permalink
feat: geohash generate
Browse files Browse the repository at this point in the history
  • Loading branch information
igzard committed Jan 21, 2025
1 parent 30a2e6c commit 71bdf4d
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 43 deletions.
71 changes: 60 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,74 @@
# php-skeleton
# php-geohash

🎉 This **Skeleton PHP** package created for your new package idea
🌐 **Geohash** calculation in PHP

<p align="center">
<p align="center">
<a href="https://github.com/igzard/php-skeleton/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/igzard/php-skeleton/tests.yml?label=tests&style=flat-square" alt="Tests passed"></a>
<a href="https://packagist.org/packages/igzard/php-skeleton"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/igzard/php-skeleton"></a>
<a href="https://packagist.org/packages/igzard/php-skeleton"><img alt="Latest Version" src="https://img.shields.io/packagist/v/igzard/php-skeleton"></a>
<p align="left">
<p align="left">
<a href="https://github.com/igzard/php-geohash/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/igzard/php-geohash/tests.yml?label=tests&style=flat-square" alt="Tests passed"></a>
<a href="https://packagist.org/packages/igzard/php-geohash"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/igzard/php-geohash"></a>
<a href="https://packagist.org/packages/igzard/php-geohash"><img alt="Latest Version" src="https://img.shields.io/packagist/v/igzard/php-geohash"></a>
</p>
</p>

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
Expand All @@ -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)**.
**Geohash PHP** was created by **[Gergely Ignácz](https://x.com/igz4rd)** under the **[MIT license](https://opensource.org/licenses/MIT)**.
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
95 changes: 95 additions & 0 deletions src/Domain/GeohashCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Igzard\Geohash\Domain;

/**
* @internal
*/
final class GeohashCalculator
{
private const string BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';

private array $latitudeInterval = [-90.0, 90.0];
private array $longitudeInterval = [-180.0, 180.0];
private int $precision = 12;

public function withLatitudeInterval(float $from, float $to): self
{
$this->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;
}
}
}
13 changes: 0 additions & 13 deletions src/Example.php

This file was deleted.

41 changes: 41 additions & 0 deletions src/Geohash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Igzard\Geohash;

use Igzard\Geohash\Domain\GeohashCalculator;

final class Geohash
{
private function __construct()
{
}

protected function __clone()
{
}

/**
* Generate geohash from latitude and longitude.
* You can customize the precision, longitude and latitude intervals in config.
*
* @param float $latitude Latitude
* @param float $longitude Longitude
* @param array $config ['precision' => 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);
}
}
15 changes: 0 additions & 15 deletions tests/ExampleTest.php

This file was deleted.

41 changes: 41 additions & 0 deletions tests/GeohashTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

use Igzard\Geohash\Geohash;
use PHPUnit\Framework\TestCase;

class GeohashTest extends TestCase
{
/**
* @dataProvider locationDataProvider
*/
public function testAssertWithDefaultData(float $latitude, float $longitude, string $expected): void
{
$this->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',
],
];
}
}

0 comments on commit 71bdf4d

Please sign in to comment.