Skip to content

Commit

Permalink
Add central coordinate calc.
Browse files Browse the repository at this point in the history
  • Loading branch information
dereuromark committed Feb 23, 2024
1 parent b5f8820 commit 840bd2c
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 14 deletions.
12 changes: 8 additions & 4 deletions docs/Geocoder/Calculator.md
Expand Up @@ -4,23 +4,27 @@
## Distance calculation

```php
$distance = this->Calculator->distance($lat, $lng);
$calculator = new \Geo\Geocoder\Calculator();
$distance = $calculator->distance(array $pointX, array $pointY);
```

Distance in miles instead:
```php
$distance = this->Calculator->distance($lat, $lng, Calculator::UNIT_MILES);
$calculator = new \Geo\Geocoder\Calculator();
$distance = $calculator->distance(array $pointX, array $pointY, Calculator::UNIT_MILES);
```

## Blur coordinates
The idea is to secure the user's exact coordinates and hide them by blurring them to a certain degree.

```php
$distance = this->Calculator->blur($lat, $lng, 2);
$calculator = new \Geo\Geocoder\Calculator();
$distance = $calculator->blur($float, 2);
```

## Convert distances

```php
$newDistance = this->Calculator->convert($oldDistance, $from, $to);
$calculator = new \Geo\Geocoder\Calculator();
$newDistance = $calculator->convert($value, $fromUnit, $toUnit);
```
13 changes: 13 additions & 0 deletions docs/Geocoder/GeoCalculator.md
@@ -0,0 +1,13 @@
# Geo Class

## Center calculation

