aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-02-04 23:34:17 +0100
committerGitHub <noreply@github.com>2020-02-04 23:34:17 +0100
commitafda50628d690bb56debbe1a4116bc32c3143943 (patch)
tree481822cd7893b6ad782847cec1123fe79da3ba42
parentc3e7876325e5d43586a7eee43db1df9468f90d65 (diff)
parent87318283eedef680356ef272a6af39ac3140d333 (diff)
downloadPROJ-afda50628d690bb56debbe1a4116bc32c3143943.tar.gz
PROJ-afda50628d690bb56debbe1a4116bc32c3143943.zip
Merge pull request #1903 from rouault/add_proj_download
Add projsync utility
-rw-r--r--configure.ac1
-rw-r--r--docs/source/apps/cct.rst2
-rw-r--r--docs/source/apps/cs2cs.rst2
-rw-r--r--docs/source/apps/geod.rst2
-rw-r--r--docs/source/apps/gie.rst2
-rw-r--r--docs/source/apps/index.rst1
-rw-r--r--docs/source/apps/proj.rst2
-rw-r--r--docs/source/apps/projinfo.rst2
-rw-r--r--docs/source/apps/projsync.rst160
-rw-r--r--docs/source/conf.py7
-rw-r--r--docs/source/usage/network.rst5
-rw-r--r--scripts/reference_exported_symbols.txt5
-rwxr-xr-xscripts/reformat_cpp.sh4
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/Makefile.am16
-rw-r--r--src/apps/projsync.cpp555
-rw-r--r--src/bin_projsync.cmake16
-rw-r--r--src/filemanager.cpp11
-rw-r--r--src/filemanager.hpp12
-rw-r--r--src/networkfilemanager.cpp12
-rw-r--r--src/proj_internal.h3
-rw-r--r--test/cli/CMakeLists.txt4
-rw-r--r--test/cli/Makefile.am14
-rwxr-xr-xtest/cli/test_projsync194
24 files changed, 1021 insertions, 22 deletions
diff --git a/configure.ac b/configure.ac
index 28e9067d..fba41302 100644
--- a/configure.ac
+++ b/configure.ac
@@ -321,6 +321,7 @@ fi
AC_SUBST(CURL_CFLAGS,$CURL_CFLAGS)
AC_SUBST(CURL_LIBS,$CURL_LIBS)
AC_SUBST(CURL_ENABLED_FLAGS,$CURL_ENABLED_FLAGS)
+AM_CONDITIONAL(HAVE_CURL, [test "x$FOUND_CURL" = "xyes"])
dnl ---------------------------------------------------------------------------
dnl Check for external Google Test
diff --git a/docs/source/apps/cct.rst b/docs/source/apps/cct.rst
index 96b5f13b..a9d9e61e 100644
--- a/docs/source/apps/cct.rst
+++ b/docs/source/apps/cct.rst
@@ -190,7 +190,7 @@ Hence, in honour of *cct* (the geodesist) this is :program:`cct` (the program).
See also
********
- **proj(1)**, **cs2cs(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**
+ **proj(1)**, **cs2cs(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/cs2cs.rst b/docs/source/apps/cs2cs.rst
index c95bbaed..1a1e785d 100644
--- a/docs/source/apps/cs2cs.rst
+++ b/docs/source/apps/cs2cs.rst
@@ -230,7 +230,7 @@ outputs
See also
********
- **proj(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**
+ **proj(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/geod.rst b/docs/source/apps/geod.rst
index c03dbb74..5267c184 100644
--- a/docs/source/apps/geod.rst
+++ b/docs/source/apps/geod.rst
@@ -200,7 +200,7 @@ Further reading
See also
********
- **proj(1)**, **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**
+ **proj(1)**, **cs2cs(1)**, **cct(1)**, **gie(1)**, **projinfo(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/gie.rst b/docs/source/apps/gie.rst
index ac197d44..af6f528e 100644
--- a/docs/source/apps/gie.rst
+++ b/docs/source/apps/gie.rst
@@ -366,7 +366,7 @@ So in honour, and hopefully also in the spirit, of Gerald Ian Evenden
See also
********
- **proj(1)**, **cs2cs(1)**, **cct(1)**, **geod(1)**
+ **proj(1)**, **cs2cs(1)**, **cct(1)**, **geod(1)**, **projinfo(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/index.rst b/docs/source/apps/index.rst
index 381b8933..751be591 100644
--- a/docs/source/apps/index.rst
+++ b/docs/source/apps/index.rst
@@ -24,3 +24,4 @@ operations.
gie
proj
projinfo
+ projsync
diff --git a/docs/source/apps/proj.rst b/docs/source/apps/proj.rst
index 21c2ea61..488d1b36 100644
--- a/docs/source/apps/proj.rst
+++ b/docs/source/apps/proj.rst
@@ -214,7 +214,7 @@ data will appear as three lines of::
See also
********
- **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**
+ **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **projinfo(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/projinfo.rst b/docs/source/apps/projinfo.rst
index 0b65b8a0..91d09941 100644
--- a/docs/source/apps/projinfo.rst
+++ b/docs/source/apps/projinfo.rst
@@ -416,7 +416,7 @@ Output:
See also
********
- **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **proj(1)**
+ **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **proj(1)**, **projsync(1)**
Bugs
****
diff --git a/docs/source/apps/projsync.rst b/docs/source/apps/projsync.rst
new file mode 100644
index 00000000..9756e0c4
--- /dev/null
+++ b/docs/source/apps/projsync.rst
@@ -0,0 +1,160 @@
+.. _projsync:
+
+================================================================================
+projsync
+================================================================================
+
+.. Index:: projsync
+
+.. only:: html
+
+ .. versionadded:: 7.0.0
+
+ Tool for synchronizing PROJ datum and transformation support data.
+
+Synopsis
+********
+
+ | **projsync**
+ | [--endpoint URL]
+ | [--local-geojson-file FILENAME]
+ | ([--user-writable-directory] | [--system-directory] | [--target-dir DIRNAME])
+ | [--bbox west_long,south_lat,east_long,north_lat]
+ | [--spatial-test contains|intersects]
+ | [--source-id ID] [--area-of-use NAME]
+ | [--file NAME]
+ | [--all] [--exclude-world-coverage]
+ | [--quiet] [--dry-run] [--list-files]
+
+Description
+***********
+
+:program:`projsync` is a program that downloads remote resource files
+into a local directory. This is an alternative to downloading a proj-data-X.Y.Z
+archive file, or using the on-demand :ref:`networking capabilities <network>` of PROJ.
+
+The following control parameters can appear in any order:
+
+.. program:: projsync
+
+.. option:: --endpoint URL
+
+ Defines the URL where to download the master ``files.geojson`` file and then
+ the resource files. Defaults to the value set in :ref:`proj-ini`
+
+.. option:: --local-geojson-file FILENAME
+
+ Defines the filename for the master GeoJSON files that references resources.
+ Defaults to ${endpoint}/files.geojson
+
+.. option:: --user-writable-directory
+
+ Specifies that resource files must be downloaded in the
+ :ref:`user writable directory <user_writable_directory>`. This is the default.
+
+.. option:: --system-directory
+
+ Specifies that resource files must be downloaded in the
+ ${installation_prefix}/share/proj directory. The user launching projsync
+ should make sure it has writing rights in that directory.
+
+.. option:: --target-dir DIRNAME
+
+ Directory into which resource files must be downloaded.
+
+.. option:: --bbox west_long,south_lat,east_long,north_lat
+
+ Specify an area of interest to restrict the resources to download.
+ The area of interest is specified as a
+ bounding box with geographic coordinates, expressed in degrees in a
+ unspecified geographic CRS.
+ `west_long` and `east_long` should be in the [-180,180] range, and
+ `south_lat` and `north_lat` in the [-90,90]. `west_long` is generally lower than
+ `east_long`, except in the case where the area of interest crosses the antimeridian.
+
+.. option:: --spatial-test contains|intersects
+
+ Specify how the extent of the resource files
+ are compared to the area of use specified explicitly with :option:`--bbox`.
+ By default, any resource files whose extent intersects the value specified
+ by :option:`--bbox` will be selected.
+ If using the ``contains`` strategy, only resource files whose extent is
+ contained in the value specified by :option:`--bbox` will be selected.
+
+.. option:: --source-id ID
+
+ Restrict resource files to be downloaded to those whose source_id property
+ contains the ID value. Specifying ``?`` as ID will list all possible values.
+
+.. option:: --area-of-use NAME
+
+ Restrict resource files to be downloaded to those whose area_of_use property
+ contains the NAME value. Specifying ``?`` as NAME will list all possible values.
+
+.. option:: --file NAME
+
+ Restrict resource files to be downloaded to those whose name property
+ contains the NAME value. Specifying ``?`` as NAME will list all possible values.
+
+.. option:: --all
+
+ Ask to download all files.
+
+.. option:: --exclude-world-coverage
+
+ Exclude files which have world coverage.
+
+.. option:: -q / --quiet
+
+ Quiet mode
+
+.. option:: --dry-run
+
+ Simulate the behaviour of the tool without downloading resource files.
+
+.. option:: --list-files
+
+ List file names, with the source_id and area_of_use properties.
+
+
+At least one of :option:`--list-files`, :option:`--file`, :option:`--source-id`,
+:option:`--area-of-use`, :option:`--bbox` or :option:`--all` must be specified.
+
+Options :option:`--file`, :option:`--source-id`, :option:`--area-of-use` and
+:option:`--bbox` are combined with a AND logic.
+
+Examples
+********
+
+1. Download all resource files
+
+.. code-block:: console
+
+ projsync --all
+
+2. Download resource files covering specified point and attributing to an agency
+
+.. code-block:: console
+
+ projsync --source-id fr_ign --bbox 2,49,2,49
+
+
+.. only:: man
+
+ See also
+ ********
+
+ **cs2cs(1)**, **cct(1)**, **geod(1)**, **gie(1)**, **proj(1)**, **projinfo(1)**
+
+ Bugs
+ ****
+
+ A list of know bugs can be found at https://github.com/OSGeo/PROJ/issues
+ where new bug reports can be submitted to.
+ Bugs specific to resource files should be submitted to
+ https://github.com/OSGeo/PROJ-data/issues
+
+ Home page
+ *********
+
+ https://proj.org/
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f2fa3961..081b0a94 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -346,6 +346,13 @@ man_pages = [
['Even Rouault'],
1
),
+ (
+ 'apps/projsync',
+ 'projsync',
+ u'Downloading tool of resource files',
+ ['Even Rouault'],
+ 1
+ ),
]
# If true, show URL addresses after external links.
diff --git a/docs/source/usage/network.rst b/docs/source/usage/network.rst
index 295232b0..1b42d0aa 100644
--- a/docs/source/usage/network.rst
+++ b/docs/source/usage/network.rst
@@ -76,6 +76,11 @@ capability to download whole grids in the
:ref:`PROJ user writable directory <user_writable_directory>` by using the
:cpp:func:`proj_is_download_needed` and :cpp:func:`proj_download_file` functions.
+Download utility
+----------------
+
+:ref:`projsync` is a tool for downloading resource files.
+
Mirroring
---------
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index ee174da9..d624a893 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -281,6 +281,9 @@ osgeo::proj::datum::TemporalDatum::temporalOrigin() const
osgeo::proj::datum::VerticalReferenceFrame::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional<std::string> const&, osgeo::proj::util::optional<osgeo::proj::datum::RealizationMethod> const&)
osgeo::proj::datum::VerticalReferenceFrame::realizationMethod() const
osgeo::proj::datum::VerticalReferenceFrame::~VerticalReferenceFrame()
+osgeo::proj::File::~File()
+osgeo::proj::FileManager::open(projCtx_t*, char const*, osgeo::proj::FileAccess)
+osgeo::proj::File::read_line(unsigned long, bool&, bool&)
osgeo::proj::GenericShiftGrid::~GenericShiftGrid()
osgeo::proj::GenericShiftGrid::GenericShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&)
osgeo::proj::GenericShiftGrid::gridAt(double, double) const
@@ -742,6 +745,7 @@ pj_context_get_grid_cache_filename(projCtx_t*)
pj_context_get_url_endpoint(projCtx_t*)
pj_context_get_user_writable_directory(projCtx_t*, bool)
pj_context_is_network_enabled(projCtx_t*)
+pj_context_set_user_writable_directory(projCtx_t*, std::string const&)
pj_ctx_alloc
pj_ctx_fclose
pj_ctx_fgets
@@ -774,6 +778,7 @@ pj_get_datums_ref()
pj_get_def
pj_get_default_ctx
pj_get_default_fileapi
+pj_get_default_searchpaths(projCtx_t*)
pj_get_errno_ref
pj_get_release
pj_get_spheroid_defn
diff --git a/scripts/reformat_cpp.sh b/scripts/reformat_cpp.sh
index 20c32b29..89b8e4e6 100755
--- a/scripts/reformat_cpp.sh
+++ b/scripts/reformat_cpp.sh
@@ -16,9 +16,11 @@ esac
TOPDIR="$SCRIPT_DIR/.."
for i in "$TOPDIR"/include/proj/*.hpp "$TOPDIR"/include/proj/internal/*.hpp \
- "$TOPDIR"/src/iso19111/*.cpp "$TOPDIR"/test/unit/*.cpp "$TOPDIR"/src/apps/projinfo.cpp \
+ "$TOPDIR"/src/iso19111/*.cpp "$TOPDIR"/test/unit/*.cpp \
+ "$TOPDIR"/src/apps/projinfo.cpp "$TOPDIR"/src/apps/projsync.cpp \
"$TOPDIR"/src/tracing.cpp "$TOPDIR"/src/grids.hpp "$TOPDIR"/src/grids.cpp \
"$TOPDIR"/src/filemanager.hpp "$TOPDIR"/src/filemanager.cpp \
+ "$TOPDIR"/src/networkfilemanager.cpp \
"$TOPDIR"/src/sqlite3_utils.hpp "$TOPDIR"/src/sqlite3_utils.cpp ; do
if ! echo "$i" | grep -q "lru_cache.hpp"; then
"$SCRIPT_DIR"/reformat.sh "$i";
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8cf57306..534bc311 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -15,6 +15,8 @@ option(BUILD_PROJ
"Build proj (cartographic projection tool)" ON)
option(BUILD_PROJINFO
"Build projinfo (SRS and coordinate operation metadata/query tool)" ON)
+option(BUILD_PROJSYNC
+ "Build projsync (synchronize transformation support data)" ON)
if(NOT MSVC)
@@ -72,6 +74,15 @@ if(BUILD_GIE)
set(BIN_TARGETS ${BIN_TARGETS} gie)
endif()
+if(BUILD_PROJSYNC)
+ if(NOT ENABLE_CURL)
+ message(SEND_ERROR "projsync requires Curl")
+ endif()
+ include(bin_projsync.cmake)
+ set(BIN_TARGETS ${BIN_TARGETS} bin_projsync)
+endif()
+
+
if(MSVC OR CMAKE_CONFIGURATION_TYPES)
if(BIN_TARGETS)
# Add _d suffix for your debug versions of the tools
diff --git a/src/Makefile.am b/src/Makefile.am
index de35a754..291bc21e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,5 @@
AM_CFLAGS = @C_WFLAGS@
-bin_PROGRAMS = proj geod cs2cs gie cct projinfo
EXTRA_PROGRAMS = multistresstest test228
TESTS = geodtest
@@ -15,8 +14,11 @@ include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesi
EXTRA_DIST = bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \
bin_geod.cmake bin_proj.cmake bin_projinfo.cmake \
- lib_proj.cmake CMakeLists.txt bin_geodtest.cmake tests/geodtest.cpp \
- wkt1_grammar.y wkt2_grammar.y apps/emess.h apps/utils.h
+ lib_proj.cmake CMakeLists.txt bin_geodtest.cmake \
+ bin_projsync.cmake \
+ tests/geodtest.cpp \
+ wkt1_grammar.y wkt2_grammar.y apps/emess.h apps/utils.h \
+ apps/projsync.cpp
proj_SOURCES = apps/proj.cpp apps/emess.cpp apps/utils.cpp
projinfo_SOURCES = apps/projinfo.cpp
@@ -24,6 +26,14 @@ cs2cs_SOURCES = apps/cs2cs.cpp apps/emess.cpp apps/utils.cpp
cct_SOURCES = apps/cct.cpp apps/proj_strtod.cpp apps/proj_strtod.h apps/optargpm.h
geod_SOURCES = apps/geod.cpp apps/geod_set.cpp apps/geod_interface.cpp apps/geod_interface.h apps/emess.cpp
+if HAVE_CURL
+projsync_SOURCES = apps/projsync.cpp
+projsync_LDADD = libproj.la
+PROJSYNC_BIN = projsync
+endif
+
+bin_PROGRAMS = proj geod cs2cs gie cct projinfo $(PROJSYNC_BIN)
+
gie_SOURCES = apps/gie.cpp apps/proj_strtod.cpp apps/proj_strtod.h apps/optargpm.h
multistresstest_SOURCES = tests/multistresstest.cpp
test228_SOURCES = tests/test228.cpp
diff --git a/src/apps/projsync.cpp b/src/apps/projsync.cpp
new file mode 100644
index 00000000..3fab38e1
--- /dev/null
+++ b/src/apps/projsync.cpp
@@ -0,0 +1,555 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Downloader tool
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.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.
+ *****************************************************************************/
+
+//! @cond Doxygen_Suppress
+
+#define FROM_PROJ_CPP
+
+#include <cstdlib>
+#include <iostream>
+#include <limits>
+#include <set>
+#include <string>
+
+#include "filemanager.hpp"
+#include "proj.h"
+#include "proj_internal.h"
+
+#include "proj/internal/include_nlohmann_json.hpp"
+#include "proj/internal/internal.hpp"
+
+using json = nlohmann::json;
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+namespace {
+class ParsingException : public std::exception {
+ std::string msg_;
+
+ public:
+ ParsingException(const char *msg) : msg_(msg) {}
+ const char *what() const noexcept override { return msg_.c_str(); }
+};
+}
+
+// ---------------------------------------------------------------------------
+
+static void usage() {
+ std::cerr << "usage: projsync " << std::endl;
+ std::cerr << " [--endpoint URL]" << std::endl;
+ std::cerr << " [--local-geojson-file FILENAME]" << std::endl;
+ std::cerr << " ([--user-writable-directory] | "
+ "[--system-directory] | [--target-dir DIRNAME])"
+ << std::endl;
+ std::cerr << " [--bbox west_long,south_lat,east_long,north_lat]"
+ << std::endl;
+ std::cerr << " [--spatial-test contains|intersects]" << std::endl;
+ std::cerr << " [--source-id ID] [--area-of-use NAME]" << std::endl;
+ std::cerr << " [--file NAME]" << std::endl;
+ std::cerr << " [--all] [--exclude-world-coverage]" << std::endl;
+ std::cerr << " [--quiet] [--dry-run] [--list-files]" << std::endl;
+ std::exit(1);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<double> get_bbox(const json &j) {
+ std::vector<double> res;
+ if (j.size() == 2 && j[0].is_number() && j[1].is_number()) {
+ res.push_back(j[0].get<double>());
+ res.push_back(j[1].get<double>());
+ res.push_back(j[0].get<double>());
+ res.push_back(j[1].get<double>());
+ } else {
+ for (const auto &obj : j) {
+ if (obj.is_array()) {
+ const auto subres = get_bbox(obj);
+ if (subres.size() == 4) {
+ if (res.empty()) {
+ res = subres;
+ } else {
+ res[0] = std::min(res[0], subres[0]);
+ res[1] = std::min(res[1], subres[1]);
+ res[2] = std::max(res[2], subres[2]);
+ res[3] = std::max(res[3], subres[3]);
+ }
+ }
+ }
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+int main(int argc, char *argv[]) {
+ auto ctx = pj_get_default_ctx();
+
+ std::string targetDir;
+ std::string endpoint(pj_context_get_url_endpoint(ctx));
+ const std::string geojsonFile("files.geojson");
+ std::string queriedSourceId;
+ std::string queriedAreaOfUse;
+ bool listFiles = false;
+ bool dryRun = false;
+ bool hasQueriedBbox = false;
+ double queried_west = 0.0;
+ double queried_south = 0.0;
+ double queried_east = 0.0;
+ double queried_north = 0.0;
+ bool intersects = true;
+ bool quiet = false;
+ bool includeWorldCoverage = true;
+ bool queryAll = false;
+ std::string queriedFilename;
+ std::string files_geojson_local;
+
+ for (int i = 1; i < argc; i++) {
+ std::string arg(argv[i]);
+ if (arg == "--endpoint" && i + 1 < argc) {
+ i++;
+ endpoint = argv[i];
+ } else if (arg == "--user-writable-directory") {
+ // do nothing
+ } else if (arg == "--system-directory") {
+ targetDir = PROJ_LIB;
+ } else if (arg == "--target-dir" && i + 1 < argc) {
+ i++;
+ targetDir = argv[i];
+ } else if (arg == "--local-geojson-file" && i + 1 < argc) {
+ i++;
+ files_geojson_local = argv[i];
+ } else if (arg == "--list-files") {
+ listFiles = true;
+ } else if (arg == "--source-id" && i + 1 < argc) {
+ i++;
+ queriedSourceId = argv[i];
+ } else if (arg == "--area-of-use" && i + 1 < argc) {
+ i++;
+ queriedAreaOfUse = argv[i];
+ } else if (arg == "--file" && i + 1 < argc) {
+ i++;
+ queriedFilename = argv[i];
+ } 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 {
+ queried_west = c_locale_stod(bbox[0]);
+ queried_south = c_locale_stod(bbox[1]);
+ queried_east = c_locale_stod(bbox[2]);
+ queried_north = c_locale_stod(bbox[3]);
+ } catch (const std::exception &e) {
+ std::cerr << "Invalid value for option --bbox: " << bboxStr
+ << ", " << e.what() << std::endl;
+ usage();
+ }
+ if (queried_west > 180 && queried_east > queried_west) {
+ queried_west -= 360;
+ queried_east -= 360;
+ } else if (queried_west < -180 && queried_east > queried_west) {
+ queried_west += 360;
+ queried_east += 360;
+ } else if (fabs(queried_west) < 180 && fabs(queried_east) < 180 &&
+ queried_east < queried_west) {
+ queried_east += 360;
+ }
+ hasQueriedBbox = true;
+ } else if (arg == "--spatial-test" && i + 1 < argc) {
+ i++;
+ const std::string value(argv[i]);
+ if (ci_equal(value, "contains")) {
+ intersects = false;
+ } else if (ci_equal(value, "intersects")) {
+ intersects = true;
+ } else {
+ std::cerr << "Unrecognized value for option --spatial-test: "
+ << value << std::endl;
+ usage();
+ }
+ } else if (arg == "--dry-run") {
+ dryRun = true;
+ } else if (arg == "--exclude-world-coverage") {
+ includeWorldCoverage = false;
+ } else if (arg == "--all") {
+ queryAll = true;
+ } else if (arg == "-q" || arg == "--quiet") {
+ quiet = true;
+ } else {
+ usage();
+ }
+ }
+ if (!listFiles && queriedFilename.empty() && queriedSourceId.empty() &&
+ queriedAreaOfUse.empty() && !hasQueriedBbox && !queryAll) {
+ std::cerr << "At least one of --list-files, --file, --source-id, "
+ "--area-of-use, --bbox or --all must be specified."
+ << std::endl
+ << std::endl;
+ usage();
+ }
+
+ if (targetDir.empty()) {
+ targetDir = pj_context_get_user_writable_directory(ctx, true);
+ } else {
+ if (targetDir.back() == '/') {
+ targetDir.resize(targetDir.size() - 1);
+ }
+
+ // This is used by projsync() to determine where to write files.
+ pj_context_set_user_writable_directory(ctx, targetDir);
+ }
+
+ if (!endpoint.empty() && endpoint.back() == '/') {
+ endpoint.resize(endpoint.size() - 1);
+ }
+
+ if (!quiet) {
+ std::cout << "Downloading from " << endpoint << " into " << targetDir
+ << std::endl;
+ }
+
+ proj_context_set_enable_network(ctx, true);
+ if (files_geojson_local.empty()) {
+ const std::string files_geojson_url(endpoint + '/' + geojsonFile);
+ if (!proj_download_file(ctx, files_geojson_url.c_str(), false, nullptr,
+ nullptr)) {
+ std::cerr << "Cannot download " << geojsonFile << std::endl;
+ std::exit(1);
+ }
+
+ files_geojson_local = targetDir + '/' + geojsonFile;
+ }
+
+ auto file = NS_PROJ::FileManager::open(ctx, files_geojson_local.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (!file) {
+ std::cerr << "Cannot open " << files_geojson_local << std::endl;
+ std::exit(1);
+ }
+
+ std::string text;
+ while (true) {
+ bool maxLenReached = false;
+ bool eofReached = false;
+ text += file->read_line(1000000, maxLenReached, eofReached);
+ if (maxLenReached) {
+ std::cerr << "Error while parsing " << geojsonFile
+ << " : too long line" << std::endl;
+ std::exit(1);
+ }
+ if (eofReached)
+ break;
+ }
+ file.reset();
+
+ if (listFiles) {
+ std::cout << "filename,source_id,area_of_use,file_size" << std::endl;
+ }
+
+ try {
+ const auto j = json::parse(text);
+ bool foundMatchSourceIdCriterion = false;
+ std::set<std::string> source_ids;
+ bool foundMatchAreaOfUseCriterion = false;
+ std::set<std::string> areas_of_use;
+ bool foundMatchFileCriterion = false;
+ std::set<std::string> files;
+ if (!j.is_object() || !j.contains("features")) {
+ throw ParsingException("no features member");
+ }
+ std::vector<std::string> to_download;
+ unsigned long long total_size_to_download = 0;
+ for (const auto feat : j["features"]) {
+ if (!feat.is_object()) {
+ continue;
+ }
+ if (!feat.contains("properties")) {
+ continue;
+ }
+ const auto properties = feat["properties"];
+ if (!properties.is_object()) {
+ continue;
+ }
+
+ if (!properties.contains("name")) {
+ continue;
+ }
+ const auto j_name = properties["name"];
+ if (!j_name.is_string()) {
+ continue;
+ }
+ const auto name(j_name.get<std::string>());
+ files.insert(name);
+
+ if (!properties.contains("source_id")) {
+ continue;
+ }
+ const auto j_source_id = properties["source_id"];
+ if (!j_source_id.is_string()) {
+ continue;
+ }
+ const auto source_id(j_source_id.get<std::string>());
+ source_ids.insert(source_id);
+
+ std::string area_of_use;
+ if (properties.contains("area_of_use")) {
+ const auto j_area_of_use = properties["area_of_use"];
+ if (j_area_of_use.is_string()) {
+ area_of_use = j_area_of_use.get<std::string>();
+ areas_of_use.insert(area_of_use);
+ }
+ }
+
+ unsigned long long file_size = 0;
+ if (properties.contains("file_size")) {
+ const auto j_file_size = properties["file_size"];
+ if (j_file_size.type() == json::value_t::number_unsigned) {
+ file_size = j_file_size.get<unsigned long long>();
+ }
+ }
+
+ if (listFiles) {
+ std::cout << name << "," << area_of_use << "," << source_id
+ << "," << file_size << std::endl;
+ continue;
+ }
+
+ const bool matchSourceId =
+ queryAll || queriedSourceId.empty() ||
+ source_id.find(queriedSourceId) != std::string::npos;
+ if (!queriedSourceId.empty() &&
+ source_id.find(queriedSourceId) != std::string::npos) {
+ foundMatchSourceIdCriterion = true;
+ }
+
+ const bool matchAreaOfUse =
+ queryAll || queriedAreaOfUse.empty() ||
+ area_of_use.find(queriedAreaOfUse) != std::string::npos;
+ if (!queriedAreaOfUse.empty() &&
+ area_of_use.find(queriedAreaOfUse) != std::string::npos) {
+ foundMatchAreaOfUseCriterion = true;
+ }
+
+ const bool matchFile =
+ queryAll || queriedFilename.empty() ||
+ name.find(queriedFilename) != std::string::npos;
+ if (!queriedFilename.empty() &&
+ name.find(queriedFilename) != std::string::npos) {
+ foundMatchFileCriterion = true;
+ }
+
+ bool matchBbox = true;
+ if (queryAll || hasQueriedBbox) {
+ matchBbox = false;
+ do {
+ if (!feat.contains("geometry")) {
+ if (queryAll) {
+ matchBbox = true;
+ }
+ break;
+ }
+ const auto j_geometry = feat["geometry"];
+ if (!j_geometry.is_object()) {
+ if (queryAll) {
+ matchBbox = true;
+ }
+ break;
+ }
+ if (!j_geometry.contains("coordinates")) {
+ break;
+ }
+ const auto j_coordinates = j_geometry["coordinates"];
+ if (!j_coordinates.is_array()) {
+ break;
+ }
+ if (!j_geometry.contains("type")) {
+ break;
+ }
+ const auto j_geometry_type = j_geometry["type"];
+ if (!j_geometry_type.is_string()) {
+ break;
+ }
+ const auto geometry_type(
+ j_geometry_type.get<std::string>());
+ std::vector<double> grid_bbox;
+ if (geometry_type == "MultiPolygon") {
+ std::vector<std::vector<double>> grid_bboxes;
+ bool foundMinus180 = false;
+ bool foundPlus180 = false;
+ for (const auto obj : j_coordinates) {
+ if (obj.is_array()) {
+ const auto tmp = get_bbox(obj);
+ if (tmp.size() == 4) {
+ if (tmp[0] == -180)
+ foundMinus180 = true;
+ else if (tmp[2] == 180)
+ foundPlus180 = true;
+ grid_bboxes.push_back(tmp);
+ }
+ }
+ }
+ for (auto &bbox : grid_bboxes) {
+ if (foundMinus180 && foundPlus180 &&
+ bbox[0] == -180) {
+ bbox[0] = 180;
+ bbox[2] += 360;
+ }
+ if (grid_bbox.empty()) {
+ grid_bbox = bbox;
+ } else {
+ grid_bbox[0] = std::min(grid_bbox[0], bbox[0]);
+ grid_bbox[1] = std::min(grid_bbox[1], bbox[1]);
+ grid_bbox[2] = std::max(grid_bbox[2], bbox[2]);
+ grid_bbox[3] = std::max(grid_bbox[3], bbox[3]);
+ }
+ }
+ } else {
+ grid_bbox = get_bbox(j_coordinates);
+ }
+ if (grid_bbox.size() != 4) {
+ break;
+ }
+ double grid_w = grid_bbox[0];
+ const double grid_s = grid_bbox[1];
+ double grid_e = grid_bbox[2];
+ const double grid_n = grid_bbox[3];
+ if (grid_e - grid_w > 359 && grid_n - grid_s > 179) {
+ if (!includeWorldCoverage) {
+ break;
+ }
+ grid_w = -std::numeric_limits<double>::max();
+ grid_e = std::numeric_limits<double>::max();
+ } else if (grid_e > 180 && queried_west < -180) {
+ grid_w -= 360;
+ grid_e -= 360;
+ }
+ if (queryAll) {
+ matchBbox = true;
+ break;
+ }
+
+ if (intersects) {
+ if (queried_west < grid_e && grid_w < queried_east &&
+ queried_south < grid_n && grid_s < queried_north) {
+ matchBbox = true;
+ }
+ } else {
+ if (grid_w >= queried_west && grid_s >= queried_south &&
+ grid_e <= queried_east && grid_n <= queried_north) {
+ matchBbox = true;
+ }
+ }
+
+ } while (false);
+ }
+
+ if (matchFile & matchSourceId && matchAreaOfUse && matchBbox) {
+ const std::string resource_url(endpoint + '/' + name);
+ if (proj_is_download_needed(ctx, resource_url.c_str(), false)) {
+ total_size_to_download += file_size;
+ to_download.push_back(resource_url);
+ } else {
+ if (!quiet) {
+ std::cout << resource_url << " already downloaded."
+ << std::endl;
+ }
+ }
+ }
+ }
+
+ if (!quiet && total_size_to_download > 0) {
+ if (total_size_to_download > 1024 * 1024)
+ std::cout << "Total size to download: "
+ << total_size_to_download / (1024 * 1024) << " MB"
+ << std::endl;
+ else
+ std::cout << "Total to download: " << total_size_to_download
+ << " bytes" << std::endl;
+ }
+ for (size_t i = 0; i < to_download.size(); ++i) {
+ const auto &url = to_download[i];
+ if (!quiet) {
+ if (dryRun) {
+ std::cout << "Would download ";
+ } else {
+ std::cout << "Downloading ";
+ }
+ std::cout << url << "... (" << i + 1 << " / "
+ << to_download.size() << ")" << std::endl;
+ }
+ if (!dryRun &&
+ !proj_download_file(ctx, url.c_str(), false, nullptr,
+ nullptr)) {
+ std::cerr << "Cannot download " << url << std::endl;
+ std::exit(1);
+ }
+ }
+
+ if (!queriedSourceId.empty() && !foundMatchSourceIdCriterion) {
+ std::cerr << "Warning: '" << queriedSourceId
+ << "' is a unknown value for --source-id." << std::endl;
+ std::cerr << "Known values are:" << std::endl;
+ for (const auto &v : source_ids) {
+ std::cerr << " " << v << std::endl;
+ }
+ std::exit(1);
+ }
+
+ if (!queriedAreaOfUse.empty() && !foundMatchAreaOfUseCriterion) {
+ std::cerr << "Warning: '" << queriedAreaOfUse
+ << "' is a unknown value for --area-of-use." << std::endl;
+ std::cerr << "Known values are:" << std::endl;
+ for (const auto &v : areas_of_use) {
+ std::cerr << " " << v << std::endl;
+ }
+ std::exit(1);
+ }
+
+ if (!queriedFilename.empty() && !foundMatchFileCriterion) {
+ std::cerr << "Warning: '" << queriedFilename
+ << "' is a unknown value for --file." << std::endl;
+ std::cerr << "Known values are:" << std::endl;
+ for (const auto &v : files) {
+ std::cerr << " " << v << std::endl;
+ }
+ std::exit(1);
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "Error: " << e.what() << std::endl;
+ std::exit(1);
+ }
+
+ return 0;
+}
+
+//! @endcond
diff --git a/src/bin_projsync.cmake b/src/bin_projsync.cmake
new file mode 100644
index 00000000..07891730
--- /dev/null
+++ b/src/bin_projsync.cmake
@@ -0,0 +1,16 @@
+set(PROJSYNC_SRC apps/projsync.cpp)
+
+source_group("Source Files\\Bin" FILES ${PROJSYNC_SRC})
+
+add_executable(bin_projsync ${PROJSYNC_SRC})
+set_target_properties(bin_projsync
+ PROPERTIES
+ OUTPUT_NAME projsync)
+target_link_libraries(bin_projsync ${PROJ_LIBRARIES})
+target_compile_options(bin_projsync PRIVATE ${PROJ_CXX_WARN_FLAGS})
+install(TARGETS bin_projsync
+ RUNTIME DESTINATION ${BINDIR})
+
+if(MSVC AND BUILD_LIBPROJ_SHARED)
+ target_compile_definitions(bin_projsync PRIVATE PROJ_MSVC_DLL_IMPORT=1)
+endif()
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
index 17e09b21..aea9b11d 100644
--- a/src/filemanager.cpp
+++ b/src/filemanager.cpp
@@ -1178,6 +1178,8 @@ static void CreateDirectoryRecursively(PJ_CONTEXT *ctx,
std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
bool create) {
+ if (!ctx)
+ ctx = pj_get_default_ctx();
if (ctx->user_writable_directory.empty()) {
// For testing purposes only
const char *env_var_PROJ_USER_WRITABLE_DIRECTORY =
@@ -1234,6 +1236,15 @@ std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
// ---------------------------------------------------------------------------
+void pj_context_set_user_writable_directory(PJ_CONTEXT *ctx,
+ const std::string &path) {
+ if (!ctx)
+ ctx = pj_get_default_ctx();
+ ctx->user_writable_directory = path;
+}
+
+// ---------------------------------------------------------------------------
+
#ifdef WIN32
static const char dir_chars[] = "/\\";
#else
diff --git a/src/filemanager.hpp b/src/filemanager.hpp
index bbd12b7e..9446a0bc 100644
--- a/src/filemanager.hpp
+++ b/src/filemanager.hpp
@@ -53,8 +53,8 @@ class FileManager {
public:
// "Low-level" interface.
- static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
- FileAccess access);
+ static PROJ_DLL std::unique_ptr<File>
+ open(PJ_CONTEXT *ctx, const char *filename, FileAccess access);
static bool exists(PJ_CONTEXT *ctx, const char *filename);
static bool mkdir(PJ_CONTEXT *ctx, const char *filename);
static bool unlink(PJ_CONTEXT *ctx, const char *filename);
@@ -81,14 +81,15 @@ class File {
explicit File(const std::string &name);
public:
- virtual ~File();
+ virtual PROJ_DLL ~File();
virtual size_t read(void *buffer, size_t sizeBytes) = 0;
virtual size_t write(const void *buffer, size_t sizeBytes) = 0;
virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0;
virtual unsigned long long tell() = 0;
virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
virtual bool hasChanged() const = 0;
- std::string read_line(size_t maxLen, bool &maxLenReached, bool &eofReached);
+ std::string PROJ_DLL read_line(size_t maxLen, bool &maxLenReached,
+ bool &eofReached);
const std::string &name() const { return name_; }
};
@@ -99,7 +100,8 @@ std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT *ctx,
const char *filename);
NS_PROJ_END
-std::vector<std::string> pj_get_default_searchpaths(PJ_CONTEXT *ctx);
+// Exported for projsync
+std::vector<std::string> PROJ_DLL pj_get_default_searchpaths(PJ_CONTEXT *ctx);
//! @endcond Doxygen_Suppress
diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp
index c4cb41d9..d252fd84 100644
--- a/src/networkfilemanager.cpp
+++ b/src/networkfilemanager.cpp
@@ -193,7 +193,7 @@ class DiskChunkCache {
std::unique_ptr<SQLite3VFS> vfs_{};
explicit DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path);
-
+
bool initialize();
void commitAndClose();
@@ -552,7 +552,8 @@ bool DiskChunkCache::checkConsistency() {
void DiskChunkCache::commitAndClose() {
if (hDB_) {
- if( sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr) != SQLITE_OK ) {
+ if (sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr) !=
+ SQLITE_OK) {
pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
}
sqlite3_close(hDB_);
@@ -562,9 +563,7 @@ void DiskChunkCache::commitAndClose() {
// ---------------------------------------------------------------------------
-DiskChunkCache::~DiskChunkCache() {
- commitAndClose();
-}
+DiskChunkCache::~DiskChunkCache() { commitAndClose(); }
// ---------------------------------------------------------------------------
@@ -1336,7 +1335,8 @@ std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
// ---------------------------------------------------------------------------
-std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT* ctx, const char* filename) {
+std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT *ctx,
+ const char *filename) {
return NetworkFile::open(ctx, filename);
}
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 69d583fc..7a777027 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -870,7 +870,10 @@ void pj_load_ini(PJ_CONTEXT* ctx);
// Exported for testing purposes only
std::string PROJ_DLL pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx);
+
+// For use by projsync
std::string PROJ_DLL pj_context_get_user_writable_directory(PJ_CONTEXT *ctx, bool create);
+void PROJ_DLL pj_context_set_user_writable_directory(PJ_CONTEXT* ctx, const std::string& path);
/* classic public API */
#include "proj_api.h"
diff --git a/test/cli/CMakeLists.txt b/test/cli/CMakeLists.txt
index feef5bf0..cf4192d7 100644
--- a/test/cli/CMakeLists.txt
+++ b/test/cli/CMakeLists.txt
@@ -5,6 +5,7 @@ set(CS2CS_BIN "cs2cs")
set(PROJ_BIN "proj")
set(PROJINFO_BIN "projinfo")
set(CCT_BIN "cct")
+set(PROJSYNC_BIN "projsync")
proj_add_test_script_sh("test27" PROJ_BIN)
proj_add_test_script_sh("test83" PROJ_BIN)
proj_add_test_script_sh("testproj" PROJ_BIN)
@@ -14,3 +15,6 @@ proj_add_test_script_sh("testIGNF" CS2CS_BIN "ntf_r93.gsb")
proj_add_test_script_sh("testntv2" CS2CS_BIN "ntv2_0.gsb")
proj_add_test_script_sh("testprojinfo" PROJINFO_BIN)
proj_add_test_script_sh("testcct" CCT_BIN)
+if(BUILD_PROJSYNC_DATA)
+proj_add_test_script_sh("test_projsync" PROJSYNC_BIN)
+endif()
diff --git a/test/cli/Makefile.am b/test/cli/Makefile.am
index 417ec137..7ff42016 100644
--- a/test/cli/Makefile.am
+++ b/test/cli/Makefile.am
@@ -6,6 +6,7 @@ PROJEXE = $(EXEPATH)/proj
CS2CSEXE = $(EXEPATH)/cs2cs
PROJINFOEXE = $(EXEPATH)/projinfo
CCTEXE = $(EXEPATH)/cct
+PROJSYNC_EXE = $(EXEPATH)/projsync
# PROJ.4 test scripts
TEST27 = $(THIS_DIR)/test27
@@ -18,6 +19,7 @@ TESTDATUMFILE = $(THIS_DIR)/testdatumfile
TESTIGN = $(THIS_DIR)/testIGNF
TESTPROJINFO = $(THIS_DIR)/testprojinfo
TESTCCT = $(THIS_DIR)/testcct
+TESTPROJSYNC = $(THIS_DIR)/test_projsync
EXTRA_DIST = pj_out27.dist pj_out83.dist td_out.dist \
test27 test83 tv_out.dist tf_out.dist \
@@ -26,6 +28,7 @@ EXTRA_DIST = pj_out27.dist pj_out83.dist td_out.dist \
testprojinfo testprojinfo_out.dist \
testcct testcct_out.dist \
testproj testproj_out.dist \
+ test_projsync \
CMakeLists.txt
testprojinfo-check:
@@ -61,4 +64,13 @@ testntv2-check:
testcct-check:
PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTCCT) $(CCTEXE)
-check-local: testprojinfo-check test27-check test83-check testproj-check testvarious-check testdatumfile-check testign-check testntv2-check testcct-check
+
+if HAVE_CURL
+testprojsync-check:
+ PROJ_LIB=$(PROJ_LIB) $(TESTPROJSYNC) $(PROJSYNC_EXE)
+else
+testprojsync-check:
+ echo "Skipping testprojsync-check"
+endif
+
+check-local: testprojinfo-check test27-check test83-check testproj-check testvarious-check testdatumfile-check testign-check testntv2-check testcct-check testprojsync-check
diff --git a/test/cli/test_projsync b/test/cli/test_projsync
new file mode 100755
index 00000000..0292ceaa
--- /dev/null
+++ b/test/cli/test_projsync
@@ -0,0 +1,194 @@
+:
+# Test projsync
+
+TEST_CLI_DIR=`dirname $0`
+EXE=$1
+
+usage()
+{
+ echo "Usage: ${0} <path to 'projsync' program>"
+ echo
+ exit 1
+}
+
+if test -z "${EXE}"; then
+ EXE=../../src/projsync
+fi
+
+if test ! -x ${EXE}; then
+ echo "*** ERROR: Can not find '${EXE}' program!"
+ exit 1
+fi
+
+echo "============================================"
+echo "Running ${0} using ${EXE}:"
+echo "============================================"
+
+if ! curl -s https://cdn.proj.org/files.geojson >/dev/null 2>/dev/null; then
+ if ! wget https://cdn.proj.org/files.geojson -O /dev/null 2>/dev/null; then
+ echo "Cannot download https://cdn.proj.org/files.geojson. Skipping test"
+ exit 0
+ fi
+fi
+
+TMP_OUT=test_projsync_out.txt
+
+rm -rf tmp_user_writable_directory
+mkdir -p tmp_user_writable_directory
+
+echo "Testing $EXE --list-files --target-dir tmp_user_writable_directory"
+if ! $EXE --list-files --target-dir tmp_user_writable_directory > ${TMP_OUT}; then
+ echo "--list-files failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "at_bev_README.txt,,at_bev" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+export PROJ_USER_WRITABLE_DIRECTORY=tmp_user_writable_directory
+
+if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/cache.db; then
+ echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/cache.db!"
+ exit 100
+fi
+if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/files.geojson; then
+ echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/files.geojson!"
+ exit 100
+fi
+
+echo "Testing $EXE --all"
+if ! $EXE --all --dry-run > ${TMP_OUT}; then
+ echo "--all failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "Would download https://cdn.proj.org/at_bev_README.txt" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --source-id fr_ign"
+if ! $EXE --source-id fr_ign --dry-run > ${TMP_OUT}; then
+ echo "--source-id fr_ign failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "fr_ign_README.txt" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --source-id non_existing"
+if $EXE --source-id non_existing >${TMP_OUT} 2>&1 ; then
+ echo "--source-id non_existing failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --source-id" >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "fr_ign" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --area-of-use France"
+if ! $EXE --area-of-use France --dry-run > ${TMP_OUT}; then
+ echo "--area-of-use France failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --area-of-use non_existing"
+if $EXE --area-of-use non_existing >${TMP_OUT} 2>&1 ; then
+ echo "--area-of-use non_existing failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --area-of-use." >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "Australia" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --file ntf_r93"
+if ! $EXE --file ntf_r93 > ${TMP_OUT}; then
+ echo "--file ntf_r93 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "Downloading https://cdn.proj.org/fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif; then
+ echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif!"
+ exit 100
+fi
+
+echo "Testing $EXE --file ntf_r93 a second time"
+if ! $EXE --file ntf_r93 > ${TMP_OUT}; then
+ echo "--file ntf_r93 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "https://cdn.proj.org/fr_ign_ntf_r93.tif already downloaded" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+rm -f ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif
+
+
+echo "Testing $EXE --file non_existing"
+if $EXE --file non_existing >${TMP_OUT} 2>&1 ; then
+ echo "--file non_existing failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --file." >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --bbox 2,49,3,50"
+if ! $EXE --bbox 2,49,3,50 --dry-run > ${TMP_OUT}; then
+ echo "--bbox 2,49,3,50 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "us_nga_egm96_15.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --bbox 2,49,3,50 --exclude-world-coverage"
+if ! $EXE --bbox 2,49,3,50 --exclude-world-coverage --dry-run > ${TMP_OUT}; then
+ echo "--bbox 2,49,3,50 --exclude-world-coverage failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+if cat ${TMP_OUT} | grep "us_nga_egm96_15.tif" >/dev/null; then
+ cat ${TMP_OUT}
+ exit 100
+fi
+
+
+echo "Testing $EXE --bbox 170,-90,-170,90"
+if ! $EXE --bbox 170,-90,-170,90 --dry-run > ${TMP_OUT}; then
+ echo "--bbox 170,-90,-170,90 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --bbox 170,-90,190,90"
+if ! $EXE --bbox 170,-90,190,90 --dry-run > ${TMP_OUT}; then
+ echo "--bbox 170,-90,190,90 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+
+echo "Testing $EXE --bbox -190,-90,-170,90"
+if ! $EXE --bbox -190,-90,-170,90 --dry-run > ${TMP_OUT}; then
+ echo "--bbox -190,-90,-170,90 failed"
+ cat ${TMP_OUT}
+ exit 100
+fi
+cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100)
+
+rm -rf ${PROJ_USER_WRITABLE_DIRECTORY}
+rm ${TMP_OUT}
+
+exit 0