diff --git a/composer.json b/composer.json index e91bfc5921..950ae8b393 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,8 @@ "matomo/matomo-php-tracker": "^3.2", "palepurple/rate-limit": "^2.0", "opauth/github": "^0.1.0", - "delatbabel/apisecurity": "^1.2" + "delatbabel/apisecurity": "^1.2", + "php-coord/php-coord": "^5.7" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 817685f880..52a53b6c8b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "786bb64e362287686eb256c369e32924", + "content-hash": "fe60425920d1d84baf0c5ee2a98908df", "packages": [ { "name": "aws/aws-crt-php", @@ -905,6 +905,77 @@ }, "time": "2022-10-25T04:18:42+00:00" }, + { + "name": "composer/pcre", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-19T10:26:25+00:00" + }, { "name": "delatbabel/apisecurity", "version": "v1.2", @@ -2563,6 +2634,196 @@ }, "time": "2018-02-25T03:18:21+00:00" }, + { + "name": "opis/json-schema", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.0", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.3.0" + }, + "time": "2022-01-08T20:38:03+00:00" + }, + { + "name": "opis/string", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.0.1" + }, + "time": "2022-01-14T15:42:23+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" + }, { "name": "palepurple/rate-limit", "version": "2.0.7", @@ -2664,6 +2925,108 @@ }, "time": "2024-07-20T06:31:49+00:00" }, + { + "name": "php-coord/php-coord", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/dvdoug/PHPCoord.git", + "reference": "478a8dcfa0a15ee076984c7b41eda9985989ab0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dvdoug/PHPCoord/zipball/478a8dcfa0a15ee076984c7b41eda9985989ab0d", + "reference": "478a8dcfa0a15ee076984c7b41eda9985989ab0d", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1", + "composer/pcre": "^3.1", + "opis/json-schema": "^2.3", + "php": "^8.0" + }, + "require-dev": { + "ext-json": "*", + "ext-sqlite3": "*", + "friendsofphp/php-cs-fixer": "^3.41", + "nikic/php-parser": "^5.0", + "php-coord/datapack-africa": "dev-master", + "php-coord/datapack-antarctic": "dev-master", + "php-coord/datapack-arctic": "dev-master", + "php-coord/datapack-asia": "dev-master", + "php-coord/datapack-europe": "dev-master", + "php-coord/datapack-northamerica": "dev-master", + "php-coord/datapack-oceania": "dev-master", + "php-coord/datapack-southamerica": "dev-master", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6||^10.0" + }, + "suggest": { + "php-coord/datapack-africa": "High-accuracy addon datapack for Africa", + "php-coord/datapack-antarctic": "High-accuracy addon datapack for the Antarctic", + "php-coord/datapack-arctic": "High-accuracy addon datapack for the Arctic", + "php-coord/datapack-asia": "High-accuracy addon datapack for Asia", + "php-coord/datapack-europe": "High-accuracy addon datapack for Europe", + "php-coord/datapack-northamerica": "High-accuracy addon datapack for North America", + "php-coord/datapack-oceania": "High-accuracy addon datapack for Oceania", + "php-coord/datapack-southamerica": "High-accuracy addon datapack for South America" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "files": [ + "src/bc_namespace.php" + ], + "psr-4": { + "PHPCoord\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(MIT and proprietary)" + ], + "authors": [ + { + "name": "Doug Wright", + "role": "Developer" + } + ], + "description": "PHPCoord is a PHP library to aid in handling coordinates. It can convert coordinates for a point from one system to another and also calculate distance between points.", + "homepage": "https://www.phpcoord.net/", + "keywords": [ + "UTM", + "coord", + "ed50", + "epsg", + "etrs", + "gda", + "geo", + "grid ref", + "latitude", + "longitude", + "map", + "nad", + "nzmg", + "osgb3", + "projection", + "wgs84" + ], + "support": { + "issues": "https://github.com/dvdoug/PHPCoord/issues", + "source": "https://github.com/dvdoug/PHPCoord/tree/v5.7.0" + }, + "funding": [ + { + "url": "https://github.com/dvdoug", + "type": "github" + } + ], + "time": "2024-01-26T23:28:58+00:00" + }, { "name": "php-http/discovery", "version": "1.19.4", @@ -6009,77 +6372,6 @@ } ], "packages-dev": [ - { - "name": "composer/pcre", - "version": "3.1.3", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", - "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-03-19T10:26:25+00:00" - }, { "name": "composer/semver", "version": "3.4.0", diff --git a/phpunit.xml b/phpunit.xml index 72cf493e58..f07e778b61 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,5 +12,8 @@ tests/unit/app/GeoKrety/Emails/ + + tests/unit/app/GeoKrety/Service/ + diff --git a/tests-qa/Makefile b/tests-qa/Makefile index a544f6ea3a..879cf85302 100644 --- a/tests-qa/Makefile +++ b/tests-qa/Makefile @@ -104,7 +104,7 @@ install_robot-framework: ## Install robot framework pip install -r requirements.txt download_geckodriver: ## Download geckodriver - curl -L https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz | tar xzf - + curl -L https://github.com/mozilla/geckodriver/releases/download/v0.35.0/geckodriver-v0.35.0-linux64.tar.gz | tar xzf - download_selenoid: ## Download selenoid wget https://github.com/aerokube/selenoid/releases/download/1.11.2/selenoid_linux_amd64 diff --git a/tests-qa/acceptance/230_Moves/060_Location.robot b/tests-qa/acceptance/230_Moves/060_Location.robot index 564813f511..01e9a11a1b 100644 --- a/tests-qa/acceptance/230_Moves/060_Location.robot +++ b/tests-qa/acceptance/230_Moves/060_Location.robot @@ -75,7 +75,7 @@ Fill Coordinates With Invalid Coordinates [Template] Fill Coordinates Validate The Form As Error ${INVALID_GC_WPT} A ${INVALID_GC_WPT} 1 - ${INVALID_GC_WPT} 1111111111 222222222 + ${INVALID_GC_WPT} N43.2 R6.4 Fill Coordinates Show Map Centered Fill Coordinates ${INVALID_GC_WPT} ${WPT_GC_1.coords} diff --git a/tests/unit/app/GeoKrety/Service/CoordinateConverterTest.php b/tests/unit/app/GeoKrety/Service/CoordinateConverterTest.php new file mode 100644 index 0000000000..7f1b3b75b3 --- /dev/null +++ b/tests/unit/app/GeoKrety/Service/CoordinateConverterTest.php @@ -0,0 +1,132 @@ + '57.462633 22.849983', 'result' => ['lat' => 57.462633, 'lon' => 22.849983, 'format' => 'fromDecimalDegree']], + ['test' => '52.796760 -8.442179', 'result' => ['lat' => 52.796760, 'lon' => -8.442179, 'format' => 'fromDecimalDegree']], + ['test' => 'N 57° 27.758 E 022° 50.999', 'result' => ['lat' => 57.462633, 'lon' => 22.849983, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N 57° 27.758' E 022° 50.999'", 'result' => ['lat' => 57.462633, 'lon' => 22.849983, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N 057° 27,758' E 022° 50,999'", 'result' => ['lat' => 57.462633, 'lon' => 22.849983, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N 57° 27' E 022° 50'", 'result' => ['lat' => 57.450000, 'lon' => 22.833333, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '32T E 327620 N 4840069', 'result' => ['lat' => 43.693627, 'lon' => 6.860929, 'format' => 'UTM']], + ['test' => "S 05° 59.349' E 039° 22.677'", 'result' => ['lat' => -5.989150, 'lon' => 39.377950, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'S 05° 59.349 E 039° 22.677', 'result' => ['lat' => -5.989150, 'lon' => 39.377950, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '37M E 541828 N 9337980', 'result' => ['lat' => -5.989154, 'lon' => 39.377944, 'format' => 'UTM']], + ['test' => 'N52°47.862 O008° 23.786', 'result' => ['lat' => 52.797700, 'lon' => -8.396433, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N52°47.862' O008° 23.786'", 'result' => ['lat' => 52.797700, 'lon' => -8.396433, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N52°47.862 O008°.23.786', 'result' => ['lat' => 52.797700, 'lon' => -8.396433, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N52°.47.862 O008°,23.786', 'result' => ['lat' => 52.797700, 'lon' => -8.396433, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N 52° 47.806 W 008° 26.530', 'result' => ['lat' => 52.796767, 'lon' => -8.442167, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '29U E 537611 N 5849808', 'result' => ['lat' => 52.796760, 'lon' => -8.442179, 'format' => 'UTM']], + ['test' => "N 52° 47.806' W 008° 26.530'", 'result' => ['lat' => 52.796767, 'lon' => -8.442167, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '50.260147°N 21.970291°E', 'result' => ['lat' => 50.260147, 'lon' => 21.970291, 'format' => 'fromDegreeHemisphere']], + ['test' => '50.260147°N, 21.970291°E', 'result' => ['lat' => 50.260147, 'lon' => 21.970291, 'format' => 'fromDegreeHemisphere']], + ['test' => '56.326N 54.235W', 'result' => ['lat' => 56.326, 'lon' => -54.235, 'format' => 'fromDegreeHemisphere']], + ['test' => '56.326N 54.235E', 'result' => ['lat' => 56.326, 'lon' => 54.235, 'format' => 'fromDegreeHemisphere']], + ['test' => 'N56.326 E54.235', 'result' => ['lat' => 56.326, 'lon' => 54.235, 'format' => 'fromHemisphereDegree']], + ['test' => '50°54′N 15°44′E', 'result' => ['lat' => 50.9, 'lon' => 15.733333, 'format' => 'fromDegreeMinuteHemisphere']], + ['test' => "53°50'16.85\"N 23° 6'19.78\"E", 'result' => ['lat' => 53.838014, 'lon' => 23.105494, 'format' => 'fromDegreeMinuteSecondHemisphere']], + ['test' => "49°25'59.146\"N, 17°28'25.660\"E", 'result' => ['lat' => 49.433096, 'lon' => 17.473794, 'format' => 'fromDegreeMinuteSecondHemisphere']], + ['test' => '46° 44′ 0″ N, 12° 11′ 0″ E', 'result' => ['lat' => 46.733333, 'lon' => 12.183333, 'format' => 'fromDegreeMinuteSecondHemisphere']], + ['test' => '55° 3′ 23.96″ N, 9° 44′ 32.71″ E', 'result' => ['lat' => 55.056656, 'lon' => 9.742419, 'format' => 'fromDegreeMinuteSecondHemisphere']], + ['test' => 'N55° 3′ 23.96″ E9° 44′ 32.71″', 'result' => ['lat' => 55.056656, 'lon' => 9.742419, 'format' => 'fromHemisphereDegreeMinuteSecond']], + + // Examples from the help page + ['test' => '52.205205 21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => '52.205205/21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => '52.205205\\21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => '52.205205,21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => 'N 52.205205 E 21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromHemisphereDegree']], + ['test' => 'N 52.205205 W 21.190891', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromHemisphereDegree']], + ['test' => '+52.205205 -21.190891', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromDecimalDegree']], + + ['test' => '52 12.3123 21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '52 12.3123 -21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '+52 12.3123 -21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '+52 12.3123 +21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N52 12.3123 W21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N 52° 10.369' W 021° 01.542'", 'result' => ['lat' => 52.172817, 'lon' => -21.0257, 'format' => 'fromHemisphereDegreeMinute']], + + ['test' => '52 12 18.74 -21 11 27.21', 'result' => ['lat' => 52.205206, 'lon' => -21.190892, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => "N52° 12' 18.74\", W21° 11' 27.21\"", 'result' => ['lat' => 52.205206, 'lon' => -21.190892, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => "52° 12' 18.74\" -21° 11' 27.21\"", 'result' => ['lat' => 52.205206, 'lon' => -21.190892, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => "52° 12' 18.74\", -21° 11' 27.21\"", 'result' => ['lat' => 52.205206, 'lon' => -21.190892, 'format' => 'fromDegreeMinuteSecond']], + + // Examples from legacy code + ['test' => "N 52° 12' 18.74\", E 21° 11' 27.21\"", 'result' => ['lat' => 52.205206, 'lon' => 21.190892, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => "N 52° 12' 18\", E 21° 11' 27\"", 'result' => ['lat' => 52.205000, 'lon' => 21.190833, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => 'N52 12.3123 E21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N 52 12.3123 E 21 11.45345', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "N 52° 10.369' - 021° 01.542'", 'result' => ['lat' => 52.172817, 'lon' => -21.025700, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'S 52°36.002 E013°19.205', 'result' => ['lat' => -52.600033, 'lon' => 13.320083, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => "S 52°36.002' E013°19.205'", 'result' => ['lat' => -52.600033, 'lon' => 13.320083, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => 'N 49°49.59 W 09°52.2', 'result' => ['lat' => 49.826500, 'lon' => -9.870000, 'format' => 'fromHemisphereDegreeMinute']], + ['test' => '- 49°49`59.282" E 09°52´21.216"', 'result' => ['lat' => -49.833134, 'lon' => 9.872560, 'format' => 'fromHemisphereDegreeMinuteSecond']], + ['test' => '52.205205 21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => '52.205205/21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => '52.205205\\21.190891', 'result' => ['lat' => 52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ['test' => 'N 52.205205 W 21.190891', 'result' => ['lat' => 52.205205, 'lon' => -21.190891, 'format' => 'fromHemisphereDegree']], + ['test' => '-52.205205 +21.190891', 'result' => ['lat' => -52.205205, 'lon' => 21.190891, 'format' => 'fromDecimalDegree']], + ]; + $cases_error = [ + 'North.: 6189860 East.: 544201', + ]; + + foreach ($cases_error as $case) { + $res = \GeoKrety\Service\CoordinatesConverter::parse($case); + $this->assertEquals('', $res[0]); + $this->assertEquals('', $res[1]); + $this->assertEquals('', $res['format']); + $this->assertEquals('Bad coordinates or unknown format.', $res['error']); + // print("TEST: " . $case . PHP_EOL); + // print_r($res); + } + + foreach ($cases_valid as $case) { + $res = \GeoKrety\Service\CoordinatesConverter::parse($case['test']); + // print("TEST: " . $case["test"] . PHP_EOL); + // print_r($res); + + $this->assertEquals($case['result']['lat'], $res[0]); + $this->assertEquals($case['result']['lon'], $res[1]); + $this->assertEquals($case['result']['format'], $res['format']); + } + } + + public function testPHPCoord() { + // the Statue of Liberty in WGS84 (unknown date), traditional arguments, decimal degrees + $crs = Geographic2D::fromSRID(Geographic2D::EPSG_WGS_84); + $point = GeographicPoint::create( + $crs, + new Degree(40.689167), + new Degree(-74.044444), + null + ); + $this->assertEquals(40.689167, $point->getLatitude()->getValue()); + $this->assertEquals(-74.044444, $point->getLongitude()->getValue()); + } + + public function testParseFromDegreeMinuteSecondHemisphere() { + // N 57° 27.758 E 022° 50.999 + $crs = Geographic2D::fromSRID(Geographic2D::EPSG_WGS_84); + $point = GeographicPoint::create( + $crs, + Degree::fromDegreeMinuteSecondHemisphere('40° 41′ 21″ N'), + Degree::fromDegreeMinuteSecondHemisphere('74° 2′ 40″ W'), + null + ); + // print_r($point->getLatitude()); + $this->assertEquals(40.689166666666665, $point->getLatitude()->getValue()); + $this->assertEquals(-74.04444444444444, $point->getLongitude()->getValue()); + } +} diff --git a/website/app-templates/smarty/help-pages/en/help.html b/website/app-templates/smarty/help-pages/en/help.html index ca3e499e7d..57b13d9ba4 100644 --- a/website/app-templates/smarty/help-pages/en/help.html +++ b/website/app-templates/smarty/help-pages/en/help.html @@ -666,6 +666,10 @@

Manually, using copy and paste

52 12 18.74 -21 11 27.21 N52° 12' 18.74", W21° 11' 27.21" 52° 12' 18.74", -21° 11' 27.21" + +
  • UTM
  • +
    +32T E 327620 N 4840069
     
    diff --git a/website/app-templates/smarty/js/moves/geokret_move.validation.tpl.js b/website/app-templates/smarty/js/moves/geokret_move.validation.tpl.js index 57a95f04dd..06603106a1 100644 --- a/website/app-templates/smarty/js/moves/geokret_move.validation.tpl.js +++ b/website/app-templates/smarty/js/moves/geokret_move.validation.tpl.js @@ -131,6 +131,7 @@ window.Parsley.addAsyncValidator('checkCoordinates', function(xhr) { this.removeError('errorLatlon'); if (valid) { positionUpdate([data.lat, data.lon]); + showMarker([data.lat, data.lon]); } else { this.addError('errorLatlon', { message: data.error }) } diff --git a/website/app/GeoKrety/Service/CoordinatesConverter.php b/website/app/GeoKrety/Service/CoordinatesConverter.php index e89dbf7fb2..5162d8c278 100644 --- a/website/app/GeoKrety/Service/CoordinatesConverter.php +++ b/website/app/GeoKrety/Service/CoordinatesConverter.php @@ -2,9 +2,14 @@ namespace GeoKrety\Service; +use PHPCoord\CoordinateReferenceSystem\Geographic2D; +use PHPCoord\Point\GeographicPoint; +use PHPCoord\Point\UTMPoint; +use PHPCoord\UnitOfMeasure\Angle\Degree; +use PHPCoord\UnitOfMeasure\Length\Metre; + class CoordinatesConverter { - // N 57° 27.758 E 022° 50.999 [(5)=53] [(7)=55] [(�)=194] [(�)=176] i na koncu: [28(�)=194] [29(�)=160] ??? - // North.: 6189860 East.: 544201 (UTM32) ... lol + // N 57° 27.758 E 022° 50.999 // N 047° 20,363' O 015° 02,705' // N52°47.862 O008°.23.786 // 50.260147°N, 21.970291°E @@ -15,226 +20,147 @@ class CoordinatesConverter { // 50°54′N 15°44′E // 56.326N 54.235O - public static function parse($coords) { - $ret[0] = ''; - $ret[1] = ''; - $ret['format'] = ''; - $ret['error'] = ''; - - if (!empty($coords)) { - $coords = trim($coords); - - $d1 = '[0-9]'; - $d2 = '[0-9]{1,2}'; - $d3 = '[0-9]{1,3}'; - $comma = "[\.\,]"; // kropka, przecinek - $sign1 = "n|s|\+|\-|"; // znak - // $sign1b = "n|s|"; // znak na koncu liczby (nie uzywane) - $sign2 = "w|e|\+|\-|"; // znak - // $sign2b = "w|e|"; // znak na koncu liczby (nie uzywane) - $sp = "[\s]"; // spacja - $deg_sp = "[\s\*\xc2\xb0\xba]"; // space or degrees symbol (dwa bajty w utf-8) - $break = "[\s\,\\/\\\\]"; // space , / \ - $minsec_sp = "[\s\x22\x27\xc2\x60\xb4]"; // space or minutes seconds, rozne wersje: "'`´′ to trzeba chyba zamienic na (?:\s|[^a-z0-9]*)* - $minsec_sp_br = "[\s\x22\x27\xc2\x60\xb4\,\\/\\\\]"; // space or minutes seconds, rozne wersje: "'`´ or / \ , - $smiec = "(?:[^a-z0-9\.\,\-\+\s])"; // smiec ale nie spacja - $smiec_sp = "(?:\s|[^a-z0-9\.\,\-\+])"; // smiec lub spacja - - // -------------------------------------------------------------------------------------------------- - - // N 52° 12' 18.74", E 21° 11' 27.21" - // - 49°49`59.282" E 09°52´21.216" - - // dd mm ss.sss sekundy musza byc z kropka, wtedy nie musza byc znaczki typu ° ' " - $regex = - "($sign1)$sp*($d3)$smiec_sp+($d2)$smiec_sp+(($d2)($comma($d1+)))$smiec_sp*". - "$break*". - "($sign2)$sp*($d3)$smiec_sp+($d2)$smiec_sp+(($d2)($comma($d1+)))$smiec_sp*"; - - if (preg_match('#^'.$regex.'$#i', strtolower($coords), $matches)) { - // for ($i=0; $i$i = [".$matches[$i]."]"; - - $deg = (int) $matches[2]; - $min = (int) $matches[3]; - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $sec = (float) str_replace(',', '.', $matches[4]); - if ($sec > 60) { - $ret['error'] .= 'Seconds > 60? '; - } - $deg += ($min / 60) + ($sec / 3600); - if (($matches[1] == '-') || ($matches[1] == 's')) { - $deg = $deg * -1; - } - $ret[0] = $deg; - - $deg = (int) $matches[9]; - $min = (int) $matches[10]; - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $sec = (float) str_replace(',', '.', $matches[11]); - if ($sec > 60) { - $ret['error'] .= 'Seconds > 60? '; - } - $deg += ($min / 60) + ($sec / 3600); - if (($matches[8] == '-') || ($matches[8] == 'w')) { - $deg = $deg * -1; - } - $ret[1] = $deg; - - $ret['format'] = 'DD MM SS.sss'; - // echo var_dump($ret); - return $ret; - } - - // -------------------------------------------------------------------------------------------------- - - // N 52° 12' 18", E 21° 11' 27" - - // dd* mm' ss" wymagamy znaczkow jezeli sekundy nie maja kropki po to aby odwalic cos takiego: 23 23 23 45 45 45 - $regex = - "($sign1)$sp*($d3)$smiec+$sp*($d2)$smiec+$sp*(($d2)($comma($d1+))?)$smiec+". - "$break*". - "($sign2)$sp*($d3)$smiec+$sp*($d2)$smiec+$sp*(($d2)($comma($d1+))?)$smiec_sp*"; - - if (preg_match('#^'.$regex.'$#i', strtolower($coords), $matches)) { - // for ($i=0; $i$i = [".$matches[$i]."]"; - - $deg = (int) $matches[2]; - $min = (int) $matches[3]; - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $sec = (float) str_replace(',', '.', $matches[4]); - if ($sec > 60) { - $ret['error'] .= 'Seconds > 60? '; - } - $deg += ($min / 60) + ($sec / 3600); - if (($matches[1] == '-') || ($matches[1] == 's')) { - $deg = $deg * -1; - } - $ret[0] = $deg; + // N 52° 12' 18.74", E 21° 11' 27.21" + // - 49°49`59.282" E 09°52´21.216" - $deg = (int) $matches[9]; - $min = (int) $matches[10]; - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $sec = (float) str_replace(',', '.', $matches[11]); - if ($sec > 60) { - $ret['error'] .= 'Seconds > 60? '; - } - $deg += ($min / 60) + ($sec / 3600); - if (($matches[8] == '-') || ($matches[8] == 'w')) { - $deg = $deg * -1; - } - $ret[1] = $deg; + // N 52° 12' 18", E 21° 11' 27" - $ret['format'] = 'DD MM SS'; - // echo var_dump($ret); - return $ret; - } + // 52 12.3123 21 11.45345 + // N52 12.3123 E21 11.45345 + // N 52 12.3123 E 21 11.45345 - // -------------------------------------------------------------------------------------------------- + // N 52° 10.369' - 021° 01.542' + // S 52°36.002 E013°19.205 - nie dzialalo, juz dziala + // N 49°49.59 W 09°52.2 - nie dzialalo, juz dziala - // 52 12.3123 21 11.45345 - // N52 12.3123 E21 11.45345 - // N 52 12.3123 E 21 11.45345 + // 52.205205 21.190891 + // 52.205205/21.190891 + // 52.205205\21.190891 + // N 52.205205 W 21.190891 + // -52.205205 +21.190891 - // N 52° 10.369' - 021° 01.542' - // S 52°36.002 E013°19.205 - nie dzialalo, juz dziala - // N 49°49.59 W 09°52.2 - nie dzialalo, juz dziala + public static function parse($coords) { + $ret[0] = ''; + $ret[1] = ''; + $ret['format'] = ''; + $ret['error'] = ''; - // dd mm.mmm - $regex = - "($sign1)$sp*($d3)$smiec_sp+(($d2)($comma($d1+))?)$smiec_sp*". - "$break*". - "($sign2)$sp*($d3)$smiec_sp+(($d2)($comma($d1+))?)$smiec_sp*"; + if (empty($coords)) { + $ret['error'] = _('Missing or invalid coordinates.'); - if (preg_match('#^'.$regex.'$#i', strtolower($coords), $matches)) { - // for ($i=0; $i$i = [".$matches[$i]."]"; + return $ret; + } - $deg = (int) $matches[2]; - if ($deg > 360) { - $ret['error'] .= 'Degrees > 360? '; - } - $min = (float) str_replace(',', '.', $matches[3]); - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $deg += ($min / 60); - if (($matches[1] == '-') || ($matches[1] == 's')) { - $deg = $deg * -1; - } - $ret[0] = $deg; + // 57.462633 22.849983 + $fromDecimalDegree = '/^(?P[\+−-]?\d+(\.\d*)?)[\s+,\/\\\](?P[\+−-]?\d+(\.\d*)?)$/u'; + $found = preg_match($fromDecimalDegree, $coords, $parts); + if ($found !== 0) { + $ret['format'] = 'fromDecimalDegree'; + $ret[0] = sprintf('%.6f', $parts['angle1']); + $ret[1] = sprintf('%.6f', $parts['angle2']); - $deg = (int) $matches[8]; - if ($deg > 360) { - $ret['error'] .= 'Degrees > 360? '; - } - $min = (float) str_replace(',', '.', $matches[9]); - if ($min > 60) { - $ret['error'] .= 'Minutes > 60? '; - } - $deg += ($min / 60); - if (($matches[7] == '-') || ($matches[7] == 'w')) { - $deg = $deg * -1; - } - $ret[1] = $deg; + return $ret; + } - $ret['format'] = 'DD MM.mmm'; - // echo var_dump($ret); - return $ret; + $regexes = [ + // 56.326N 54.235E + 'fromDegreeHemisphere' => '/^(?P\d+\.?\d*([°º][,\.]?)?[NS])(?P\d+\.?\d*([°º][,\.]?)?[EWO])$/u', + // N 56.326 E 54.235 + 'fromHemisphereDegree' => '/^(?P[NS]\d+\.?\d*[°º]?)(?P[EWO]\d+\.?\d*[°º]?)$/u', + // 50°54′N 15°44′E + 'fromDegreeMinuteHemisphere' => '/^(?P\d+([°º][,\.]?)?(\d+\.?\d*[′\'])?[NS])(?P\d+([°º][,\.]?)?(\d+\.?\d*[′\'])?[EWO])$/u', + // 50°54′N 15°44′E + 'fromDegreeMinute' => '/^(?P[\+−-]?\d+[°º]?(\d+\.?\d*[′\'])?)(?P[−-]?\d+[°º]?(\d+\.?\d*[′\'])?)$/u', + // 53°50'16.85"N 23° 6'19.78"E + 'fromDegreeMinuteSecondHemisphere' => '/^(?P\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?[NS])(?P\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?[EWO])$/u', + + // 52° 12' 18.74", -21° 11' 27.21" + 'fromDegreeMinuteSecond' => '/^(?P[−-]?\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?)(,)?(?P[−-]?\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?)$/u', + // N 57° 27.758' E 022° 50.999' + 'fromHemisphereDegreeMinute' => '/^(?P[NS]\d+([°º][,\.]?)?(\d+[,\.]?\d*[′\'])?)(?P[EWO]\d+([°º][,\.]?)?(\d+[,\.]?\d*[′\'])?)$/u', + // # N 57° 27.758' E 022° 50.999' + // 'fromHemisphereDegreeMinuteGC' => '/^(?P[NS]\d+([°º][,\.]?)?(\d+[,\.]?\d*)?)(?P[EWO]\d+([°º][,\.]?)?(\d+[,\.]?\d*)?)$/u', + // N55° 3′ 23.96″ E9° 44′ 32.71″ + 'fromHemisphereDegreeMinuteSecond' => '/^(?P[NS]\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?)(?P[EWO]\d+([°º][,\.]?)?(\d+[′\'])?(\d+\.?\d*[″"])?)$/u', + + // 32T E 327620 N 4840069 + 'UTM' => '/^(?P\d{2})(?P[A-Z])E(?P\d+)N(?P\d+)$/u', + ]; + + // 52 12.3123 -21 11.45345 -> 52° 12.3123' -21° 11.45345' + $coords = preg_replace_callback( + '/^(?P[NS\+−-])?\s*(?P\d+)([°º][,\.]?\s*|\s+|[,\/\\\])(?P\d+\.?\d*)[`´′\']?\s+(?P[EWO\+−-])?\s*(?P\d+)([°º][,\.]?\s*|\s+|[,\/\\\])(?P\d+\.?\d*)[`´′\']?$/u', + function ($m) { + $m['negative1'] = in_array($m['negative1'], ['-', 'S']) ? 'S' : 'N'; + $m['negative2'] = in_array($m['negative2'], ['-', 'W', 'O']) ? 'W' : 'E'; + + return "{$m['negative1']}{$m['degrees1']}°{$m['arcminutes1']}'{$m['negative2']}{$m['degrees2']}°{$m['arcminutes2']}'"; + }, $coords); + + // 52 12 18.74 -21 11 27.21 -> 52° 12' 18.74" -21° 11' 27.21" + $coords = preg_replace_callback( + '/^(?P[NS\+−-])?\s*(?P\d+)([°º][,\.]?\s*|\s+|[,\/\\\])(?P\d+)([`´′\']?\s*|s+)(?P\d+\.?\d*)[″"]?\s+(?P[EWO\+−-])?\s*(?P\d+)([°º][,\.]?\s*|\s+|[,\/\\\])(?P\d+)([`´′\']?\s*|s+)(?P\d+\.?\d*)[″"]?$/u', + function ($m) { + $m['negative1'] = in_array($m['negative1'], ['-', 'S']) ? 'S' : 'N'; + $m['negative2'] = in_array($m['negative2'], ['-', 'W', 'O']) ? 'W' : 'E'; + + return "{$m['negative1']}{$m['degrees1']}°{$m['arcminutes1']}'{$m['arcseconds1']}\"{$m['negative2']}{$m['degrees2']}°{$m['arcminutes2']}'{$m['arcseconds2']}\""; + }, $coords); + + $input = str_replace(' ', '', $coords); + $input = str_replace('O', 'W', $input); + $input = str_replace('+', '', $input); + $input = preg_replace('/[″"],/', '"', $input); + $input = str_replace(',', '.', $input); + $input = preg_replace('/([°º])[,\.]/', '${1}', $input); + $input = preg_replace('/([NSEWO])[,\.]/', '${1}', $input); + + $crs = Geographic2D::fromSRID(Geographic2D::EPSG_WGS_84); + + $matched_format = null; + foreach ($regexes as $format => $regex) { + $found = preg_match($regex, $input, $parts); + if ($found !== 0) { + $matched_format = $ret['format'] = $format; + break; } + } - // -------------------------------------------------------------------------------------------------- - - // 52.205205 21.190891 - // 52.205205/21.190891 - // 52.205205\21.190891 - // N 52.205205 W 21.190891 - // -52.205205 +21.190891 - // i podobne wariacje - - // dd.ddd - $regex = - "($sign1)$sp*(($d3)($comma($d1+))?)$deg_sp*". - "$break*". - "($sign2)$sp*(($d3)($comma($d1+))?)$deg_sp*"; - - if (preg_match('#^'.$regex.'$#i', strtolower($coords), $matches)) { - // for ($i=0; $i$i = [".$matches[$i]."]"; + if ($matched_format === null) { + $ret['error'] = _('Bad coordinates or unknown format.'); - $deg = (float) str_replace(',', '.', $matches[2]); - if ($deg > 360) { - $ret['error'] .= 'Degrees > 360? '; - } - if (($matches[1] == '-') || ($matches[1] == 's')) { - $deg = $deg * -1; - } - $ret[0] = $deg; + return $ret; + } - $deg = (float) str_replace(',', '.', $matches[7]); - if ($deg > 360) { - $ret['error'] .= 'Degrees > 360? '; - } - if (($matches[6] == '-') || ($matches[6] == 'w')) { - $deg = $deg * -1; + if ($matched_format === 'UTM') { + $utm = new UTMPoint( + $crs, + new Metre($parts['dist1']), + new Metre($parts['dist2']), + intval($parts['zone']), + $parts['hemisphere'] < 'N' ? UTMPoint::HEMISPHERE_SOUTH : UTMPoint::HEMISPHERE_NORTH, + ); + $point = $utm->asGeographicPoint(); + } else { + if ($ret['format'] == 'fromHemisphereDegreeMinuteGC') { + $matched_format = 'fromHemisphereDegreeMinute'; + foreach (['angle1', 'angle2'] as $part) { + if (substr($parts[$part], -1) != '\'') { + $parts[$part] .= '\''; + } } - $ret[1] = $deg; - - $ret['format'] = 'DD.ddd'; - // echo var_dump($ret); - return $ret; } - - $ret['error'] = _('Bad coordinates or unknown format.'); - } else { // if empty $coords - $ret['error'] = _('Missing or invalid coordinates.'); + $point = GeographicPoint::create( + $crs, + Degree::$matched_format($parts['angle1']), + Degree::$matched_format($parts['angle2']), + null + ); } + $ret[0] = sprintf('%.6f', $point->getLatitude()->getValue()); + $ret[1] = sprintf('%.6f', $point->getLongitude()->getValue()); + return $ret; } }