```php
$geo = new \Geo\Geocoder\GeoCalculator();
$coordinates = [
new GeoCoordinate(48.1, 17.2),
new GeoCoordinate(48.5, 17.1),
new GeoCoordinate(48.8, 16.8),
];
$centralCoordinate = $geo->getCentralGeoCoordinate(array $coordinates);
```
1 change: 1 addition & 0 deletions docs/README.md
Expand Up @@ -7,6 +7,7 @@
* [Geocoder behavior](Behavior/Geocoder.md)
* [Geocoder lib](Geocoder/Geocoder.md)
* [Calculator lib](Geocoder/Calculator.md)
* [Geo lib](Geocoder/Geo.md)
* [GoogleMap helper](Helper/GoogleMap.md)
* [GeocodedAddresses result cache](Model/GeocodedAddresses.md)

Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Expand Up @@ -10,3 +10,4 @@ parameters:
ignoreErrors:
- '#Access to an undefined property .+EntityInterface::\$geo.+.#'
- '#Cannot assign offset int\|string to ArrayObject\|.+GeocodedAddress.#'
- '#Unsafe usage of new static\(\)#'
22 changes: 12 additions & 10 deletions src/Geocoder/Calculator.php
Expand Up @@ -87,7 +87,7 @@ public function __construct(array $config = []) {
* @throws \Exception
* @return float convertedValue
*/
public function convert($value, $fromUnit, $toUnit) {
public function convert(float $value, string $fromUnit, string $toUnit): float {
$fromUnit = strtoupper($fromUnit);
$toUnit = strtoupper($toUnit);

Expand Down Expand Up @@ -122,7 +122,7 @@ public function convert($value, $fromUnit, $toUnit) {
* @param string|null $unit Unit char or constant (M=miles, K=kilometers, N=nautical miles, I=inches, F=feet)
* @return int Distance in km
*/
public function distance(array $pointX, array $pointY, $unit = null) {
public function distance(array $pointX, array $pointY, ?string $unit = null): int {
if (empty($unit)) {
$unit = array_keys($this->_units);
$unit = $unit[0];
Expand All @@ -141,13 +141,18 @@ public function distance(array $pointX, array $pointY, $unit = null) {
}

/**
* Geocoder::calculateDistance()
*
* @param \ArrayObject|array $pointX
* @param \ArrayObject|array $pointY
* @param \ArrayObject|\Geo\Geocoder\GeoCoordinate|array $pointX
* @param \ArrayObject|\Geo\Geocoder\GeoCoordinate|array $pointY
* @return float
*/
public static function calculateDistance($pointX, $pointY) {
if ($pointX instanceof GeoCoordinate) {
$pointX = $pointX->toArray(true);
}
if ($pointY instanceof GeoCoordinate) {
$pointY = $pointY->toArray(true);
}

$res = 69.09 * rad2deg(acos(sin(deg2rad($pointX['lat'])) * sin(deg2rad($pointY['lat']))
+ cos(deg2rad($pointX['lat'])) * cos(deg2rad($pointY['lat'])) * cos(deg2rad($pointX['lng']
- $pointY['lng']))));
Expand All @@ -165,17 +170,14 @@ public static function calculateDistance($pointX, $pointY) {
* @throws \Exception
* @return float Coordinates
*/
public static function blur($coordinate, $level = 0) {
public static function blur(float $coordinate, int $level = 0): float {
if (!$level) {
return $coordinate;
}

$scrambleVal = 0.000001 * mt_rand(10, 200) * pow(2, $level) * (mt_rand(0, 1) === 0 ? 1 : -1);

return $coordinate + $scrambleVal;

//$scrambleVal *= (float)(2^$level);
// TODO: + - by chance!!!
}

}
48 changes: 48 additions & 0 deletions src/Geocoder/GeoCalculator.php
@@ -0,0 +1,48 @@
<?php

namespace Geo\Geocoder;

use RuntimeException;

class GeoCalculator {

/**
* @param array<\Geo\Geocoder\GeoCoordinate> $geoCoordinates
*
* @return \Geo\Geocoder\GeoCoordinate
*/
public static function getCentralGeoCoordinate(array $geoCoordinates): GeoCoordinate {
if (!$geoCoordinates) {
throw new RuntimeException('No geo coordinates provided');
}
if (count($geoCoordinates) === 1) {
return array_shift($geoCoordinates);
}

$x = 0;
$y = 0;
$z = 0;

foreach ($geoCoordinates as $geoCoordinate) {
$latitude = $geoCoordinate->getLatitude() * M_PI / 180;
$longitude = $geoCoordinate->getLongitude() * M_PI / 180;

$x += cos($latitude) * cos($longitude);
$y += cos($latitude) * sin($longitude);
$z += sin($latitude);
}

$total = count($geoCoordinates);

$x /= $total;
$y /= $total;
$z /= $total;

$centralLongitude = atan2($y, $x);
$centralSquareRoot = sqrt($x * $x + $y * $y);
$centralLatitude = atan2($z, $centralSquareRoot);

return new GeoCoordinate($centralLatitude * 180 / M_PI, $centralLongitude * 180 / M_PI);
}

}
65 changes: 65 additions & 0 deletions src/Geocoder/GeoCoordinate.php
@@ -0,0 +1,65 @@
<?php

namespace Geo\Geocoder;

class GeoCoordinate implements \Stringable {

protected float $latitude;

protected float $longitude;

/**
* @param float $latitude
* @param float $longitude
*/
public function __construct(float $latitude, float $longitude) {
$this->latitude = $latitude;
$this->longitude = $longitude;
}

/**
* @return float
*/
public function getLatitude(): float {
return $this->latitude;
}

/**
* @return float
*/
public function getLongitude(): float {
return $this->longitude;
}

/**
* @param bool $abbr
*
* @return array<string, float>
*/
public function toArray(bool $abbr = false): array {
return [
($abbr ? 'lat' : 'latitude') => $this->latitude,
($abbr ? 'lng' : 'longitude') => $this->longitude,
];
}

/**
* @param array<string, float> $data
*
* @return static
*/
public static function fromArray(array $data): static {
$lat = $data['latitude'] ?? $data['lat'];
$lng = $data['longitude'] ?? $data['lng'];

return new static($lat, $lng);
}

/**
* @return string
*/
public function __toString(): string {
return $this->latitude . ',' . $this->longitude;
}

}
36 changes: 36 additions & 0 deletions tests/TestCase/Geocoder/GeoCalculatorTest.php
@@ -0,0 +1,36 @@
<?php

namespace Geo\Test\Geocoder;

use Cake\TestSuite\TestCase;
use Geo\Geocoder\GeoCalculator;
use Geo\Geocoder\GeoCoordinate;

class GeoCalculatorTest extends TestCase {

/**
* @var \Geocoder\Provider\Provider
*/
protected $Geocoder;

/**
* @return void
*/
public function testGetCentralGeoCoordinate() {
$coordinates = [
new GeoCoordinate(48.1, 17.2),
];
$result = GeoCalculator::getCentralGeoCoordinate($coordinates);
$this->assertEquals($coordinates[0], $result);

$coordinates = [
new GeoCoordinate(48.1, 17.2),
new GeoCoordinate(48.5, 17.1),
new GeoCoordinate(48.8, 16.8),
];
$result = GeoCalculator::getCentralGeoCoordinate($coordinates);
$this->assertWithinRange(48.46, $result->getLatitude(), 0.01);
$this->assertWithinRange(17.03, $result->getLongitude(), 0.01);
}

}

0 comments on commit 840bd2c

Please sign in to comment.