uupdate admin

This commit is contained in:
全栈小学生 2023-10-10 16:52:54 +08:00
parent 657dc44003
commit 4b682cbf17
146 changed files with 18704 additions and 0 deletions

25
admin/vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitf5da4e3fca64ccd39e0027088099d184::getLoader();

585
admin/vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,585 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@ -0,0 +1,352 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

21
admin/vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

10
admin/vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Location\\' => array($vendorDir . '/mjaschen/phpgeo/src'),
);

38
admin/vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitf5da4e3fca64ccd39e0027088099d184
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitf5da4e3fca64ccd39e0027088099d184', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitf5da4e3fca64ccd39e0027088099d184', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitf5da4e3fca64ccd39e0027088099d184::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

View File

@ -0,0 +1,36 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitf5da4e3fca64ccd39e0027088099d184
{
public static $prefixLengthsPsr4 = array (
'L' =>
array (
'Location\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'Location\\' =>
array (
0 => __DIR__ . '/..' . '/mjaschen/phpgeo/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitf5da4e3fca64ccd39e0027088099d184::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitf5da4e3fca64ccd39e0027088099d184::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitf5da4e3fca64ccd39e0027088099d184::$classMap;
}, null, ClassLoader::class);
}
}

88
admin/vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,88 @@
{
"packages": [
{
"name": "mjaschen/phpgeo",
"version": "4.2.0",
"version_normalized": "4.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/mjaschen/phpgeo.git",
"reference": "b2e593cf1e9aceea36510158ddb80c7395a80d5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mjaschen/phpgeo/zipball/b2e593cf1e9aceea36510158ddb80c7395a80d5a",
"reference": "b2e593cf1e9aceea36510158ddb80c7395a80d5a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.3 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.6",
"vimeo/psalm": "^4.13"
},
"time": "2022-07-25T08:36:36+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Location\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marcus Jaschen",
"email": "mjaschen@gmail.com",
"homepage": "https://www.marcusjaschen.de/"
}
],
"description": "Simple Yet Powerful Geo Library",
"homepage": "https://phpgeo.marcusjaschen.de/",
"keywords": [
"Polygon",
"area",
"bearing",
"bounds",
"calculation",
"coordinate",
"distance",
"earth",
"ellipsoid",
"geo",
"geofence",
"gis",
"gps",
"haversine",
"length",
"perpendicular",
"point",
"polyline",
"projection",
"simplify",
"track",
"vincenty"
],
"support": {
"docs": "https://phpgeo.marcusjaschen.de/Installation.html",
"email": "mjaschen@gmail.com",
"issues": "https://github.com/mjaschen/phpgeo/issues",
"source": "https://github.com/mjaschen/phpgeo/tree/4.2.0"
},
"install-path": "../mjaschen/phpgeo"
}
],
"dev": true,
"dev-package-names": []
}

32
admin/vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,32 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '0e6af7d100b3d33b511309de65f355528845c27f',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '0e6af7d100b3d33b511309de65f355528845c27f',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'mjaschen/phpgeo' => array(
'pretty_version' => '4.2.0',
'version' => '4.2.0.0',
'reference' => 'b2e593cf1e9aceea36510158ddb80c7395a80d5a',
'type' => 'library',
'install_path' => __DIR__ . '/../mjaschen/phpgeo',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70300)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -0,0 +1,61 @@
name: phpgeo Tests
on:
push:
branches:
- "**"
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions:
- "7.3"
- "7.4"
- "8.0"
- "8.1"
name: "phpgeo build - PHP ${{ matrix.php-versions }}"
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: xdebug
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php-${{ matrix.php-versions }}-${{ hashFiles('composer.json') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php-versions }}-
- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer install --prefer-dist --no-progress --no-suggest
- name: Lint PHP Sources
run: composer run-script ci:lint
- name: PHP Code Sniffer
run: composer run-script ci:sniff
- name: Static Analysis
run: composer run-script ci:psalm
- name: Unit Tests
run: composer run-script ci:tests

11
admin/vendor/mjaschen/phpgeo/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
.DS_Store
*~
/.idea/
/composer.lock
/vendor/
/docs/phpgeo.html
/docs/coverage/
/docs/phpdox/
/tools/
/build/
.phpunit.result.cache

View File

@ -0,0 +1,9 @@
filter:
paths: ["src/*"]
tools:
php_code_coverage: true
php_sim: true
php_mess_detector: true
php_pdepend: true
php_analyzer: true
php_cpd: false

View File

@ -0,0 +1,50 @@
os: linux
dist: xenial
language: php
php:
- "7.4"
- "7.3"
- "7.2"
cache:
directories:
- vendor
- $HOME/.composer/cache
env:
jobs:
- DEPENDENCIES=latest
- DEPENDENCIES=oldest
install:
- >
if [ "$DEPENDENCIES" = "latest" ]; then
echo "Installing the latest dependencies";
composer update --with-dependencies --prefer-stable --prefer-dist
else
echo "Installing the lowest dependencies";
composer update --with-dependencies --prefer-stable --prefer-dist --prefer-lowest
fi;
composer show;
script:
- >
echo;
echo "Validating the composer.json";
composer ci:composer-validate;
- >
echo;
echo "Linting all PHP files";
composer ci:lint;
- >
echo;
echo "Running the Psalm static analyzer";
composer ci:psalm;
- >
echo;
echo "Running the PHPUnit tests";
composer ci:tests;

View File

