Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add transformation grid sync API/CLI #648

Merged
merged 5 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ test-coverage: ## run tests and generate coverage report
py.test --cov-report term-missing --cov=pyproj -v -s

install-docs: ## Install requirements for building documentation
pip install sphinx sphinx_rtd_theme
pip install sphinx sphinx_rtd_theme sphinx-argparse

docs: ## generate Sphinx HTML documentation, including API docs
$(MAKE) -C docs clean
Expand Down
1 change: 1 addition & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ API Documentation
proj
list
datadir
sync
enums
exceptions
show_versions
19 changes: 19 additions & 0 deletions docs/api/sync.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Sync Transformation Grids
=========================

pyproj.sync.get_transform_grid_list
------------------------------------

.. autofunction:: pyproj.sync.get_transform_grid_list


pyproj.sync.BBox
-----------------

.. autoclass:: pyproj.sync.BBox
:members:

pyproj.sync.get_proj_endpoint
------------------------------

.. autofunction:: pyproj.sync.get_proj_endpoint
6 changes: 6 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CLI
====

.. argparse::
:ref: pyproj.__main__.parser
:prog: pyproj
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
"sphinxarg.ext",
]

# Add any paths that contain templates here, relative to this directory.
Expand Down
5 changes: 3 additions & 2 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ Change Log
* Refactor Proj to inherit from Transformer (issue #624)
* ENH: Support obects with '__array__' method (pandas.Series, xarray.DataArray, dask.array.Array) (issue #573)
* ENH: Added :func:`pyproj.datadir.get_user_data_dir` (pull #636)
* ENH: Add network methods to Transformer (issue #629)
* ENH: Added network methods to Transformer (issue #629)
* ENH: Use 'proj_get_units_from_database' in :func:`pyproj.get_units_map` & cleanup :func:`pyproj.get_codes` (issue #619)
* ENH: Add support for radians for Proj & Transformer.from_pipeline & use less gil (issue #612)
* ENH: Added support for radians for Proj & Transformer.from_pipeline & use less gil (issue #612)
* ENH: Datum.from_name default to check all datum types (issue #606)
* ENH: Use from_user_input in __eq__ when comparing CRS sub-classes (i.e. PrimeMeridian, Datum, Ellipsoid, etc.) (issue #606)
* ENH: Added :meth:`pyproj.transformer.TransformerGroup.download_grids` (pull #643)
* ENH: Added transformation grid sync API/CLI (issue #572)

2.6.1
~~~~~
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GitHub Repository: https://github.com/pyproj4/pyproj
advanced_examples
build_crs
api/index
cli
optimize_transformations
crs_compatibility
history
Expand Down
2 changes: 2 additions & 0 deletions docs/transformation_grids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Available methods for download include:

- The `projsync <https://proj.org/apps/projsync.html>`__ command line program.

- `pyproj sync <cli.html#sync>`__ command line program (pyproj 3+; useful if you use pyproj wheels).

- Enabling `PROJ network <https://proj.org/usage/network.html>`__ capabilities.

.. note:: You can use the `network` kwarg when initializing
Expand Down
188 changes: 180 additions & 8 deletions pyproj/__main__.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,196 @@
"""
This is the main entry point for pyproj
This is the main entry point for pyproj CLI

e.g. python -m pyproj

"""

import argparse
import os

from pyproj import __proj_version__, __version__, _show_versions
from pyproj.datadir import get_data_dir, get_user_data_dir
from pyproj.sync import (
BBox,
_download_resource_file,
get_proj_endpoint,
get_transform_grid_list,
)

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
description=f"pyproj version: {__version__} [PROJ version: {__proj_version__}]"
)
parser.add_argument(
"-v",
"--verbose",
help="Show verbose debugging version information.",
action="store_true",
)
args = parser.parse_args()
if args.verbose:
_show_versions.show_versions()
else:
print(f"pyproj version: {__version__} [PROJ version: {__proj_version__}]")
parser.print_help()
subparsers = parser.add_subparsers(title="commands")
sync_parser = subparsers.add_parser(
name="sync",
description="Tool for synchronizing PROJ datum and transformation support data.",
)
sync_parser.add_argument(
"--bbox",
help=(
"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 an "
"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."
),
)
sync_parser.add_argument(
"--spatial-test",
help=(
"Specify how the extent of the resource files "
"are compared to the area of use specified explicitly with `--bbox`. "
"By default, any resource files whose extent intersects the value specified "
"by `--bbox` will be selected. If using the ``contains`` strategy, "
"only resource files whose extent is contained in the value specified by "
"`--bbox` will be selected."
),
choices=["intersects", "contains"],
default="intersects",
)
sync_parser.add_argument(
"--source-id",
help=(
"Restrict resource files to be downloaded to those whose source_id property "
"contains the ID value. Default is all possible values."
),
)
sync_parser.add_argument(
"--area-of-use",
help=(
"Restrict resource files to be downloaded to those whose area_of_use property "
"contains the AREA_OF_USE value. Default is all possible values."
),
)
sync_parser.add_argument(
"--file",
help=(
"Restrict resource files to be downloaded to those whose name property "
" (file name) contains the FILE value. Default is all possible values."
),
)
sync_parser.add_argument(
"--exclude-world-coverage",
help="Exclude files which have world coverage.",
action="store_true",
)
sync_parser.add_argument(
"--include-already-downloaded",
help="Include grids that are already downloaded.",
action="store_true",
)
sync_parser.add_argument(
"--list-files", help="List the files without downloading.", action="store_true",
)
sync_parser.add_argument(
"--all", help="Download all missing transform grids.", action="store_true",
)
sync_parser.add_argument(
"--system-directory",
help=(
"If enabled, it will sync grids to the main PROJ data directory "
"instead of the user writable directory."
),
action="store_true",
)
sync_parser.add_argument(
"--target-directory",
help="The directory to sync grids to instead of the user writable directory.",
)
sync_parser.add_argument(
"-v", "--verbose", help="Print download information.", action="store_true",
)


def main():
args = parser.parse_args()
if hasattr(args, "bbox") and any(
(
args.bbox,
args.list_files,
args.all,
args.source_id,
args.area_of_use,
args.file,
)
):
if args.all and (
args.list_files
or args.source_id
or args.area_of_use
or args.file
or args.bbox
):
raise RuntimeError(
"Cannot use '--all' with '--list-files', '--source-id',"
"'--area-of-use', '--bbox', or '--file'."
)
bbox = None
if args.bbox is not None:
west, south, east, north = args.bbox.split(",")
bbox = BBox(
west=float(west),
south=float(south),
east=float(east),
north=float(north),
)
if args.target_directory and args.system_directory:
raise RuntimeError(
"Cannot set both --target-directory and --system-directory."
)
target_directory = args.target_directory
if args.system_directory:
target_directory = get_data_dir().split(os.path.sep)[0]
elif not target_directory:
target_directory = get_user_data_dir(True)
grids = get_transform_grid_list(
source_id=args.source_id,
area_of_use=args.area_of_use,
filename=args.file,
bbox=bbox,
spatial_test=args.spatial_test,
include_world_coverage=not args.exclude_world_coverage,
include_already_downloaded=args.include_already_downloaded,
target_directory=target_directory,
)
if args.list_files:
print("filename | source_id | area_of_use")
print("----------------------------------")
else:
endpoint = get_proj_endpoint()
for grid in grids:
if args.list_files:
print(
grid["properties"]["name"],
grid["properties"]["source_id"],
grid["properties"].get("area_of_use"),
sep=" | ",
)
else:
filename = grid["properties"]["name"]
_download_resource_file(
file_url=f"{endpoint}/{filename}",
short_name=filename,
directory=target_directory,
verbose=args.verbose,
sha256=grid["properties"]["sha256sum"],
)
elif not hasattr(args, "bbox") and args.verbose:
_show_versions.show_versions()
elif hasattr(args, "bbox"):
sync_parser.print_help()
else:
parser.print_help()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions pyproj/_sync.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def get_proj_endpoint() -> str: ...
13 changes: 13 additions & 0 deletions pyproj/_sync.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
include "proj.pxi"

from pyproj.compat import pystrdecode


def get_proj_endpoint() -> str:
"""
Returns
-------
str:
URL of the endpoint where PROJ grids are stored.
"""
return pystrdecode(proj_context_get_url_endpoint(NULL))
1 change: 1 addition & 0 deletions pyproj/proj.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,4 @@ cdef extern from "proj.h":
int *out_result_count,
)
void proj_unit_list_destroy(PROJ_UNIT_INFO** list)
const char *proj_context_get_url_endpoint(PJ_CONTEXT* ctx)
Loading