aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier Jimenez Shaw <j1@jimenezshaw.com>2021-04-11 21:44:52 +0200
committerGitHub <noreply@github.com>2021-04-11 21:44:52 +0200
commit78792a7e42928565b06398f146a43d121160b8a2 (patch)
tree21cbfd26b8010feb67dc86e879282455ec5d9daa
parent6fffba7efa94fe989ec023294850aeca6b2cd5c6 (diff)
downloadPROJ-78792a7e42928565b06398f146a43d121160b8a2.tar.gz
PROJ-78792a7e42928565b06398f146a43d121160b8a2.zip
projinfo: add option --list-crs (#2663)
-rw-r--r--docs/source/apps/projinfo.rst11
-rw-r--r--src/apps/projinfo.cpp271
-rwxr-xr-xtest/cli/testprojinfo52
-rw-r--r--test/cli/testprojinfo_out.dist54
4 files changed, 306 insertions, 82 deletions
diff --git a/docs/source/apps/projinfo.rst b/docs/source/apps/projinfo.rst
index a05316ad..068ac06d 100644
--- a/docs/source/apps/projinfo.rst
+++ b/docs/source/apps/projinfo.rst
@@ -32,6 +32,7 @@ Synopsis
| [--output-id AUTH:CODE]
| [--c-ify] [--single-line]
| --searchpaths | --remote-data |
+ | --list-crs [list-crs-filter] |
| --dump-db-structure [{object_definition} | {object_reference}] |
| {object_definition} | {object_reference} | (-s {srs_def} -t {srs_def})
|
@@ -285,6 +286,16 @@ The following control parameters can appear in any order:
If also specifying a CRS object and the :option:`--output-id` option, the
definition of the object as SQL statements will be appended.
+.. option:: --list-crs [list-crs-filter]
+
+ .. versionadded:: 8.1
+
+ Outputs a list (authority name:code and CRS name) of the filtered CRSs from the database.
+ If no filter is provided all authority names and types of non deprecated CRSs are dumped.
+ list-crs-filter is a comma separated combination of: allow_deprecated,geodetic,geocentric,
+ geographic,geographic_2d,geographic_3d,vertical,projected,compound.
+ Affected by options :option:`--authority`, :option:`--area`, :option:`--bbox` and :option:`--spatial-test`
+
.. option:: --3d
.. versionadded:: 6.3
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
index dd94b652..f5c19cc5 100644
--- a/src/apps/projinfo.cpp
+++ b/src/apps/projinfo.cpp
@@ -33,6 +33,7 @@
#include <cstdlib>
#include <fstream> // std::ifstream
#include <iostream>
+#include <set>
#include <utility>
#include "proj.h"
@@ -112,6 +113,7 @@ static void usage() {
<< " [--output-id AUTH:CODE]" << std::endl
<< " [--c-ify] [--single-line]" << std::endl
<< " --searchpaths | --remote-data |" << std::endl
+ << " --list-crs [list-crs-filter] |" << std::endl
<< " --dump-db-structure [{object_definition} | "
"{object_reference}] |"
<< std::endl
@@ -127,6 +129,12 @@ static void usage() {
"by '-' to disable them"
<< std::endl;
std::cerr << std::endl;
+ std::cerr << "list-crs-filter is a comma separated combination of: "
+ "allow_deprecated,geodetic,geocentric,"
+ << std::endl;
+ std::cerr << "geographic,geographic_2d,geographic_3d,vertical,projected,compound"
+ << std::endl;
+ std::cerr << std::endl;
std::cerr << "{object_definition} might be a PROJ string, a WKT string, "
"a AUTHORITY:CODE, or urn:ogc:def:OBJECT_TYPE:AUTHORITY::CODE"
<< std::endl;
@@ -157,6 +165,90 @@ static std::string c_ify_string(const std::string &str) {
// ---------------------------------------------------------------------------
+static ExtentPtr makeBboxFilter(DatabaseContextPtr dbContext, const std::string& bboxStr, const std::string& area)
+{
+ ExtentPtr bboxFilter = nullptr;
+ if (!bboxStr.empty()) {
+ auto bbox(split(bboxStr, ','));
+ if (bbox.size() != 4) {
+ std::cerr << "Incorrect number of values for option --bbox: "
+ << bboxStr << std::endl;
+ usage();
+ }
+ try {
+ std::vector<double> bboxValues = {c_locale_stod(bbox[0]), c_locale_stod(bbox[1]),
+ c_locale_stod(bbox[2]), c_locale_stod(bbox[3])};
+ bboxFilter = Extent::createFromBBOX(
+ bboxValues[0], bboxValues[1], bboxValues[2], bboxValues[3])
+ .as_nullable();
+ } catch (const std::exception &e) {
+ std::cerr << "Invalid value for option --bbox: " << bboxStr
+ << ", " << e.what() << std::endl;
+ usage();
+ }
+ } else if (!area.empty()) {
+ assert(dbContext);
+ try {
+ if (area.find(' ') == std::string::npos &&
+ area.find(':') != std::string::npos) {
+ auto tokens = split(area, ':');
+ if (tokens.size() == 2) {
+ const std::string &areaAuth = tokens[0];
+ const std::string &areaCode = tokens[1];
+ bboxFilter = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), areaAuth)
+ ->createExtent(areaCode)
+ .as_nullable();
+ }
+ }
+ if (!bboxFilter) {
+ auto authFactory = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), std::string());
+ auto res = authFactory->listAreaOfUseFromName(area, false);
+ if (res.size() == 1) {
+ bboxFilter =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext),
+ res.front().first)
+ ->createExtent(res.front().second)
+ .as_nullable();
+ } else {
+ res = authFactory->listAreaOfUseFromName(area, true);
+ if (res.size() == 1) {
+ bboxFilter =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext),
+ res.front().first)
+ ->createExtent(res.front().second)
+ .as_nullable();
+ } else if (res.empty()) {
+ std::cerr << "No area of use matching provided name"
+ << std::endl;
+ std::exit(1);
+ } else {
+ std::cerr << "Several candidates area of use "
+ "matching provided name :"
+ << std::endl;
+ for (const auto &candidate : res) {
+ auto obj =
+ AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), candidate.first)
+ ->createExtent(candidate.second);
+ std::cerr << " " << candidate.first << ":"
+ << candidate.second << " : "
+ << *obj->description() << std::endl;
+ }
+ std::exit(1);
+ }
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "Area of use retrieval failed: " << e.what()
+ << std::endl;
+ std::exit(1);
+ }
+ }
+ return bboxFilter;
+}
+
static BaseObjectNNPtr buildObject(
DatabaseContextPtr dbContext, const std::string &user_string,
const std::string &kind, const std::string &context,
@@ -838,7 +930,7 @@ int main(int argc, char **argv) {
OutputOptions outputOpt;
std::string objectKind;
bool summary = false;
- ExtentPtr bboxFilter = nullptr;
+ std::string bboxStr;
std::string area;
bool spatialCriterionExplicitlySpecified = false;
CoordinateOperationContext::SpatialCriterion spatialCriterion =
@@ -865,6 +957,8 @@ int main(int argc, char **argv) {
double minimumAccuracy = -1;
bool outputAll = false;
bool dumpDbStructure = false;
+ std::string listCRS;
+ bool listCRSSpecified = false;
for (int i = 1; i < argc; i++) {
std::string arg(argv[i]);
@@ -969,23 +1063,7 @@ int main(int argc, char **argv) {
}
} else if (arg == "--bbox" && i + 1 < argc) {
i++;
- auto bboxStr(argv[i]);
- auto bbox(split(bboxStr, ','));
- if (bbox.size() != 4) {
- std::cerr << "Incorrect number of values for option --bbox: "
- << bboxStr << std::endl;
- usage();
- }
- try {
- bboxFilter = Extent::createFromBBOX(
- c_locale_stod(bbox[0]), c_locale_stod(bbox[1]),
- c_locale_stod(bbox[2]), c_locale_stod(bbox[3]))
- .as_nullable();
- } catch (const std::exception &e) {
- std::cerr << "Invalid value for option --bbox: " << bboxStr
- << ", " << e.what() << std::endl;
- usage();
- }
+ bboxStr = argv[i];
} else if (arg == "--accuracy" && i + 1 < argc) {
i++;
try {
@@ -1155,6 +1233,12 @@ int main(int argc, char **argv) {
outputOpt.outputCode = tokens[1];
} else if (arg == "--dump-db-structure") {
dumpDbStructure = true;
+ } else if (arg == "--list-crs") {
+ listCRSSpecified = true;
+ if (i + 1 < argc && argv[i + 1][0] != '-') {
+ i++;
+ listCRS = argv[i];
+ }
} else if (ci_equal(arg, "--searchpaths")) {
#ifdef _WIN32
constexpr char delim = ';';
@@ -1199,7 +1283,7 @@ int main(int argc, char **argv) {
}
}
- if (bboxFilter && !area.empty()) {
+ if (!bboxStr.empty() && !area.empty()) {
std::cerr << "ERROR: --bbox and --area are exclusive" << std::endl;
std::exit(1);
}
@@ -1247,6 +1331,90 @@ int main(int argc, char **argv) {
}
}
+ if (listCRSSpecified) {
+ bool allow_deprecated = false;
+ std::set<AuthorityFactory::ObjectType> types;
+ auto tokens = split(listCRS, ',');
+ if (listCRS.empty()) {
+ tokens.clear();
+ }
+ for (auto token : tokens) {
+ if (ci_equal(token, "allow_deprecated")) {
+ allow_deprecated = true;
+ } else if (ci_equal(token, "geodetic")) {
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS);
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ types.insert(AuthorityFactory::ObjectType::GEOCENTRIC_CRS);
+ } else if (ci_equal(token, "geocentric")) {
+ types.insert(AuthorityFactory::ObjectType::GEOCENTRIC_CRS);
+ } else if (ci_equal(token, "geographic")) {
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS);
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ } else if (ci_equal(token, "geographic_2d")) {
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS);
+ } else if (ci_equal(token, "geographic_3d")) {
+ types.insert(AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ } else if (ci_equal(token, "vertical")) {
+ types.insert(AuthorityFactory::ObjectType::VERTICAL_CRS);
+ } else if (ci_equal(token, "projected")) {
+ types.insert(AuthorityFactory::ObjectType::PROJECTED_CRS);
+ } else if (ci_equal(token, "compound")) {
+ types.insert(AuthorityFactory::ObjectType::COMPOUND_CRS);
+ } else {
+ std::cerr << "Unrecognized value for option --list-crs: "
+ << token << std::endl;
+ usage();
+ }
+ }
+
+ auto bboxFilter = makeBboxFilter(dbContext, bboxStr, area);
+ auto allowedAuthorities(outputOpt.allowedAuthorities);
+ if (allowedAuthorities.empty()) {
+ allowedAuthorities.emplace_back(std::string());
+ }
+ for (auto auth_name : allowedAuthorities) {
+ try {
+ auto factory = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), auth_name);
+ auto list = factory->getCRSInfoList();
+ for (const auto &info : list) {
+ if (!allow_deprecated && info.deprecated) {
+ continue;
+ }
+ if (!types.empty() && types.find(info.type) == types.end()) {
+ continue;
+ }
+ if (bboxFilter) {
+ if (!info.bbox_valid) {
+ continue;
+ }
+ auto crsExtent = Extent::createFromBBOX(
+ info.west_lon_degree, info.south_lat_degree,
+ info.east_lon_degree, info.north_lat_degree);
+ if (spatialCriterion == CoordinateOperationContext::
+ SpatialCriterion::STRICT_CONTAINMENT) {
+ if (!bboxFilter->contains(crsExtent)) {
+ continue;
+ }
+ } else {
+ if (!bboxFilter->intersects(crsExtent)) {
+ continue;
+ }
+ }
+ }
+ std::cout << info.authName << ":" << info.code
+ << " \"" << info.name << "\""
+ << (info.deprecated ? " [deprecated]" : "")
+ << std::endl;
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "ERROR: list-crs failed with: " << e.what()
+ << std::endl;
+ std::exit(1);
+ }
+ }
+ }
+
if (!sourceCRSStr.empty() && targetCRSStr.empty()) {
std::cerr << "Source CRS specified, but missing target CRS"
<< std::endl;
@@ -1261,7 +1429,7 @@ int main(int argc, char **argv) {
usage();
}
} else if (!user_string_specified) {
- if (dumpDbStructure) {
+ if (dumpDbStructure || listCRSSpecified) {
std::exit(0);
}
std::cerr << "Missing user string" << std::endl;
@@ -1380,68 +1548,7 @@ int main(int argc, char **argv) {
std::exit(1);
}
} else {
-
- if (!area.empty()) {
- assert(dbContext);
- try {
- if (area.find(' ') == std::string::npos &&
- area.find(':') != std::string::npos) {
- auto tokens = split(area, ':');
- if (tokens.size() == 2) {
- const std::string &areaAuth = tokens[0];
- const std::string &areaCode = tokens[1];
- bboxFilter = AuthorityFactory::create(
- NN_NO_CHECK(dbContext), areaAuth)
- ->createExtent(areaCode)
- .as_nullable();
- }
- }
- if (!bboxFilter) {
- auto authFactory = AuthorityFactory::create(
- NN_NO_CHECK(dbContext), std::string());
- auto res = authFactory->listAreaOfUseFromName(area, false);
- if (res.size() == 1) {
- bboxFilter =
- AuthorityFactory::create(NN_NO_CHECK(dbContext),
- res.front().first)
- ->createExtent(res.front().second)
- .as_nullable();
- } else {
- res = authFactory->listAreaOfUseFromName(area, true);
- if (res.size() == 1) {
- bboxFilter =
- AuthorityFactory::create(NN_NO_CHECK(dbContext),
- res.front().first)
- ->createExtent(res.front().second)
- .as_nullable();
- } else if (res.empty()) {
- std::cerr << "No area of use matching provided name"
- << std::endl;
- std::exit(1);
- } else {
- std::cerr << "Several candidates area of use "
- "matching provided name :"
- << std::endl;
- for (const auto &candidate : res) {
- auto obj =
- AuthorityFactory::create(
- NN_NO_CHECK(dbContext), candidate.first)
- ->createExtent(candidate.second);
- std::cerr << " " << candidate.first << ":"
- << candidate.second << " : "
- << *obj->description() << std::endl;
- }
- std::exit(1);
- }
- }
- }
- } catch (const std::exception &e) {
- std::cerr << "Area of use retrieval failed: " << e.what()
- << std::endl;
- std::exit(1);
- }
- }
-
+ auto bboxFilter = makeBboxFilter(dbContext, bboxStr, area);
try {
outputOperations(
dbContext, sourceCRSStr, targetCRSStr, bboxFilter,
diff --git a/test/cli/testprojinfo b/test/cli/testprojinfo
index fb4fef1f..0ee256a2 100755
--- a/test/cli/testprojinfo
+++ b/test/cli/testprojinfo
@@ -310,6 +310,58 @@ echo 'Checks that ED50 to ETRS89 (12) is in the output (superseded transformatio
$EXE -s EPSG:23030 -t EPSG:25830 --bbox -6,40,-5,41 --grid-check known_available --hide-ballpark --summary >>${OUT} 2>&1
echo "" >>${OUT}
+echo 'Testing --list-crs | grep "EPSG:32632\|ESRI:103668\|OGC" | sort' >> ${OUT}
+$EXE --list-crs | grep "EPSG:32632\|ESRI:103668\|OGC" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs --authority OGC,EPSG | grep "EPSG:4326\|OGC" | sort' >> ${OUT}
+$EXE --list-crs --authority OGC,EPSG | grep "EPSG:4326\|OGC" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs | grep deprecated | sort' >> ${OUT}
+$EXE --list-crs | grep deprecated | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs vertical --bbox 0,40,1,41 --spatial-test intersects | grep "Alicante\|NAVD88" | sort' >> ${OUT}
+$EXE --list-crs vertical --bbox 0,40,1,41 --spatial-test intersects | grep "Alicante\|NAVD88" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs vertical --bbox -10,35,5,45 --spatial-test contains | grep "Alicante\|NAVD88" | sort' >> ${OUT}
+$EXE --list-crs vertical --bbox -10,35,5,45 --spatial-test contains | grep "Alicante\|NAVD88" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs --area Spain --spatial-test intersects | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort' >> ${OUT}
+$EXE --list-crs --area Spain --spatial-test intersects | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs --area Spain --spatial-test contains | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort' >> ${OUT}
+$EXE --list-crs --area Spain --spatial-test contains | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs --area Spain | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort' >> ${OUT}
+$EXE --list-crs --area Spain | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs geodetic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort' >> ${OUT}
+$EXE --list-crs geodetic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs geographic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort' >> ${OUT}
+$EXE --list-crs geographic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs geocentric,geographic_3d | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort' >> ${OUT}
+$EXE --list-crs geocentric,geographic_3d | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs geographic_2d,allow_deprecated --bbox -100,40,-90,41 --spatial-test intersects | grep deprecated | grep "NAD83(FBN)\|GCS_IGS08" | sort' >> ${OUT}
+$EXE --list-crs geographic_2d,allow_deprecated --bbox -100,40,-90,41 --spatial-test intersects | grep deprecated | grep "NAD83(FBN)\|GCS_IGS08" | sort >> ${OUT}
+echo "" >>${OUT}
+
+echo 'Testing --list-crs projected --bbox -100,40,-90,41 --spatial-test intersects | grep "(2011).*Missouri East\|York" | sort' >> ${OUT}
+$EXE --list-crs projected --bbox -100,40,-90,41 --spatial-test intersects | grep "(2011).*Missouri East\|York" | sort >> ${OUT}
+echo "" >>${OUT}
+
# do 'diff' with distribution results
echo "diff ${OUT} with testprojinfo_out.dist"
diff -u ${OUT} ${TEST_CLI_DIR}/testprojinfo_out.dist
diff --git a/test/cli/testprojinfo_out.dist b/test/cli/testprojinfo_out.dist
index ccb9e070..3f549fd9 100644
--- a/test/cli/testprojinfo_out.dist
+++ b/test/cli/testprojinfo_out.dist
@@ -1600,3 +1600,57 @@ Candidate operations found: 2
unknown id, Inverse of UTM zone 30N + ED50 to ETRS89 (12) + UTM zone 30N, 0.2 m, Spain - mainland, Balearic Islands, Ceuta and Melila - onshore.
unknown id, Inverse of UTM zone 30N + ED50 to ETRS89 (7) + UTM zone 30N, 1.5 m, Spain - onshore mainland except northwest (north of 41°30'N and west of 4°30'W).
+Testing --list-crs | grep "EPSG:32632\|ESRI:103668\|OGC" | sort
+EPSG:32632 "WGS 84 / UTM zone 32N"
+ESRI:103668 "NAD_1983_HARN_Adj_MN_Ramsey_Meters"
+OGC:CRS27 "NAD27 (CRS27)"
+OGC:CRS83 "NAD83 (CRS83)"
+OGC:CRS84 "WGS 84 (CRS84)"
+
+Testing --list-crs --authority OGC,EPSG | grep "EPSG:4326\|OGC" | sort
+EPSG:4326 "WGS 84"
+OGC:CRS27 "NAD27 (CRS27)"
+OGC:CRS83 "NAD83 (CRS83)"
+OGC:CRS84 "WGS 84 (CRS84)"
+
+Testing --list-crs | grep deprecated | sort
+
+Testing --list-crs vertical --bbox 0,40,1,41 --spatial-test intersects | grep "Alicante\|NAVD88" | sort
+EPSG:5782 "Alicante height"
+
+Testing --list-crs vertical --bbox -10,35,5,45 --spatial-test contains | grep "Alicante\|NAVD88" | sort
+EPSG:5782 "Alicante height"
+
+Testing --list-crs --area Spain --spatial-test intersects | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort
+EPSG:4258 "ETRS89"
+EPSG:9398 "Tenerife height"
+EPSG:9505 "ETRS89 + Alicante height"
+
+Testing --list-crs --area Spain --spatial-test contains | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort
+EPSG:9398 "Tenerife height"
+EPSG:9505 "ETRS89 + Alicante height"
+
+Testing --list-crs --area Spain | grep "EPSG:9505\|EPSG:9398\|EPSG:4258\|EPSG:5703" | sort
+EPSG:9398 "Tenerife height"
+EPSG:9505 "ETRS89 + Alicante height"
+
+Testing --list-crs geodetic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort
+EPSG:4326 "WGS 84"
+EPSG:4978 "WGS 84"
+EPSG:4979 "WGS 84"
+
+Testing --list-crs geographic | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort
+EPSG:4326 "WGS 84"
+EPSG:4979 "WGS 84"
+
+Testing --list-crs geocentric,geographic_3d | grep "EPSG:4326\|EPSG:4979\|EPSG:4978" | sort
+EPSG:4978 "WGS 84"
+EPSG:4979 "WGS 84"
+
+Testing --list-crs geographic_2d,allow_deprecated --bbox -100,40,-90,41 --spatial-test intersects | grep deprecated | grep "NAD83(FBN)\|GCS_IGS08" | sort
+EPSG:8449 "NAD83(FBN)" [deprecated]
+ESRI:104010 "GCS_IGS08" [deprecated]
+
+Testing --list-crs projected --bbox -100,40,-90,41 --spatial-test intersects | grep "(2011).*Missouri East\|York" | sort
+EPSG:6512 "NAD83(2011) / Missouri East"
+