@ -0,0 +1,317 @@
# Change Log
All notable changes to `mjaschen/phpgeo` will be documented in this file.
Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## Unreleased/Upcoming
### Added
- `GeometryInterface` provides two new methods: `getBounds()` and `getSegments()`
### Removed
- Support for PHP 7.3
- `setPoint1()` and `setPoint2()` methods from `Line`
- `setSeparator()`, `useCardinalLetters()` and `setUnits()` methods from `DMS`
## [4.2.0] - Current Version, 2022-07-25
## Changed
- point-to-line distance is calculated iteratively now, fixes #92
- improved intersection checking for polygon/polygon
## [4.1.0] - 2022-06-03
This release has no breaking changes.
Thanks, @nilshoerrmann, for contributing!
### Added
- method `Bounds::getAsPolygon()` which returns a polygon containing the four nodes of the Bounds instance
- methods `Bounds::getNorthEast()` and `Bounds::getSouthWest()`
- new public methods: `CardinalDirection::isStrictlyNorth()`, `CardinalDirection::isStrictlyEast()`, `CardinalDirection::isStrictlySouth()` and `CardinalDirection::isStrictlyWest()`
- new class `Direction` for checking if one point is north, eat, south or west from another point
- new Class `Intersection` for checking if two geometries intersect each other
## [4.0.0] - 2021-11-29
### Changed
- drop support for PHP 7.2 **breaking change**
- add support for PHP 8.1
- add deprecations for setter methods in `DMS` and `Line` classes
## [3.2.1] - 2021-03-04
### Fixed
- Division by zero in `SimplifyBearing` if two consecutive points share the same location, fixes #79.
## [3.2.0] - 2020-10-09
### Added
- Calculation of [Cardinal Distances](https://phpgeo.marcusjaschen.de/Calculations/Cardinal_Distance.html) between two points. Thanks @LeoVie!
### Changed
- change `static` to `self` to prevent accidentally calling the constructor with wrong arguments in child classes (`Ellipsoid`, `Line`, `Polygon`, `Polyline`)
## [3.1.0] - 2020-07-24
### Added
- Simplifying polygons is now supported as well, see `simplifyGeometry()` methods in `SimplifyBearing` and `SimplifyDouglasPeucker` classes (fixes #69).
## [3.0.1] - 2020-05-18
### Fixed
- \#68 `CoordinateFactory` emitted a warning if a coordindates string without arc seconds was passed to the `fromString()` method
## [3.0.0] - 2020-02-07
### Changed
- *phpgeo* requires PHP >= 7.2 now
- **backwards compatibility breaking:** fix double space in Ellipsoid Name `World␣Geodetic␣System␣␣1984``World␣Geodetic␣System␣1984` (#49)
- updated tests for PHPUnit 8
### Added
- class constant visibiliy modifiers
### Removed
- support for PHP 7.0 and PHP 7.1 from Travis CI config
## [2.6.0] - 2020-02-05
### Added
- method `getIntermediatePoint()` to the `Line` class which calculates an intermediate point on a line by following the Great Circle between the two line ends and dividing the line by the given fraction (0.0 ... 1.0)
## [2.5.0] - 2020-02-04
### Added
- method `getMidpoint()` to the `Line` class which calculates the midpoint of a line by following the Great Circle between the two line ends and dividing the line into two halves.
- utility class `Cartesian` which abstracts three-dimensional cartesian coordinates *x*, *y*, and *z*
## [2.4.1] - 2020-01-29
### Changed
- access modifier for the `tolerance` attribute is now protected (`SimplifyDouglasPeucker`)
## [2.4.0] - 2020-01-27
### Added
- `BoundsFactory` to create a bounds instance for a center point and a given distance to the bounds' corners. Thanks @sdennler!
## [2.3.1] - 2019-12-21
### Fixed
- improve precision in `PointToLineDistance`
## [2.3.0] - 2019-12-19
### Added
- `PointToLineDistance` calculates the smallest distance between a point and a line
## [2.2.0] - 2019-11-25
### Added
- `hasSameLocation()` checks if two points share the same location (optionally within a distance which defaults to 0.001 m = 1 mm)
- `addUniquePoint` adds unique points to a polyline (i.e., points that doesn't already exist in that polyline)
- `getAveragePoint()` returns the average value of latitude and longitude values for a polyline
### Fixed
- wrongly placed parenthesis in `Polygon::contains()`
## [2.1.0] - 2019-03-22
### Added
- The bounds for a `Polyline` can now be retrieved in form of a `Bound` object.
### Changed
- The auto-loader is now PSR-4 compatible; directory structure was flattened by one level.
## [2.0.5] - 2019-02-27
### Changed
- improvements to the Douglas-Peucker processor. Thanks @iamskey!
## [2.0.3] - 2018-07-19
### Fixed
- Links to documentation in README. Thanks @JonathanMH
### Changed
- better floating point number comparisons in `Vincenty`
- add exception message in `Vincenty`
- type-cast regexp matches before doing calculations in `CoordinateFactory`
## [2.0.2] - 2018-03-27
### Added
- Information on how to run checks and tests for developers in the README.
### Changed
- Updated internal stuff like type and return hints after running a static analysis.
- Updated some PHPDoc blocks after running a static analysis.
### Fixed
- Wrongly typed return value in `BearingEllipsoidal::inverseVincenty()`.
## [2.0.1] - 2018-02-16
### Added
- new supported format for coordinates parser. Thanks to @petrknap
## [2.0.0] - 2017-09-27
### Changed
* License: *phpgeo* is now distributed under the MIT license
* phpgeo requires at least PHP 7.0
### Removed
* deprecated class `Simplify` was removed; alternatives: `SimplifyBearing` or `SimplifyDouglasPeucker`
* PHP versions 5.4, 5.5, and 5.6 are no longer supported
## [1.3.8] - 2017-07-05
### Fixed
* Area calculation for polygons works now. Thanks to @felixveysseyre
## [1.3.7] - 2017-07-01
### Fixed
* GeoJSON output for polygon is now compliant with RFC 7946. Thanks to @arsonik
## [1.3.5] - 2016-08-19
### Added
* add method for calculating the final bearing for a `Line` object
## [1.3.3] - 2016-08-16
### Fixed
* bugifx for a division-by-zero error which occurred when symplifying a polyline
with the Douglas-Peucker algorithm.
## [1.3.2] - 2016-03-26
### Added
* add an utility class to calculate the perpendicular distance between a point
and a line; [documentation](https://phpgeo.marcusjaschen.de/#_perpendicular_distance)
## [1.3.1] - 2016-03-26
### Added
* add method to calculate the bearing of a `Line` instance (point 1 -> point 2)
## [1.3.0] - 2016-03-26
### Added
* A new `SimplifyInterface` was introduced and is implemented in two classes:
`SimplifyDouglasPeucker` and `SimplifyBearing`
* Added documentation
### Deprecated
* The `Simplify` processor class is now deprecated and will be removed in the
2.0 release.
## [1.2.1] - 2016-03-15
### Added
* Added functionality to change the direction of Polygon instances
* Added documentation
## [1.2.0] - 2016-03-14
### Added
* Added geofence check for arbitrary geometry objects
* Extended and updated documentation
## [1.1.1] - 2016-03-13
### Added
* Added formatter for "Decimal Minutes" format, e.g. `43° 37.386' N, 070° 12.472' W`
* Added documentation for the new formatter
## [1.1.0] - 2016-03-12
### Added
* Added calculation of the bearing angle between two points (initial and final bearing)
* Added calculation of the destination point for a given starting point, the bearing angle, and the distance
* Support for spherical and ellipsoidal algorithms for the described bearing calculations
* Added documentation for the bearing calculations
## [1.0.4] - 2016-03-11
### Added
* Added functionality to change the direction of Line/Polyline instances
* Added documentation
## [1.0.3] - 2016-03-10
### Added
* Added documentation sources in mkdocs format. Documentation is now available online at http://phpgeo.marcusjaschen.de/
## [1.0.2] - 2016-03-04
### Changed
* several optimizations in control structures
## [1.0.0] - 2016-02-11
### Added
* Added license information. *phpgeo* is now licensed under the GPL 3. (see issue [#8](https://github.com/mjaschen/phpgeo/issues/8))
## [0.4.0] - 2015-10-29
### Deprecated
* removed support for PHP 5.3; introduced short array syntax
## [0.3.0] - 2015-10-29
### Added
* added the new Polyline class (thanks [@paulvl](https://github.com/paulvl))

View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mjaschen@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -0,0 +1,17 @@
# Contributing to phpgeo
Note: It's a good idea to [open an issue](https://github.com/mjaschen/phpgeo/issues)
for bugs or feature proposals first.
The contribution workflow is described as follows:
1. Fork phpgeo, clone repository (`git clone git@github.com:yourname/phpgeo.git`)
2. Checkout your feature or bug-fix branch (e. g. `git checkout -b fix-random-bug`)
3. Install dependencies: `composer install`
4. Add tests for your changes
5. Make your changes
6. Run the tests (`composer ci`)
7. Iterate through steps 3 to 5 until all tests pass.
8. Commit your changes (`git add -A -- . && git commit`)
9. Push to your fork (`git push --set-upstream origin fix-random-bug`)
10. Create a pull request from your feature or bug-fix branch to phpgeo's "master" branch

21
admin/vendor/mjaschen/phpgeo/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright 2017 Marcus Jaschen, https://www.marcusjaschen.de/ <mjaschen@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

48
admin/vendor/mjaschen/phpgeo/Makefile vendored Normal file
View File

@ -0,0 +1,48 @@
UPLOAD_HOST=phpgeo.marcusjaschen.de
UPLOAD_PATH=phpgeo.marcusjaschen.de
PHP ?= php
.PHONY: docs
docs: daux
.PHONY: daux
daux:
rm -Rf build/daux
mkdir -p build/daux
docker run --rm -v "$(PWD)":/src -w /src daux/daux.io daux generate -d build/daux
.PHONY: clean
clean:
rm -Rf build
.PHONY: upload_docs
upload_docs: docs
rsync --recursive --delete build/daux/ $(UPLOAD_HOST):$(UPLOAD_PATH)/
.PHONY: ci
ci: lint coding-standards composer-validate sniff static-analysis-psalm unit-tests
.PHONY: coding-standards
coding-standards: sniff
.PHONY: composer-validate
composer-validate:
composer validate --no-check-publish
.PHONY: lint
lint:
$(PHP) ./vendor/bin/parallel-lint src
.PHONY: sniff
sniff:
# the `-` prefix ignores the exit status of the command
-$(PHP) ./vendor/bin/phpcs --standard=codesniffer_rules.xml src
.PHONY: static-analysis-psalm
static-analysis-psalm:
$(PHP) ./vendor/bin/psalm
.PHONY: unit-tests
unit-tests:
$(PHP) ./vendor/bin/phpunit

273
admin/vendor/mjaschen/phpgeo/README.md vendored Normal file
View File

@ -0,0 +1,273 @@
# phpgeo - A Simple Geo Library for PHP
phpgeo provides abstractions to geographical coordinates (including support for different ellipsoids) and allows you to calculate geographical distances between coordinates with high precision.
[![Latest Stable Version](https://poser.pugx.org/mjaschen/phpgeo/v)](//packagist.org/packages/mjaschen/phpgeo)
[![Total Downloads](https://poser.pugx.org/mjaschen/phpgeo/downloads)](//packagist.org/packages/mjaschen/phpgeo)
[![phpgeo Tests](https://github.com/mjaschen/phpgeo/actions/workflows/php.yml/badge.svg)](https://github.com/mjaschen/phpgeo/actions/workflows/php.yml)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mjaschen/phpgeo/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mjaschen/phpgeo/?branch=master)
[![License](https://poser.pugx.org/mjaschen/phpgeo/license)](//packagist.org/packages/mjaschen/phpgeo)
## Table of Contents
<!-- MarkdownTOC autolink=true bracket=round depth=0 autoanchor=false -->
- [phpgeo - A Simple Geo Library for PHP](#phpgeo---a-simple-geo-library-for-php)
- [Table of Contents](#table-of-contents)
- [Requirements](#requirements)
- [Documentation](#documentation)
- [Installation](#installation)
- [License](#license)
- [Features](#features)
- [Examples/Usage](#examplesusage)
- [Distance between two coordinates (Vincenty's Formula)](#distance-between-two-coordinates-vincentys-formula)
- [Simplifying a polyline](#simplifying-a-polyline)
- [Polygon contains a point (e.g. "GPS geofence")](#polygon-contains-a-point-eg-gps-geofence)
- [Formatted output of coordinates](#formatted-output-of-coordinates)
- [Decimal Degrees](#decimal-degrees)
- [Degrees/Minutes/Seconds (DMS)](#degreesminutesseconds-dms)
- [GeoJSON](#geojson)
- [Development](#development)
- [Run Tests](#run-tests)
- [Miscellaneous](#miscellaneous)
- [Credits](#credits)
<!-- /MarkdownTOC -->
## Requirements
Minimum required PHP version is 7.3. *phpgeo* fully supports PHP 8.
The 3.x releases require PHP >= 7.2 but don't get feature updates any longer. Bugfixes will be backported.
The 2.x releases require PHP >= 7.0 but don't get feature updates any longer. Bugfixes won't be backported.
The 1.x release line has support for PHP >= 5.4. Bugfixes won't be backported.
## Documentation
The documentation is available at https://phpgeo.marcusjaschen.de/
## Installation
Using [Composer](https://getcomposer.org), just add it to your `composer.json` by running:
```
composer require mjaschen/phpgeo
```
## Upgrading
Update the version constraint in the project's `composer.json` and
run `composer update` or require the new version by running:
```shell
composer require mjaschen/phpgeo:^4.0
```
## License
Starting with version 2.0.0 phpgeo is licensed under the MIT license. Older versions were GPL-licensed.
## Features
**Info:** Please visit the **[documentation site](https://phpgeo.marcusjaschen.de/)** for complete and up-to-date documentation with many examples!
phpgeo provides the following features (follow the links for examples):
- abstractions of several geometry objects ([coordinate/point](https://phpgeo.marcusjaschen.de/Geometries/Coordinate.html),
[line](https://phpgeo.marcusjaschen.de/Geometries/Line.html),
[polyline/GPS track](https://phpgeo.marcusjaschen.de/Geometries/Polyline.html),
[polygon](https://phpgeo.marcusjaschen.de/Geometries/Polygon.html)
- support for different [ellipsoids](https://phpgeo.marcusjaschen.de/Geometries/Ellipsoid.html), e.g. WGS-84
- [length/distance/perimeter calculations](https://phpgeo.marcusjaschen.de/Calculations/Distance_and_Length.html)
with different implementations (Haversine, Vincenty)
- [Geofence](https://phpgeo.marcusjaschen.de/Calculations/Geofence.html) calculation,
i.e. answering the question "Is this point contained in that area/polygon?" and other [intersection](https://phpgeo.marcusjaschen.de/Comparisons/Intersections.html) checks between different geometries
- [formatting and output](https://phpgeo.marcusjaschen.de/Formatting_and_Output/index.html) of geometry objects
(GeoJSON, nice strings, e. g. `18° 54 41″ -155° 40 42″`)
- calculation of [bearing angle between two points](https://phpgeo.marcusjaschen.de/Calculations/Bearing_and_Destination.html#page_Bearing-between-two-points)
(spherical or with Vincenty's formula)
- calculation of a [destination point for a given starting point](https://phpgeo.marcusjaschen.de/Calculations/Bearing_and_Destination.html#page_Destination-point-for-given-bearing-and-distance),
bearing angle, and distance (spherical or with Vincenty's formula)
- calculation of the [perpendicular distance between a point and a line](https://phpgeo.marcusjaschen.de/Calculations/Perpendicular_Distance.html)
- calculation of the [Cardinal Distances between two points](https://phpgeo.marcusjaschen.de/Calculations/Cardinal_Distance.html)
- getting segments of a [polyline](https://phpgeo.marcusjaschen.de/Geometries/Polyline.html#page_Segments)
/[polygon](https://phpgeo.marcusjaschen.de/Geometries/Polygon.html#page_Segments),
- [reversing direction](https://phpgeo.marcusjaschen.de/Geometries/Polygon.html#page_Reverse-Direction)
of polyline/polygon
## Examples/Usage
This list is incomplete, please visit the [documentation site](https://phpgeo.marcusjaschen.de/)
for the full monty of documentation and examples!
### Distance between two coordinates (Vincenty's Formula)
Use the calculator object directly:
```php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
$calculator = new Vincenty();
echo $calculator->getDistance($coordinate1, $coordinate2); // returns 128130.850 (meters; ≈128 kilometers)
```
or call the `getDistance()` method of a Coordinate object by injecting a calculator object:
```php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
echo $coordinate1->getDistance($coordinate2, new Vincenty()); // returns 128130.850 (meters; ≈128 kilometers)
```
### Simplifying a polyline
Polylines can be simplified to save storage space or bandwidth. Simplification is done with the [RamerDouglasPeucker algorithm](https://en.wikipedia.org/wiki/RamerDouglasPeucker_algorithm) (AKA Douglas-Peucker algorithm).
```php
<?php
use Location\Coordinate;
use Location\Polyline;
use Location\Distance\Vincenty;
$polyline = new Polyline();
$polyline->addPoint(new Coordinate(10.0, 10.0));
$polyline->addPoint(new Coordinate(20.0, 20.0));
$polyline->addPoint(new Coordinate(30.0, 10.0));
$processor = new Simplify($polyline);
// remove all points which perpendicular distance is less
// than 1500 km from the surrounding points.
$simplified = $processor->simplify(1500000);
// simplified is the polyline without the second point (which
// perpendicular distance is ~1046 km and therefore below
// the simplification threshold)
```
### Polygon contains a point (e.g. "GPS geofence")
phpgeo has a polygon implementation which can be used to determinate if a point is contained in it or not.
A polygon consists of at least three points. Points are instances of the `Coordinate` class.
**Warning:** The calculation gives wrong results if the polygons has points on both sides of the 180/-180 degrees meridian.
```php
<?php
use Location\Coordinate;
use Location\Polygon;
$geofence = new Polygon();
$geofence->addPoint(new Coordinate(-12.085870,-77.016261));
$geofence->addPoint(new Coordinate(-12.086373,-77.033813));
$geofence->addPoint(new Coordinate(-12.102823,-77.030938));
$geofence->addPoint(new Coordinate(-12.098669,-77.006476));
$outsidePoint = new Coordinate(-12.075452, -76.985079);
$insidePoint = new Coordinate(-12.092542, -77.021540);
var_dump($geofence->contains($outsidePoint)); // returns bool(false) the point is outside the polygon
var_dump($geofence->contains($insidePoint)); // returns bool(true) the point is inside the polygon
```
### Formatted output of coordinates
You can format a coordinate in different styles.
#### Decimal Degrees
```php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
$coordinate = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
echo $coordinate->format(new DecimalDegrees());
```
#### Degrees/Minutes/Seconds (DMS)
```php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DMS;
$coordinate = new Coordinate(18.911306, -155.678268); // South Point, HI, USA
$formatter = new DMS();
echo $coordinate->format($formatter); // 18° 54 41″ -155° 40 42″
$formatter->setSeparator(", ")
->useCardinalLetters(true)
->setUnits(DMS::UNITS_ASCII);
echo $coordinate->format($formatter); // 18° 54' 41" N, 155° 40' 42" W
```
#### GeoJSON
```php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\GeoJSON;
$coordinate = new Coordinate(18.911306, -155.678268); // South Point, HI, USA
echo $coordinate->format(new GeoJSON()); // { "type" : "point" , "coordinates" : [ -155.678268, 18.911306 ] }
```
## Development
### Run Tests
Before submitting a pull request, please be sure to run all checks and tests and ensure everything is green.
- lint PHP files for syntax errors: `composer ci:lint`
- run static analysis with [Psalm][] and report errors: `composer ci:psalm`
- run unit tests with PHPUnit: `composer ci:tests`
To run all checks and tests at once, just use `composer ci`.
Of course, it's possible to use the test runners directly, e.g. for PHPUnit:
```shell
./vendor/bin/phpunit
```
Psalm:
```shell
./vendor/bin/psalm
```
## Credits
* Marcus Jaschen <mail@marcusjaschen.de> and [all contributors](https://github.com/mjaschen/phpgeo/graphs/contributors)
* [Chris Veness](http://www.movable-type.co.uk/scripts/latlong-vincenty.html) - JavaScript implementation of the [Vincenty formula](http://en.wikipedia.org/wiki/Vincenty%27s_formulae) for distance calculation
* Ersts,P.J., Horning, N., and M. Polin[Internet] Perpendicular Distance Calculator(version 1.2.2) [Documentation](http://biodiversityinformatics.amnh.org/open_source/pdc/documentation.php). American Museum of Natural History, Center for Biodiversity and Conservation. Available from http://biodiversityinformatics.amnh.org/open_source/pdc. Accessed on 2013-07-07.
* W. Randolph Franklin, PNPOLY - Point Inclusion in Polygon Test [Documentation](http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html)
[Psalm]: https://github.com/vimeo/psalm

View File

@ -0,0 +1,77 @@
{
"name": "mjaschen/phpgeo",
"description": "Simple Yet Powerful Geo Library",
"keywords": [
"distance",
"area",
"coordinate",
"geo",
"gis",
"bounds",
"ellipsoid",
"calculation",
"polyline",
"polygon",
"geofence",
"simplify",
"length",
"vincenty",
"haversine",
"bearing",
"projection",
"gps",
"earth",
"track",
"point",
"perpendicular"
],
"homepage": "https://phpgeo.marcusjaschen.de/",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Marcus Jaschen",
"email": "mjaschen@gmail.com",
"homepage": "https://www.marcusjaschen.de/"
}
],
"readme": "README.md",
"support" : {
"issues" : "https://github.com/mjaschen/phpgeo/issues",
"docs": "https://phpgeo.marcusjaschen.de/Installation.html",
"email" : "mjaschen@gmail.com"
},
"require": {
"php": "^7.3 || ^8.0"
},
"autoload": {
"psr-4": {
"Location\\": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^4.13",
"squizlabs/php_codesniffer": "^3.6"
},
"scripts": {
"ci:composer-validate": "composer validate --no-check-all --no-check-lock --strict",
"ci:lint": "find src tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l",
"ci:psalm": "./vendor/bin/psalm",
"ci:sniff": "./vendor/bin/phpcs src tests",
"ci:tests": "./vendor/bin/phpunit tests/",
"ci:static": [
"@ci:composer-validate",
"@ci:lint",
"@ci:psalm",
"@ci:sniff"
],
"ci:dynamic": [
"@ci:tests"
],
"ci": [
"@ci:static",
"@ci:dynamic"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
# What is *phpgeo?*
_phpgeo_ is a small PHP library which provides abstractions to geographical
coordinates (including support for different ellipsoids), polylines
("GPS Tracks"), polygons, bounds, and more. _phpgeo_ allows you to perform
different calculations with these abstractions, as distances, track
lengths, etc.
_phpgeo_ is developed by [Marcus Jaschen](https://www.marcusjaschen.de/) and all
[contributors](https://github.com/mjaschen/phpgeo/graphs/contributors).
_phpgeo_ is licensed under the [MIT License](https://opensource.org/licenses/MIT).
The project is hosted on Github:
- [Github Project Site](https://github.com/mjaschen/phpgeo)
- [Issue Tracker](https://github.com/mjaschen/phpgeo/issues)
## Privacy
The privacy statement for this documentation site can be found here:
[Datenschutzerklärung](https://www.marcusjaschen.de/datenschutzerklaerung/)

View File

@ -0,0 +1,24 @@
# Getting phpgeo
## Requirements
_phpgeo_ requires at least PHP 7.3. _phpgeo_ fully supports PHP 8.
The 3.x releases require PHP >= 7.2 but don't get feature updates any longer. Bugfixes will be backported.
The 2.x releases require PHP >= 7.0 but don't get feature updates any longer. Bugfixes won't be backported.
The 1.x release line has support for PHP >= 5.4. Bugfixes won't be backported.
## Installation
_phpgeo_ is best be installed using Composer. Please visit the
[Composer website](https://getcomposer.org/) website for more information.
To install _phpgeo,_ simply “require” it using Composer:
``` shell
composer require mjaschen/phpgeo
```
_phpgeo_ is now ready to be used in your project!

View File

@ -0,0 +1,38 @@
# Upgrading phpgeo
## Update from phpgeo 3.x to phpgeo 4.x
### Requirements
- _phpgeo_ 4.x requires at least PHP 7.3 and fully supports PHP 8
### Update phpgeo
- run `composer require mjaschen/phpgeo:^4.0` or
- update the version constraint in your `composer.json` to `^4.0` and run `composer update`
### Update Your Code
- Setters in `DMS` and `Line` classes are deprecated and will be removed
with the next release. Use constructor arguments instead.
No breaking changes were introduced with *phpgeo* 3.0.
## Update from phpgeo 2.x to phpgeo 3.x
### Requirements
- _phpgeo_ 3.x requires at least PHP 7.2
### Update phpgeo
- run `composer require mjaschen/phpgeo:^3.0` or
- update the version constraint in your `composer.json` to `^3.0` and run `composer update`
### Update Your Code
The result of `Ellipsoid::getName()` for the built-in *WGS-84* ellipsoid returned `World␣Geodetic␣System␣␣1984` in *phpgeo* 2.x (two space characters before `1984`). Starting with *phpgeo* 3.0, the result is `World␣Geodetic␣System␣1984` (single space character before `1984`). Please verify that your code is still working if you're using that result.
Starting with *phpgeo* 3.0 class constant visiblity modifiers are used. One class constant was changed to *private* visibility: `BearingSpherical::EARTH_RADIUS`. Please verify that your code isn't using that class constant.
No further breaking changes were introduced with *phpgeo* 3.0.

View File

@ -0,0 +1,96 @@
# Development
## Run Tests
_phpgeo_ provides unit tests with a quite good coverage. For an easy usage,
the test command is wrapped as a Composer script:
``` shell
composer ci:tests
```
Of course it's possible to run PHPUnit directly:
``` shell
./vendor/bin/phpunit
```
To test against another PHP version you can use Docker. The following command runs
the tests using PHP 7.3:
``` shell
docker run -it --rm --name phpgeo-phpunit \
-v "$PWD":/usr/src/phpgeo \
-w /usr/src/phpgeo php:7.3-cli \
php vendor/bin/phpunit
```
Or PHP 7.4:
``` shell
docker run -it --rm --name phpgeo-phpunit \
-v "$PWD":/usr/src/phpgeo \
-w /usr/src/phpgeo php:7.4-cli \
php vendor/bin/phpunit
```
PHP 8.0:
``` shell
docker run -it --rm --name phpgeo-phpunit \
-v "$PWD":/usr/src/phpgeo \
-w /usr/src/phpgeo php:8.0-cli \
php vendor/bin/phpunit
```
PHP 8.1:
``` shell
docker run -it --rm --name phpgeo-phpunit \
-v "$PWD":/usr/src/phpgeo \
-w /usr/src/phpgeo php:8.1-cli \
php vendor/bin/phpunit
```
Alongside with the unit tests, static test runners are also provided. Run the lint
command to ensure the sources don't contain any syntax error:
``` shell
composer ci:lint
```
A static code analysis with [Psalm](https://psalm.dev/) is configured as well:
``` shell
composer ci:psalm
```
It's possible to run all tests at once:
``` shell
composer ci
```
… or run all CI tasks with different PHP versions one after another:
```shell
for PHP_VERSION in 7.3 7.4 8.0 8.1 ; do \
docker run -it --rm -v "$PWD":/phpgeo -w /phpgeo \
ghcr.io/mjaschen/php:${PHP_VERSION}-cli-mj composer ci || break ; \
done
```
## Creating the documentation
*phpgeo's* documentation is generated with [Daux](https://daux.io/) from Markdown files.
The `Makefile` provides a helper target for generating the complete documentation:
``` shell
make docs
```
*Daux* can also be run from its official Docker image:
``` shell
docker run --rm -it -v "$(pwd)":/phpgeo -w /phpgeo daux/daux.io daux generate -d build/daux
```

View File

@ -0,0 +1,15 @@
# Coordinate
The `Coordinate` class is the most important class of phpgeo and provides the
base for all features. It's a representation of a geographic location and
consists of three parts:
- Geographic Latitude
- Geographic Longitude
- Ellipsoid
Geographic latitude and longitude values are float numbers between
-90.0 and 90.0 (degrees latitude) and -180.0 and 180.0 (degrees longitude).
The Ellipsoid is a representation of an approximated shape of the earth and
is abstracted in its own [`Ellipsoid`](Ellipsoid) Link class.

View File

@ -0,0 +1,170 @@
# Line
[TOC]
A line consists of two points, i. e. instances of the `Coordinate` class.
## Length
The `Line` class provides a method to calculate its own length. The method
expects an instance of a class which implements the `DistanceInterface`.
``` php
<?php
use Location\Coordinate;
use Location\Distance\Haversine;
use Location\Line;
$line = new Line(
new Coordinate(52.5, 13.5),
new Coordinate(52.6, 13.4)
);
$length = $line->getLength(new Haversine());
printf("The line has a length of %.3f meters\n", $length);
```
`Haversine` is one of the currently two available classes for
distance calculation. The other one is named `Vincenty`.
The code above will produce the output below:
``` plaintext
The line has a length of 13013.849 meters
```
## Midpoint
The midpoint of a line is calculated by following the Great Circle (defined by the two endpoints) and dividing the line into two halves.
``` php
<?php
declare(strict_types=1);
use Location\Coordinate;
use Location\Distance\Haversine;
use Location\Line;
$line = new Line(
new Coordinate(35, 45),
new Coordinate(35, 135)
);
$midpoint = $line->getMidpoint();
printf(
'The midpoint of the line is located at %.3f degrees latitude and %.3f degrees longitude.%s',
$midpoint->getLat(),
$midpoint->getLng(),
PHP_EOL
);
printf(
'Its distance from the first point is %.1f meters, its distance from the second point is %.1f meters.%s',
$line->getPoint1()->getDistance($midpoint, new Haversine()),
$line->getPoint2()->getDistance($midpoint, new Haversine()),
PHP_EOL
);
```
The code above produces the output below:
``` plaintext
The midpoint of the line is located at 44.719 degrees latitude and 90.000 degrees longitude.
Its distance from the first point is 3935890.0 meters, its distance from the second point is 3935890.0 meters.
```
## Intermediate Point
Similar to the midpoint calculation but divides the line at the given fraction (between 0.0 … 1.0; but values outside that range work as well).
``` php
<?php
declare(strict_types=1);
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalMinutes;
use Location\Line;
$line = new Line(
new Coordinate(0, 0),
new Coordinate(1, 1)
);
$result = $line->getIntermediatePoint(0.25);
printf(
'The first quarter of the line ends at %s%s',
$result->format(new DecimalMinutes(' ')),
PHP_EOL
);
```
The code above produces the output below:
``` plaintext
The first quarter of the line ends at 00° 15.001 000° 14.999
```
## Bearing
The bearing of an instance can be calculated using the `getBearing()` method.
An instance of `BearingInterface` must be provided as method argument.
``` php
<?php
use Location\Bearing\BearingEllipsoidal;
use Location\Coordinate;
use Location\Line;
$line = new Line(
new Coordinate(52.5, 13.5),
new Coordinate(52.6, 13.4)
);
$bearing = $line->getBearing(new BearingEllipsoidal());
printf("The line has a bearing of %.2f degrees\n", $bearing);
```
`BearingEllipsoidal` is one of the currently two available classes for
bearing calculation. The other one is named `BearingSpherical`.
The code above will produce the output below:
``` plaintext
The line has a bearing of 328.67 degrees
```
This ist the so called _initial bearing._ There exist another bearing angle,
called the _final bearing._ It can be calculated as well:
``` php
<?php
use Location\Bearing\BearingEllipsoidal;
use Location\Coordinate;
use Location\Line;
$line = new Line(
new Coordinate(52.5, 13.5),
new Coordinate(52.6, 13.4)
);
$bearing = $line->getFinalBearing(new BearingEllipsoidal());
printf("The line has a final bearing of %.2f degrees\n", $bearing);
```
The code above will produce the output below:
``` plaintext
The line has a final bearing of 328.59 degrees
```
See Bearing between two points @TODO Link for more information about bearings.

View File

@ -0,0 +1,126 @@
# Polyline
[TOC]
A polyline consists of an ordered list of locations, i. e. instances of
the `Coordinate` class.
## Create a polyline
To create a polyline, just instantiate the class and add points:
``` php
<?php
use Location\Coordinate;
use Location\Polyline;
$polyline = new Polyline();
$polyline->addPoint(new Coordinate(52.5, 13.5));
$polyline->addPoint(new Coordinate(54.5, 12.5));
$polyline->addPoint(new Coordinate(55.5, 14.5));
?>
```
It's possible to add points to the end of the polyline at every time with the `addPoint()` method.
Use `addUniquePoint()` to add unique points, i.e. points which doesn't exist already in the polyline.
## Segments
It's possible to get a list of polyline segments. Segments are returned as an
array of `Line` instances.
``` php
<?php
use Location\Coordinate;
use Location\Polyline;
$track = new Polyline();
$track->addPoint(new Coordinate(52.5, 13.5));
$track->addPoint(new Coordinate(54.5, 12.5));
$track->addPoint(new Coordinate(55.5, 14.5));
foreach ($track->getSegments() as $segment) {
printf(
"Segment length: %0.2f kilometers\n",
($segment->getLength(new Haversine()) / 1000)
);
}
```
The code above will produce the output below:
``` plaintext
Segment length: 232.01 kilometers
Segment length: 169.21 kilometers
```
## Length
Length calculation is described in the [Distance and Length](../Calculations/Distance_and_Length) section.
## Average Point
The `getAveragePoint()` method returns a point which latitude and longitude is the average of latitude/longitude values from all polyline points.
CAUTION: This method currently returns wrong values if the polyline crosses the date line at 180/-180 degrees longitude.
## Reverse Direction
It's possible to get a new instance with reversed direction while the
original polyline stays unchanged:
``` php
<?php
use Location\Coordinate;
use Location\Polyline;
$track = new Polyline();
$track->addPoint(new Coordinate(52.5, 13.5));
$track->addPoint(new Coordinate(54.5, 12.5));
$reversed = $track->getReverse();
print_r($reversed);
```
The code above will produce the output below:
``` plaintext
Location\Polyline Object
(
[points:protected] => Array
(
[0] => Location\Coordinate Object
(
[lat:protected] => 54.5
[lng:protected] => 12.5
[ellipsoid:protected] => Location\Ellipsoid Object
(
[name:protected] => WGS-84
[a:protected] => 6378137
[f:protected] => 298.257223563
)
)
[1] => Location\Coordinate Object
(
[lat:protected] => 52.5
[lng:protected] => 13.5
[ellipsoid:protected] => Location\Ellipsoid Object
(
[name:protected] => WGS-84
[a:protected] => 6378137
[f:protected] => 298.257223563
)
)
)
)
```

View File

@ -0,0 +1,167 @@
# Polygon
[TOC]
A polygon consists of an ordered list of locations, i. e. instances of
the `Coordinate` class. It's very similar to a polyline, but its start
and end points are connected.
## Create a polygon
To create a polygon, just instantiate the class and add points:
``` php
<?php
use Location\Coordinate;
use Location\Polygon;
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(52.5, 13.5));
$polygon->addPoint(new Coordinate(54.5, 12.5));
$polygon->addPoint(new Coordinate(55.5, 14.5));
?>
```
It's possible to add points to the end at every time.
## Get list of points
`getPoints()` is used to get the list of points, the number of points can be
retrieved by calling `getNumberOfPoints()`:
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DMS;
use Location\Polygon;
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(52.5, 13.5));
$polygon->addPoint(new Coordinate(54.5, 12.5));
$polygon->addPoint(new Coordinate(55.5, 14.5));
printf("The polygon consists of %d points:\n", $polygon->getNumberOfPoints());
foreach ($polygon->getPoints() as $point) {
echo $point->format(new DMS()) . PHP_EOL;
}
```
The code above will produce the output below:
``` plaintext
The polygon consists of 3 points:
52° 30 00″ 013° 30 00″
54° 30 00″ 012° 30 00″
55° 30 00″ 014° 30 00″
```
## Segments
It's possible to get a list of polygon segments. Segments are
returned as an array of `Line` instances.
``` php
<?php
use Location\Coordinate;
use Location\Distance\Haversine;
use Location\Polygon;
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(52.5, 13.5));
$polygon->addPoint(new Coordinate(54.5, 12.5));
$polygon->addPoint(new Coordinate(55.5, 14.5));
foreach ($polygon->getSegments() as $line) {
printf("%0.3f m\n", $line->getLength(new Haversine()));
}
```
The code above will produce the output below:
``` plaintext
232011.020 m
169207.795 m
339918.069 m
```
## Length/Perimeter
Length calculation is described in the [Distance and Length](../Calculations/Distance_and_Length) section.
## Area
It's possible to calculate the area of an polygon. The result is given in square meters (m²).
WARNING: The calculation gives inaccurate results. For relatively small polygons the error should be less than 1 %.
``` php
<?php
use Location\Coordinate;
use Location\Polygon;
$formatter = new \Location\Formatter\Coordinate\DecimalDegrees(' ', 10);
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(0.0000000000, 0.0000000000));
$polygon->addPoint(new Coordinate(0.0000000000, 0.0008983153));
$polygon->addPoint(new Coordinate(0.0009043695, 0.0008983153));
$polygon->addPoint(new Coordinate(0.0009043695, 0.0000000000));
printf(
'Polygon Area = %f m², Perimeter = %f m%s',
$polygon->getArea(),
$polygon->getPerimeter(new \Location\Distance\Vincenty()),
PHP_EOL
);
```
The code above produces the output below:
``` plaintext
Polygon Area = 10044.905261 m², Perimeter = 400.000000 m
```
## Geofence
It's possible to check if a geometry object (point, line, polyline,
polygon) lies inside a polygon. The documentation can be found in
the <<Geofence>> @TODO section.
## Reverse Direction
It's possible to get a new instance with reversed direction while the
original polygon stays unchanged:
``` php
<?php
use Location\Coordinate;
use Location\Polygon;
use Location\Formatter\Coordinate\DecimalDegrees;
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(52.5, 13.5));
$polygon->addPoint(new Coordinate(64.1, - 21.9));
$polygon->addPoint(new Coordinate(40.7, - 74.0));
$polygon->addPoint(new Coordinate(33.9, - 118.4));
$reversed = $polygon->getReverse();
foreach ($reversed->getPoints() as $point) {
echo $point->format(new DecimalDegrees(', ')) . PHP_EOL;
}
```
The code above produces the output below:
``` plaintext
33.90000, -118.40000
40.70000, -74.00000
64.10000, -21.90000
52.50000, 13.50000
```

View File

@ -0,0 +1,26 @@
# Bounds
Bounds describe an area which is defined by its north-eastern and south-western points.
All of *phpgeo's* geometries except for the `Coordindate` class provide a `getBounds()` method via the `GetBoundsTrait`.
The `Bounds` class has a method to calculate the center point of the bounds object (works correctly for bounds that cross the dateline at 180/-180 degrees longitude too).
## Create Bounds for a given center point and distance to the corners
``` php
<?php
use Location\Factory\BoundsFactory;
use Location\Coordinate;
$bounds = BoundsFactory::expandFromCenterCoordinate(
new Coordinate(52, 13),
1000,
new BearingSpherical()
);
```
The following image illustrates how the bounds (dashed line in red) are created:
![bounds-factory](bounds-factory.png)

View File

@ -0,0 +1,61 @@
# Ellipsoid
An ellipsoid is a mathematically defined approximation of the earth's surface.
An ellipsoid is defined by two parameters:
* the semi-major axis _a_ (equatorial radius)
* the semi-minor axis _b_ (polar radius)
_a_ and _b_ together define the flattening of the ellipsoid _f_:
*f = (a-b) / a*
NOTE: _phpgeo's_ ellipsoids are defined by _a_ and _1/f_ instead of _a_
and _b_. That's not a problem because each of the three values can be
calculated from the other two.
_phpgeo_ supports arbitrary ellipsoids. _WGS-84_ is used as default when
no other ellipsoid is given. For day-to-day calculations it's not needed
to care about ellipsoids in the most cases.
It's possible to create an instance of the Ellipsoid class either by
specifing a name or by providing the three parameters _name,_ _a_, and _1/f_.
``` php
<?php
use Location\Ellipsoid;
$ellipsoid = Ellipsoid::createDefault('WGS-84');
printf(
"%s: a=%f; b=%f; 1/f=%f\n",
$ellipsoid->getName(),
$ellipsoid->getA(),
$ellipsoid->getB(),
$ellipsoid->getF()
);
$ellipsoid = new Ellipsoid('GRS-80', 6378137, 298.257222);
printf(
"%s: a=%f; b=%f; 1/f=%f\n",
$ellipsoid->getName(),
$ellipsoid->getA(),
$ellipsoid->getB(),
$ellipsoid->getF()
);
```
The first ellipsoid is created from one the the default configurations. The second one is created by providing a name and the values of *a* and *1/f.*
The code above will produce the output below:
``` plaintext
WGS-84: a=6378137.000000; b=6356752.314245; 1/f=298.257224
GRS-80: a=6378137.000000; b=6356752.314133; 1/f=298.257222
```
Please take a look into the [`Ellipsoid` source file](https://github.com/mjaschen/phpgeo/blob/master/src/Ellipsoid.php)
for a list of pre-defined ellipsoids.

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -0,0 +1,14 @@
# Geometries
_phpgeo_ provides several geometry classes:
* [`Coordinate`](Coordinate)
* [`Line`](Line)
* [`Polyline`](Polyline)
* [`Polygon`](Polygon)
A Coordinate represents a geographic location, i. e. it contains a latitude
and a longitude - together with an so called Ellipsoid.
A Line consists of two coordinates, while polylines and polygons are built
from two or more coordinates.

View File

@ -0,0 +1,30 @@
## Same Point Comparison
It's possible to check if two points describe the same location and optionally allow an error margin.
``` php
<?php
use Location\Coordinate;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
echo $coordinate1->hasSameLocation($coordinate2)
? 'Mauna Kea and Haleakala share the same location.'
: 'Mauna Kea and Haleakala have different locations.';
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(19.82365, -155.46905); // Gemini North Telescope
echo $coordinate1->hasSameLocation($coordinate2, 1000)
? 'Mauna Kea and the Gemini North Telescope are located within the same 1 km-radius.'
: 'Mauna Kea and the Gemini North Telescope are located more than 1 km apart.';
```
The code above will produce the output below:
``` plaintext
Mauna Kea and Haleakala have different locations.
Mauna Kea and the Gemini North Telescope are located within the same 1 km-radius.
```

View File

@ -0,0 +1,34 @@
## Directions
With the `Direction` class it's possible to determine if one point is north, east, south or west of another point.
```php
<?php
use Location\Coordinate;
$berlin = new Coordinate(52.5, 13.5);
$rome = new Coordinate(42, 12.5);
$helsinki = new Coordinate(60, 25);
$direction = new Direction();
if ($direction->pointIsNorthOf(point: $helsinki, compareAgainst: $berlin)) {
echo 'Helsinki is located north of Berlin.' . PHP_EOL;
} else {
echo 'Berlin is located north of Helsinki.' . PHP_EOL;
}
if ($direction->pointIsEastOf(point: $rome, compareAgainst: $berlin)) {
echo 'Rome is located east of Berlin.' . PHP_EOL;
} else {
echo 'Berlin is located east of Rome.' . PHP_EOL;
}
```
The code above will produce the output below:
``` plaintext
Helsinki is located north of Berlin.
Berlin is located east of Rome.
```

View File

@ -0,0 +1,14 @@
## Intersection Checking
It's possible to check if two geometries intersect each other.
(Until this documentation page is finished, take a look into `IntersectionTest` here you'll find some examples on how to use this feature.)
The following combinations of geometries can be checked for intersection:
| | Point | Line | Polyline | Polygon |
|----------|----------------------------------------------------------------|------|----------|-----------------------------------------------------------|
| Point | yes ([same location](/Comparisons/Same_Point_Comparison.html)) | no | no | yes ([point inside polygon](/Calculations/Geofence.html)) |
| Line | no | yes | yes | yes |
| Polyline | no | yes | yes | yes |
| Polygon | yes ([point inside polygon](/Calculations/Geofence.html)) | yes | yes | yes |

View File

@ -0,0 +1,121 @@
# Distance and Length
[TOC]
## Distance Between Two Points (Vincenty's Formula)
Use the calculator object directly:
``` php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
$calculator = new Vincenty();
echo $calculator->getDistance($coordinate1, $coordinate2);
```
The code above will produce the output below:
``` plaintext
128130.850
```
or call the `getDistance()` method of a `Coordinate` instance by injecting
a calculator instance:
``` php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
echo $coordinate1->getDistance($coordinate2, new Vincenty());
```
The code above will produce the output below:
``` plaintext
128130.850
```
## Distance Between Two Points (Haversine Formula)
There exist different methods for calculating the distance between
two points. The [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula#Law)
is much faster than Vincenty's method but less precise:
``` php
<?php
use Location\Coordinate;
use Location\Distance\Haversine;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
echo $coordinate1->getDistance($coordinate2, new Haversine());
```
The code above will produce the output below:
``` plaintext
128384.515
```
## Length of a Polyline
*phpgeo* has a polyline implementation which can be used to calculate the
length of a GPS track or a route. A polyline consists of at least two points.
Points are instances of the `Coordinate` class.
For more details about polylines/GPS tracks see the [`Polyline`](../Geometries/Polyline) section.
``` php
<?php
use Location\Coordinate;
use Location\Polyline;
use Location\Distance\Vincenty;
$track = new Polyline();
$track->addPoint(new Coordinate(52.5, 13.5));
$track->addPoint(new Coordinate(54.5, 12.5));
echo $track->getLength(new Vincenty());
```
## Perimeter of a Polygon
The perimeter is calculated as the sum of the length of all segments.
The result is given in meters.
``` php
<?php
use Location\Distance\Vincenty;
use Location\Coordinate;
use Location\Polygon;
$polygon = new Polygon();
$polygon->addPoint(new Coordinate(10, 10));
$polygon->addPoint(new Coordinate(10, 20));
$polygon->addPoint(new Coordinate(20, 20));
$polygon->addPoint(new Coordinate(20, 10));
echo $polygon->getPerimeter(new Vincenty());
```
The code above will produce the output below:
``` plaintext
4355689.472
```

View File

@ -0,0 +1,143 @@
# Bearing and Destination
[TOC]
phpgeo can be used to calculate the bearing between two points and to
get a destination point for a given start point together with a bearing
angle and a distance.
Multiple calculation algorithms are supported. Currently phpgeo provides
methods for calculations with a _spherical_ earth model and with an
_ellipsoidal_ model. The spherical calculations are very fast, compared
to the ellipsoidal methods. The ellipsoidal algorithms are a bit more
precise on the other hand.
## Bearing between two points
Given two points, it's possible to calculate the bearing angled between
those points.
phpgeo can calculate the initial bearing (bearing as seen from the first
point) and the final bearing (bearing as seen approaching the destination
point).
### Calculation with a spherical earth model
``` php
<?php
use Location\Bearing\BearingSpherical;
use Location\Coordinate;
$berlin = new Coordinate(52.5, 13.5);
$london = new Coordinate(51.5, -0.12);
$bearingCalculator = new BearingSpherical();
$startTime = microtime(true);
var_dump($bearingCalculator->calculateBearing($berlin, $london));
var_dump($bearingCalculator->calculateFinalBearing($berlin, $london));
$endTime = microtime(true);
printf("Time elapsed: %0.6f s\n", ($endTime - $startTime));
```
The code above will produce the following output:
``` plaintext
double(268.60722336693)
double(257.85494586285)
Time elapsed: 0.000285 s
```
### Calculation with an ellipsoidal earth model
``` php
<?php
use Location\Bearing\BearingEllipsoidal;
use Location\Coordinate;
$berlin = new Coordinate(52.5, 13.5);
$london = new Coordinate(51.5, -0.12);
$bearingCalculator = new BearingEllipsoidal();
$startTime = microtime(true);
var_dump($bearingCalculator->calculateBearing($berlin, $london));
var_dump($bearingCalculator->calculateFinalBearing($berlin, $london));
$endTime = microtime(true);
printf("Time elapsed: %0.6f s\n", ($endTime - $startTime));
```
The code above will produce the following output:
``` plaintext
double(268.62436347111)
double(257.87203657292)
Time elapsed: 0.000304 s
```
Both calculations finish in roughly the same time. One would expect the
second calculation to be clearly slower than the first one. It seems
the exit condition for the iteration is reached quite fast. There might
exist other conditions where the ellipsoidal calculation is noticeable
slower.
## Destination point for given bearing and distance
As an example, starting from Berlin, calculate the destination point in
56.1 km distance with an initial bearing of 153 degrees:
``` php
<?php
use Location\Bearing\BearingEllipsoidal;
use Location\Bearing\BearingSpherical;
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
$berlin = new Coordinate(52.5, 13.5);
$bearingSpherical = new BearingSpherical();
$bearingEllipsoidal = new BearingEllipsoidal();
$destination1 = $BearingSpherical->calculateDestination($berlin, 153, 56100);
$destination2 = $bearingEllipsoidal->calculateDestination($berlin, 153, 56100);
echo "Spherical: " . $destination1->format(new DecimalDegrees()) . PHP_EOL;
echo "Ellipsoidal: " . $destination2->format(new DecimalDegrees()) . PHP_EOL;
```
The code above will produce the output below:
``` plaintext
Spherical: 52.04988 13.87628
Ellipsoidal: 52.05020 13.87126
```
Oh, look, what a [beautiful spot on earth](https://www.openstreetmap.org/?mlat=52.0499&mlon=13.8762#map=13/52.0499/13.8762) it is. ;-)
## Final Bearing for a calculated destination
*phpgeo* can calculate the final bearing angle for a given starting point,
an initial bearing, and the distance to the destination.
``` php
<?php
use Location\Bearing\BearingEllipsoidal;
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
$berlin = new Coordinate(52.5, 13.5);
$bearingEllipsoidal = new BearingEllipsoidal();
$finalBearing = $bearingEllipsoidal->calculateDestinationFinalBearing($berlin, 153, 56100);
var_dump($finalBearing);
```
The code above will produce the output below:
``` plaintext
float(153.29365182147)
```

View File

@ -0,0 +1,70 @@
# Cardinal Distance Between Two Points
The distances how far you have to go to one cardinal direction and eventually to another to reach the second point
*P<sub>2</sub>* from the first one *P<sub>1</sub>* is called the Cardinal Distances.
In the following example the Cardinal Distances are labeled *N* and *E:*
![Cardinal Distance](cardinal-distance.png)
[TOC]
With *phpgeo* there are two ways to calculate the Cardinal Distances:
## Using the Calculator Instance
```php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
use Location\CardinalDirection\CardinalDirectionDistancesCalculator;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
$calculator = new Vincenty();
$cardinalDirectionDistancesCalculator = new CardinalDirectionDistancesCalculator();
$result = $cardinalDirectionDistancesCalculator->getCardinalDirectionDistances($coordinate1, $coordinate2, $calculator);
echo 'Cardinal Distances: north=' . $result->getNorth()
. ' m; east=' . $result->getEast()
. ' m; south=' . $result->getSouth(
. ' m; west=' . $result->getWest() . ' m.';
```
The code above will produce the following output:
```
Cardinal Distances: north=98425.507 m; east=0 m; south=0 m; west=82268.492 m.
```
## Using the `getCardinalDirectionDistances()` method of a Coordinate instance
```php
<?php
use Location\CardinalDirection\CardinalDirection;
use Location\Coordinate;
use Location\Distance\Vincenty;
$point1 = new Coordinate(52.5072, 13.4248); // Berlin, Germany
$point2 = new Coordinate(52.4284, 13.0276); // Potsdam, Germany
$direction = (new CardinalDirection())->getCardinalDirection($point1, $point2);
$result = $point1->getCardinalDirectionDistances($point2, new Vincenty());
echo 'Cardinal Distances: direction=' . $direction
. '; north=' . $result->getNorth()
. ' m; east=' . $result->getEast()
. ' m; south=' . $result->getSouth()
. ' m; west=' . $result->getWest() . ' m.';
```
The code above will produce the following output:
```
Cardinal Distances: direction=north-east; north=0 m; east=0 m; south=8768.566 m; west=26969.504 m.
```

View File

@ -0,0 +1,43 @@
# Perpendicular Distance
The _perpendicular distance_ is defined as the shortest distance between a point
a line (in the two-dimensional plane) respectively between a point and a
[great circle](https://en.wikipedia.org/wiki/Great_circle) on a spherical surface.
With _phpgeo_ it is possible to calculate the perpendicular distance between a
point (instance of the [`Coordinate`](../Geometries/Coordinate) class) and a
Great Circle - which is defined by a [`Line`](../Geometries/Line). A line is
defined by a pair of coordinates.
The distance between points *P* and *X* is the perpendicular distance in the following sketch:
![perpendicular_distance](perpendicular-distance.png)
## Example
``` php
<?php
use Location\Coordinate;
use Location\Line;
use Location\Utility\PerpendicularDistance;
$point = new Coordinate(52.44468, 13.57455);
$line = new Line(
new Coordinate(52.4554, 13.5582),
new Coordinate(52.4371, 13.5623)
);
$pdCalc = new PerpendicularDistance();
printf(
"perpendicular distance: %.1f meters\n",
$pdCalc->getPerpendicularDistance($point, $line)
);
```
The code above will produce the output below:
``` plaintext
perpendicular distance: 936.7 meters
```

View File

@ -0,0 +1,46 @@
# Distance Between a Point and a Line
It's possible to calculate the shortest distance between a point and a
[`Line`](../Geometries/Line). As [`Polyline`](../Geometries/Polyline)
and [`Polygon`](../Geometries/Polygon) are also built upon the `Line` class
it's also possible to calculate distances from a point the polyline/polygon
by iterating over their segments.
The following image explains how the distance is calcualated: *P* and *R* are
located in such a way that the nearest distance to the line is the distance between
*P*, *R* and the line end points. Point *Q* is nearer to the actual line than to
any of the end points, so the actual distance is the perpendicular distance between *Q*
and the line.
![Point to Line Distance](point-to-line-distance.png)
## Example
``` php
<?php
use Location\Coordinate;
use Location\Distance\Vincenty;
use Location\Line;
use Location\Utility\PointToLineDistance;
$point = new Coordinate(52.5, 13.5);
$line = new Line(
new Coordinate(52.5, 13.1),
new Coordinate(52.5, 13.1)
);
$pointToLineDistanceCalculator = new PointToLineDistance(new Vincenty());
printf(
'Distance from point to line: %.1f meters%s',
$pointToLineDistanceCalculator->getDistance($point, $line),
PHP_EOL
);
```
The code above will produce the output below:
``` plaintext
Distance from point to line: 27164.1 meters
```

View File

@ -0,0 +1,40 @@
# Geofence
_phpgeo_ has a polygon implementation which can be used to determinate
if a geometry (point, line, polyline, polygon) is contained in it or not.
A polygon consists of at least three points.
WARNING: The calculation gives wrong results if the polygons crosses
the 180/-180 degrees meridian.
``` php
<?php
use Location\Coordinate;
use Location\Polygon;
$geofence = new Polygon();
$geofence->addPoint(new Coordinate(-12.085870,-77.016261));
$geofence->addPoint(new Coordinate(-12.086373,-77.033813));
$geofence->addPoint(new Coordinate(-12.102823,-77.030938));
$geofence->addPoint(new Coordinate(-12.098669,-77.006476));
$outsidePoint = new Coordinate(-12.075452, -76.985079);
$insidePoint = new Coordinate(-12.092542, -77.021540);
echo $geofence->contains($outsidePoint)
? 'Point 1 is located inside the polygon' . PHP_EOL
: 'Point 1 is located outside the polygon' . PHP_EOL;
echo $geofence->contains($insidePoint)
? 'Point 2 is located inside the polygon' . PHP_EOL
: 'Point 2 is located outside the polygon' . PHP_EOL;
```
The code above will produce the output below:
``` plaintext
Point 1 is located outside the polygon
Point 2 is located inside the polygon
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,3 @@
# Calculations
The following chapters describe the possibilities to do geographical calculations with *phpgeo,* e.g. length and distance calculations, determining bearings, etc.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,91 @@
# Simplifying a polyline/polygon
[TOC]
Polylines and polygons can be simplified to save storage space or bandwidth.
_phpgeo_ provides two implementations for simplifying polyline/polygons.
The first implementation uses the [_RamerDouglasPeucker algorithm_](https://en.wikipedia.org/wiki/RamerDouglasPeucker_algorithm)
(also known as _Douglas-Peucker algorithm_). The other implementation examines
the bearings of the segments and removes a segment when its bearing
angle is similar to the bearing angle of its predecessor segment. I named it
the _Delta-Bearing algorithm_.
## Ramer-Douglas-Peucker Algorithm
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
use Location\Polyline;
use Location\Processor\Polyline\SimplifyDouglasPeucker;
$polyline = new Polyline();
$polyline->addPoint(new Coordinate(10.0, 10.0));
$polyline->addPoint(new Coordinate(20.0, 20.0));
$polyline->addPoint(new Coordinate(30.0, 10.0));
$processor = new SimplifyDouglasPeucker(1500000);
$simplified = $processor->simplify($polyline);
foreach ($simplified->getPoints() as $point) {
echo $point->format(new DecimalDegrees()) . PHP_EOL;
}
```
The example code will remove all points which perpendicular distance is less
than 1,500,000 meters (1,500 km) from the surrounding points.
The code above produces the output below:
``` plaintext
10.00000 10.00000
30.00000 10.00000
```
## Delta-Bearing Algorithm
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
use Location\Polyline;
use Location\Processor\Polyline\SimplifyBearing;
$polyline = new Polyline();
$polyline->addPoint(new Coordinate(10.0, 10.0));
$polyline->addPoint(new Coordinate(20.0, 20.0));
$polyline->addPoint(new Coordinate(30.0, 10.0));
$processor = new SimplifyBearing(90);
$simplified = $processor->simplify($polyline);
foreach ($simplified->getPoints() as $point) {
echo $point->format(new DecimalDegrees()) . PHP_EOL;
}
```
The constructor argument for the `SimplifyBearing` class is the minimum
required angle in degrees between two adjacent polyline segments so that
no points will be removed. If the bearing angle difference is less that
the given value, the middle point will be removed from the resulting
polyline.
The code above produces the output below:
``` plaintext
10.00000 10.00000
30.00000 10.00000
```
The following image shows both a polyline and its simplified version. The
simplification was done with the Delta-Bearing Algorithm with a threshold angle
of 20 degrees. The original polyline is painted in blue, the simplified polyline
is magenta.
![Delta-Bearing simplifying](simplify.png)

View File

@ -0,0 +1,5 @@
# Transformations and Processing
*phpgeo* provides tools for transforming and processing geometry instances.
It's possible to simplify a Polyline by removing unneeded points to save
storage space for example.

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@ -0,0 +1,125 @@
# Formatting Coordinates
You can format a coordinate in different styles.
## Decimal Degrees
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
$coordinate = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
echo $coordinate->format(new DecimalDegrees());
```
The code above produces the output below:
``` plaintext
19.82066 -155.46807
```
The separator string between latitude and longitude can be configured via
constructor argument, as well as the number of decimals (default value is
5 digits):
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalDegrees;
$coordinate = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
echo $coordinate->format(new DecimalDegrees(', ', 3));
```
The code above produces the output below:
``` plaintext
19.821, -155.468
```
## Degrees/Minutes/Seconds (DMS)
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DMS;
$coordinate = new Coordinate(18.911306, -155.678268); // South Point, HI, USA
$formatter = new DMS();
echo $coordinate->format($formatter) . PHP_EOL;
$formatter->setSeparator(', ')
->useCardinalLetters(true)
->setUnits(DMS::UNITS_ASCII);
echo $coordinate->format($formatter) . PHP_EOL;
```
The code above produces the output below:
``` plaintext
18° 54 41″ -155° 40 42″
18° 54' 41" N, 155° 40' 42" W
```
## Decimal Minutes
This format is commonly used in the Geocaching community.
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\DecimalMinutes;
$coordinate = new Coordinate(43.62310, -70.20787); // Portland Head Light, ME, USA
$formatter = new DecimalMinutes();
echo $coordinate->format($formatter) . PHP_EOL;
$formatter->setSeparator(', ')
->useCardinalLetters(true)
->setUnits(DecimalMinutes::UNITS_ASCII);
echo $coordinate->format($formatter) . PHP_EOL;
```
The code above produces the output below:
``` plaintext
43° 37.386 -070° 12.472
43° 37.386' N, 070° 12.472' W
```
## GeoJSON
``` php
<?php
use Location\Coordinate;
use Location\Formatter\Coordinate\GeoJSON;
$coordinate = new Coordinate(18.911306, -155.678268); // South Point, HI, USA
echo $coordinate->format(new GeoJSON());
```
The code above produces the output below:
``` json
{"type":"Point","coordinates":[-155.678268,18.911306]}
```
NOTE: Float values processed by `json_encode()` are affected by the ini-setting
[`serialize_precision`](https://secure.php.net/manual/en/ini.core.php#ini.serialize-precision).
You can change the number of decimal places in the JSON output by changing
that ini-option, e. g. with `ini_set('serialize_precision', 8)`.

View File

@ -0,0 +1,32 @@
# Formatting Polylines
You can format a polyline in different styles.
## GeoJSON
``` php
<?php
use Location\Coordinate;
use Location\Polyline;
use Location\Formatter\Polyline\GeoJSON;
$polyline = new Polyline;
$polyline->addPoint(new Coordinate(52.5, 13.5));
$polyline->addPoint(new Coordinate(62.5, 14.5));
$formatter = new GeoJSON;
echo $formatter->format($polyline);
```
The code above produces the output below:
``` json
{"type":"LineString","coordinates":[[13.5,52.5],[14.5,62.5]]}
```
NOTE: Float values processed by `json_encode()` are affected by the ini-setting
[`serialize_precision`](https://secure.php.net/manual/en/ini.core.php#ini.serialize-precision).
You can change the number of decimal places in the JSON output by changing
that ini-option, e. g. with `ini_set('serialize_precision', 8)`.

View File

@ -0,0 +1,34 @@
# Formatting Polygons
You can format a polygon in different styles.
## GeoJSON
``` php
<?php
use Location\Coordinate;
use Location\Polygon;
use Location\Formatter\Polygon\GeoJSON;
$polygon = new Polygon;
$polygon->addPoint(new Coordinate(10, 20));
$polygon->addPoint(new Coordinate(20, 40));
$polygon->addPoint(new Coordinate(30, 40));
$polygon->addPoint(new Coordinate(30, 20));
$formatter = new GeoJSON;
echo $formatter->format($polygon);
```
The code above produces the output below:
``` json
{"type":"Polygon","coordinates":[[20,10],[40,20],[40,30],[20,30]]}
```
NOTE: Float values processed by `json_encode()` are affected by the ini-setting
[`serialize_precision`](https://secure.php.net/manual/en/ini.core.php#ini.serialize-precision).
You can change the number of decimal places in the JSON output by changing
that ini-option, e. g. with `ini_set('serialize_precision', 8)`.

View File

@ -0,0 +1,5 @@
# Formatting and Output
*phpgeo* is able to output supported Geometry instances in many different
formats. You're able to provide your own Formatter classes for customization
of the output format.

View File

@ -0,0 +1,53 @@
# Coordinates Parser
_phpgeo_ comes with a parser for several types of coordinate formats.
The parser works as a factory which creates an instance of the
`Coordinate` class.
## Supported Formats
**Decimal Degrees** with or without *cardinal letters*,
with or without a comma as separator, with or without
whitespace between values and cardinal letters.
Examples of supported formats:
- 52.5, 13.5
- 52.5 13.5
- -52.5 -13.5
- 52.345 N, 13.456 E
- N52.345 E13.456
**Decimal Minutes** with or without cardinal letters, with
or without degree and minute signs, with or without a comma
as separator, with or without whitespace between values
and cardinal letters.
Examples of supported formats:
- 345, E13° 34.567
- 45 N, E13° 34.567 E
- 5, 013 34.567
- 45, -013 34.567
The [unit test](https://github.com/mjaschen/phpgeo/blob/master/tests/Location/Factory/CoordinateFactoryTest.php)
shows some more examples.
## Example
```php
use Location\Factory\CoordinateFactory;
use Location\Formatter\Coordinate\DecimalDegrees;
require_once __DIR__ . '/vendor/autoload.php';
$point = CoordinateFactory::fromString('52° 13.698 020° 58.536');
echo $point->format(new DecimalDegrees());
```
The code above produces the output below:
``` plaintext
52.22830 20.97560
```

View File

@ -0,0 +1,7 @@
# Parsing and Input
The constructor for the `Coordinate` class accepts a pair of float values
for creating an instance. Coordinates often come in formatted versions,
e.g. with cardinal letters, formatted in degrees, arc minutes, and arc
seconds and so on. *phpgeo* provides parsers for many formats which create
correctly instantiated `Coordinate` objects.

View File

@ -0,0 +1,6 @@
# Further Reading / Sources
- [Movable Type Scripts](https://www.movable-type.co.uk/scripts/latlong.html)
- [Aviation Formulary V1.24 by Ed Williams](https://www.edwilliams.org/ftp/avsig/avform.txt)
- [Perpendicular Distance Calculator](https://biodiversityinformatics.amnh.org/open_source/pdc/index.html) ([Github](https://github.com/persts/GeographicDistanceTools))
- [W. Randolph Franklin, PNPOLY - Point Inclusion in Polygon Test](https://wrfranklin.org/Research/Short_Notes/pnpoly.html)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,25 @@
{
"title": "phpgeo",
"tagline": "A Simple Yet Powerful Geo Library for PHP",
"author": "Marcus Jaschen",
"format": "html",
"timezone": "Europe/Berlin",
"html": {
"theme": "daux-red",
"breadcrumbs": true,
"breadcrumb_separator": "Chevrons",
"toggle_code": true,
"date_modified": true,
"inherit_index": true,
"search": true,
"repo": "mjaschen/phpgeo",
"edit_on_github": "mjaschen/phpgeo/blob/master/docs",
"piwik_analytics": "metrics.m11n.de",
"piwik_analytics_id": "15",
"links": {
"Download": "https://github.com/mjaschen/phpgeo/archive/master.zip",
"GitHub Repo": "https://github.com/mjaschen/phpgeo",
"Help/Support/Bugs": "https://github.com/mjaschen/phpgeo/issues"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Coding Standard">
<description>
This standard requires PHP_CodeSniffer >= 3.4.
</description>
<arg name="colors"/>
<arg name="extensions" value="php"/>
<!--The complete PSR-12 rule set-->
<rule ref="PSR12"/>
<!-- Arrays -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
<!-- Classes -->
<rule ref="Generic.Classes.DuplicateClassName"/>
<rule ref="Squiz.Classes.ClassFileName"/>
<rule ref="Squiz.Classes.DuplicateProperty"/>
<rule ref="Squiz.Classes.LowercaseClassKeywords"/>
<rule ref="Squiz.Classes.SelfMemberReference"/>
<!-- Code analysis -->
<rule ref="Generic.CodeAnalysis.AssignmentInCondition"/>
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
<!-- Commenting -->
<rule ref="Generic.Commenting.Fixme"/>
<rule ref="Generic.Commenting.Todo"/>
<rule ref="PEAR.Commenting.InlineComment"/>
<rule ref="Squiz.Commenting.DocCommentAlignment"/>
<rule ref="Squiz.Commenting.EmptyCatchComment"/>
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
<rule ref="Squiz.Commenting.PostStatementComment"/>
<!-- Control structures -->
<rule ref="PEAR.ControlStructures.ControlSignature"/>
<!-- Debug -->
<rule ref="Generic.Debug.ClosureLinter"/>
<!-- Files -->
<rule ref="Generic.Files.OneClassPerFile"/>
<rule ref="Generic.Files.OneInterfacePerFile"/>
<rule ref="Generic.Files.OneObjectStructurePerFile"/>
<rule ref="Zend.Files.ClosingTag"/>
<!-- Formatting -->
<rule ref="PEAR.Formatting.MultiLineAssignment"/>
<!-- Functions -->
<rule ref="Generic.Functions.CallTimePassByReference"/>
<rule ref="PSR12.Functions.NullableTypeDeclaration"/>
<rule ref="Squiz.Functions.FunctionDuplicateArgument"/>
<rule ref="Squiz.Functions.GlobalFunction"/>
<!-- Metrics -->
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
<rule ref="Generic.Metrics.NestingLevel"/>
<!-- Naming conventions -->
<rule ref="Generic.NamingConventions.ConstructorName"/>
<rule ref="PEAR.NamingConventions.ValidClassName"/>
<!-- Objects -->
<rule ref="Squiz.Objects.ObjectMemberComma"/>
<!-- Operators -->
<rule ref="Squiz.Operators.IncrementDecrementUsage"/>
<rule ref="Squiz.Operators.ValidLogicalOperators"/>
<!-- PHP -->
<rule ref="Generic.PHP.BacktickOperator"/>
<rule ref="Generic.PHP.CharacterBeforePHPOpeningTag"/>
<rule ref="Generic.PHP.DeprecatedFunctions"/>
<rule ref="Generic.PHP.DisallowAlternativePHPTags"/>
<rule ref="Generic.PHP.DiscourageGoto"/>
<rule ref="Generic.PHP.ForbiddenFunctions"/>
<rule ref="Generic.PHP.NoSilencedErrors"/>
<rule ref="Squiz.PHP.CommentedOutCode">
<properties>
<property name="maxPercentage" value="70"/>
</properties>
</rule>
<rule ref="Squiz.PHP.DisallowMultipleAssignments"/>
<rule ref="Squiz.PHP.DisallowSizeFunctionsInLoops"/>
<rule ref="Squiz.PHP.DiscouragedFunctions"/>
<rule ref="Squiz.PHP.Eval"/>
<rule ref="Squiz.PHP.GlobalKeyword"/>
<rule ref="Squiz.PHP.Heredoc"/>
<rule ref="Squiz.PHP.InnerFunctions"/>
<rule ref="Squiz.PHP.LowercasePHPFunctions"/>
<rule ref="Squiz.PHP.NonExecutableCode"/>
<!-- Scope -->
<rule ref="Squiz.Scope.MemberVarScope"/>
<rule ref="Squiz.Scope.StaticThisUsage"/>
<!-- Strings -->
<rule ref="Squiz.Strings.DoubleQuoteUsage"/>
<!-- Whitespace -->
<rule ref="PEAR.WhiteSpace.ObjectOperatorIndent"/>
<rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>
<rule ref="Squiz.WhiteSpace.CastSpacing"/>
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.OperatorSpacing">
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.PropertyLabelSpacing"/>
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
</ruleset>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="tests/bootstrap.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory>src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Location Test Suite">
<directory>tests/Location/</directory>
<directory>tests/Regression/</directory>
</testsuite>
</testsuites>
</phpunit>

13
admin/vendor/mjaschen/phpgeo/psalm.xml vendored Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="/Users/mjaschen/Code/MarcusJaschen/phpgeo/vendor/vimeo/psalm/src/config.xsd"
>
<projectFiles>
<directory name="src" />
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
</issueHandlers>
</psalm>

View File

@ -0,0 +1,256 @@
<?php
declare(strict_types=1);
namespace Location\Bearing;
use InvalidArgumentException;
use Location\Coordinate;
use Location\Exception\NotConvergingException;
/**
* Calculation of bearing between two points using a
* ellipsoidal model of the earth.
*
* This class is based on the awesome work Chris Veness
* has done. For more information visit the following URL.
*
* @see http://www.movable-type.co.uk/scripts/latlong-vincenty.html
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class BearingEllipsoidal implements BearingInterface
{
/**
* This method calculates the initial bearing between the
* two points.
*
* If the two points share the same location, the bearing
* value will be 0.0.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float Bearing Angle
*/
public function calculateBearing(Coordinate $point1, Coordinate $point2): float
{
if ($point1->hasSameLocation($point2)) {
return 0.0;
}
return $this->inverseVincenty($point1, $point2)->getBearingInitial();
}
/**
* Calculates the final bearing between the two points.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float
*/
public function calculateFinalBearing(Coordinate $point1, Coordinate $point2): float
{
return $this->inverseVincenty($point1, $point2)->getBearingFinal();
}
/**
* Calculates a destination point for the given point, bearing angle,
* and distance.
*
* @param Coordinate $point
* @param float $bearing the bearing angle between 0 and 360 degrees
* @param float $distance the distance to the destination point in meters
*
* @return Coordinate
*/
public function calculateDestination(Coordinate $point, float $bearing, float $distance): Coordinate
{
return $this->directVincenty($point, $bearing, $distance)->getDestination();
}
/**
* Calculates the final bearing angle for a destination point.
* The method expects a starting point point, the bearing angle,
* and the distance to destination.
*
* @param Coordinate $point
* @param float $bearing
* @param float $distance
*
* @return float
*
* @throws NotConvergingException
*/
public function calculateDestinationFinalBearing(Coordinate $point, float $bearing, float $distance): float
{
return $this->directVincenty($point, $bearing, $distance)->getBearingFinal();
}
/**
* @param Coordinate $point
* @param float $bearing
* @param float $distance
*
* @return DirectVincentyBearing
*
* @throws NotConvergingException
*/
private function directVincenty(Coordinate $point, float $bearing, float $distance): DirectVincentyBearing
{
$phi1 = deg2rad($point->getLat());
$lambda1 = deg2rad($point->getLng());
$alpha1 = deg2rad($bearing);
$a = $point->getEllipsoid()->getA();
$b = $point->getEllipsoid()->getB();
$f = 1 / $point->getEllipsoid()->getF();
$sinAlpha1 = sin($alpha1);
$cosAlpha1 = cos($alpha1);
$tanU1 = (1 - $f) * tan($phi1);
$cosU1 = 1 / sqrt(1 + $tanU1 * $tanU1);
$sinU1 = $tanU1 * $cosU1;
$sigma1 = atan2($tanU1, $cosAlpha1);
$sinAlpha = $cosU1 * $sinAlpha1;
$cosSquAlpha = 1 - $sinAlpha * $sinAlpha;
$uSq = $cosSquAlpha * ($a * $a - $b * $b) / ($b * $b);
$A = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
$B = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
$sigmaS = $distance / ($b * $A);
$sigma = $sigmaS;
$iterations = 0;
do {
$cos2SigmaM = cos(2 * $sigma1 + $sigma);
$sinSigma = sin($sigma);
$cosSigma = cos($sigma);
$deltaSigma = $B * $sinSigma
* ($cos2SigmaM + $B / 4
* ($cosSigma
* (-1 + 2 * $cos2SigmaM * $cos2SigmaM) - $B / 6
* $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma)
* (-3 + 4 * $cos2SigmaM * $cos2SigmaM)
)
);
$sigmaS = $sigma;
$sigma = $distance / ($b * $A) + $deltaSigma;
$iterations++;
} while (abs($sigma - $sigmaS) > 1e-12 && $iterations < 200);
if ($iterations >= 200) {
throw new NotConvergingException('Inverse Vincenty Formula did not converge');
}
$tmp = $sinU1 * $sinSigma - $cosU1 * $cosSigma * $cosAlpha1;
$phi2 = atan2(
$sinU1 * $cosSigma + $cosU1 * $sinSigma * $cosAlpha1,
(1 - $f) * sqrt($sinAlpha * $sinAlpha + $tmp * $tmp)
);
$lambda = atan2($sinSigma * $sinAlpha1, $cosU1 * $cosSigma - $sinU1 * $sinSigma * $cosAlpha1);
$C = $f / 16 * $cosSquAlpha * (4 + $f * (4 - 3 * $cosSquAlpha));
$L = $lambda
- (1 - $C) * $f * $sinAlpha
* ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM ** 2)));
$lambda2 = fmod($lambda1 + $L + 3 * M_PI, 2 * M_PI) - M_PI;
$alpha2 = atan2($sinAlpha, -$tmp);
$alpha2 = fmod($alpha2 + 2 * M_PI, 2 * M_PI);
return new DirectVincentyBearing(
new Coordinate(rad2deg($phi2), rad2deg($lambda2), $point->getEllipsoid()),
rad2deg($alpha2)
);
}
/**
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return InverseVincentyBearing
*
* @throws NotConvergingException
*/
private function inverseVincenty(Coordinate $point1, Coordinate $point2): InverseVincentyBearing
{
$φ1 = deg2rad($point1->getLat());
$φ2 = deg2rad($point2->getLat());
$λ1 = deg2rad($point1->getLng());
$λ2 = deg2rad($point2->getLng());
$a = $point1->getEllipsoid()->getA();
$b = $point1->getEllipsoid()->getB();
$f = 1 / $point1->getEllipsoid()->getF();
$L = $λ2 - $λ1;
$tanU1 = (1 - $f) * tan($φ1);
$cosU1 = 1 / sqrt(1 + $tanU1 * $tanU1);
$sinU1 = $tanU1 * $cosU1;
$tanU2 = (1 - $f) * tan($φ2);
$cosU2 = 1 / sqrt(1 + $tanU2 * $tanU2);
$sinU2 = $tanU2 * $cosU2;
= $L;
$iterations = 0;
do {
$sinλ = sin();
$cosλ = cos();
$sinSqσ = ($cosU2 * $sinλ) * ($cosU2 * $sinλ)
+ ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ);
$sinσ = sqrt($sinSqσ);
if ($sinσ == 0) {
new InverseVincentyBearing(0, 0, 0);
}
$cosσ = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosλ;
$σ = atan2($sinσ, $cosσ);
$sinα = $cosU1 * $cosU2 * $sinλ / $sinσ;
$cosSqα = 1 - $sinα * $sinα;
$cos2σM = 0;
if ($cosSqα !== 0.0) {
$cos2σM = $cosσ - 2 * $sinU1 * $sinU2 / $cosSqα;
}
$C = $f / 16 * $cosSqα * (4 + $f * (4 - 3 * $cosSqα));
$λp = ;
= $L + (1 - $C) * $f * $sinα
* ($σ + $C * $sinσ * ($cos2σM + $C * $cosσ * (-1 + 2 * $cos2σM * $cos2σM)));
$iterations++;
} while (abs( - $λp) > 1e-12 && $iterations < 200);
if ($iterations >= 200) {
throw new NotConvergingException('Inverse Vincenty Formula did not converge');
}
$uSq = $cosSqα * ($a * $a - $b * $b) / ($b * $b);
$A = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
$B = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
$Δσ = $B * $sinσ
* ($cos2σM + $B / 4
* ($cosσ * (-1 + 2 * $cos2σM * $cos2σM) - $B / 6
* $cos2σM * (-3 + 4 * $sinσ * $sinσ)
* (-3 + 4 * $cos2σM * $cos2σM)
)
);
$s = $b * $A * ($σ - $Δσ);
$α1 = atan2($cosU2 * $sinλ, $cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ);
$α2 = atan2($cosU1 * $sinλ, -$sinU1 * $cosU2 + $cosU1 * $sinU2 * $cosλ);
$α1 = fmod($α1 + 2 * M_PI, 2 * M_PI);
$α2 = fmod($α2 + 2 * M_PI, 2 * M_PI);
$s = round($s, 3);
return new InverseVincentyBearing($s, rad2deg($α1), rad2deg($α2));
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Location\Bearing;
use Location\Coordinate;
/**
* Interface BearingInterface
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
interface BearingInterface
{
/**
* This method calculates the initial bearing between the
* two points.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float Bearing Angle
*/
public function calculateBearing(Coordinate $point1, Coordinate $point2): float;
/**
* Calculates the final bearing between the two points.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float
*/
public function calculateFinalBearing(Coordinate $point1, Coordinate $point2): float;
/**
* Calculates a destination point for the given point, bearing angle,
* and distance.
*
* @param Coordinate $point
* @param float $bearing the bearing angle between 0 and 360 degrees
* @param float $distance the distance to the destination point in meters
*
* @return Coordinate
*/
public function calculateDestination(Coordinate $point, float $bearing, float $distance): Coordinate;
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Location\Bearing;
use InvalidArgumentException;
use Location\Coordinate;
/**
* Calculation of bearing between two points using a
* simple spherical model of the earth.
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class BearingSpherical implements BearingInterface
{
/**
* Earth radius in meters.
*/
private const EARTH_RADIUS = 6371009.0;
/**
* This method calculates the initial bearing (forward azimut) between
* the two given points.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float Bearing Angle in degrees
*/
public function calculateBearing(Coordinate $point1, Coordinate $point2): float
{
$lat1 = deg2rad($point1->getLat());
$lat2 = deg2rad($point2->getLat());
$lng1 = deg2rad($point1->getLng());
$lng2 = deg2rad($point2->getLng());
$y = sin($lng2 - $lng1) * cos($lat2);
$x = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($lng2 - $lng1);
$bearing = rad2deg(atan2($y, $x));
if ($bearing < 0) {
$bearing = fmod($bearing + 360, 360);
}
return $bearing;
}
/**
* Calculates the final bearing between the two points.
*
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float
*/
public function calculateFinalBearing(Coordinate $point1, Coordinate $point2): float
{
$initialBearing = $this->calculateBearing($point2, $point1);
return fmod($initialBearing + 180, 360);
}
/**
* Calculates a destination point for the given point, bearing angle,
* and distance.
*
* @param Coordinate $point
* @param float $bearing the bearing angle between 0 and 360 degrees
* @param float $distance the distance to the destination point in meters
*
* @return Coordinate
* @throws InvalidArgumentException
*/
public function calculateDestination(Coordinate $point, float $bearing, float $distance): Coordinate
{
$D = $distance / self::EARTH_RADIUS;
$B = deg2rad($bearing);
= deg2rad($point->getLat());
= deg2rad($point->getLng());
= asin(sin() * cos($D) + cos() * sin($D) * cos($B));
= + atan2(sin($B) * sin($D) * cos(), cos($D) - sin() * sin());
return new Coordinate(rad2deg(), rad2deg());
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Location\Bearing;
use Location\Coordinate;
/**
* Value object for a "Direct Vincenty" bearing calculation result.
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class DirectVincentyBearing
{
/**
* @var Coordinate
*/
private $destination;
/**
* @var float
*/
private $bearingFinal;
/**
* Bearing constructor.
*
* @param Coordinate $destination
* @param float $bearingFinal
*/
public function __construct(Coordinate $destination, float $bearingFinal)
{
$this->destination = $destination;
$this->bearingFinal = $bearingFinal;
}
/**
* @return Coordinate
*/
public function getDestination(): Coordinate
{
return $this->destination;
}
/**
* @return float
*/
public function getBearingFinal(): float
{
return $this->bearingFinal;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Location\Bearing;
/**
* Value object for a "Direct Vincenty" bearing calculation result.
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class InverseVincentyBearing
{
/**
* @var float
*/
private $distance;
/**
* @var float
*/
private $bearingInitial;
/**
* @var float
*/
private $bearingFinal;
/**
* InverseVincentyBearing constructor.
*
* @param float $distance
* @param float $bearingInitial
* @param float $bearingFinal
*/
public function __construct(float $distance, float $bearingInitial, float $bearingFinal)
{
$this->distance = $distance;
$this->bearingInitial = $bearingInitial;
$this->bearingFinal = $bearingFinal;
}
/**
* @return float
*/
public function getDistance(): float
{
return $this->distance;
}
/**
* @return float
*/
public function getBearingInitial(): float
{
return $this->bearingInitial;
}
/**
* @return float
*/
public function getBearingFinal(): float
{
return $this->bearingFinal;
}
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Location;
class Bounds
{
/**
* @var Coordinate
*/
protected $northWest;
/**
* @var Coordinate
*/
protected $southEast;
/**
* @param Coordinate $northWest
* @param Coordinate $southEast
*/
public function __construct(Coordinate $northWest, Coordinate $southEast)
{
$this->northWest = $northWest;
$this->southEast = $southEast;
}
public function getNorthWest(): Coordinate
{
return $this->northWest;
}
public function getSouthEast(): Coordinate
{
return $this->southEast;
}
public function getNorthEast(): Coordinate
{
return new Coordinate($this->getNorth(), $this->getEast());
}
public function getSouthWest(): Coordinate
{
return new Coordinate($this->getSouth(), $this->getWest());
}
public function getNorth(): float
{
return $this->northWest->getLat();
}
public function getSouth(): float
{
return $this->southEast->getLat();
}
public function getWest(): float
{
return $this->northWest->getLng();
}
public function getEast(): float
{
return $this->southEast->getLng();
}
/**
* Calculates the center of this bounds object and returns it as a
* Coordinate instance.
*
* @throws \InvalidArgumentException
*/
public function getCenter(): Coordinate
{
$centerLat = ($this->getNorth() + $this->getSouth()) / 2;
return new Coordinate($centerLat, $this->getCenterLng());
}
protected function getCenterLng(): float
{
$centerLng = ($this->getEast() + $this->getWest()) / 2;
$overlap = $this->getWest() > 0 && $this->getEast() < 0;
if ($overlap && $centerLng > 0) {
return -180.0 + $centerLng;
}
if ($overlap && $centerLng < 0) {
return 180.0 + $centerLng;
}
if ($overlap && $centerLng == 0) {
return 180.0;
}
return $centerLng;
}
/**
* Creates the polygon described by this bounds object and returns the
* Polygon instance.
*/
public function getAsPolygon(): Polygon
{
$polygon = new Polygon();
$polygon->addPoint($this->getNorthWest());
$polygon->addPoint($this->getNorthEast());
$polygon->addPoint($this->getSouthEast());
$polygon->addPoint($this->getSouthWest());
return $polygon;
}
}

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Location\CardinalDirection;
use Location\Coordinate;
use Location\Direction\Direction;
class CardinalDirection
{
public const CARDINAL_DIRECTION_NONE = 'none';
public const CARDINAL_DIRECTION_NORTH = 'north';
public const CARDINAL_DIRECTION_EAST = 'east';
public const CARDINAL_DIRECTION_SOUTH = 'south';
public const CARDINAL_DIRECTION_WEST = 'west';
public const CARDINAL_DIRECTION_NORTHEAST = 'north-east';
public const CARDINAL_DIRECTION_NORTHWEST = 'north-west';
public const CARDINAL_DIRECTION_SOUTHEAST = 'south-east';
public const CARDINAL_DIRECTION_SOUTHWEST = 'south-west';
/**
* @var Direction
*/
private $direction;
public function __construct()
{
$this->direction = new Direction();
}
public function getCardinalDirection(Coordinate $point1, Coordinate $point2): string
{
$directionFunctionMapping = [
self::CARDINAL_DIRECTION_NORTH => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isStrictlyNorth($point1, $point2);
},
self::CARDINAL_DIRECTION_EAST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isStrictlyEast($point1, $point2);
},
self::CARDINAL_DIRECTION_SOUTH => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isStrictlySouth($point1, $point2);
},
self::CARDINAL_DIRECTION_WEST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isStrictlyWest($point1, $point2);
},
self::CARDINAL_DIRECTION_NORTHEAST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isNorthEast($point1, $point2);
},
self::CARDINAL_DIRECTION_SOUTHEAST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isSouthEast($point1, $point2);
},
self::CARDINAL_DIRECTION_SOUTHWEST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isSouthWest($point1, $point2);
},
self::CARDINAL_DIRECTION_NORTHWEST => function (Coordinate $point1, Coordinate $point2): bool {
return $this->isNorthWest($point1, $point2);
},
];
foreach ($directionFunctionMapping as $direction => $checkFunction) {
if ($checkFunction($point1, $point2)) {
return $direction;
}
}
return self::CARDINAL_DIRECTION_NONE;
}
private function isStrictlyNorth(Coordinate $point1, Coordinate $point2): bool
{
return !$this->direction->pointIsEastOf($point1, $point2)
&& !$this->direction->pointIsSouthOf($point1, $point2)
&& !$this->direction->pointIsWestOf($point1, $point2)
&& $this->direction->pointIsNorthOf($point1, $point2);
}
private function isStrictlyEast(Coordinate $point1, Coordinate $point2): bool
{
return $this->direction->pointIsEastOf($point1, $point2)
&& !$this->direction->pointIsSouthOf($point1, $point2)
&& !$this->direction->pointIsWestOf($point1, $point2)
&& !$this->direction->pointIsNorthOf($point1, $point2);
}
private function isStrictlySouth(Coordinate $point1, Coordinate $point2): bool
{
return !$this->direction->pointIsEastOf($point1, $point2)
&& $this->direction->pointIsSouthOf($point1, $point2)
&& !$this->direction->pointIsWestOf($point1, $point2)
&& !$this->direction->pointIsNorthOf($point1, $point2);
}
private function isStrictlyWest(Coordinate $point1, Coordinate $point2): bool
{
return !$this->direction->pointIsEastOf($point1, $point2)
&& !$this->direction->pointIsSouthOf($point1, $point2)
&& $this->direction->pointIsWestOf($point1, $point2)
&& !$this->direction->pointIsNorthOf($point1, $point2);
}
private function isNorthEast(Coordinate $point1, Coordinate $point2): bool
{
return $this->direction->pointIsEastOf($point1, $point2)
&& !$this->direction->pointIsSouthOf($point1, $point2)
&& !$this->direction->pointIsWestOf($point1, $point2)
&& $this->direction->pointIsNorthOf($point1, $point2);
}
private function isSouthEast(Coordinate $point1, Coordinate $point2): bool
{
return $this->direction->pointIsEastOf($point1, $point2)
&& $this->direction->pointIsSouthOf($point1, $point2)
&& !$this->direction->pointIsWestOf($point1, $point2)
&& !$this->direction->pointIsNorthOf($point1, $point2);
}
private function isSouthWest(Coordinate $point1, Coordinate $point2): bool
{
return !$this->direction->pointIsEastOf($point1, $point2)
&& $this->direction->pointIsSouthOf($point1, $point2)
&& $this->direction->pointIsWestOf($point1, $point2)
&& !$this->direction->pointIsNorthOf($point1, $point2);
}
private function isNorthWest(Coordinate $point1, Coordinate $point2): bool
{
return !$this->direction->pointIsEastOf($point1, $point2)
&& !$this->direction->pointIsSouthOf($point1, $point2)
&& $this->direction->pointIsWestOf($point1, $point2)
&& $this->direction->pointIsNorthOf($point1, $point2);
}
}

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Location\CardinalDirection;
use Location\Exception\InvalidDistanceException;
/** @psalm-immutable */
class CardinalDirectionDistances
{
/**
* @var float
*/
private $north;
/**
* @var float
*/
private $east;
/**
* @var float
*/
private $south;
/**
* @var float
*/
private $west;
private function __construct(float $north, float $east, float $south, float $west)
{
$this->north = $north;
$this->east = $east;
$this->south = $south;
$this->west = $west;
}
/**
* @psalm-pure
* @psalm-mutation-free
*/
public static function create(): self
{
return new self(0, 0, 0, 0);
}
/**
* @psalm-mutation-free
*/
public function setNorth(float $north): self
{
$this->assertPositiveFloat($north);
return new self($north, $this->east, $this->south, $this->west);
}
/**
* @psalm-mutation-free
*/
public function setEast(float $east): self
{
$this->assertPositiveFloat($east);
return new self($this->north, $east, $this->south, $this->west);
}
/**
* @psalm-mutation-free
*/
public function setSouth(float $south): self
{
$this->assertPositiveFloat($south);
return new self($this->north, $this->east, $south, $this->west);
}
/**
* @psalm-mutation-free
*/
public function setWest(float $west): self
{
$this->assertPositiveFloat($west);
return new self($this->north, $this->east, $this->south, $west);
}
/**
* @psalm-mutation-free
*/
public function getNorth(): float
{
return $this->north;
}
/**
* @psalm-mutation-free
*/
public function getEast(): float
{
return $this->east;
}
/**
* @psalm-mutation-free
*/
public function getSouth(): float
{
return $this->south;
}
/**
* @psalm-mutation-free
*/
public function getWest(): float
{
return $this->west;
}
/**
* @psalm-pure
* @psalm-mutation-free
*
* @throws InvalidDistanceException
*/
private function assertPositiveFloat(float $value): void
{
if ($value < 0) {
throw new InvalidDistanceException('Negative distance is invalid.', 1857757416);
}
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Location\CardinalDirection;
use Location\Bounds;
use Location\Coordinate;
use Location\Distance\DistanceInterface;
class CardinalDirectionDistancesCalculator
{
public function getCardinalDirectionDistances(
Coordinate $point1,
Coordinate $point2,
DistanceInterface $distanceCalculator
): CardinalDirectionDistances {
$cardinalDirection = (new CardinalDirection())->getCardinalDirection($point1, $point2);
$directDistance = $point1->getDistance($point2, $distanceCalculator);
switch ($cardinalDirection) {
case CardinalDirection::CARDINAL_DIRECTION_NONE:
return CardinalDirectionDistances::create();
case CardinalDirection::CARDINAL_DIRECTION_NORTH:
return CardinalDirectionDistances::create()->setSouth($directDistance);
case CardinalDirection::CARDINAL_DIRECTION_EAST:
return CardinalDirectionDistances::create()->setWest($directDistance);
case CardinalDirection::CARDINAL_DIRECTION_SOUTH:
return CardinalDirectionDistances::create()->setNorth($directDistance);
case CardinalDirection::CARDINAL_DIRECTION_WEST:
return CardinalDirectionDistances::create()->setEast($directDistance);
case CardinalDirection::CARDINAL_DIRECTION_NORTHWEST:
$bounds = new Bounds($point1, $point2);
$point3 = new Coordinate($bounds->getNorth(), $bounds->getEast());
return CardinalDirectionDistances::create()
->setEast($point1->getDistance($point3, $distanceCalculator))
->setSouth($point3->getDistance($point2, $distanceCalculator));
case CardinalDirection::CARDINAL_DIRECTION_SOUTHWEST:
$bounds = new Bounds(
new Coordinate($point2->getLat(), $point1->getLng()),
new Coordinate($point1->getLat(), $point2->getLng())
);
$point3 = new Coordinate($bounds->getSouth(), $bounds->getEast());
return CardinalDirectionDistances::create()
->setNorth($point3->getDistance($point2, $distanceCalculator))
->setEast($point1->getDistance($point3, $distanceCalculator));
case CardinalDirection::CARDINAL_DIRECTION_NORTHEAST:
$bounds = new Bounds(
new Coordinate($point1->getLat(), $point2->getLng()),
new Coordinate($point2->getLat(), $point1->getLng())
);
$point3 = new Coordinate($bounds->getNorth(), $bounds->getWest());
return CardinalDirectionDistances::create()
->setSouth($point3->getDistance($point2, $distanceCalculator))
->setWest($point1->getDistance($point3, $distanceCalculator));
case CardinalDirection::CARDINAL_DIRECTION_SOUTHEAST:
$bounds = new Bounds($point2, $point1);
$point3 = new Coordinate($bounds->getSouth(), $bounds->getWest());
return CardinalDirectionDistances::create()
->setNorth($point3->getDistance($point2, $distanceCalculator))
->setWest($point1->getDistance($point3, $distanceCalculator));
}
return CardinalDirectionDistances::create();
}
}

View File

@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace Location;
use InvalidArgumentException;
use Location\CardinalDirection\CardinalDirectionDistances;
use Location\CardinalDirection\CardinalDirectionDistancesCalculator;
use Location\Distance\DistanceInterface;
use Location\Distance\Haversine;
use Location\Exception\InvalidGeometryException;
use Location\Formatter\Coordinate\FormatterInterface;
/**
* Coordinate Implementation
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class Coordinate implements GeometryInterface
{
/**
* @var float
*/
protected $lat;
/**
* @var float
*/
protected $lng;
/**
* @var Ellipsoid
*/
protected $ellipsoid;
/**
* @param float $lat -90.0 .. +90.0
* @param float $lng -180.0 .. +180.0
* @param ?Ellipsoid $ellipsoid if omitted, WGS-84 is used
*
* @throws InvalidArgumentException
*/
public function __construct(float $lat, float $lng, ?Ellipsoid $ellipsoid = null)
{
if (! $this->isValidLatitude($lat)) {
throw new InvalidArgumentException('Latitude value must be numeric -90.0 .. +90.0 (given: ' . $lat . ')');
}
if (! $this->isValidLongitude($lng)) {
throw new InvalidArgumentException(
'Longitude value must be numeric -180.0 .. +180.0 (given: ' . $lng . ')'
);
}
$this->lat = $lat;
$this->lng = $lng;
if ($ellipsoid instanceof Ellipsoid) {
$this->ellipsoid = $ellipsoid;
return;
}
$this->ellipsoid = Ellipsoid::createDefault();
}
public function getLat(): float
{
return $this->lat;
}
public function getLng(): float
{
return $this->lng;
}
/**
* @return array<Coordinate>
*/
public function getPoints(): array
{
return [$this];
}
public function getEllipsoid(): Ellipsoid
{
return $this->ellipsoid;
}
/**
* Calculates the distance between the given coordinate
* and this coordinate.
*/
public function getDistance(Coordinate $coordinate, DistanceInterface $calculator): float
{
return $calculator->getDistance($this, $coordinate);
}
/**
* Calculates the cardinal direction distances from this coordinate
* to given coordinate.
*/
public function getCardinalDirectionDistances(
Coordinate $coordinate,
DistanceInterface $calculator
): CardinalDirectionDistances {
return (new CardinalDirectionDistancesCalculator())
->getCardinalDirectionDistances($this, $coordinate, $calculator);
}
/**
* Checks if two points describe the same location within an allowed distance.
*
* Uses the Haversine distance calculator for distance calculation as it's
* precise enough for short-distance calculations.
*
* @see Haversine
*/
public function hasSameLocation(Coordinate $coordinate, float $allowedDistance = .001): bool
{
return $this->getDistance($coordinate, new Haversine()) <= $allowedDistance;
}
/**
* Checks if this point intersects a given geometry.
*
* @throws InvalidGeometryException
*/
public function intersects(GeometryInterface $geometry): bool
{
if ($geometry instanceof self) {
return $this->hasSameLocation($geometry);
}
if ($geometry instanceof Polygon) {
return $geometry->contains($this);
}
throw new InvalidGeometryException('Only polygons can contain other geometries', 1655191821);
}
public function format(FormatterInterface $formatter): string
{
return $formatter->format($this);
}
protected function isValidLatitude(float $latitude): bool
{
return $this->isNumericInBounds($latitude, -90.0, 90.0);
}
protected function isValidLongitude(float $longitude): bool
{
return $this->isNumericInBounds($longitude, -180.0, 180.0);
}
/**
* Checks if the given value is (1) numeric, and (2) between lower
* and upper bounds (including the bounds values).
*/
protected function isNumericInBounds(float $value, float $lower, float $upper): bool
{
return !($value < $lower || $value > $upper);
}
public function getBounds(): Bounds
{
return new Bounds($this, $this);
}
public function getSegments(): array
{
throw new \RuntimeException('A single point instance does not contain valid segments', 6029644914);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Location\Direction;
use Location\Coordinate;
class Direction
{
public function pointIsNorthOf(Coordinate $point, Coordinate $compareAgainst): bool
{
return $point->getLat() > $compareAgainst->getLat();
}
public function pointIsSouthOf(Coordinate $point, Coordinate $compareAgainst): bool
{
return $point->getLat() < $compareAgainst->getLat();
}
public function pointIsEastOf(Coordinate $point, Coordinate $compareAgainst): bool
{
return $point->getLng() > $compareAgainst->getLng();
}
public function pointIsWestOf(Coordinate $point, Coordinate $compareAgainst): bool
{
return $point->getLng() < $compareAgainst->getLng();
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Location\Distance;
use Location\Coordinate;
/**
* Interface for Distance Calculator Classes
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
interface DistanceInterface
{
/**
* @param Coordinate $point1
* @param Coordinate $point2
*
* @return float distance between the two coordinates in meters
*/
public function getDistance(Coordinate $point1, Coordinate $point2): float;
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Location\Distance;
use Location\Coordinate;
use Location\Exception\NotConvergingException;
use Location\Exception\NotMatchingEllipsoidException;
/**
* Implementation of distance calculation with http://en.wikipedia.org/wiki/Law_of_haversines
*
* @see http://en.wikipedia.org/wiki/Law_of_haversines
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class Haversine implements DistanceInterface
{
/**
* @param Coordinate $point1
* @param Coordinate $point2
*
* @throws NotMatchingEllipsoidException
*
* @return float
*/
public function getDistance(Coordinate $point1, Coordinate $point2): float
{
if ($point1->getEllipsoid()->getName() !== $point2->getEllipsoid()->getName()) {
throw new NotMatchingEllipsoidException('The ellipsoids for both coordinates must match');
}
$lat1 = deg2rad($point1->getLat());
$lat2 = deg2rad($point2->getLat());
$lng1 = deg2rad($point1->getLng());
$lng2 = deg2rad($point2->getLng());
$dLat = $lat2 - $lat1;
$dLng = $lng2 - $lng1;
$radius = $point1->getEllipsoid()->getArithmeticMeanRadius();
$distance = 2 * $radius * asin(
sqrt(
(sin($dLat / 2) ** 2)
+ cos($lat1) * cos($lat2) * (sin($dLng / 2) ** 2)
)
);
return round($distance, 3);
}
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Location\Distance;
use Location\Coordinate;
use Location\Exception\NotConvergingException;
use Location\Exception\NotMatchingEllipsoidException;
/**
* Implementation of distance calculation with Vincenty Method
*
* @see http://www.movable-type.co.uk/scripts/latlong-vincenty.html
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class Vincenty implements DistanceInterface
{
/**
* @param Coordinate $point1
* @param Coordinate $point2
*
* @throws NotMatchingEllipsoidException
* @throws NotConvergingException
*
* @return float
*/
public function getDistance(Coordinate $point1, Coordinate $point2): float
{
if ($point1->getEllipsoid()->getName() !== $point2->getEllipsoid()->getName()) {
throw new NotMatchingEllipsoidException('The ellipsoids for both coordinates must match');
}
$lat1 = deg2rad($point1->getLat());
$lat2 = deg2rad($point2->getLat());
$lng1 = deg2rad($point1->getLng());
$lng2 = deg2rad($point2->getLng());
$a = $point1->getEllipsoid()->getA();
$b = $point1->getEllipsoid()->getB();
$f = 1 / $point1->getEllipsoid()->getF();
$L = $lng2 - $lng1;
$U1 = atan((1 - $f) * tan($lat1));
$U2 = atan((1 - $f) * tan($lat2));
$iterationsLeft = 100;
$lambda = $L;
$sinU1 = sin($U1);
$sinU2 = sin($U2);
$cosU1 = cos($U1);
$cosU2 = cos($U2);
do {
$sinLambda = sin($lambda);
$cosLambda = cos($lambda);
$sinSigma = sqrt(
$cosU2 * $sinLambda * $cosU2 * $sinLambda +
($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda)
);
if (abs($sinSigma) < 1E-12) {
return 0.0;
}
$cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
$sigma = atan2($sinSigma, $cosSigma);
$sinAlpha = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
$cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
$cos2SigmaM = 0;
if (abs($cosSqAlpha) > 1E-12) {
$cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
}
$C = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
$lambdaP = $lambda;
$lambda = $L
+ (1 - $C)
* $f
* $sinAlpha
* ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (- 1 + 2 * $cos2SigmaM * $cos2SigmaM)));
$iterationsLeft--;
} while (abs($lambda - $lambdaP) > 1e-12 && $iterationsLeft > 0);
if ($iterationsLeft === 0) {
throw new NotConvergingException('Vincenty calculation does not converge');
}
$uSq = $cosSqAlpha * ($a * $a - $b * $b) / ($b * $b);
$A = 1 + $uSq / 16384 * (4096 + $uSq * (- 768 + $uSq * (320 - 175 * $uSq)));
$B = $uSq / 1024 * (256 + $uSq * (- 128 + $uSq * (74 - 47 * $uSq)));
$deltaSigma = $B * $sinSigma * (
$cos2SigmaM
+ $B / 4 * ($cosSigma * (- 1 + 2 * $cos2SigmaM * $cos2SigmaM)
- $B / 6 * $cos2SigmaM * (- 3 + 4 * $sinSigma * $sinSigma) * (- 3 + 4 * $cos2SigmaM * $cos2SigmaM))
);
$s = $b * $A * ($sigma - $deltaSigma);
return round($s, 3);
}
}

View File

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace Location;
/**
* Ellipsoid
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class Ellipsoid
{
/**
* @var string
*/
protected $name;
/**
* The semi-major axis
*
* @var float
*/
protected $a;
/**
* The Inverse Flattening (1/f)
*
* @var float
*/
protected $f;
/**
* Some often used ellipsoids
*
* @var array<string, array{name: string, a: float, f: float}>
*/
protected static $configs = [
'WGS-84' => [
'name' => 'World Geodetic System 1984',
'a' => 6378137.0,
'f' => 298.257223563,
],
'GRS-80' => [
'name' => 'Geodetic Reference System 1980',
'a' => 6378137.0,
'f' => 298.257222100,
],
];
/**
* @param string $name
* @param float $a
* @param float $f
*/
public function __construct(string $name, float $a, float $f)
{
$this->name = $name;
$this->a = $a;
$this->f = $f;
}
/**
* @param string $name
*
* @return Ellipsoid
*/
public static function createDefault(string $name = 'WGS-84'): Ellipsoid
{
return static::createFromArray(static::$configs[$name]);
}
/**
* @param array $config
*
* @return Ellipsoid
*/
public static function createFromArray(array $config): Ellipsoid
{
return new self($config['name'], $config['a'], $config['f']);
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return float
*/
public function getA(): float
{
return $this->a;
}
/**
* Calculation of the semi-minor axis
*
* @return float
*/
public function getB(): float
{
return $this->a * (1 - 1 / $this->f);
}
/**
* @return float
*/
public function getF(): float
{
return $this->f;
}
/**
* Calculates the arithmetic mean radius
*
* @see http://home.online.no/~sigurdhu/WGS84_Eng.html
*
* @return float
*/
public function getArithmeticMeanRadius(): float
{
return $this->a * (1 - 1 / $this->f / 3);
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class BearingNotAvailableException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class InvalidDistanceException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class InvalidGeometryException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class InvalidPolygonException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class NotConvergingException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Location\Exception;
class NotMatchingEllipsoidException extends \InvalidArgumentException
{
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Location\Factory;
use InvalidArgumentException;
use Location\Bearing\BearingInterface;
use Location\Bounds;
use Location\Coordinate;
/**
* Bounds Factory
*/
class BoundsFactory
{
/**
* Creates a Bounds instance which corners have the given distance from its center.
*
* @param Coordinate $center
* @param float $distance in meters
* @param BearingInterface $bearing
* @return Bounds
* @throws InvalidArgumentException if bounds crosses the 180/-180 degrees meridian.
*/
public static function expandFromCenterCoordinate(
Coordinate $center,
float $distance,
BearingInterface $bearing
): Bounds {
$northWest = $bearing->calculateDestination($center, 315, $distance);
$southEast = $bearing->calculateDestination($center, 135, $distance);
return new Bounds($northWest, $southEast);
}
}

View File

@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Location\Factory;
use InvalidArgumentException;
use Location\Coordinate;
use Location\Ellipsoid;
/**
* Coordinate Factory
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class CoordinateFactory implements GeometryFactoryInterface
{
/**
* Creates a Coordinate instance from the given string.
*
* The string is parsed by a regular expression for a known
* format of geographical coordinates.
*
* @param string $string formatted geographical coordinate
*
* @throws InvalidArgumentException
*/
public static function fromString(string $string, ?Ellipsoid $ellipsoid = null): Coordinate
{
$string = self::mergeSecondsToMinutes($string);
$result = self::parseDecimalMinutesWithoutCardinalLetters($string, $ellipsoid);
if ($result instanceof Coordinate) {
return $result;
}
$result = self::parseDecimalMinutesWithCardinalLetters($string, $ellipsoid);
if ($result instanceof Coordinate) {
return $result;
}
$result = self::parseDecimalDegreesWithoutCardinalLetters($string, $ellipsoid);
if ($result instanceof Coordinate) {
return $result;
}
$result = self::parseDecimalDegreesWithCardinalLetters($string, $ellipsoid);
if ($result instanceof Coordinate) {
return $result;
}
throw new InvalidArgumentException('Format of coordinates was not recognized');
}
/**
* @return Coordinate|null
* @throws InvalidArgumentException
*/
private static function parseDecimalMinutesWithoutCardinalLetters(string $string, ?Ellipsoid $ellipsoid = null)
{
// Decimal minutes without cardinal letters, e. g. "52 12.345, 13 23.456",
// "52° 12.345, 13° 23.456", "52° 12.345, 13° 23.456", "52 12.345 N, 13 23.456 E",
// "N52° 12.345 E13° 23.456"
$regexp = '/(-?\d{1,2})°?\s+(\d{1,2}\.?\d*)[\']?[, ]\s*(-?\d{1,3})°?\s+(\d{1,2}\.?\d*)[\']?/u';
if (preg_match($regexp, $string, $match) === 1) {
$latitude = (int)$match[1] >= 0
? (int)$match[1] + (float)$match[2] / 60
: (int)$match[1] - (float)$match[2] / 60;
$longitude = (int)$match[3] >= 0
? (int)$match[3] + (float)$match[4] / 60
: (int)$match[3] - (float)$match[4] / 60;
return new Coordinate($latitude, $longitude, $ellipsoid);
}
return null;
}
/**
* @return ?Coordinate
* @throws InvalidArgumentException
*/
private static function parseDecimalMinutesWithCardinalLetters(string $string, ?Ellipsoid $ellipsoid = null)
{
// Decimal minutes with cardinal letters, e. g. "52 12.345, 13 23.456",
// "52° 12.345, 13° 23.456", "52° 12.345, 13° 23.456", "52 12.345 N, 13 23.456 E",
// "N52° 12.345 E13° 23.456"
$regexp = '/([NS]?\s*)(\d{1,2})°?\s+(\d{1,2}\.?\d*)[\']?(\s*[NS]?)';
$regexp .= '[, ]\s*([EW]?\s*)(\d{1,3})°?\s+(\d{1,2}\.?\d*)[\']?(\s*[EW]?)/ui';
if (preg_match($regexp, $string, $match) === 1) {
$latitude = (int)$match[2] + (float)$match[3] / 60;
if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[4])) === 'S') {
$latitude = - $latitude;
}
$longitude = (int)$match[6] + (float)$match[7] / 60;
if (strtoupper(trim($match[5])) === 'W' || strtoupper(trim($match[8])) === 'W') {
$longitude = - $longitude;
}
return new Coordinate($latitude, $longitude, $ellipsoid);
}
return null;
}
/**
* @return Coordinate|null
* @throws InvalidArgumentException
*/
private static function parseDecimalDegreesWithoutCardinalLetters(string $string, ?Ellipsoid $ellipsoid = null)
{
// The most simple format: decimal degrees without cardinal letters,
// e. g. "52.5, 13.5" or "53.25732 14.24984"
if (preg_match('/(-?\d{1,2}\.?\d*)°?[, ]\s*(-?\d{1,3}\.?\d*)°?/u', $string, $match) === 1) {
return new Coordinate((float)$match[1], (float)$match[2], $ellipsoid);
}
return null;
}
/**
* @return Coordinate|null
* @throws InvalidArgumentException
*/
private static function parseDecimalDegreesWithCardinalLetters(string $string, ?Ellipsoid $ellipsoid = null)
{
// Decimal degrees with cardinal letters, e. g. "N52.5, E13.5",
// "40.2S, 135.3485W", or "56.234°N, 157.245°W"
$regexp = '/([NS]?\s*)(\d{1,2}\.?\d*)°?(\s*[NS]?)[, ]\s*([EW]?\s*)(\d{1,3}\.?\d*)°?(\s*[EW]?)/ui';
if (preg_match($regexp, $string, $match) === 1) {
$latitude = $match[2];
if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[3])) === 'S') {
$latitude = - $latitude;
}
$longitude = $match[5];
if (strtoupper(trim($match[4])) === 'W' || strtoupper(trim($match[6])) === 'W') {
$longitude = - $longitude;
}
return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
}
return null;
}
private static function mergeSecondsToMinutes(string $string): string
{
return preg_replace_callback(
'/(\d+)(°|\s)\s*(\d+)(\'||\s)(\s*([0-9\.]*))("|\'\'|″|)?/u',
static function (array $matches): string {
return sprintf('%d %f', $matches[1], (float)$matches[3] + (float)$matches[6] / 60);
},
$string
);
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Location\Factory;
use Location\GeometryInterface;
/**
* Geometry Factory Interface
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
interface GeometryFactoryInterface
{
/**
* @param string $string
*
* @return GeometryInterface
*/
public static function fromString(string $string);
}

View File

@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Coordinate;
use InvalidArgumentException;
use Location\Coordinate;
/**
* Coordinate Formatter "DMS"
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class DMS implements FormatterInterface
{
public const UNITS_UTF8 = 'UTF-8';
public const UNITS_ASCII = 'ASCII';
/**
* @var string Separator string between latitude and longitude
*/
protected $separator;
/**
* Use cardinal letters for N/S and W/E instead of minus sign
*
* @var bool
*/
protected $useCardinalLetters;
/**
* @var string
*
* @psalm-suppress PropertyNotSetInConstructor
*/
protected $unitType;
/**
* @var array<string, array{deg: string, min: string, sec: string}>
*/
protected $units = [
'UTF-8' => [
'deg' => '°',
'min' => '',
'sec' => '″',
],
'ASCII' => [
'deg' => '°',
'min' => '\'',
'sec' => '"',
],
];
/**
* @throws InvalidArgumentException
*/
public function __construct(
string $separator = ' ',
bool $useCardinalLetters = false,
string $unitType = self::UNITS_UTF8
) {
$this->separator = $separator;
$this->useCardinalLetters = $useCardinalLetters;
$this->unitType = $unitType;
}
/**
* Sets the separator between latitude and longitude values
*
* @param string $separator
*
* @return DMS
*
* @deprecated
*/
public function setSeparator(string $separator): DMS
{
$this->separator = $separator;
return $this;
}
/**
* @param bool $value
*
* @return DMS
*
* @deprecated
*/
public function useCardinalLetters(bool $value): DMS
{
$this->useCardinalLetters = $value;
return $this;
}
/**
* @param string $type
*
* @return DMS
* @throws InvalidArgumentException
*
* @deprecated
*/
public function setUnits(string $type): DMS
{
if (!array_key_exists($type, $this->units)) {
throw new InvalidArgumentException('Invalid unit type');
}
$this->unitType = $type;
return $this;
}
/**
* @return string
*/
public function getUnitType(): string
{
return $this->unitType;
}
/**
* @param Coordinate $coordinate
*
* @return string
*/
public function format(Coordinate $coordinate): string
{
$lat = $coordinate->getLat();
$lng = $coordinate->getLng();
$latValue = abs($lat);
$latDegrees = (int)$latValue;
$latMinutesDecimal = $latValue - $latDegrees;
$latMinutes = (int)(60 * $latMinutesDecimal);
$latSeconds = 60 * (60 * $latMinutesDecimal - $latMinutes);
$lngValue = abs($lng);
$lngDegrees = (int)$lngValue;
$lngMinutesDecimal = $lngValue - $lngDegrees;
$lngMinutes = (int)(60 * $lngMinutesDecimal);
$lngSeconds = 60 * (60 * $lngMinutesDecimal - $lngMinutes);
return sprintf(
'%s%02d%s %02d%s %02d%s%s%s%s%03d%s %02d%s %02d%s%s',
$this->getLatPrefix($lat),
abs($latDegrees),
$this->units[$this->unitType]['deg'],
$latMinutes,
$this->units[$this->unitType]['min'],
round($latSeconds, 0),
$this->units[$this->unitType]['sec'],
$this->getLatSuffix($lat),
$this->separator,
$this->getLngPrefix($lng),
abs($lngDegrees),
$this->units[$this->unitType]['deg'],
$lngMinutes,
$this->units[$this->unitType]['min'],
round($lngSeconds, 0),
$this->units[$this->unitType]['sec'],
$this->getLngSuffix($lng)
);
}
protected function getLatPrefix(float $lat): string
{
if ($this->useCardinalLetters || $lat >= 0) {
return '';
}
return '-';
}
protected function getLngPrefix(float $lng): string
{
if ($this->useCardinalLetters || $lng >= 0) {
return '';
}
return '-';
}
protected function getLatSuffix(float $lat): string
{
if (!$this->useCardinalLetters) {
return '';
}
if ($lat >= 0) {
return ' N';
}
return ' S';
}
protected function getLngSuffix(float $lng): string
{
if (!$this->useCardinalLetters) {
return '';
}
if ($lng >= 0) {
return ' E';
}
return ' W';
}
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Coordinate;
use Location\Coordinate;
/**
* Coordinate Formatter "Decimal Degrees"
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class DecimalDegrees implements FormatterInterface
{
/**
* @var string Separator string between latitude and longitude
*/
protected $separator;
/**
* @var int
*/
protected $digits = 5;
/**
* @param string $separator
* @param int $digits
*/
public function __construct(string $separator = ' ', int $digits = 5)
{
$this->separator = $separator;
$this->digits = $digits;
}
/**
* @param Coordinate $coordinate
*
* @return string
*/
public function format(Coordinate $coordinate): string
{
return sprintf(
'%.' . $this->digits . 'f%s%.' . $this->digits . 'f',
$coordinate->getLat(),
$this->separator,
$coordinate->getLng()
);
}
/**
* Sets the separator between latitude and longitude values
*
* @param string $separator
*
* @return $this
*/
public function setSeparator(string $separator): DecimalDegrees
{
$this->separator = $separator;
return $this;
}
}

View File

@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Coordinate;
use InvalidArgumentException;
use Location\Coordinate;
/**
* Coordinate Formatter "DecimalMinutes"
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class DecimalMinutes implements FormatterInterface
{
public const UNITS_UTF8 = 'UTF-8';
public const UNITS_ASCII = 'ASCII';
/**
* @var string Separator string between latitude and longitude
*/
protected $separator;
/**
* Use cardinal letters for N/S and W/E instead of minus sign
*
* @var bool
*/
protected $useCardinalLetters;
/**
* @var string
*
* @psalm-suppress PropertyNotSetInConstructor
*/
protected $unitType;
/**
* @var int
*/
protected $digits = 3;
/**
* @var string
*/
protected $decimalPoint = '.';
/**
* @var array
*/
protected $units = [
'UTF-8' => [
'deg' => '°',
'min' => '',
],
'ASCII' => [
'deg' => '°',
'min' => '\'',
],
];
/**
* @param string $separator
*/
public function __construct(string $separator = ' ')
{
$this->separator = $separator;
$this->useCardinalLetters = false;
$this->setUnits(self::UNITS_UTF8);
}
/**
* Sets the separator between latitude and longitude values
*
* @param string $separator
*
* @return DecimalMinutes
*/
public function setSeparator(string $separator): DecimalMinutes
{
$this->separator = $separator;
return $this;
}
/**
* @param bool $value
*
* @return DecimalMinutes
*/
public function useCardinalLetters(bool $value): DecimalMinutes
{
$this->useCardinalLetters = $value;
return $this;
}
/**
* @param string $type
*
* @return DecimalMinutes
* @throws \InvalidArgumentException
*/
public function setUnits(string $type): DecimalMinutes
{
if (! array_key_exists($type, $this->units)) {
throw new InvalidArgumentException('Invalid unit type');
}
$this->unitType = $type;
return $this;
}
/**
* @return string
*/
public function getUnitType(): string
{
return $this->unitType;
}
/**
* @param int $digits
*
* @return DecimalMinutes
*/
public function setDigits(int $digits): DecimalMinutes
{
$this->digits = $digits;
return $this;
}
/**
* @param string $decimalPoint
*
* @return DecimalMinutes
*/
public function setDecimalPoint(string $decimalPoint): DecimalMinutes
{
$this->decimalPoint = $decimalPoint;
return $this;
}
/**
* @param Coordinate $coordinate
*
* @return string
*/
public function format(Coordinate $coordinate): string
{
$lat = $coordinate->getLat();
$lng = $coordinate->getLng();
$latValue = abs($lat);
$latDegrees = (int)$latValue;
$latMinutesDecimal = $latValue - $latDegrees;
$latMinutes = 60 * $latMinutesDecimal;
$lngValue = abs($lng);
$lngDegrees = (int)$lngValue;
$lngMinutesDecimal = $lngValue - $lngDegrees;
$lngMinutes = 60 * $lngMinutesDecimal;
return sprintf(
'%s%02d%s %s%s%s%s%s%03d%s %s%s%s',
$this->getLatPrefix($lat),
abs($latDegrees),
$this->units[$this->unitType]['deg'],
number_format($latMinutes, $this->digits, $this->decimalPoint, $this->decimalPoint),
$this->units[$this->unitType]['min'],
$this->getLatSuffix($lat),
$this->separator,
$this->getLngPrefix($lng),
abs($lngDegrees),
$this->units[$this->unitType]['deg'],
number_format($lngMinutes, $this->digits, $this->decimalPoint, $this->decimalPoint),
$this->units[$this->unitType]['min'],
$this->getLngSuffix($lng)
);
}
/**
* @param float $lat
*
* @return string
*/
protected function getLatPrefix(float $lat): string
{
if ($this->useCardinalLetters || $lat >= 0) {
return '';
}
return '-';
}
/**
* @param float $lng
*
* @return string
*/
protected function getLngPrefix(float $lng): string
{
if ($this->useCardinalLetters || $lng >= 0) {
return '';
}
return '-';
}
/**
* @param float $lat
*
* @return string
*/
protected function getLatSuffix(float $lat): string
{
if (! $this->useCardinalLetters) {
return '';
}
if ($lat >= 0) {
return ' N';
}
return ' S';
}
/**
* @param float $lng
*
* @return string
*/
protected function getLngSuffix(float $lng): string
{
if (! $this->useCardinalLetters) {
return '';
}
if ($lng >= 0) {
return ' E';
}
return ' W';
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Coordinate;
use Location\Coordinate;
/**
* Coordinate Formatter Interface
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
interface FormatterInterface
{
/**
* @param Coordinate $coordinate
*
* @return string
*/
public function format(Coordinate $coordinate): string;
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Coordinate;
use Location\Coordinate;
/**
* GeoJSON Coordinate Formatter
*
* @author Marcus Jaschen <mjaschen@gmail.com>
*/
class GeoJSON implements FormatterInterface
{
/**
* @param Coordinate $coordinate
*
* @return string
*/
public function format(Coordinate $coordinate): string
{
return json_encode(
[
'type' => 'Point',
'coordinates' => [
$coordinate->getLng(),
$coordinate->getLat(),
],
]
);
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Polygon;
use Location\Polygon;
/**
* Polygon Formatter Interface
*
* @author Marcus Jaschen <mjaschen@gmail.com>
* @author Richard Barnes <rbarnes@umn.edu>
*/
interface FormatterInterface
{
/**
* @param Polygon $polygon
*
* @return string
*/
public function format(Polygon $polygon): string;
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Polygon;
use Location\Coordinate;
use Location\Exception\InvalidPolygonException;
use Location\Polygon;
/**
* GeoJSON Polygon Formatter
*
* @author Richard Barnes <rbarnes@umn.edu>
*/
class GeoJSON implements FormatterInterface
{
/**
* @param Polygon $polygon
*
* @return string
*
* @throws InvalidPolygonException
*/
public function format(Polygon $polygon): string
{
if ($polygon->getNumberOfPoints() < 3) {
throw new InvalidPolygonException('A polygon must consist of at least three points.');
}
$points = [];
/** @var Coordinate $point */
foreach ($polygon->getPoints() as $point) {
$points[] = [$point->getLng(), $point->getLat()];
}
return json_encode(
[
'type' => 'Polygon',
'coordinates' => [$points],
]
);
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Location\Formatter\Polyline;
use Location\Polyline;
/**
* Polyline Formatter Interface
*
* @author Richard Barnes <rbarnes@umn.edu>
*/
interface FormatterInterface
{
/**
* @param Polyline $polyline
*
* @return string
*/
public function format(Polyline $polyline): string;
}

Some files were not shown because too many files have changed in this diff Show More