├── .coveragerc ├── .gitignore ├── .travis.yml ├── CHANGES ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HOWTO_RELEASE.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── constraints.txt ├── examples ├── README.md ├── cities.geojson ├── cities.txt ├── directions.md ├── geocoding.md ├── mapmatching.md ├── oldtrace.png ├── portland.png ├── san_diego.png ├── static_maps.md ├── taranaki_sat.png ├── trace.geojson ├── trace.png └── waypoints.txt ├── mapboxcli ├── __init__.py ├── __main__.py ├── compat.py ├── errors.py └── scripts │ ├── __init__.py │ ├── cli.py │ ├── config.py │ ├── datasets.py │ ├── directions.py │ ├── geocoding.py │ ├── mapmatching.py │ ├── static.py │ └── uploads.py ├── requirements-dev.txt ├── setup.py └── tests ├── line_feature.geojson ├── moors.json ├── test_config.py ├── test_datasets.py ├── test_directions.py ├── test_geocoding.py ├── test_helpers.py ├── test_mapmatching.py ├── test_static.py ├── test_uploads.py ├── twopoints.geojson ├── twopoints_seq.geojson └── twopoints_seqrs.geojson /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | mapboxcli/__main__.py 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | .*.swp 60 | .pytest_cache/ 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - '2.7' 5 | - '3.3' 6 | - '3.4' 7 | - '3.5' 8 | - '3.6' 9 | - pypy 10 | cache: 11 | directories: 12 | - $HOME/.pip-cache/ 13 | env: 14 | global: 15 | - PIP_CACHE_DIR=$HOME/.pip-cache 16 | - PIP_CONSTRAINT=constraints.txt 17 | install: 18 | - pip install -U pip 19 | - pip install --no-cache -r requirements-dev.txt 20 | - pip install --no-cache -e .[test] 21 | - pip list 22 | script: 23 | - py.test -vv --cov mapboxcli --cov-report term-missing 24 | after_success: 25 | - coveralls 26 | deploy: 27 | on: 28 | tags: true 29 | provider: pypi 30 | distributions: "sdist bdist_wheel" 31 | user: mapboxci 32 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.8.0 2 | ----- 3 | - Breaking change: remove the deprecated Distance API 4 | - Breaking change: remove the deprecated Surface API 5 | - Pin to mapbox-sdk-py version 0.16.1 6 | 7 | 0.7.1 8 | ----- 9 | - Pin to mapbox-sdk-py version 0.14.0, the most recent compatible version 10 | 11 | 0.7.0 12 | ----- 13 | - Breaking change: mapbox subcommands like 'upload' are no longer plugins. We 14 | are trading extensibility for speed (#95). 15 | - New feature: when saving a fraction of a second matters, mapbox subcommands 16 | can now be executed more quickly via `python -m mapboxcli upload` (#95). 17 | The `mapbox` console script remains the best command for ordinary usage on 18 | all platforms. 19 | - mapbox sdk 0.12.2 20 | 21 | 0.6.2 (2017-05-04) 22 | ------------------ 23 | - use mapbox sdk 0.12.1 24 | 25 | 0.6.1 (2017-04-10) 26 | ------------------ 27 | - Bugfix, ascii charaters in upload progressbar (see #98) 28 | 29 | 0.6.0 (2017-01-23) 30 | ------------------ 31 | - Breaking change: in mapbox-upload TILESET is now the first positional 32 | argument and source file the second, reversing the order set in 0.4.0 (#85). 33 | - Add a uploads progress bar (#86). 34 | - Skip staging for datasources in accessible S3 buckets (#49). 35 | - Requirements: use mapbox SDK version >=0.11. 36 | 37 | 0.5.1 (2015-11-22) 38 | ------------------ 39 | - Use mapbox sdk 0.10.1 40 | 41 | 0.5.0 (2016-11-22) 42 | ------------------ 43 | - Output sequence of geojson --features from geocoding (#76) 44 | - Ability to --limit number of geocoding results (#77) 45 | - Removed the datasets batch-update-delete functionality (#79) 46 | - Added patch mode functionality for uploads (#80) 47 | 48 | 0.4.0 (2016-06-08) 49 | ------------------ 50 | - Add support for geocoder bounding box filter (#73) 51 | - mapbox upload arguments in INPUT OUTPUT order, takes input on stdin (#68) 52 | 53 | 0.3.1 (2016-02-23) 54 | ------------------ 55 | - Bug fix for staticmap feature input (#60) 56 | 57 | 0.3.0 (2016-02-22) 58 | ------------------ 59 | - Testing and Documentation improvements (#38, #47, #54) 60 | - Geocoding options to specify country code and dataset (#55) 61 | 62 | 0.2.0 (2015-12-18) 63 | ------------------ 64 | - Addition of mapbox-mapmatching command (#17). 65 | - Access tokens can be set in a mapbox.ini config file (#1). See 66 | `mapbox --help` for directions. 67 | - Addition of mapbox-config command (#30). 68 | - The mapbox-upload infile arg type has been changed to `click.File` (#29). 69 | - This release requires a patched distribution of `boto3`. Either follow the 70 | two step installation process in the README or satisfy the requirement in 71 | a single command using pip's `--constraint` option and the mapbox-cli-py 72 | constraints.txt file (#22). 73 | 74 | 0.1.0 (2015-12-08) 75 | ------------------ 76 | - Initial release consists of a `mapbox` command and the following 77 | sub-commands: directions, distance, geocoding, staticmap, surface, upload. 78 | Each corresponds to one of the Mapbox APIs described at 79 | https://www.mapbox.com/developers/api/. 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # PAUSED DEVELOPMENT 2 | 3 | Since [the SDK that it wraps](https://github.com/mapbox/mapbox-sdk-py/blob/master/CONTRIBUTING.md#attention-this-project-is-paused) has been deprecated, we have also archived this CLI. We encourage you to migrate to [the equivalent tiling and upload endpoints.](https://docs.mapbox.com/api/maps/mapbox-tiling-service/) 4 | 5 | # Contributing 6 | 7 | ## Installation 8 | 9 | We recommend using [`virtualenv`](https://virtualenv.readthedocs.org/en/latest/) to isolate dependencies for development. 10 | This guide assumes that you have already created and activated a virtualenv for this project. 11 | 12 | Ensure that you have the latest version of pip installed: 13 | ``` 14 | pip install -U pip 15 | ``` 16 | 17 | Clone the repository (alternatively, if you plan on making a pull request and are not in the Mapbox organization, use the [github page](https://github.com/mapbox/mapbox-cli-py) to create your own fork) 18 | ``` 19 | git clone git@github.com:mapbox/mapbox-cli-py.git 20 | cd mapbox-cli-py 21 | ``` 22 | 23 | Install in "editable" mode with testing dependencies 24 | ``` 25 | pip install -U -r requirements-dev.txt 26 | pip install -e .[test] 27 | ``` 28 | 29 | And finally create a separate branch to begin work 30 | ``` 31 | git checkout -b my-new-feature 32 | ``` 33 | 34 | 35 | ## Submitting Pull Requests 36 | 37 | Pull requests are welcomed! We'd like to review the design and implementation as early as 38 | possible so please submit the pull request even if it's not 100%. 39 | Let us know the purpose of the change and list the remaining items which need to be 40 | addressed before merging. Finally, PR's should include unit tests and documentation 41 | where appropriate. 42 | 43 | 44 | ## Code of conduct 45 | 46 | Everyone is invited to participate in Mapbox’s open source projects and public discussions: we want to create a welcoming and friendly environment. Harassment of participants or other unethical and unprofessional behavior will not be tolerated in our spaces. The [Contributor Covenant](http://contributor-covenant.org) applies to all projects under the Mapbox organization and we ask that you please read [the full text](http://contributor-covenant.org/version/1/2/0/). 47 | 48 | You can learn more about our open source philosophy on [mapbox.com](https://www.mapbox.com/about/open/). 49 | 50 | -------------------------------------------------------------------------------- /HOWTO_RELEASE.md: -------------------------------------------------------------------------------- 1 | # How to release 2 | 3 | A source distribution (sdist) and wheels (bdist_wheel) for Python 2 and 3 are published to PyPI under the 'mapboxci' account 4 | using [twine](https://twine.readthedocs.io/en/latest/index.html). 5 | 6 | This process has been automated on Travis-CI. The 'mapboxci' account name and its token are stored on Travis-CI (see 7 | `PYPI_USERNAME` and `PYPI_PASSWORD` in the project settings). Pushing a tag to GitHub will result in uploads to PyPI if the 8 | build succeeds. You can make releases from the GitHub web page if you like. 9 | 10 | Steps for making a release: 11 | 12 | * Update the `__version__` attribute in mapboxcli/\_\_init\_\_.py. This project uses semantic versioning. 13 | * Update the CHANGES file. 14 | * Make a new tag that exactly matches the `__version__` attribute above. 15 | * Push the tag and look for the new release and its binaries at https://pypi.org/project/mapboxcli/#history. 16 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ### What you've tried already 9 | Enough information to reproduce the issue, including code and links to any relevant datasets. 10 | 11 | ### Expected outcome 12 | Tell us what should happen 13 | 14 | ### Actual outcome 15 | Tell us what happens instead 16 | 17 | ### Other information 18 | * Version numbers for `mapboxcli` and `mapbox` (from, e.g., `pip list`) : 19 | * Operating System : 20 | * Python version number : 21 | * How did you install the mapbox cli? Homebrew, pip, other? : 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mapbox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PAUSED DEVELOPMENT 2 | 3 | Since [the SDK that it wraps](https://github.com/mapbox/mapbox-sdk-py/blob/master/CONTRIBUTING.md#attention-this-project-is-paused) has been deprecated, we have also archived this CLI. We encourage you to migrate to [the equivalent tiling and upload endpoints.](https://docs.mapbox.com/api/maps/mapbox-tiling-service/) 4 | 5 | # mapbox-cli-py 6 | 7 | [![Build Status](https://travis-ci.org/mapbox/mapbox-cli-py.svg?branch=master)](https://travis-ci.org/mapbox/mapbox-cli-py) [![Coverage Status](https://coveralls.io/repos/mapbox/mapbox-cli-py/badge.svg?branch=master&service=github)](https://coveralls.io/github/mapbox/mapbox-cli-py?branch=master) 8 | 9 | Command line interface to Mapbox Web Services based on https://github.com/mapbox/mapbox-sdk-py. 10 | 11 | ## Installation 12 | 13 | **For users on OS X**, we recommend installing with [homebrew](http://brew.sh/) 14 | ``` 15 | $ brew install mapbox/cli/mapbox 16 | ``` 17 | 18 | **For users familiar with Python** and who already have [`pip`](https://pip.pypa.io/en/stable/installing/) installed on their system, you can create a [virtual environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) and install with 19 | ``` 20 | (venv)$ pip install mapboxcli 21 | ``` 22 | 23 | Installing locally without a virtual environment using 24 | ``` 25 | $ pip install --user mapboxcli 26 | ``` 27 | You'll then need to include `~/.local/bin` in your $PATH. 28 | 29 | Installing globally is *not recommended* but some users may want to do so under certain circumstances 30 | ``` 31 | $ sudo pip install mapboxcli 32 | ``` 33 | 34 | If you're interested in contributing, you'll want to [install from master branch](https://github.com/mapbox/mapbox-cli-py/blob/master/CONTRIBUTING.md#contributing). 35 | 36 | ## Setup 37 | 38 | Use of the `mapbox` command line interface requires an access token. 39 | Your token is shown on the [API access tokens](https://www.mapbox.com/studio/account/tokens/) page when you are logged in. 40 | The token can be provided on the command line 41 | 42 | ``` 43 | $ mapbox --access-token MY_TOKEN ... 44 | ``` 45 | 46 | or as an environment variable named `MAPBOX_ACCESS_TOKEN` 47 | 48 | ``` 49 | $ export MAPBOX_ACCESS_TOKEN=MY_TOKEN 50 | $ mapbox ... 51 | ``` 52 | 53 | ## Usage 54 | 55 | * [directions](#directions) 56 | * [geocoding](#geocoding) 57 | * [mapmatching](#mapmatching) 58 | * [staticmap](#staticmap) 59 | * [upload](#upload) 60 | * [datasets](#datasets) 61 | 62 | For any command that takes waypoints or features as an input you can either specify: 63 | 64 | * Coordinate pair(s) of the form `"[0, 0]"` or `"0,0"` or `"0 0"` 65 | * Sequence of GeoJSON features on `stdin` 66 | * GeoJSON FeatureCollection on `stdin` 67 | * Paths to GeoJSON file(s) containing either a single Feature or FeatureCollection. 68 | 69 | Note that functions that accept points only, any non-point feature is filtered out. 70 | 71 | ### directions 72 | ``` 73 | Usage: mapbox directions [OPTIONS] FEATURES... 74 | 75 | The Mapbox Directions API will show you how to get where you're going. 76 | 77 | mapbox directions "[0, 0]" "[1, 1]" 78 | 79 | An access token is required. See "mapbox --help". 80 | 81 | Options: 82 | --profile [mapbox/driving|mapbox/driving-traffic|mapbox/walking|mapbox/cycling] 83 | Routing profile 84 | --alternatives / --no-alternatives 85 | Whether to try to return alternative routes 86 | --geometries [geojson|polyline|polyline6] 87 | Format of returned geometry 88 | --overview [full|simplified|False] 89 | Type of returned overview geometry 90 | --steps / --no-steps Whether to return steps and turn-by-turn 91 | instructions 92 | --continue-straight / --no-continue-straight 93 | Whether to see the allowed direction of 94 | travel when departing the original waypoint 95 | --waypoint-snapping TEXT Controls waypoint snapping 96 | --annotations TEXT Additional metadata along the route 97 | --language TEXT Language of returned turn-by-turn 98 | instructions 99 | -o, --output TEXT Save output to a file 100 | --help Show this message and exit. 101 | ``` 102 | 103 | 104 | ### geocoding 105 | ``` 106 | Usage: mapbox geocoding [OPTIONS] [QUERY] 107 | 108 | This command returns places matching an address (forward mode) or places 109 | matching coordinates (reverse mode). 110 | 111 | In forward (the default) mode the query argument shall be an address such 112 | as '1600 pennsylvania ave nw'. 113 | 114 | $ mapbox geocoding '1600 pennsylvania ave nw' 115 | 116 | In reverse mode the query argument shall be a JSON encoded array of 117 | longitude and latitude (in that order) in decimal degrees. 118 | 119 | $ mapbox geocoding --reverse '[-77.4371, 37.5227]' 120 | 121 | An access token is required, see `mapbox --help`. 122 | 123 | Options: 124 | --forward / --reverse Perform a forward or reverse geocode. 125 | [default: forward] 126 | -i, --include Include HTTP headers in the output. 127 | --lat FLOAT Bias results toward this latitude (decimal 128 | degrees). --lon is also required. 129 | --lon FLOAT Bias results toward this longitude (decimal 130 | degrees). --lat is also required. 131 | -t, --place-type NAME Restrict results to one or more place types. 132 | -o, --output TEXT Save output to a file. 133 | -d, --dataset [mapbox.places|mapbox.places-permanent] 134 | Source dataset for geocoding, [default: 135 | mapbox.places] 136 | --country TEXT Restrict forward geocoding to specified 137 | country codes,comma-separated 138 | --bbox TEXT Restrict forward geocoding to specified 139 | bounding box,given in minX,minY,maxX,maxY 140 | coordinates. 141 | --features Return results as line-delimited GeoJSON 142 | Feature sequence, not a FeatureCollection 143 | --limit INTEGER Limit the number of returned features 144 | --help Show this message and exit. 145 | ``` 146 | 147 | ### mapmatching 148 | ``` 149 | Usage: mapbox mapmatching [OPTIONS] FEATURES... 150 | 151 | Mapbox Map Matching API lets you use snap your GPS traces to the 152 | OpenStreetMap road and path network. 153 | 154 | $ mapbox mapmatching trace.geojson 155 | 156 | An access token is required, see `mapbox --help`. 157 | 158 | Options: 159 | --gps-precision INTEGER Assumed precision of tracking device 160 | (default 4 meters) 161 | --profile [mapbox.driving|mapbox.cycling|mapbox.walking] 162 | Mapbox profile id 163 | --help Show this message and exit. 164 | ``` 165 | 166 | ### staticmap 167 | ``` 168 | Usage: mapbox staticmap [OPTIONS] MAPID OUTPUT 169 | 170 | Generate static map images from existing Mapbox map ids. Optionally 171 | overlay with geojson features. 172 | 173 | $ mapbox staticmap --features features.geojson mapbox.satellite out.png 174 | $ mapbox staticmap --lon -61.7 --lat 12.1 --zoom 12 mapbox.satellite 175 | out2.png 176 | 177 | An access token is required, see `mapbox --help`. 178 | 179 | Options: 180 | --features TEXT GeoJSON Features to render as overlay 181 | --lat FLOAT Latitude 182 | --lon FLOAT Longitude 183 | --zoom INTEGER Zoom 184 | --size ... Image width and height in pixels 185 | --help Show this message and exit. 186 | ``` 187 | 188 | 189 | ### upload 190 | ``` 191 | Usage: mapbox upload [OPTIONS] TILESET [INFILE] 192 | 193 | Upload data to Mapbox accounts. All endpoints require authentication. 194 | Uploaded data lands at https://www.mapbox.com/studio/tilesets/ and can be used in new 195 | or existing projects. 196 | 197 | You can specify the tileset id and input file 198 | 199 | $ mapbox upload username.data mydata.geojson 200 | 201 | Or specify just the tileset id and take an input file on stdin 202 | 203 | $ cat mydata.geojson | mapbox upload username.data 204 | 205 | The --name option defines the title as it appears in Studio and defaults 206 | to the last part of the tileset id, e.g. "data" 207 | 208 | Note that the tileset must start with your username. An access token with 209 | upload scope is required, see `mapbox --help`. 210 | 211 | Options: 212 | --name TEXT Name for the data upload 213 | --help Show this message and exit. 214 | ``` 215 | 216 | ### datasets 217 | ``` 218 | Usage: mapbox datasets [OPTIONS] COMMAND [ARGS]... 219 | 220 | Read and write GeoJSON from Mapbox-hosted datasets 221 | 222 | All endpoints require authentication. An access token with appropriate 223 | dataset scopes is required, see `mapbox --help`. 224 | 225 | Note that this API is currently a limited-access beta. 226 | 227 | Options: 228 | --help Show this message and exit. 229 | 230 | Commands: 231 | create Create an empty dataset 232 | create-tileset Generate a tileset from a dataset 233 | delete-dataset Delete a dataset 234 | delete-feature Delete a single feature from a dataset 235 | list List datasets 236 | list-features List features in a dataset 237 | put-feature Insert or update a single feature in a dataset 238 | read-dataset Return information about a dataset 239 | read-feature Read a single feature from a dataset 240 | update-dataset Update information about a dataset 241 | ``` 242 | 243 | ### datasets list 244 | ``` 245 | Usage: mapbox datasets list [OPTIONS] 246 | 247 | List datasets. 248 | 249 | Prints a list of objects describing datasets. 250 | 251 | $ mapbox datasets list 252 | 253 | All endpoints require authentication. An access token with `datasets:read` 254 | scope is required, see `mapbox --help`. 255 | 256 | Options: 257 | -o, --output TEXT Save output to a file 258 | --help Show this message and exit. 259 | ``` 260 | 261 | ### datasets create 262 | ``` 263 | Usage: mapbox datasets create [OPTIONS] 264 | 265 | Create a new dataset. 266 | 267 | Prints a JSON object containing the attributes of the new dataset. 268 | 269 | $ mapbox datasets create 270 | 271 | All endpoints require authentication. An access token with 272 | `datasets:write` scope is required, see `mapbox --help`. 273 | 274 | Options: 275 | -n, --name TEXT Name for the dataset 276 | -d, --description TEXT Description for the dataset 277 | --help Show this message and exit. 278 | ``` 279 | 280 | ### datasets read-dataset 281 | ``` 282 | Usage: mapbox datasets read-dataset [OPTIONS] DATASET 283 | 284 | Read the attributes of a dataset. 285 | 286 | Prints a JSON object containing the attributes of a dataset. The 287 | attributes: owner (a Mapbox account), id (dataset id), created (Unix 288 | timestamp), modified (timestamp), name (string), and description (string). 289 | 290 | $ mapbox datasets read-dataset dataset-id 291 | 292 | All endpoints require authentication. An access token with `datasets:read` 293 | scope is required, see `mapbox --help`. 294 | 295 | Options: 296 | -o, --output TEXT Save output to a file 297 | --help Show this message and exit. 298 | ``` 299 | 300 | ### datasets update-dataset 301 | ``` 302 | Usage: mapbox datasets update-dataset [OPTIONS] DATASET 303 | 304 | Update the name and description of a dataset. 305 | 306 | Prints a JSON object containing the updated dataset attributes. 307 | 308 | $ mapbox datasets update-dataset dataset-id 309 | 310 | All endpoints require authentication. An access token with 311 | `datasets:write` scope is required, see `mapbox --help`. 312 | 313 | Options: 314 | -n, --name TEXT Name for the dataset 315 | -d, --description TEXT Description for the dataset 316 | --help Show this message and exit. 317 | ``` 318 | 319 | ### datasets delete-dataset 320 | ``` 321 | Usage: mapbox datasets delete-dataset [OPTIONS] DATASET 322 | 323 | Delete a dataset. 324 | 325 | $ mapbox datasets delete-dataset dataset-id 326 | 327 | All endpoints require authentication. An access token with 328 | `datasets:write` scope is required, see `mapbox --help`. 329 | 330 | Options: 331 | --help Show this message and exit. 332 | ``` 333 | 334 | ### datasets list-features 335 | ``` 336 | Usage: mapbox datasets list-features [OPTIONS] DATASET 337 | 338 | Get features of a dataset. 339 | 340 | Prints the features of the dataset as a GeoJSON feature collection. 341 | 342 | $ mapbox datasets list-features dataset-id 343 | 344 | All endpoints require authentication. An access token with `datasets:read` 345 | scope is required, see `mapbox --help`. 346 | 347 | Options: 348 | -r, --reverse TEXT Read features in reverse 349 | -s, --start TEXT Feature id to begin reading from 350 | -l, --limit TEXT Maximum number of features to return 351 | -o, --output TEXT Save output to a file 352 | --help Show this message and exit. 353 | ``` 354 | 355 | ### datasets put-feature 356 | ``` 357 | Usage: mapbox datasets put-feature [OPTIONS] DATASET FID [FEATURE] 358 | 359 | Create or update a dataset feature. 360 | 361 | The semantics of HTTP PUT apply: if the dataset has no feature with the 362 | given `fid` a new feature will be created. Returns a GeoJSON 363 | representation of the new or updated feature. 364 | 365 | $ mapbox datasets put-feature dataset-id feature-id 'geojson-feature' 366 | 367 | All endpoints require authentication. An access token with 368 | `datasets:write` scope is required, see `mapbox --help`. 369 | 370 | Options: 371 | -i, --input TEXT File containing a feature to put 372 | --help Show this message and exit. 373 | ``` 374 | 375 | ### datasets read-feature 376 | ``` 377 | Usage: mapbox datasets read-feature [OPTIONS] DATASET FID 378 | 379 | Read a dataset feature. 380 | 381 | Prints a GeoJSON representation of the feature. 382 | 383 | $ mapbox datasets read-feature dataset-id feature-id 384 | 385 | All endpoints require authentication. An access token with `datasets:read` 386 | scope is required, see `mapbox --help`. 387 | 388 | Options: 389 | -o, --output TEXT Save output to a file 390 | --help Show this message and exit. 391 | ``` 392 | 393 | ### datasets delete-feature 394 | ``` 395 | Usage: mapbox datasets delete-feature [OPTIONS] DATASET FID 396 | 397 | Delete a feature. 398 | 399 | $ mapbox datasets delete-feature dataset-id feature-id 400 | 401 | All endpoints require authentication. An access token with 402 | `datasets:write` scope is required, see `mapbox --help`. 403 | 404 | Options: 405 | --help Show this message and exit. 406 | ``` 407 | 408 | ### datasets create-tileset 409 | ``` 410 | Usage: mapbox datasets create-tileset [OPTIONS] DATASET TILESET 411 | 412 | Create a vector tileset from a dataset. 413 | 414 | $ mapbox datasets create-tileset dataset-id username.data 415 | 416 | Note that the tileset must start with your username and the dataset must 417 | be one that you own. To view processing status, visit 418 | https://www.mapbox.com/data/. You may not generate another tilesets from 419 | the same dataset until the first processing job has completed. 420 | 421 | All endpoints require authentication. An access token with `uploads:write` 422 | scope is required, see `mapbox --help`. 423 | 424 | Options: 425 | -n, --name TEXT Name for the tileset 426 | --help Show this message and exit. 427 | ``` 428 | 429 | ## Alternative command syntax 430 | 431 | When saving a fraction of a second matters you can call the mapboxcli module 432 | directly instead of using the installed program. 433 | 434 | ```$ python -m mapboxcli --help``` 435 | -------------------------------------------------------------------------------- /constraints.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/constraints.txt -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | * [Directions](directions.md) 2 | * [Geocoding](geocoding.md) 3 | * [Mapmatching](mapmatching.md) 4 | * [Staticmaps](static_maps.md) 5 | -------------------------------------------------------------------------------- /examples/cities.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "text": "New York", 7 | "relevance": 0.995, 8 | "properties": { 9 | "maki": "town-hall", 10 | "category": "community, government", 11 | "address": "340 Livingston St" 12 | }, 13 | "place_name": "New York, 340 Livingston St, Brooklyn, New York 11217, United States", 14 | "id": "poi.16267137545855570", 15 | "geometry": { 16 | "type": "Point", 17 | "coordinates": [ 18 | -73.981335, 19 | 40.68773 20 | ] 21 | }, 22 | "context": [ 23 | { 24 | "text": "Boerum Hill", 25 | "id": "neighborhood.9959358344695340" 26 | }, 27 | { 28 | "text": "Brooklyn", 29 | "id": "place.6081" 30 | }, 31 | { 32 | "text": "11217", 33 | "id": "postcode.18793257174414170" 34 | }, 35 | { 36 | "text": "New York", 37 | "id": "region.14113019950053660" 38 | }, 39 | { 40 | "text": "United States", 41 | "short_code": "us", 42 | "id": "country.5877825732302570" 43 | } 44 | ], 45 | "center": [ 46 | -73.981335, 47 | 40.68773 48 | ] 49 | }, 50 | { 51 | "type": "Feature", 52 | "text": "Los Angeles", 53 | "relevance": 0.998, 54 | "properties": null, 55 | "place_name": "Los Angeles, California, United States", 56 | "id": "place.28560", 57 | "geometry": { 58 | "type": "Point", 59 | "coordinates": [ 60 | -118.2427, 61 | 34.0537 62 | ] 63 | }, 64 | "context": [ 65 | { 66 | "text": "90012", 67 | "id": "postcode.8055854450707500" 68 | }, 69 | { 70 | "text": "California", 71 | "id": "region.11871630867311220" 72 | }, 73 | { 74 | "text": "United States", 75 | "short_code": "us", 76 | "id": "country.5877825732302570" 77 | } 78 | ], 79 | "center": [ 80 | -118.2427, 81 | 34.0537 82 | ], 83 | "bbox": [ 84 | -118.521455009776, 85 | 33.90189299, 86 | -118.12130699003, 87 | 34.1614390095055 88 | ] 89 | }, 90 | { 91 | "type": "Feature", 92 | "text": "Chicago", 93 | "relevance": 0.998, 94 | "properties": null, 95 | "place_name": "Chicago, Illinois, United States", 96 | "id": "place.8898", 97 | "geometry": { 98 | "type": "Point", 99 | "coordinates": [ 100 | -87.6244, 101 | 41.8756 102 | ] 103 | }, 104 | "context": [ 105 | { 106 | "text": "60605", 107 | "id": "postcode.3941132441741760" 108 | }, 109 | { 110 | "text": "Illinois", 111 | "id": "region.3290978599018380" 112 | }, 113 | { 114 | "text": "United States", 115 | "short_code": "us", 116 | "id": "country.5877825732302570" 117 | } 118 | ], 119 | "center": [ 120 | -87.6244, 121 | 41.8756 122 | ], 123 | "bbox": [ 124 | -87.8696169995243, 125 | 41.6299249900368, 126 | -87.5236879900007, 127 | 42.023431008437 128 | ] 129 | }, 130 | { 131 | "type": "Feature", 132 | "text": "Houston", 133 | "relevance": 0.998, 134 | "properties": null, 135 | "place_name": "Houston, Texas, United States", 136 | "id": "place.22974", 137 | "geometry": { 138 | "type": "Point", 139 | "coordinates": [ 140 | -95.3677, 141 | 29.7589 142 | ] 143 | }, 144 | "context": [ 145 | { 146 | "text": "77002", 147 | "id": "postcode.13474302067000040" 148 | }, 149 | { 150 | "text": "Texas", 151 | "id": "region.5362387430486520" 152 | }, 153 | { 154 | "text": "United States", 155 | "short_code": "us", 156 | "id": "country.5877825732302570" 157 | } 158 | ], 159 | "center": [ 160 | -95.3677, 161 | 29.7589 162 | ], 163 | "bbox": [ 164 | -95.7204500099997, 165 | 29.5289229922278, 166 | -95.0612099905765, 167 | 30.0403620083959 168 | ] 169 | }, 170 | { 171 | "type": "Feature", 172 | "text": "Philadelphia", 173 | "relevance": 0.998, 174 | "properties": null, 175 | "place_name": "Philadelphia, Pennsylvania, United States", 176 | "id": "place.38150", 177 | "geometry": { 178 | "type": "Point", 179 | "coordinates": [ 180 | -75.1327, 181 | 40.0115 182 | ] 183 | }, 184 | "context": [ 185 | { 186 | "text": "19140", 187 | "id": "postcode.8313127361643680" 188 | }, 189 | { 190 | "text": "Pennsylvania", 191 | "id": "region.10935335870218730" 192 | }, 193 | { 194 | "text": "United States", 195 | "short_code": "us", 196 | "id": "country.5877825732302570" 197 | } 198 | ], 199 | "center": [ 200 | -75.1327, 201 | 40.0115 202 | ], 203 | "bbox": [ 204 | -75.2946570099346, 205 | 39.8557329901965, 206 | -74.9557769900663, 207 | 40.1379920098759 208 | ] 209 | }, 210 | { 211 | "type": "Feature", 212 | "text": "Phoenix", 213 | "relevance": 0.998, 214 | "properties": null, 215 | "place_name": "Phoenix, Arizona, United States", 216 | "id": "place.38200", 217 | "geometry": { 218 | "type": "Point", 219 | "coordinates": [ 220 | -112.0773, 221 | 33.4486 222 | ] 223 | }, 224 | "context": [ 225 | { 226 | "text": "85003", 227 | "id": "postcode.15629053940122330" 228 | }, 229 | { 230 | "text": "Arizona", 231 | "id": "region.6181432665758870" 232 | }, 233 | { 234 | "text": "United States", 235 | "short_code": "us", 236 | "id": "country.5877825732302570" 237 | } 238 | ], 239 | "center": [ 240 | -112.0773, 241 | 33.4486 242 | ], 243 | "bbox": [ 244 | -112.312825000097, 245 | 33.2902599927111, 246 | -111.925318990001, 247 | 33.801259009997 248 | ] 249 | }, 250 | { 251 | "type": "Feature", 252 | "text": "San Antonio", 253 | "relevance": 0.998, 254 | "properties": null, 255 | "place_name": "San Antonio, Texas, United States", 256 | "id": "place.43241", 257 | "geometry": { 258 | "type": "Point", 259 | "coordinates": [ 260 | -98.4951, 261 | 29.4246 262 | ] 263 | }, 264 | "context": [ 265 | { 266 | "text": "78205", 267 | "id": "postcode.16602948184468340" 268 | }, 269 | { 270 | "text": "Texas", 271 | "id": "region.5362387430486520" 272 | }, 273 | { 274 | "text": "United States", 275 | "short_code": "us", 276 | "id": "country.5877825732302570" 277 | } 278 | ], 279 | "center": [ 280 | -98.4951, 281 | 29.4246 282 | ], 283 | "bbox": [ 284 | -98.8864100074998, 285 | 29.1074249974253, 286 | -98.2380289921584, 287 | 29.7490000093609 288 | ] 289 | }, 290 | { 291 | "type": "Feature", 292 | "text": "San Diego", 293 | "relevance": 0.998, 294 | "properties": null, 295 | "place_name": "San Diego, California, United States", 296 | "id": "place.43311", 297 | "geometry": { 298 | "type": "Point", 299 | "coordinates": [ 300 | -117.1628, 301 | 32.7174 302 | ] 303 | }, 304 | "context": [ 305 | { 306 | "text": "92101", 307 | "id": "postcode.5918139479985540" 308 | }, 309 | { 310 | "text": "California", 311 | "id": "region.11871630867311220" 312 | }, 313 | { 314 | "text": "United States", 315 | "short_code": "us", 316 | "id": "country.5877825732302570" 317 | } 318 | ], 319 | "center": [ 320 | -117.1628, 321 | 32.7174 322 | ], 323 | "bbox": [ 324 | -117.266222008727, 325 | 32.5341749918273, 326 | -116.853121990037, 327 | 33.072207008512 328 | ] 329 | }, 330 | { 331 | "type": "Feature", 332 | "text": "Dallas", 333 | "relevance": 0.998, 334 | "properties": null, 335 | "place_name": "Dallas, Texas, United States", 336 | "id": "place.11855", 337 | "geometry": { 338 | "type": "Point", 339 | "coordinates": [ 340 | -96.7969, 341 | 32.7763 342 | ] 343 | }, 344 | "context": [ 345 | { 346 | "text": "75201", 347 | "id": "postcode.9855079973691700" 348 | }, 349 | { 350 | "text": "Texas", 351 | "id": "region.5362387430486520" 352 | }, 353 | { 354 | "text": "United States", 355 | "short_code": "us", 356 | "id": "country.5877825732302570" 357 | } 358 | ], 359 | "center": [ 360 | -96.7969, 361 | 32.7763 362 | ], 363 | "bbox": [ 364 | -96.9901020098665, 365 | 32.6185739900006, 366 | -96.5555159900026, 367 | 33.0164920099023 368 | ] 369 | }, 370 | { 371 | "type": "Feature", 372 | "text": "San Jose", 373 | "relevance": 0.999, 374 | "properties": null, 375 | "place_name": "San Jose, Carlsbad, 88220, New Mexico, United States", 376 | "id": "neighborhood.9668069999165690", 377 | "geometry": { 378 | "type": "Point", 379 | "coordinates": [ 380 | -104.2316, 381 | 32.4082 382 | ] 383 | }, 384 | "context": [ 385 | { 386 | "text": "Carlsbad", 387 | "id": "place.7606" 388 | }, 389 | { 390 | "text": "88220", 391 | "id": "postcode.7496934476256710" 392 | }, 393 | { 394 | "text": "New Mexico", 395 | "id": "region.5223832384511090" 396 | }, 397 | { 398 | "text": "United States", 399 | "short_code": "us", 400 | "id": "country.5877825732302570" 401 | } 402 | ], 403 | "center": [ 404 | -104.2316, 405 | 32.4082 406 | ], 407 | "bbox": [ 408 | -104.240041504, 409 | 32.3815583425999, 410 | -104.209055176, 411 | 32.4124290544001 412 | ] 413 | } 414 | ] 415 | } 416 | -------------------------------------------------------------------------------- /examples/cities.txt: -------------------------------------------------------------------------------- 1 | New York, NY 2 | Los Angeles, CA 3 | Chicago, IL 4 | Houston, TX 5 | Philadelphia, PA 6 | Phoenix, AZ 7 | San Antonio, TX 8 | San Diego, CA 9 | Dallas, TX 10 | San Jose, CA 11 | -------------------------------------------------------------------------------- /examples/directions.md: -------------------------------------------------------------------------------- 1 | # Turn by Turn Directions 2 | 3 | 4 | 5 | Here we use [`jq`](https://stedolan.github.io/jq/) to parse json results and [geojsonio](https://github.com/mapbox/geojson.io) for quick map creation. 6 | 7 | First, we take two addresses between which we'll find directions. We geocode them and parse the first (most relevant) feature from the returned GeoJSON feature collection. 8 | 9 | ``` 10 | $ mapbox geocoding "4001 Southwest Canyon Road, Portland, OR" | jq -c .features[0] > waypoints.txt 11 | $ mapbox geocoding "1945 SE Water Ave, Portland, OR" | jq -c .features[0] >> waypoints.txt 12 | ``` 13 | 14 | Now find travel directions between them as a geojson Feature collection, then piping the result to geojsonio 15 | ``` 16 | $ mapbox directions --geojson < waypoints.txt | geojsonio 17 | ``` 18 | 19 | Which opens a web browser to [an interactive web map](http://bl.ocks.org/d/07c9145cfe465467f7e2). 20 | 21 | If you're more interested in the turn-by-turn directions than the map, you can parse the full directions response 22 | 23 | ``` 24 | $ mapbox directions < waypoints.txt | jq '.routes[0].steps[] .maneuver.instruction' 25 | 26 | "Head northeast on Southwest Zoo Road" 27 | "Make a sharp left" 28 | "Bear left" 29 | "Continue" 30 | "Turn left onto Southwest Knights Boulevard" 31 | "Turn left" 32 | "Continue on Sunset Highway (US 26)" 33 | "Continue on Vista Ridge Tunnel (US 26)" 34 | "Continue on Sunset Highway (US 26)" 35 | "Continue on US 26" 36 | "Continue on Stadium Freeway (I 405;US 26)" 37 | "Continue on Stadium Freeway (I 405)" 38 | "Continue" 39 | "Continue on Marquam Bridge (I 5)" 40 | "Continue on Eastbank Freeway (I 5)" 41 | "Continue" 42 | "Continue" 43 | "Turn right onto Southeast Water Avenue" 44 | "Turn right" 45 | "Continue" 46 | "Continue" 47 | "You have arrived at your destination" 48 | ``` 49 | -------------------------------------------------------------------------------- /examples/geocoding.md: -------------------------------------------------------------------------------- 1 | # Geocoding 2 | 3 | Let's geocode some addresses. Here we use [`jq`](https://stedolan.github.io/jq/) to parse the first (most relevant) feature from the returned GeoJSON feature collection. 4 | ``` 5 | $ mapbox geocoding "4001 Southwest Canyon Road, Portland, OR" | jq -c .features[0] >> waypoints.txt 6 | $ mapbox geocoding "1945 SE Water Ave, Portland, OR" | jq -c .features[0] >> waypoints.txt 7 | ``` 8 | 9 | generate static maps, 10 | ``` 11 | $ cat waypoints.txt | mapbox staticmap --features - mapbox.streets out.png 12 | ``` 13 | ![pdx](https://gist.githubusercontent.com/perrygeo/eef0db7967d0db1b05d4/raw/82214f6592db39cfbba6f8a161e894cddb6087d9/out.png) 14 | 15 | Or batch geocode text files 16 | ``` 17 | $ mapbox geocoding places.txt | jq -c .features[0] | fio collect > places.geojson 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/mapmatching.md: -------------------------------------------------------------------------------- 1 | # Mapmatching 2 | 3 | 4 | If you've collected data with a GPS unit, either through a fitness device, mobile phone or dedicated GPS unit, 5 | chances are good that it doesn't line up perfectly with other data. The mapmatching service 6 | allows you to correct the GPS traces against a common base map, OSM. 7 | 8 | You might need to [preprocess the data](https://www.mapbox.com/developers/api/map-matching/#Preprocessing.traces) 9 | in order to get GeoJSON linestring features. 10 | 11 | ```json 12 | { 13 | "type": "Feature", 14 | "properties": { 15 | "coordTimes": [ 16 | "2015-04-21T06:00:00Z", 17 | "2015-04-21T06:00:05Z", 18 | "2015-04-21T06:00:10Z", 19 | "2015-04-21T06:00:15Z", 20 | "2015-04-21T06:00:20Z" 21 | ] 22 | }, 23 | "geometry": { 24 | "type": "LineString", 25 | "coordinates": [ 26 | [ 13.418946862220764, 52.50055852688439 ], 27 | [ 13.419011235237122, 52.50113000479732 ], 28 | [ 13.419756889343262, 52.50171780290061 ], 29 | [ 13.419885635375975, 52.50237416816131 ], 30 | [ 13.420631289482117, 52.50294888790448 ] 31 | ] 32 | } 33 | } 34 | ``` 35 | 36 | Once you've got that, you can use the CLI 37 | 38 | ``` 39 | $ mapbox mapmatching trace.geojson > newtrace.geojson 40 | ``` 41 | 42 | And visualize the output with a staticmap. 43 | 44 | The original trace 45 | 46 | ``` 47 | $ cat trace.geojson | mapbox staticmap --features - mapbox.streets oldtrace.png 48 | ``` 49 | 50 | ![oldtrace](oldtrace.png) 51 | 52 | And the corrected 53 | ``` 54 | $ cat newtrace.geojson | mapbox staticmap --features - mapbox.streets newtrace.png 55 | ``` 56 | 57 | ![trace](trace.png) 58 | -------------------------------------------------------------------------------- /examples/oldtrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/examples/oldtrace.png -------------------------------------------------------------------------------- /examples/portland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/examples/portland.png -------------------------------------------------------------------------------- /examples/san_diego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/examples/san_diego.png -------------------------------------------------------------------------------- /examples/static_maps.md: -------------------------------------------------------------------------------- 1 | # Static map images 2 | 3 | We know the coordinate of our destination (Mount Taranaki in New Zealand) 4 | 5 | ``` 6 | $ lat="-39.2978" 7 | $ lon="174.0632" 8 | ``` 9 | 10 | Now let's see what that region looks like using the `mapbox.satellite` map featuring [super high-res 11 | imagery from Land Information New Zealand](https://www.mapbox.com/blog/new-zealand-aerial/) 12 | 13 | Here we request an 800x800 PNG image at zoom 13 14 | ``` 15 | $ mapbox staticmap --lat $lat --lon $lon --zoom 13 --size 800 800 mapbox.satellite taranaki_sat.png 16 | ``` 17 | 18 | ![taranaki_sat.png](taranaki_sat.png) 19 | 20 | Of course we don't need to manually type the coordinates. With a short bash function, 21 | we can grab the lat/lon from the geocoding results and create a map 22 | 23 | ```bash 24 | function showme() { 25 | local location="$(mapbox geocoding "$1" | jq -c .features[0])" 26 | local lon=$(echo $location | jq .center[0]) 27 | local lat=$(echo $location | jq .center[1]) 28 | local tmp=$(mktemp $TMPDIR/$(uuidgen).XXX.png) 29 | mapbox staticmap --lat $lat --lon $lon --zoom ${2:-13} --size 800 800 \ 30 | mapbox.satellite $tmp 31 | if [ "$(uname)" = "Linux" ]; then 32 | xdg-open $tmp 33 | else 34 | open $tmp 35 | fi 36 | } 37 | ``` 38 | 39 | Which you use like so, in this case to get an image of downtown San Digeo at zoom 14 40 | 41 | ``` 42 | $ showme "San Diego, CA" 14 43 | ``` 44 | 45 | ![san_diego.png](san_diego.png) 46 | 47 | 48 | You can also render small GeoJSON feature collections using the `--features` option. 49 | 50 | ``` 51 | mapbox staticmap --features waypoints.geojson mapbox.streets portland.png 52 | ``` 53 | 54 | ![portland.png](portland.png) 55 | -------------------------------------------------------------------------------- /examples/taranaki_sat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/examples/taranaki_sat.png -------------------------------------------------------------------------------- /examples/trace.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "properties": { 4 | "coordTimes": [ 5 | "2015-04-21T06:00:00Z", 6 | "2015-04-21T06:00:05Z", 7 | "2015-04-21T06:00:10Z", 8 | "2015-04-21T06:00:15Z", 9 | "2015-04-21T06:00:20Z" 10 | ] 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 13.418946862220764, 52.50055852688439 ], 16 | [ 13.419011235237122, 52.50113000479732 ], 17 | [ 13.419756889343262, 52.50171780290061 ], 18 | [ 13.419885635375975, 52.50237416816131 ], 19 | [ 13.420631289482117, 52.50294888790448 ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-cli-py/cbea312fcf3f04af520c02ac6c34d1b379f9cb49/examples/trace.png -------------------------------------------------------------------------------- /examples/waypoints.txt: -------------------------------------------------------------------------------- 1 | {"id":"address.13664328754666580","type":"Feature","text":"SW Canyon Rd","place_name":"4001 SW Canyon Rd, Portland, Oregon 97221, United States","relevance":0.9959999999999999,"properties":{},"bbox":[-122.74047399999999,45.50935899999999,-122.69616899999997,45.519394999999996],"center":[-122.71602,45.509988],"geometry":{"type":"Point","coordinates":[-122.71602,45.509988]},"address":"4001","context":[{"id":"neighborhood.6199811613447400","text":"Southwest Hills"},{"id":"place.39347","text":"Portland"},{"id":"postcode.14415097033049430","text":"97221"},{"id":"region.16025278559490090","text":"Oregon"},{"id":"country.5877825732302570","text":"United States","short_code":"us"}]} 2 | {"id":"address.8277619442849160","type":"Feature","text":"SE Water Ave","place_name":"1945 SE Water Ave, Portland, Oregon 97202, United States","relevance":0.9959999999999999,"properties":{},"bbox":[-122.66695099999998,45.50678949999998,-122.66158239999996,45.515469999999986],"center":[-122.665016,45.508056],"geometry":{"type":"Point","coordinates":[-122.665016,45.508056]},"address":"1945","context":[{"id":"neighborhood.10653013892548830","text":"Central Eastside"},{"id":"place.39347","text":"Portland"},{"id":"postcode.13219706552282580","text":"97202"},{"id":"region.16025278559490090","text":"Oregon"},{"id":"country.5877825732302570","text":"United States","short_code":"us"}]} 3 | -------------------------------------------------------------------------------- /mapboxcli/__init__.py: -------------------------------------------------------------------------------- 1 | # Command line interface to Mapbox Web Services 2 | __version__ = '0.8.0' 3 | -------------------------------------------------------------------------------- /mapboxcli/__main__.py: -------------------------------------------------------------------------------- 1 | # This module allows the Mapbox CLI to be invoked more quickly 2 | # without scanning the PYTHONPATH for 'console_script' entry points. 3 | # Usage: 4 | # 5 | # $ python -m mapboxcli --help 6 | 7 | import sys 8 | 9 | from mapboxcli.scripts.cli import main_group 10 | 11 | sys.exit(main_group()) 12 | -------------------------------------------------------------------------------- /mapboxcli/compat.py: -------------------------------------------------------------------------------- 1 | # compatibility module. 2 | 3 | import itertools 4 | import sys 5 | 6 | from six.moves import configparser 7 | 8 | 9 | map = itertools.imap if sys.version_info < (3,) else map 10 | 11 | -------------------------------------------------------------------------------- /mapboxcli/errors.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | class MapboxCLIException(click.ClickException): 4 | pass 5 | -------------------------------------------------------------------------------- /mapboxcli/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # module. 2 | -------------------------------------------------------------------------------- /mapboxcli/scripts/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main click group for CLI 3 | """ 4 | 5 | import logging 6 | import os 7 | import sys 8 | 9 | import click 10 | import cligj 11 | 12 | import mapboxcli 13 | from mapboxcli.compat import configparser 14 | from mapboxcli.scripts import ( 15 | config, geocoding, directions, mapmatching, uploads, static, datasets) 16 | 17 | 18 | def configure_logging(verbosity): 19 | log_level = max(10, 30 - 10 * verbosity) 20 | logging.basicConfig(stream=sys.stderr, level=log_level) 21 | 22 | 23 | def read_config(cfg): 24 | parser = configparser.ConfigParser() 25 | parser.read(cfg) 26 | rv = {} 27 | for section in parser.sections(): 28 | for key, value in parser.items(section): 29 | rv['{0}.{1}'.format(section, key)] = value 30 | return rv 31 | 32 | 33 | @click.group() 34 | @click.version_option(version=mapboxcli.__version__, message='%(version)s') 35 | @cligj.verbose_opt 36 | @cligj.quiet_opt 37 | @click.option('--access-token', help="Your Mapbox access token.") 38 | @click.option('--config', '-c', type=click.Path(exists=True, 39 | resolve_path=True), 40 | help="Config file (default: '{0}/mapbox.ini'".format( 41 | click.get_app_dir('mapbox'))) 42 | @click.pass_context 43 | def main_group(ctx, verbose, quiet, access_token, config): 44 | """This is the command line interface to Mapbox web services. 45 | 46 | Mapbox web services require an access token. Your token is shown 47 | on the https://www.mapbox.com/studio/account/tokens/ page when you are 48 | logged in. The token can be provided on the command line 49 | 50 | $ mapbox --access-token MY_TOKEN ... 51 | 52 | as an environment variable named MAPBOX_ACCESS_TOKEN (higher 53 | precedence) or MapboxAccessToken (lower precedence). 54 | 55 | \b 56 | $ export MAPBOX_ACCESS_TOKEN=MY_TOKEN 57 | $ mapbox ... 58 | 59 | or in a config file 60 | 61 | \b 62 | ; configuration file mapbox.ini 63 | [mapbox] 64 | access-token = MY_TOKEN 65 | 66 | The OS-dependent default config file path is something like 67 | 68 | \b 69 | ~/Library/Application Support/mapbox/mapbox.ini 70 | ~/.config/mapbox/mapbox.ini 71 | ~/.mapbox/mapbox.ini 72 | 73 | """ 74 | ctx.obj = {} 75 | config = config or os.path.join(click.get_app_dir('mapbox'), 'mapbox.ini') 76 | cfg = read_config(config) 77 | if cfg: 78 | ctx.obj['config_file'] = config 79 | ctx.obj['cfg'] = cfg 80 | ctx.default_map = cfg 81 | 82 | verbosity = (os.environ.get('MAPBOX_VERBOSE') or 83 | ctx.lookup_default('mapbox.verbosity') or 0) 84 | if verbose or quiet: 85 | verbosity = verbose - quiet 86 | verbosity = int(verbosity) 87 | configure_logging(verbosity) 88 | 89 | access_token = (access_token or os.environ.get('MAPBOX_ACCESS_TOKEN') or 90 | os.environ.get('MapboxAccessToken') or 91 | ctx.lookup_default('mapbox.access-token')) 92 | 93 | ctx.obj['verbosity'] = verbosity 94 | ctx.obj['access_token'] = access_token 95 | 96 | 97 | # mapbox commands are added here. 98 | main_group.add_command(config.config) 99 | main_group.add_command(geocoding.geocoding) 100 | main_group.add_command(directions.directions) 101 | main_group.add_command(mapmatching.match) 102 | main_group.add_command(uploads.upload) 103 | main_group.add_command(static.staticmap) 104 | main_group.add_command(datasets.datasets) 105 | -------------------------------------------------------------------------------- /mapboxcli/scripts/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import click 4 | 5 | 6 | @click.command(short_help="Show all config settings.") 7 | @click.pass_context 8 | def config(ctx): 9 | """Show access token and other configuration settings. 10 | 11 | The access token and command verbosity level can be set on the 12 | command line, as environment variables, and in mapbox.ini config 13 | files. 14 | """ 15 | ctx.default_map = ctx.obj['cfg'] 16 | click.echo("CLI:") 17 | click.echo("access-token = {0}".format(ctx.obj['access_token'])) 18 | click.echo("verbosity = {0}".format(ctx.obj['verbosity'])) 19 | click.echo("") 20 | 21 | click.echo("Environment:") 22 | if 'MAPBOX_ACCESS_TOKEN' in os.environ: 23 | click.echo("MAPBOX_ACCESS_TOKEN = {0}".format( 24 | os.environ['MAPBOX_ACCESS_TOKEN'])) 25 | if 'MapboxAccessToken' in os.environ: 26 | click.echo("MapboxAccessToken = {0}".format( 27 | os.environ['MapboxAccessToken'])) 28 | if 'MAPBOX_VERBOSE' in os.environ: 29 | click.echo("MAPBOX_VERBOSE = {0}".format( 30 | os.environ['MAPBOX_VERBOSE'])) 31 | click.echo("") 32 | 33 | if 'config_file' in ctx.obj: 34 | click.echo("Config file {0}:".format(ctx.obj['config_file'])) 35 | for key, value in ctx.default_map.items(): 36 | click.echo("{0} = {1}".format(key, value)) 37 | click.echo("") 38 | -------------------------------------------------------------------------------- /mapboxcli/scripts/datasets.py: -------------------------------------------------------------------------------- 1 | # Datasets. 2 | 3 | import json 4 | 5 | import click 6 | 7 | import mapbox 8 | from mapboxcli.errors import MapboxCLIException 9 | 10 | 11 | @click.group(short_help="Read and write Mapbox datasets (has subcommands)") 12 | @click.pass_context 13 | def datasets(ctx): 14 | """Read and write GeoJSON from Mapbox-hosted datasets 15 | 16 | All endpoints require authentication. An access token with 17 | appropriate dataset scopes is required, see `mapbox --help`. 18 | 19 | Note that this API is currently a limited-access beta. 20 | """ 21 | 22 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 23 | service = mapbox.Datasets(access_token=access_token) 24 | ctx.obj['service'] = service 25 | 26 | 27 | @datasets.command(short_help="List datasets") 28 | @click.option('--output', '-o', default='-', help="Save output to a file") 29 | @click.pass_context 30 | def list(ctx, output): 31 | """List datasets. 32 | 33 | Prints a list of objects describing datasets. 34 | 35 | $ mapbox datasets list 36 | 37 | All endpoints require authentication. An access token with 38 | `datasets:read` scope is required, see `mapbox --help`. 39 | """ 40 | 41 | stdout = click.open_file(output, 'w') 42 | service = ctx.obj.get('service') 43 | res = service.list() 44 | 45 | if res.status_code == 200: 46 | click.echo(res.text, file=stdout) 47 | else: 48 | raise MapboxCLIException(res.text.strip()) 49 | 50 | 51 | @datasets.command(short_help="Create an empty dataset") 52 | @click.option('--name', '-n', default=None, help="Name for the dataset") 53 | @click.option('--description', '-d', default=None, 54 | help="Description for the dataset") 55 | @click.pass_context 56 | def create(ctx, name, description): 57 | """Create a new dataset. 58 | 59 | Prints a JSON object containing the attributes 60 | of the new dataset. 61 | 62 | $ mapbox datasets create 63 | 64 | All endpoints require authentication. An access token with 65 | `datasets:write` scope is required, see `mapbox --help`. 66 | """ 67 | 68 | service = ctx.obj.get('service') 69 | res = service.create(name, description) 70 | 71 | if res.status_code == 200: 72 | click.echo(res.text) 73 | else: 74 | raise MapboxCLIException(res.text.strip()) 75 | 76 | 77 | @datasets.command(name="read-dataset", 78 | short_help="Return information about a dataset") 79 | @click.argument('dataset', required=True) 80 | @click.option('--output', '-o', default='-', help="Save output to a file") 81 | @click.pass_context 82 | def read_dataset(ctx, dataset, output): 83 | """Read the attributes of a dataset. 84 | 85 | Prints a JSON object containing the attributes 86 | of a dataset. The attributes: owner (a Mapbox account), 87 | id (dataset id), created (Unix timestamp), modified 88 | (timestamp), name (string), and description (string). 89 | 90 | $ mapbox datasets read-dataset dataset-id 91 | 92 | All endpoints require authentication. An access token with 93 | `datasets:read` scope is required, see `mapbox --help`. 94 | """ 95 | 96 | stdout = click.open_file(output, 'w') 97 | service = ctx.obj.get('service') 98 | res = service.read_dataset(dataset) 99 | 100 | if res.status_code == 200: 101 | click.echo(res.text, file=stdout) 102 | else: 103 | raise MapboxCLIException(res.text.strip()) 104 | 105 | 106 | @datasets.command(name="update-dataset", 107 | short_help="Update information about a dataset") 108 | @click.argument('dataset', required=True) 109 | @click.option('--name', '-n', default=None, help="Name for the dataset") 110 | @click.option('--description', '-d', default=None, 111 | help="Description for the dataset") 112 | @click.pass_context 113 | def update_dataset(ctx, dataset, name, description): 114 | """Update the name and description of a dataset. 115 | 116 | Prints a JSON object containing the updated dataset 117 | attributes. 118 | 119 | $ mapbox datasets update-dataset dataset-id 120 | 121 | All endpoints require authentication. An access token with 122 | `datasets:write` scope is required, see `mapbox --help`. 123 | """ 124 | 125 | service = ctx.obj.get('service') 126 | res = service.update_dataset(dataset, name, description) 127 | 128 | if res.status_code == 200: 129 | click.echo(res.text) 130 | else: 131 | raise MapboxCLIException(res.text.strip()) 132 | 133 | 134 | @datasets.command(name="delete-dataset", short_help="Delete a dataset") 135 | @click.argument('dataset', required=True) 136 | @click.pass_context 137 | def delete_dataset(ctx, dataset): 138 | """Delete a dataset. 139 | 140 | $ mapbox datasets delete-dataset dataset-id 141 | 142 | All endpoints require authentication. An access token with 143 | `datasets:write` scope is required, see `mapbox --help`. 144 | """ 145 | 146 | service = ctx.obj.get('service') 147 | res = service.delete_dataset(dataset) 148 | 149 | if res.status_code != 204: 150 | raise MapboxCLIException(res.text.strip()) 151 | 152 | 153 | @datasets.command(name="list-features", 154 | short_help="List features in a dataset") 155 | @click.argument('dataset', required=True) 156 | @click.option('--reverse', '-r', default=False, 157 | help="Read features in reverse") 158 | @click.option('--start', '-s', default=None, 159 | help="Feature id to begin reading from") 160 | @click.option('--limit', '-l', default=None, 161 | help="Maximum number of features to return") 162 | @click.option('--output', '-o', default='-', 163 | help="Save output to a file") 164 | @click.pass_context 165 | def list_features(ctx, dataset, reverse, start, limit, output): 166 | """Get features of a dataset. 167 | 168 | Prints the features of the dataset as a GeoJSON feature collection. 169 | 170 | $ mapbox datasets list-features dataset-id 171 | 172 | All endpoints require authentication. An access token with 173 | `datasets:read` scope is required, see `mapbox --help`. 174 | """ 175 | 176 | stdout = click.open_file(output, 'w') 177 | service = ctx.obj.get('service') 178 | res = service.list_features(dataset, reverse, start, limit) 179 | 180 | if res.status_code == 200: 181 | click.echo(res.text, file=stdout) 182 | else: 183 | raise MapboxCLIException(res.text.strip()) 184 | 185 | 186 | @datasets.command(name="read-feature", 187 | short_help="Read a single feature from a dataset") 188 | @click.argument('dataset', required=True) 189 | @click.argument('fid', required=True) 190 | @click.option('--output', '-o', default='-', help="Save output to a file") 191 | @click.pass_context 192 | def read_feature(ctx, dataset, fid, output): 193 | """Read a dataset feature. 194 | 195 | Prints a GeoJSON representation of the feature. 196 | 197 | $ mapbox datasets read-feature dataset-id feature-id 198 | 199 | All endpoints require authentication. An access token with 200 | `datasets:read` scope is required, see `mapbox --help`. 201 | """ 202 | 203 | stdout = click.open_file(output, 'w') 204 | service = ctx.obj.get('service') 205 | res = service.read_feature(dataset, fid) 206 | 207 | if res.status_code == 200: 208 | click.echo(res.text, file=stdout) 209 | else: 210 | raise MapboxCLIException(res.text.strip()) 211 | 212 | 213 | @datasets.command(name="put-feature", 214 | short_help="Insert or update a single feature in a dataset") 215 | @click.argument('dataset', required=True) 216 | @click.argument('fid', required=True) 217 | @click.argument('feature', required=False, default=None) 218 | @click.option('--input', '-i', default='-', 219 | help="File containing a feature to put") 220 | @click.pass_context 221 | def put_feature(ctx, dataset, fid, feature, input): 222 | """Create or update a dataset feature. 223 | 224 | The semantics of HTTP PUT apply: if the dataset has no feature 225 | with the given `fid` a new feature will be created. Returns a 226 | GeoJSON representation of the new or updated feature. 227 | 228 | $ mapbox datasets put-feature dataset-id feature-id 'geojson-feature' 229 | 230 | All endpoints require authentication. An access token with 231 | `datasets:write` scope is required, see `mapbox --help`. 232 | """ 233 | 234 | if feature is None: 235 | stdin = click.open_file(input, 'r') 236 | feature = stdin.read() 237 | 238 | feature = json.loads(feature) 239 | 240 | service = ctx.obj.get('service') 241 | res = service.update_feature(dataset, fid, feature) 242 | 243 | if res.status_code == 200: 244 | click.echo(res.text) 245 | else: 246 | raise MapboxCLIException(res.text.strip()) 247 | 248 | 249 | @datasets.command(name="delete-feature", 250 | short_help="Delete a single feature from a dataset") 251 | @click.argument('dataset', required=True) 252 | @click.argument('fid', required=True) 253 | @click.pass_context 254 | def delete_feature(ctx, dataset, fid): 255 | """Delete a feature. 256 | 257 | $ mapbox datasets delete-feature dataset-id feature-id 258 | 259 | All endpoints require authentication. An access token with 260 | `datasets:write` scope is required, see `mapbox --help`. 261 | """ 262 | 263 | service = ctx.obj.get('service') 264 | res = service.delete_feature(dataset, fid) 265 | 266 | if res.status_code != 204: 267 | raise MapboxCLIException(res.text.strip()) 268 | 269 | 270 | @datasets.command(name="create-tileset", 271 | short_help="Generate a tileset from a dataset") 272 | @click.argument('dataset', required=True) 273 | @click.argument('tileset', required=True) 274 | @click.option('--name', '-n', default=None, help="Name for the tileset") 275 | @click.pass_context 276 | def create_tileset(ctx, dataset, tileset, name): 277 | """Create a vector tileset from a dataset. 278 | 279 | $ mapbox datasets create-tileset dataset-id username.data 280 | 281 | Note that the tileset must start with your username and the dataset 282 | must be one that you own. To view processing status, visit 283 | https://www.mapbox.com/data/. You may not generate another tilesets 284 | from the same dataset until the first processing job has completed. 285 | 286 | All endpoints require authentication. An access token with 287 | `uploads:write` scope is required, see `mapbox --help`. 288 | """ 289 | 290 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 291 | service = mapbox.Uploader(access_token=access_token) 292 | 293 | uri = "mapbox://datasets/{username}/{dataset}".format( 294 | username=tileset.split('.')[0], dataset=dataset) 295 | 296 | res = service.create(uri, tileset, name) 297 | 298 | if res.status_code == 201: 299 | click.echo(res.text) 300 | else: 301 | raise MapboxCLIException(res.text.strip()) 302 | -------------------------------------------------------------------------------- /mapboxcli/scripts/directions.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | import click 5 | import cligj 6 | 7 | import mapbox 8 | from mapboxcli.errors import MapboxCLIException 9 | 10 | 11 | def waypoint_snapping_callback(ctx, param, value): 12 | results = [] 13 | 14 | tuple_pattern = re.compile("[,]") 15 | int_pattern = re.compile("[0-9]") 16 | 17 | # value is an n-tuple, each element of 18 | # which contains input from the user. 19 | # 20 | # Iterate over each element, determining 21 | # whether to convert it to a tuple, 22 | # convert it to an int, or leave it as 23 | # a str. 24 | # 25 | # Append each element to results, which 26 | # the Directions SDK will attempt to 27 | # validate. 28 | 29 | if len(value) == 0: 30 | return None 31 | 32 | for element in value: 33 | 34 | # If the element contains a comma, then assume 35 | # that the user intended to pass in a tuple. 36 | # 37 | # Convert each item in the element to an int, 38 | # and create a tuple containing all items. 39 | # 40 | # Raise an error if the item is not a valid int. 41 | # 42 | # (The SDK accepts a three-tuple with ints for 43 | # radius, angle, and range.) 44 | 45 | if re.search(tuple_pattern, element): 46 | element = re.split(tuple_pattern, element) 47 | 48 | for index in range(0, len(element)): 49 | try: 50 | element[index] = int(element[index]) 51 | except ValueError as exc: 52 | raise mapbox.errors.ValidationError(str(exc)) 53 | 54 | element = tuple(element) 55 | 56 | results.append(element) 57 | 58 | # If the element contains a decimal number but not 59 | # a comma, then assume that the user intended to 60 | # pass in an int. 61 | # 62 | # Convert the element to an int. 63 | # 64 | # Raise an error if the item is not a valid int. 65 | # 66 | # (The Directions SDK accepts an int for radius.) 67 | 68 | elif re.search(int_pattern, element): 69 | try: 70 | element = int(element) 71 | except ValueError as exc: 72 | raise mapbox.errors.ValidationError(str(exc)) 73 | 74 | results.append(element) 75 | 76 | # If the element contains neither a decimal number 77 | # nor a comma, then assume that the user intended 78 | # to pass in a str. 79 | # 80 | # Do nothing since the element is already a str. 81 | # 82 | # (The Directions SDK accepts a str for unlimited radius.) 83 | 84 | else: 85 | results.append(element) 86 | 87 | return results 88 | 89 | 90 | @click.command(short_help="Routing between waypoints") 91 | 92 | @cligj.features_in_arg 93 | 94 | @click.option( 95 | "--profile", 96 | type=click.Choice(mapbox.Directions.valid_profiles), 97 | default="mapbox/driving", 98 | help="Routing profile" 99 | ) 100 | 101 | @click.option( 102 | "--alternatives/--no-alternatives", 103 | default=True, 104 | help="Whether to try to return alternative routes" 105 | ) 106 | 107 | @click.option( 108 | "--geometries", 109 | type=click.Choice(mapbox.Directions.valid_geom_encoding), 110 | default="geojson", 111 | help="Format of returned geometry" 112 | ) 113 | 114 | # Directions.valid_geom_overview contains two 115 | # elements of type str and one element of type bool. 116 | # This causes the Directions CLI's --help option to 117 | # raise a TypeError. To prevent this, we convert 118 | # the bool to a str. 119 | 120 | @click.option( 121 | "--overview", 122 | type=click.Choice(str(item) for item in mapbox.Directions.valid_geom_overview), 123 | help="Type of returned overview geometry" 124 | ) 125 | 126 | @click.option( 127 | "--steps/--no-steps", 128 | default=True, 129 | help="Whether to return steps and turn-by-turn instructions" 130 | ) 131 | 132 | @click.option( 133 | "--continue-straight/--no-continue-straight", 134 | default=True, 135 | help="Whether to see the allowed direction of travel when departing the original waypoint" 136 | ) 137 | 138 | @click.option( 139 | "--waypoint-snapping", 140 | multiple=True, 141 | callback=waypoint_snapping_callback, 142 | help="Controls waypoint snapping" 143 | ) 144 | 145 | @click.option( 146 | "--annotations", 147 | help="Additional metadata along the route" 148 | ) 149 | 150 | @click.option( 151 | "--language", 152 | help="Language of returned turn-by-turn instructions" 153 | ) 154 | 155 | @click.option( 156 | "-o", 157 | "--output", 158 | default="-", 159 | help="Save output to a file" 160 | ) 161 | 162 | @click.pass_context 163 | def directions(ctx, features, profile, alternatives, 164 | geometries, overview, steps, continue_straight, 165 | waypoint_snapping, annotations, language, output): 166 | """The Mapbox Directions API will show you how to get 167 | where you're going. 168 | 169 | mapbox directions "[0, 0]" "[1, 1]" 170 | 171 | An access token is required. See "mapbox --help". 172 | """ 173 | 174 | access_token = (ctx.obj and ctx.obj.get("access_token")) or None 175 | 176 | service = mapbox.Directions(access_token=access_token) 177 | 178 | # The Directions SDK expects False to be 179 | # a bool, not a str. 180 | 181 | if overview == "False": 182 | overview = False 183 | 184 | # When using waypoint snapping, the 185 | # Directions SDK expects features to be 186 | # a list, not a generator. 187 | 188 | if waypoint_snapping is not None: 189 | features = list(features) 190 | 191 | if annotations: 192 | annotations = annotations.split(",") 193 | 194 | stdout = click.open_file(output, "w") 195 | 196 | try: 197 | res = service.directions( 198 | features, 199 | profile=profile, 200 | alternatives=alternatives, 201 | geometries=geometries, 202 | overview=overview, 203 | steps=steps, 204 | continue_straight=continue_straight, 205 | waypoint_snapping=waypoint_snapping, 206 | annotations=annotations, 207 | language=language 208 | ) 209 | except mapbox.errors.ValidationError as exc: 210 | raise click.BadParameter(str(exc)) 211 | 212 | if res.status_code == 200: 213 | if geometries == "geojson": 214 | click.echo(json.dumps(res.geojson()), file=stdout) 215 | else: 216 | click.echo(res.text, file=stdout) 217 | else: 218 | raise MapboxCLIException(res.text.strip()) 219 | -------------------------------------------------------------------------------- /mapboxcli/scripts/geocoding.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from itertools import chain 3 | import json 4 | import re 5 | 6 | import click 7 | import mapbox 8 | from mapbox import Geocoder 9 | 10 | from mapboxcli.compat import map 11 | from mapboxcli.errors import MapboxCLIException 12 | 13 | 14 | def iter_query(query): 15 | """Accept a filename, stream, or string. 16 | Returns an iterator over lines of the query.""" 17 | try: 18 | itr = click.open_file(query).readlines() 19 | except IOError: 20 | itr = [query] 21 | return itr 22 | 23 | 24 | def coords_from_query(query): 25 | """Transform a query line into a (lng, lat) pair of coordinates.""" 26 | try: 27 | coords = json.loads(query) 28 | except ValueError: 29 | vals = re.split(r'[,\s]+', query.strip()) 30 | coords = [float(v) for v in vals] 31 | return tuple(coords[:2]) 32 | 33 | 34 | def echo_headers(headers, file=None): 35 | """Echo headers, sorted.""" 36 | for k, v in sorted(headers.items()): 37 | click.echo("{0}: {1}".format(k.title(), v), file=file) 38 | click.echo(file=file) 39 | 40 | 41 | @click.command(short_help="Geocode an address or coordinates.") 42 | @click.argument('query', default='-', required=False) 43 | @click.option( 44 | '--forward/--reverse', 45 | default=True, 46 | help="Perform a forward or reverse geocode. [default: forward]") 47 | @click.option('--include', '-i', 'include_headers', 48 | is_flag=True, default=False, 49 | help="Include HTTP headers in the output.") 50 | @click.option( 51 | '--lat', type=float, default=None, 52 | help="Bias results toward this latitude (decimal degrees). --lon " 53 | "is also required.") 54 | @click.option( 55 | '--lon', type=float, default=None, 56 | help="Bias results toward this longitude (decimal degrees). --lat " 57 | "is also required.") 58 | @click.option( 59 | '--place-type', '-t', multiple=True, metavar='NAME', default=None, 60 | type=click.Choice(Geocoder().place_types.keys()), 61 | help="Restrict results to one or more place types.") 62 | @click.option('--output', '-o', default='-', help="Save output to a file.") 63 | @click.option('--dataset', '-d', default='mapbox.places', 64 | type=click.Choice(("mapbox.places", "mapbox.places-permanent")), 65 | help="Source dataset for geocoding, [default: mapbox.places]") 66 | @click.option('--country', default=None, 67 | help="Restrict forward geocoding to specified country codes," 68 | "comma-separated") 69 | @click.option('--bbox', default=None, 70 | help="Restrict forward geocoding to specified bounding box," 71 | "given in minX,minY,maxX,maxY coordinates.") 72 | @click.option('--features', is_flag=True, default=False, 73 | help="Return results as line-delimited GeoJSON Feature sequence, " 74 | "not a FeatureCollection") 75 | @click.option('--limit', type=int, default=None, 76 | help="Limit the number of returned features") 77 | @click.pass_context 78 | def geocoding(ctx, query, forward, include_headers, lat, lon, 79 | place_type, output, dataset, country, bbox, features, limit): 80 | """This command returns places matching an address (forward mode) or 81 | places matching coordinates (reverse mode). 82 | 83 | In forward (the default) mode the query argument shall be an address 84 | such as '1600 pennsylvania ave nw'. 85 | 86 | $ mapbox geocoding '1600 pennsylvania ave nw' 87 | 88 | In reverse mode the query argument shall be a JSON encoded array 89 | of longitude and latitude (in that order) in decimal degrees. 90 | 91 | $ mapbox geocoding --reverse '[-77.4371, 37.5227]' 92 | 93 | An access token is required, see `mapbox --help`. 94 | """ 95 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 96 | stdout = click.open_file(output, 'w') 97 | 98 | geocoder = Geocoder(name=dataset, access_token=access_token) 99 | 100 | if forward: 101 | if country: 102 | country = [x.lower() for x in country.split(",")] 103 | 104 | if bbox: 105 | try: 106 | bbox = tuple(map(float, bbox.split(','))) 107 | except ValueError: 108 | bbox = json.loads(bbox) 109 | 110 | for q in iter_query(query): 111 | try: 112 | resp = geocoder.forward( 113 | q, types=place_type, lat=lat, lon=lon, 114 | country=country, bbox=bbox, limit=limit) 115 | except mapbox.errors.ValidationError as exc: 116 | raise click.BadParameter(str(exc)) 117 | 118 | if include_headers: 119 | echo_headers(resp.headers, file=stdout) 120 | if resp.status_code == 200: 121 | if features: 122 | collection = json.loads(resp.text) 123 | for feat in collection['features']: 124 | click.echo(json.dumps(feat), file=stdout) 125 | else: 126 | click.echo(resp.text, file=stdout) 127 | else: 128 | raise MapboxCLIException(resp.text.strip()) 129 | else: 130 | for lon, lat in map(coords_from_query, iter_query(query)): 131 | try: 132 | resp = geocoder.reverse( 133 | lon=lon, lat=lat, types=place_type, limit=limit) 134 | except mapbox.errors.ValidationError as exc: 135 | raise click.BadParameter(str(exc)) 136 | 137 | if include_headers: 138 | echo_headers(resp.headers, file=stdout) 139 | if resp.status_code == 200: 140 | if features: 141 | collection = json.loads(resp.text) 142 | for feat in collection['features']: 143 | click.echo(json.dumps(feat), file=stdout) 144 | else: 145 | click.echo(resp.text, file=stdout) 146 | else: 147 | raise MapboxCLIException(resp.text.strip()) 148 | -------------------------------------------------------------------------------- /mapboxcli/scripts/mapmatching.py: -------------------------------------------------------------------------------- 1 | import click 2 | import cligj 3 | 4 | import mapbox 5 | from mapboxcli.errors import MapboxCLIException 6 | 7 | @click.command('mapmatching', short_help="Snap GPS traces to OpenStreetMap") 8 | @cligj.features_in_arg 9 | @click.option("--gps-precision", default=4, type=int, 10 | help="Assumed precision of tracking device (default 4 meters)") 11 | @click.option('--profile', default="mapbox.driving", 12 | type=click.Choice(mapbox.MapMatcher().valid_profiles), 13 | help="Mapbox profile id") 14 | @click.pass_context 15 | def match(ctx, features, profile, gps_precision): 16 | """Mapbox Map Matching API lets you use snap your GPS traces 17 | to the OpenStreetMap road and path network. 18 | 19 | $ mapbox mapmatching trace.geojson 20 | 21 | An access token is required, see `mapbox --help`. 22 | """ 23 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 24 | 25 | features = list(features) 26 | if len(features) != 1: 27 | raise click.BadParameter( 28 | "Mapmatching requires a single LineString feature") 29 | 30 | service = mapbox.MapMatcher(access_token=access_token) 31 | try: 32 | res = service.match( 33 | features[0], 34 | profile=profile, 35 | gps_precision=gps_precision) 36 | except mapbox.errors.ValidationError as exc: 37 | raise click.BadParameter(str(exc)) 38 | 39 | if res.status_code == 200: 40 | stdout = click.open_file('-', 'w') 41 | click.echo(res.text, file=stdout) 42 | else: 43 | raise MapboxCLIException(res.text.strip()) 44 | -------------------------------------------------------------------------------- /mapboxcli/scripts/static.py: -------------------------------------------------------------------------------- 1 | import click 2 | import cligj 3 | 4 | import mapbox 5 | from mapboxcli.errors import MapboxCLIException 6 | 7 | 8 | @click.command(short_help="Static map images.") 9 | @click.argument('mapid', required=True) 10 | @click.argument('output', type=click.File('wb'), required=True) 11 | @click.option('--features', help="GeoJSON Features to render as overlay") 12 | @click.option('--lat', type=float, help="Latitude") 13 | @click.option('--lon', type=float, help="Longitude") 14 | @click.option('--zoom', type=int, help="Zoom") 15 | @click.option('--size', default=(600, 600), nargs=2, type=(int, int), 16 | help="Image width and height in pixels") 17 | @click.pass_context 18 | def staticmap(ctx, mapid, output, features, lat, lon, zoom, size): 19 | """ 20 | Generate static map images from existing Mapbox map ids. 21 | Optionally overlay with geojson features. 22 | 23 | $ mapbox staticmap --features features.geojson mapbox.satellite out.png 24 | $ mapbox staticmap --lon -61.7 --lat 12.1 --zoom 12 mapbox.satellite out2.png 25 | 26 | An access token is required, see `mapbox --help`. 27 | """ 28 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 29 | if features: 30 | features = list( 31 | cligj.normalize_feature_inputs(None, 'features', [features])) 32 | 33 | service = mapbox.Static(access_token=access_token) 34 | 35 | try: 36 | res = service.image( 37 | mapid, 38 | lon=lon, lat=lat, z=zoom, 39 | width=size[0], height=size[1], 40 | features=features, sort_keys=True) 41 | except mapbox.errors.ValidationError as exc: 42 | raise click.BadParameter(str(exc)) 43 | 44 | if res.status_code == 200: 45 | output.write(res.content) 46 | else: 47 | raise MapboxCLIException(res.text.strip()) 48 | -------------------------------------------------------------------------------- /mapboxcli/scripts/uploads.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | import os 3 | import sys 4 | 5 | import click 6 | 7 | import mapbox 8 | from mapboxcli.errors import MapboxCLIException 9 | 10 | 11 | @click.command(short_help="Upload datasets to Mapbox accounts") 12 | @click.argument('tileset', required=True, type=str, metavar='TILESET') 13 | @click.argument('datasource', type=str, default='-', metavar='[SOURCE]') 14 | @click.option('--name', default=None, help="Name for the data upload") 15 | @click.option('--patch', is_flag=True, default=False, help="Enable patch mode") 16 | @click.pass_context 17 | def upload(ctx, tileset, datasource, name, patch): 18 | """Upload data to Mapbox accounts. 19 | 20 | Uploaded data lands at https://www.mapbox.com/data/ and can be used 21 | in new or existing projects. All endpoints require authentication. 22 | 23 | You can specify the tileset id and input file 24 | 25 | $ mapbox upload username.data mydata.geojson 26 | 27 | Or specify just the tileset id and take an input file on stdin 28 | 29 | $ cat mydata.geojson | mapbox upload username.data 30 | 31 | The --name option defines the title as it appears in Studio and 32 | defaults to the last part of the tileset id, e.g. "data" 33 | 34 | Note that the tileset must start with your username. An access 35 | token with upload scope is required, see `mapbox --help`. 36 | 37 | Your account must be flagged in order to use the patch mode 38 | feature. 39 | """ 40 | access_token = (ctx.obj and ctx.obj.get('access_token')) or None 41 | 42 | service = mapbox.Uploader(access_token=access_token) 43 | 44 | if name is None: 45 | name = tileset.split(".")[-1] 46 | 47 | if datasource.startswith('https://'): 48 | # Skip staging. Note this this only works for specific buckets. 49 | res = service.create(datasource, tileset, name=name, patch=patch) 50 | 51 | else: 52 | sourcefile = click.File('rb')(datasource) 53 | 54 | if hasattr(sourcefile, 'name'): 55 | filelen = ( 56 | 1 if sourcefile.name == '' 57 | else os.stat(sourcefile.name).st_size) 58 | else: 59 | filelen = (len(sourcefile.getbuffer()) 60 | if hasattr(sourcefile, 'getbuffer') else 1) 61 | 62 | with click.progressbar(length=filelen, label='Uploading data source', 63 | fill_char="#", empty_char='-', 64 | file=sys.stderr) as bar: 65 | 66 | def callback(num_bytes): 67 | """Update the progress bar""" 68 | bar.update(num_bytes) 69 | 70 | res = service.upload(sourcefile, tileset, name, patch=patch, 71 | callback=callback) 72 | 73 | if res.status_code == 201: 74 | click.echo(res.text) 75 | else: 76 | raise MapboxCLIException(res.text.strip()) 77 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -e git+https://github.com/mapbox/mapbox-sdk-py.git#egg=mapbox 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | # Parse the version from the mapbox module. 5 | with open('mapboxcli/__init__.py') as f: 6 | for line in f: 7 | if "__version__" in line: 8 | version = line.split("=")[1].strip().strip('"').strip("'") 9 | continue 10 | 11 | setup(name='mapboxcli', 12 | version=version, 13 | description="Command line interface to Mapbox Web Services", 14 | classifiers=['Development Status :: 5 - Production/Stable', 15 | 'Environment :: Console', 16 | 'Intended Audience :: Developers', 17 | 'License :: OSI Approved :: MIT License', 18 | 'Programming Language :: Python', 19 | 'Programming Language :: Python :: 2.7', 20 | 'Programming Language :: Python :: 3', 21 | 'Programming Language :: Python :: 3.3', 22 | 'Programming Language :: Python :: 3.4', 23 | 'Programming Language :: Python :: 3.5', 24 | 'Programming Language :: Python :: 3.6', 25 | 'Programming Language :: Python :: Implementation :: PyPy'], 26 | keywords='', 27 | author="Sean Gillies", 28 | author_email='sean@mapbox.com', 29 | url='https://github.com/mapbox/mapbox-cli-py', 30 | license='MIT', 31 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 32 | include_package_data=True, 33 | zip_safe=False, 34 | install_requires=[ 35 | 'click', 36 | 'click-plugins', 37 | 'cligj>=0.4', 38 | 'mapbox==0.16.1', 39 | 'six'], 40 | extras_require={ 41 | 'test': ['coveralls', 'pytest>=2.8', 'pytest-cov', 'responses', 42 | 'mock']}, 43 | entry_points=""" 44 | [console_scripts] 45 | mapbox=mapboxcli.scripts.cli:main_group 46 | """) 47 | -------------------------------------------------------------------------------- /tests/line_feature.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "type": "LineString", 4 | "coordinates": [ 5 | [ 6 | -122.716307, 7 | 45.509932 8 | ], 9 | [ 10 | -122.716288, 11 | 45.510023 12 | ], 13 | [ 14 | -122.716241, 15 | 45.510104 16 | ] 17 | ] 18 | }, 19 | "type": "Feature", 20 | "properties": { 21 | "summary": "Southwest Main Street - Sunset Highway (US 26)" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | from mapboxcli.scripts.cli import main_group 4 | 5 | 6 | def test_config_file(tmpdir): 7 | """Get options from a config file.""" 8 | config = str(tmpdir.join('mapbox.ini')) 9 | with open(config, 'w') as cfg: 10 | cfg.write("[mapbox]\n") 11 | cfg.write("access-token = pk.test_config_file\n") 12 | cfg.write("verbosity = 11\n") 13 | runner = CliRunner() 14 | result = runner.invoke(main_group, ['-c', config, 'config'], catch_exceptions=False) 15 | assert config in result.output 16 | assert "access-token = pk.test_config_file" in result.output 17 | assert "verbosity = 11" in result.output 18 | 19 | 20 | def test_config_options(): 21 | """Get options from command line.""" 22 | runner = CliRunner() 23 | result = runner.invoke( 24 | main_group, 25 | ['--access-token', 'pk.test_config_options', '-vvvv', '-q', 'config'], 26 | catch_exceptions=False) 27 | assert "Config file" not in result.output 28 | assert "access-token = pk.test_config_options" in result.output 29 | assert "verbosity = 3" in result.output 30 | 31 | 32 | def test_config_envvar(monkeypatch): 33 | """Get access token from MAPBOX_ACCESS_TOKEN.""" 34 | monkeypatch.setenv('MAPBOX_ACCESS_TOKEN', 'pk.test_config_envvar_2') 35 | runner = CliRunner() 36 | result = runner.invoke(main_group, ['config'], catch_exceptions=False) 37 | assert "Config file" not in result.output 38 | assert "access-token = pk.test_config_envvar_2" in result.output 39 | assert "MAPBOX_ACCESS_TOKEN = pk.test_config_envvar_2" in result.output 40 | monkeypatch.undo() 41 | 42 | 43 | def test_config_envvar_2(monkeypatch): 44 | """Get access token from MapboxAccessToken.""" 45 | monkeypatch.setenv('MapboxAccessToken', 'pk.test_config_envvar_2') 46 | monkeypatch.delenv('MAPBOX_ACCESS_TOKEN', raising=False) 47 | runner = CliRunner() 48 | result = runner.invoke(main_group, ['config'], catch_exceptions=False) 49 | assert "Config file" not in result.output 50 | assert "MapboxAccessToken = pk.test_config_envvar_2" in result.output 51 | assert "access-token = pk.test_config_envvar_2" in result.output 52 | monkeypatch.undo() 53 | 54 | 55 | def test_config_envvar_verbosity(monkeypatch): 56 | """Get verbosity from MAPBOX_VERBOSE.""" 57 | monkeypatch.setenv('MAPBOX_VERBOSE', '11') 58 | runner = CliRunner() 59 | result = runner.invoke(main_group, ['config'], catch_exceptions=False) 60 | assert "Config file" not in result.output 61 | assert "verbosity = 11" in result.output 62 | assert "MAPBOX_VERBOSE = 11" in result.output 63 | monkeypatch.undo() 64 | -------------------------------------------------------------------------------- /tests/test_datasets.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import re 3 | import json 4 | 5 | from click.testing import CliRunner 6 | import responses 7 | 8 | from mapboxcli.scripts.cli import main_group 9 | import mapbox 10 | 11 | username = 'testuser' 12 | access_token = 'sk.{0}.test'.format( 13 | base64.b64encode(b'{"u":"testuser"}').decode('utf-8')) 14 | 15 | @responses.activate 16 | def test_cli_dataset_list_stdout(): 17 | datasets = """ 18 | [ 19 | {{ 20 | "owner":"{username}", 21 | "id":"120c1c5aec87030449dfe2dff4e2a7c8", 22 | "name":"first", 23 | "description":"the first one", 24 | "created":"2015-07-03T00:14:09.622Z", 25 | "modified":"2015-07-03T00:14:09.622Z" 26 | }}, 27 | {{ 28 | "owner":"{username}", 29 | "name":"second", 30 | "description":"the second one" 31 | "id":"18571b87d4c139b6d10911d13cb0561f", 32 | "created":"2015-05-05T22:43:10.832Z", 33 | "modified":"2015-05-05T22:43:10.832Z" 34 | }} 35 | ]""".format(username=username) 36 | 37 | datasets = "".join(datasets.split()) 38 | 39 | responses.add( 40 | responses.GET, 41 | 'https://api.mapbox.com/datasets/v1/{0}?access_token={1}'.format(username, access_token), 42 | match_querystring=True, 43 | body=datasets, status=200, 44 | content_type='application/json') 45 | 46 | runner = CliRunner() 47 | result = runner.invoke( 48 | main_group, 49 | ['--access-token', access_token, 50 | 'datasets', 51 | 'list']) 52 | 53 | assert result.exit_code == 0 54 | assert result.output.strip() == datasets.strip() 55 | 56 | @responses.activate 57 | def test_cli_dataset_list_tofile(tmpdir): 58 | tmpfile = str(tmpdir.join('test.list.json')) 59 | 60 | datasets = """ 61 | [ 62 | {{ 63 | "owner":"{username}", 64 | "id":"120c1c5aec87030449dfe2dff4e2a7c8", 65 | "name":"first", 66 | "description":"the first one", 67 | "created":"2015-07-03T00:14:09.622Z", 68 | "modified":"2015-07-03T00:14:09.622Z" 69 | }}, 70 | {{ 71 | "owner":"{username}", 72 | "name":"second", 73 | "description":"the second one" 74 | "id":"18571b87d4c139b6d10911d13cb0561f", 75 | "created":"2015-05-05T22:43:10.832Z", 76 | "modified":"2015-05-05T22:43:10.832Z" 77 | }} 78 | ]""".format(username=username) 79 | 80 | datasets = "".join(datasets.split()) 81 | 82 | responses.add( 83 | responses.GET, 84 | 'https://api.mapbox.com/datasets/v1/{0}?access_token={1}'.format(username, access_token), 85 | match_querystring=True, 86 | body=datasets, status=200, 87 | content_type='application/json') 88 | 89 | 90 | runner = CliRunner() 91 | result = runner.invoke( 92 | main_group, 93 | ['--access-token', access_token, 94 | 'datasets', 95 | 'list', 96 | '--output', tmpfile]) 97 | 98 | assert result.exit_code == 0 99 | assert open(tmpfile).read().strip() == datasets.strip() 100 | assert result.output.strip() == "" 101 | 102 | @responses.activate 103 | def test_cli_dataset_create_noargs(): 104 | created = """ 105 | {{"owner":"{username}", 106 | "id":"cii9dtexw0039uelz7nzk1lq3", 107 | "name":null, 108 | "description":null, 109 | "created":"2015-12-16T22:20:38.847Z", 110 | "modified":"2015-12-16T22:20:38.847Z"}} 111 | """.format(username=username) 112 | 113 | created = "".join(created.split()) 114 | 115 | responses.add( 116 | responses.POST, 117 | 'https://api.mapbox.com/datasets/v1/{0}?access_token={1}'.format(username, access_token), 118 | match_querystring=True, 119 | body=created, status=200, 120 | content_type='application/json') 121 | 122 | runner = CliRunner() 123 | result = runner.invoke( 124 | main_group, 125 | ['--access-token', access_token, 126 | 'datasets', 127 | 'create']) 128 | 129 | assert result.exit_code == 0 130 | assert result.output.strip() == created.strip() 131 | 132 | @responses.activate 133 | def test_cli_dataset_create_withargs(): 134 | name = "the-name" 135 | description = "the-description" 136 | created = """ 137 | {{"owner":"{username}", 138 | "id":"cii9dtexw0039uelz7nzk1lq3", 139 | "name":{name}, 140 | "description":{description}, 141 | "created":"2015-12-16T22:20:38.847Z", 142 | "modified":"2015-12-16T22:20:38.847Z"}} 143 | """.format(username=username, name=name, description=description) 144 | 145 | created = "".join(created.split()) 146 | 147 | responses.add( 148 | responses.POST, 149 | 'https://api.mapbox.com/datasets/v1/{0}?access_token={1}'.format(username, access_token), 150 | match_querystring=True, 151 | body=created, status=200, 152 | content_type='application/json') 153 | 154 | runner = CliRunner() 155 | result = runner.invoke( 156 | main_group, 157 | ['--access-token', access_token, 158 | 'datasets', 159 | 'create', 160 | '--name', name, 161 | '-d', description]) 162 | 163 | assert result.exit_code == 0 164 | assert result.output.strip() == created.strip() 165 | 166 | @responses.activate 167 | def test_cli_dataset_read_dataset_stdout(): 168 | id = "cii9dtexw0039uelz7nzk1lq3" 169 | created = """ 170 | {{"owner":"{username}", 171 | "id":"{id}", 172 | "name":null, 173 | "description":null, 174 | "created":"2015-12-16T22:20:38.847Z", 175 | "modified":"2015-12-16T22:20:38.847Z"}} 176 | """.format(username=username, id=id) 177 | 178 | created = "".join(created.split()) 179 | 180 | responses.add( 181 | responses.GET, 182 | 'https://api.mapbox.com/datasets/v1/{0}/{1}?access_token={2}'.format(username, id, access_token), 183 | match_querystring=True, 184 | body=created, status=200, 185 | content_type='application/json') 186 | 187 | runner = CliRunner() 188 | result = runner.invoke( 189 | main_group, 190 | ['--access-token', access_token, 191 | 'datasets', 192 | 'read-dataset', id]) 193 | 194 | assert result.exit_code == 0 195 | assert result.output.strip() == created.strip() 196 | 197 | @responses.activate 198 | def test_cli_dataset_read_dataset_tofile(tmpdir): 199 | tmpfile = str(tmpdir.join('test.read-dataset.json')) 200 | id = "cii9dtexw0039uelz7nzk1lq3" 201 | created = """ 202 | {{"owner":"{username}", 203 | "id":"{id}", 204 | "name":null, 205 | "description":null, 206 | "created":"2015-12-16T22:20:38.847Z", 207 | "modified":"2015-12-16T22:20:38.847Z"}} 208 | """.format(username=username, id=id) 209 | 210 | created = "".join(created.split()) 211 | 212 | responses.add( 213 | responses.GET, 214 | 'https://api.mapbox.com/datasets/v1/{0}/{1}?access_token={2}'.format(username, id, access_token), 215 | match_querystring=True, 216 | body=created, status=200, 217 | content_type='application/json') 218 | 219 | runner = CliRunner() 220 | result = runner.invoke( 221 | main_group, 222 | ['--access-token', access_token, 223 | 'datasets', 224 | 'read-dataset', id, 225 | '-o', tmpfile]) 226 | 227 | assert result.exit_code == 0 228 | assert open(tmpfile).read().strip() == created.strip() 229 | 230 | @responses.activate 231 | def test_cli_dataset_update_dataset(): 232 | id = "cii9dtexw0039uelz7nzk1lq3" 233 | name = "the-name" 234 | description = "the-description" 235 | created = """ 236 | {{"owner":"{username}", 237 | "id":"{id}", 238 | "name":{name}, 239 | "description":{description}, 240 | "created":"2015-12-16T22:20:38.847Z", 241 | "modified":"2015-12-16T22:20:38.847Z"}} 242 | """.format(username=username, id=id, name=name, description=description) 243 | 244 | created = "".join(created.split()) 245 | 246 | responses.add( 247 | responses.PATCH, 248 | 'https://api.mapbox.com/datasets/v1/{0}/{1}?access_token={2}'.format(username, id, access_token), 249 | match_querystring=True, 250 | body=created, status=200, 251 | content_type='application/json') 252 | 253 | runner = CliRunner() 254 | result = runner.invoke( 255 | main_group, 256 | ['--access-token', access_token, 257 | 'datasets', 258 | 'update-dataset', id, 259 | '--name', name, 260 | '-d', description]) 261 | 262 | assert result.exit_code == 0 263 | assert result.output.strip() == created.strip() 264 | 265 | @responses.activate 266 | def test_cli_dataset_delete_dataset(): 267 | id = "cii9dtexw0039uelz7nzk1lq3" 268 | 269 | responses.add( 270 | responses.DELETE, 271 | 'https://api.mapbox.com/datasets/v1/{0}/{1}?access_token={2}'.format(username, id, access_token), 272 | match_querystring=True, 273 | status=204 274 | ) 275 | 276 | runner = CliRunner() 277 | result = runner.invoke( 278 | main_group, 279 | ['--access-token', access_token, 280 | 'datasets', 281 | 'delete-dataset', id]) 282 | 283 | assert result.exit_code == 0 284 | 285 | @responses.activate 286 | def test_cli_dataset_list_features_stdout(): 287 | id = "cii9dtexw0039uelz7nzk1lq3" 288 | collection='{"type":"FeatureCollection","features":[]}' 289 | responses.add( 290 | responses.GET, 291 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features?access_token={2}'.format(username, id, access_token), 292 | match_querystring=True, 293 | status=200, body=collection, 294 | content_type='application/json' 295 | ) 296 | 297 | runner = CliRunner() 298 | result = runner.invoke( 299 | main_group, 300 | ['--access-token', access_token, 301 | 'datasets', 302 | 'list-features', id]) 303 | 304 | assert result.exit_code == 0 305 | assert result.output.strip() == collection.strip() 306 | 307 | @responses.activate 308 | def test_cli_dataset_list_features_pagination(): 309 | id = "cii9dtexw0039uelz7nzk1lq3" 310 | collection='{"type":"FeatureCollection","features":[]}' 311 | responses.add( 312 | responses.GET, 313 | 'https://api.mapbox.com/datasets/v1/{0}/dataset-1/features'.format(username), 314 | match_querystring=False, 315 | status=200, body=collection, 316 | content_type='application/json' 317 | ) 318 | 319 | runner = CliRunner() 320 | result = runner.invoke( 321 | main_group, 322 | ['--access-token', access_token, 323 | 'datasets', 324 | 'list-features', 'dataset-1', 325 | '--start', id, 326 | '--limit', '1', 327 | '--reverse', True]) 328 | 329 | url = responses.calls[0].request.url 330 | assert re.search('start=cii9dtexw0039uelz7nzk1lq3', url) != None 331 | assert re.search('limit=1', url) != None 332 | assert re.search('reverse=true', url) != None 333 | assert result.exit_code == 0 334 | 335 | @responses.activate 336 | def test_cli_dataset_list_features_tofile(tmpdir): 337 | tmpfile=str(tmpdir.join('test.list-features.json')) 338 | id = "dataset-1" 339 | collection='{"type":"FeatureCollection","features":[]}' 340 | responses.add( 341 | responses.GET, 342 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features?access_token={2}'.format(username, id, access_token), 343 | match_querystring=True, 344 | status=200, body=collection, 345 | content_type='application/json' 346 | ) 347 | 348 | runner = CliRunner() 349 | result = runner.invoke( 350 | main_group, 351 | ['--access-token', access_token, 352 | 'datasets', 353 | 'list-features', id, 354 | '--output', tmpfile]) 355 | 356 | assert result.exit_code == 0 357 | assert result.output.strip() == "" 358 | assert open(tmpfile).read().strip() == collection.strip() 359 | 360 | @responses.activate 361 | def test_cli_dataset_read_feature_stdout(): 362 | dataset = "cii9dtexw0039uelz7nzk1lq3" 363 | id = "abc" 364 | feature = """ 365 | {{ 366 | "type":"Feature", 367 | "id":"{0}", 368 | "properties":{{}}, 369 | "geometry":{{"type":"Point","coordinates":[0,0]}} 370 | }}""".format(id) 371 | 372 | responses.add( 373 | responses.GET, 374 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 375 | match_querystring=True, 376 | status=200, body=feature, 377 | content_type='application/json' 378 | ) 379 | 380 | runner = CliRunner() 381 | result = runner.invoke( 382 | main_group, 383 | ['--access-token', access_token, 384 | 'datasets', 385 | 'read-feature', dataset, id]) 386 | 387 | assert result.exit_code == 0 388 | assert result.output.strip() == feature.strip() 389 | 390 | @responses.activate 391 | def test_cli_dataset_read_feature_tofile(tmpdir): 392 | tmpfile = str(tmpdir.join('test.read-feature.json')) 393 | dataset = "dataset-2" 394 | id = "abc" 395 | feature = """ 396 | {{ 397 | "type":"Feature", 398 | "id":"{0}", 399 | "properties":{{}}, 400 | "geometry":{{"type":"Point","coordinates":[0,0]}} 401 | }}""".format(id) 402 | 403 | responses.add( 404 | responses.GET, 405 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 406 | match_querystring=True, 407 | status=200, body=feature, 408 | content_type='application/json' 409 | ) 410 | 411 | runner = CliRunner() 412 | result = runner.invoke( 413 | main_group, 414 | ['--access-token', access_token, 415 | 'datasets', 416 | 'read-feature', dataset, id, 417 | '--output', tmpfile]) 418 | 419 | assert result.exit_code == 0 420 | assert result.output.strip() == "" 421 | assert open(tmpfile).read().strip() == feature.strip() 422 | 423 | @responses.activate 424 | def test_cli_dataset_put_feature_inline(): 425 | dataset = "dataset-2" 426 | id = "abc" 427 | 428 | feature = """ 429 | {{ 430 | "type":"Feature", 431 | "id":"{0}", 432 | "properties":{{}}, 433 | "geometry":{{"type":"Point","coordinates":[0,0]}} 434 | }}""".format(id) 435 | 436 | feature = "".join(feature.split()) 437 | 438 | responses.add( 439 | responses.PUT, 440 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 441 | match_querystring=True, 442 | status=200, body=feature, 443 | content_type='application/json' 444 | ) 445 | 446 | runner = CliRunner() 447 | result = runner.invoke( 448 | main_group, 449 | ['--access-token', access_token, 450 | 'datasets', 451 | 'put-feature', dataset, id, feature]) 452 | 453 | assert result.exit_code == 0 454 | assert result.output.strip() == feature.strip() 455 | 456 | @responses.activate 457 | def test_cli_dataset_put_feature_fromfile(tmpdir): 458 | tmpfile = str(tmpdir.join('test.put-feature.json')) 459 | dataset = "dataset-2" 460 | id = "abc" 461 | 462 | feature = """ 463 | {{ 464 | "type":"Feature", 465 | "id":"{0}", 466 | "properties":{{}}, 467 | "geometry":{{"type":"Point","coordinates":[0,0]}} 468 | }}""".format(id) 469 | 470 | feature = "".join(feature.split()) 471 | 472 | f = open(tmpfile, 'w') 473 | f.write(feature) 474 | f.close() 475 | 476 | responses.add( 477 | responses.PUT, 478 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 479 | match_querystring=True, 480 | status=200, body=feature, 481 | content_type='application/json' 482 | ) 483 | 484 | runner = CliRunner() 485 | result = runner.invoke( 486 | main_group, 487 | ['--access-token', access_token, 488 | 'datasets', 489 | 'put-feature', dataset, id, 490 | '--input', tmpfile]) 491 | 492 | assert result.exit_code == 0 493 | assert result.output.strip() == feature.strip() 494 | 495 | @responses.activate 496 | def test_cli_dataset_put_feature_stdin(): 497 | dataset = "dataset-2" 498 | id = "abc" 499 | 500 | feature = """ 501 | {{ 502 | "type":"Feature", 503 | "id":"{0}", 504 | "properties":{{}}, 505 | "geometry":{{"type":"Point","coordinates":[0,0]}} 506 | }}""".format(id) 507 | 508 | feature = "".join(feature.split()) 509 | 510 | responses.add( 511 | responses.PUT, 512 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 513 | match_querystring=True, 514 | status=200, body=feature, 515 | content_type='application/json' 516 | ) 517 | 518 | runner = CliRunner() 519 | result = runner.invoke( 520 | main_group, 521 | ['--access-token', access_token, 522 | 'datasets', 523 | 'put-feature', dataset, id], 524 | input=feature) 525 | 526 | assert result.exit_code == 0 527 | assert result.output.strip() == feature.strip() 528 | 529 | @responses.activate 530 | def test_cli_dataset_delete_feature(): 531 | dataset = "dataset-2" 532 | id = "abc" 533 | 534 | responses.add( 535 | responses.DELETE, 536 | 'https://api.mapbox.com/datasets/v1/{0}/{1}/features/{2}?access_token={3}'.format(username, dataset, id, access_token), 537 | match_querystring=True, 538 | status=204 539 | ) 540 | 541 | runner = CliRunner() 542 | result = runner.invoke( 543 | main_group, 544 | ['--access-token', access_token, 545 | 'datasets', 546 | 'delete-feature', dataset, id]) 547 | 548 | assert result.exit_code == 0 549 | 550 | @responses.activate 551 | def test_cli_dataset_create_tileset(): 552 | dataset = "dataset-1" 553 | name = "test" 554 | tileset = "testuser.data" 555 | owner = tileset.split('.')[0] 556 | 557 | expected = """{{ 558 | "id":"ciiae5gc40041mblzuluvu7jr", 559 | "name":"{0}", 560 | "complete":false, 561 | "error":null, 562 | "created":"2015-12-17T15:17:46.703Z", 563 | "modified":"2015-12-17T15:17:46.703Z", 564 | "tileset":"{1}", 565 | "owner":"{2}", 566 | "progress":0 567 | }}""".format(name, tileset, owner) 568 | 569 | responses.add( 570 | responses.POST, 571 | 'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(owner, access_token), 572 | status=201, body=expected, 573 | match_querystring=True, 574 | content_type='application/json' 575 | ) 576 | 577 | runner = CliRunner() 578 | result = runner.invoke( 579 | main_group, 580 | ['--access-token', access_token, 581 | 'datasets', 582 | 'create-tileset', 583 | dataset, 584 | tileset, 585 | '--name', name]) 586 | 587 | assert result.exit_code == 0 588 | body = json.loads(responses.calls[0].request.body.decode()) 589 | assert body['url'] == 'mapbox://datasets/{0}/{1}'.format(owner, dataset) 590 | assert body['tileset'] == tileset 591 | assert body['name'] == name 592 | -------------------------------------------------------------------------------- /tests/test_directions.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from mapboxcli.scripts.cli import main_group 4 | from mapboxcli.scripts.directions import waypoint_snapping_callback 5 | 6 | from click.testing import CliRunner 7 | import pytest 8 | import responses 9 | 10 | 11 | GEOJSON_BODY = "{\"routes\": []}" 12 | 13 | NON_GEOJSON_BODY = "" 14 | 15 | OUTPUT_FILE = "test.json" 16 | 17 | ENCODED_COORDS = "/0.0%2C0.0%3B1.0%2C1.0.json" 18 | 19 | 20 | def test_cli_directions_validation_error(): 21 | # --annotations invalid 22 | 23 | runner = CliRunner() 24 | 25 | result = runner.invoke( 26 | main_group, 27 | [ 28 | "--access-token", "test-token", 29 | "directions", 30 | "--annotations", "invalid", 31 | "[0, 0]", "[1, 1]" 32 | ] 33 | ) 34 | 35 | assert result.exit_code != 0 36 | assert "Error" in result.output 37 | 38 | # --waypoint-snapping 1.1,1,1 --waypoint-snapping 1.1,1,1 39 | 40 | runner = CliRunner() 41 | 42 | result = runner.invoke( 43 | main_group, 44 | [ 45 | "--access-token", "test-token", 46 | "directions", 47 | "--waypoint-snapping", "1.1,1,1", 48 | "--waypoint-snapping", "1.1,1,1", 49 | "[0, 0]", "[1, 1]" 50 | ] 51 | ) 52 | 53 | assert result.exit_code != 0 54 | 55 | # --waypoint-snapping 1.1 --waypoint-snapping 1.1 56 | 57 | runner = CliRunner() 58 | 59 | result = runner.invoke( 60 | main_group, 61 | [ 62 | "--access-token", "test-token", 63 | "directions", 64 | "--waypoint-snapping", "1.1", 65 | "--waypoint-snapping", "1.1", 66 | "[0, 0]", "[1, 1]" 67 | ] 68 | ) 69 | 70 | assert result.exit_code != 0 71 | 72 | 73 | @responses.activate 74 | def test_cli_directions_server_error(): 75 | responses.add( 76 | method=responses.GET, 77 | url="https://api.mapbox.com/directions/v5" + 78 | "/mapbox/driving" + 79 | ENCODED_COORDS + 80 | "?access_token=test-token" + 81 | "&alternatives=true" + 82 | "&geometries=geojson" + 83 | "&steps=true" + 84 | "&continue_straight=true", 85 | match_querystring=True, 86 | body=NON_GEOJSON_BODY, 87 | status=500 88 | ) 89 | 90 | runner = CliRunner() 91 | 92 | result = runner.invoke( 93 | main_group, 94 | [ 95 | "--access-token", "test-token", 96 | "directions", 97 | "[0, 0]", "[1, 1]" 98 | ] 99 | ) 100 | 101 | assert result.exit_code != 0 102 | assert "Error" in result.output 103 | 104 | 105 | @responses.activate 106 | def test_cli_directions(): 107 | responses.add( 108 | method=responses.GET, 109 | url="https://api.mapbox.com/directions/v5" + 110 | "/mapbox/driving" + 111 | ENCODED_COORDS + 112 | "?access_token=test-token" + 113 | "&alternatives=true" + 114 | "&geometries=geojson" + 115 | "&steps=true" + 116 | "&continue_straight=true", 117 | match_querystring=True, 118 | body=GEOJSON_BODY, 119 | status=200 120 | ) 121 | 122 | runner = CliRunner() 123 | 124 | result = runner.invoke( 125 | main_group, 126 | [ 127 | "--access-token", "test-token", 128 | "directions", 129 | "[0, 0]", "[1, 1]" 130 | ] 131 | ) 132 | 133 | assert result.exit_code == 0 134 | 135 | 136 | @responses.activate 137 | def test_cli_directions_with_profile(): 138 | # --profile mapbox/driving-traffic 139 | 140 | responses.add( 141 | method=responses.GET, 142 | url="https://api.mapbox.com/directions/v5" + 143 | "/mapbox/driving-traffic" + 144 | ENCODED_COORDS + 145 | "?access_token=test-token" + 146 | "&alternatives=true" + 147 | "&geometries=geojson" + 148 | "&steps=true" + 149 | "&continue_straight=true", 150 | match_querystring=True, 151 | body=GEOJSON_BODY, 152 | status=200 153 | ) 154 | 155 | runner = CliRunner() 156 | 157 | result = runner.invoke( 158 | main_group, 159 | [ 160 | "--access-token", "test-token", 161 | "directions", 162 | "--profile", "mapbox/driving-traffic", 163 | "[0, 0]", "[1, 1]" 164 | ] 165 | ) 166 | 167 | assert result.exit_code == 0 168 | 169 | # --profile mapbox/driving 170 | 171 | responses.add( 172 | method=responses.GET, 173 | url="https://api.mapbox.com/directions/v5" + 174 | "/mapbox/driving" + 175 | ENCODED_COORDS + 176 | "?access_token=test-token" + 177 | "&alternatives=true" + 178 | "&geometries=geojson" + 179 | "&steps=true" + 180 | "&continue_straight=true", 181 | match_querystring=True, 182 | body=GEOJSON_BODY, 183 | status=200 184 | ) 185 | 186 | runner = CliRunner() 187 | 188 | result = runner.invoke( 189 | main_group, 190 | [ 191 | "--access-token", "test-token", 192 | "directions", 193 | "--profile", "mapbox/driving", 194 | "[0, 0]", "[1, 1]" 195 | ] 196 | ) 197 | 198 | assert result.exit_code == 0 199 | 200 | # --profile mapbox/walking 201 | 202 | responses.add( 203 | method=responses.GET, 204 | url="https://api.mapbox.com/directions/v5" + 205 | "/mapbox/walking" + 206 | ENCODED_COORDS + 207 | "?access_token=test-token" + 208 | "&alternatives=true" + 209 | "&geometries=geojson" + 210 | "&steps=true" + 211 | "&continue_straight=true", 212 | match_querystring=True, 213 | body=GEOJSON_BODY, 214 | status=200 215 | ) 216 | 217 | runner = CliRunner() 218 | 219 | result = runner.invoke( 220 | main_group, 221 | [ 222 | "--access-token", "test-token", 223 | "directions", 224 | "--profile", "mapbox/walking", 225 | "[0, 0]", "[1, 1]" 226 | ] 227 | ) 228 | 229 | assert result.exit_code == 0 230 | 231 | # --profile mapbox/cycling 232 | 233 | responses.add( 234 | method=responses.GET, 235 | url="https://api.mapbox.com/directions/v5" + 236 | "/mapbox/cycling" + 237 | ENCODED_COORDS + 238 | "?access_token=test-token" + 239 | "&alternatives=true" + 240 | "&geometries=geojson" + 241 | "&steps=true" + 242 | "&continue_straight=true", 243 | match_querystring=True, 244 | body=GEOJSON_BODY, 245 | status=200 246 | ) 247 | 248 | runner = CliRunner() 249 | 250 | result = runner.invoke( 251 | main_group, 252 | [ 253 | "--access-token", "test-token", 254 | "directions", 255 | "--profile", "mapbox/cycling", 256 | "[0, 0]", "[1, 1]" 257 | ] 258 | ) 259 | 260 | assert result.exit_code == 0 261 | 262 | 263 | @responses.activate 264 | def test_cli_directions_with_alternatives(): 265 | # --alternatives 266 | 267 | responses.add( 268 | method=responses.GET, 269 | url="https://api.mapbox.com/directions/v5" + 270 | "/mapbox/driving" + 271 | ENCODED_COORDS + 272 | "?access_token=test-token" + 273 | "&alternatives=true" + 274 | "&geometries=geojson" + 275 | "&steps=true" + 276 | "&continue_straight=true", 277 | match_querystring=True, 278 | body=GEOJSON_BODY, 279 | status=200 280 | ) 281 | 282 | runner = CliRunner() 283 | 284 | result = runner.invoke( 285 | main_group, 286 | [ 287 | "--access-token", "test-token", 288 | "directions", 289 | "--alternatives", 290 | "[0, 0]", "[1, 1]" 291 | ] 292 | ) 293 | 294 | assert result.exit_code == 0 295 | 296 | # --no-alternatives 297 | 298 | responses.add( 299 | method=responses.GET, 300 | url="https://api.mapbox.com/directions/v5" + 301 | "/mapbox/driving" + 302 | ENCODED_COORDS + 303 | "?access_token=test-token" + 304 | "&alternatives=false" + 305 | "&geometries=geojson" + 306 | "&steps=true" + 307 | "&continue_straight=true", 308 | match_querystring=True, 309 | body=GEOJSON_BODY, 310 | status=200 311 | ) 312 | 313 | runner = CliRunner() 314 | 315 | result = runner.invoke( 316 | main_group, 317 | [ 318 | "--access-token", "test-token", 319 | "directions", 320 | "--no-alternatives", 321 | "[0, 0]", "[1, 1]" 322 | ] 323 | ) 324 | 325 | assert result.exit_code == 0 326 | 327 | 328 | @responses.activate 329 | def test_cli_directions_with_geometries(): 330 | # --geometries geojson 331 | 332 | responses.add( 333 | method=responses.GET, 334 | url="https://api.mapbox.com/directions/v5" + 335 | "/mapbox/driving" + 336 | ENCODED_COORDS + 337 | "?access_token=test-token" + 338 | "&alternatives=true" + 339 | "&geometries=geojson" + 340 | "&steps=true" + 341 | "&continue_straight=true", 342 | match_querystring=True, 343 | body=GEOJSON_BODY, 344 | status=200 345 | ) 346 | 347 | runner = CliRunner() 348 | 349 | result = runner.invoke( 350 | main_group, 351 | [ 352 | "--access-token", "test-token", 353 | "directions", 354 | "--geometries", "geojson", 355 | "[0, 0]", "[1, 1]" 356 | ] 357 | ) 358 | 359 | assert result.exit_code == 0 360 | 361 | # --geometries polyline 362 | 363 | responses.add( 364 | method=responses.GET, 365 | url="https://api.mapbox.com/directions/v5" + 366 | "/mapbox/driving" + 367 | ENCODED_COORDS + 368 | "?access_token=test-token" + 369 | "&alternatives=true" + 370 | "&geometries=polyline" + 371 | "&steps=true" + 372 | "&continue_straight=true", 373 | match_querystring=True, 374 | body=NON_GEOJSON_BODY, 375 | status=200 376 | ) 377 | 378 | runner = CliRunner() 379 | 380 | result = runner.invoke( 381 | main_group, 382 | [ 383 | "--access-token", "test-token", 384 | "directions", 385 | "--geometries", "polyline", 386 | "[0, 0]", "[1, 1]" 387 | ] 388 | ) 389 | 390 | assert result.exit_code == 0 391 | 392 | # --geometries polyline6 393 | 394 | responses.add( 395 | method=responses.GET, 396 | url="https://api.mapbox.com/directions/v5" + 397 | "/mapbox/driving" + 398 | ENCODED_COORDS + 399 | "?access_token=test-token" + 400 | "&alternatives=true" + 401 | "&geometries=polyline6" + 402 | "&steps=true" + 403 | "&continue_straight=true", 404 | match_querystring=True, 405 | body=NON_GEOJSON_BODY, 406 | status=200 407 | ) 408 | 409 | runner = CliRunner() 410 | 411 | result = runner.invoke( 412 | main_group, 413 | [ 414 | "--access-token", "test-token", 415 | "directions", 416 | "--geometries", "polyline6", 417 | "[0, 0]", "[1, 1]" 418 | ] 419 | ) 420 | 421 | assert result.exit_code == 0 422 | 423 | 424 | @responses.activate 425 | def test_cli_directions_with_overview(): 426 | # --overview full 427 | 428 | responses.add( 429 | method=responses.GET, 430 | url="https://api.mapbox.com/directions/v5" + 431 | "/mapbox/driving" + 432 | ENCODED_COORDS + 433 | "?access_token=test-token" + 434 | "&alternatives=true" + 435 | "&geometries=geojson" + 436 | "&overview=full" + 437 | "&steps=true" + 438 | "&continue_straight=true", 439 | match_querystring=True, 440 | body=GEOJSON_BODY, 441 | status=200 442 | ) 443 | 444 | runner = CliRunner() 445 | 446 | result = runner.invoke( 447 | main_group, 448 | [ 449 | "--access-token", "test-token", 450 | "directions", 451 | "--overview", "full", 452 | "[0, 0]", "[1, 1]" 453 | ] 454 | ) 455 | 456 | assert result.exit_code == 0 457 | 458 | # --overview simplified 459 | 460 | responses.add( 461 | method=responses.GET, 462 | url="https://api.mapbox.com/directions/v5" + 463 | "/mapbox/driving" + 464 | ENCODED_COORDS + 465 | "?access_token=test-token" + 466 | "&alternatives=true" + 467 | "&geometries=geojson" + 468 | "&overview=simplified" + 469 | "&steps=true" + 470 | "&continue_straight=true", 471 | match_querystring=True, 472 | body=GEOJSON_BODY, 473 | status=200 474 | ) 475 | 476 | runner = CliRunner() 477 | 478 | result = runner.invoke( 479 | main_group, 480 | [ 481 | "--access-token", "test-token", 482 | "directions", 483 | "--overview", "simplified", 484 | "[0, 0]", "[1, 1]" 485 | ] 486 | ) 487 | 488 | assert result.exit_code == 0 489 | 490 | # --overview False 491 | 492 | responses.add( 493 | method=responses.GET, 494 | url="https://api.mapbox.com/directions/v5" + 495 | "/mapbox/driving" + 496 | ENCODED_COORDS + 497 | "?access_token=test-token" + 498 | "&alternatives=true" + 499 | "&geometries=geojson" + 500 | "&overview=false" + 501 | "&steps=true" + 502 | "&continue_straight=true", 503 | match_querystring=True, 504 | body=GEOJSON_BODY, 505 | status=200 506 | ) 507 | 508 | runner = CliRunner() 509 | 510 | result = runner.invoke( 511 | main_group, 512 | [ 513 | "--access-token", "test-token", 514 | "directions", 515 | "--overview", "False", 516 | "[0, 0]", "[1, 1]" 517 | ] 518 | ) 519 | 520 | assert result.exit_code == 0 521 | 522 | 523 | @responses.activate 524 | def test_cli_directions_with_steps(): 525 | # --steps 526 | 527 | responses.add( 528 | method=responses.GET, 529 | url="https://api.mapbox.com/directions/v5" + 530 | "/mapbox/driving" + 531 | ENCODED_COORDS + 532 | "?access_token=test-token" + 533 | "&alternatives=true" + 534 | "&geometries=geojson" + 535 | "&steps=true" + 536 | "&continue_straight=true", 537 | match_querystring=True, 538 | body=GEOJSON_BODY, 539 | status=200 540 | ) 541 | 542 | runner = CliRunner() 543 | 544 | result = runner.invoke( 545 | main_group, 546 | [ 547 | "--access-token", "test-token", 548 | "directions", 549 | "--steps", 550 | "[0, 0]", "[1, 1]" 551 | ] 552 | ) 553 | 554 | assert result.exit_code == 0 555 | 556 | # --no-steps 557 | 558 | responses.add( 559 | method=responses.GET, 560 | url="https://api.mapbox.com/directions/v5" + 561 | "/mapbox/driving" + 562 | ENCODED_COORDS + 563 | "?access_token=test-token" + 564 | "&alternatives=true" + 565 | "&geometries=geojson" + 566 | "&steps=false" + 567 | "&continue_straight=false", 568 | match_querystring=True, 569 | body=GEOJSON_BODY, 570 | status=200 571 | ) 572 | 573 | runner = CliRunner() 574 | 575 | result = runner.invoke( 576 | main_group, 577 | [ 578 | "--access-token", "test-token", 579 | "directions", 580 | "--no-steps", 581 | "[0, 0]", "[1, 1]" 582 | ] 583 | ) 584 | 585 | assert result.exit_code == 0 586 | 587 | 588 | @responses.activate 589 | def test_cli_directions_with_continue_straight(): 590 | # --continue-straight 591 | 592 | responses.add( 593 | method=responses.GET, 594 | url="https://api.mapbox.com/directions/v5" + 595 | "/mapbox/driving" + 596 | ENCODED_COORDS + 597 | "?access_token=test-token" + 598 | "&alternatives=true" + 599 | "&geometries=geojson" + 600 | "&steps=true" + 601 | "&continue_straight=true", 602 | match_querystring=True, 603 | body=GEOJSON_BODY, 604 | status=200 605 | ) 606 | 607 | runner = CliRunner() 608 | 609 | result = runner.invoke( 610 | main_group, 611 | [ 612 | "--access-token", "test-token", 613 | "directions", 614 | "--continue-straight", 615 | "[0, 0]", "[1, 1]" 616 | ] 617 | ) 618 | 619 | assert result.exit_code == 0 620 | 621 | # --no-continue-straight 622 | 623 | responses.add( 624 | method=responses.GET, 625 | url="https://api.mapbox.com/directions/v5" + 626 | "/mapbox/driving" + 627 | ENCODED_COORDS + 628 | "?access_token=test-token" + 629 | "&alternatives=true" + 630 | "&geometries=geojson" + 631 | "&steps=false" + 632 | "&continue_straight=false", 633 | match_querystring=True, 634 | body=GEOJSON_BODY, 635 | status=200 636 | ) 637 | 638 | runner = CliRunner() 639 | 640 | result = runner.invoke( 641 | main_group, 642 | [ 643 | "--access-token", "test-token", 644 | "directions", 645 | "--no-continue-straight", 646 | "[0, 0]", "[1, 1]" 647 | ] 648 | ) 649 | 650 | assert result.exit_code == 0 651 | 652 | 653 | @responses.activate 654 | def test_cli_directions_with_waypoint_snapping(): 655 | # --waypoint-snapping 1,1,1 --waypoint-snapping 1,1,1 656 | 657 | responses.add( 658 | method=responses.GET, 659 | url="https://api.mapbox.com/directions/v5" + 660 | "/mapbox/driving" + 661 | ENCODED_COORDS + 662 | "?access_token=test-token" + 663 | "&alternatives=true" + 664 | "&geometries=geojson" + 665 | "&steps=true" + 666 | "&continue_straight=true" + 667 | "&bearings=1%2C1%3B1%2C1" + 668 | "&radiuses=1%3B1", 669 | match_querystring=True, 670 | body=GEOJSON_BODY, 671 | status=200 672 | ) 673 | 674 | runner = CliRunner() 675 | 676 | result = runner.invoke( 677 | main_group, 678 | [ 679 | "--access-token", "test-token", 680 | "directions", 681 | "--waypoint-snapping", "1,1,1", 682 | "--waypoint-snapping", "1,1,1", 683 | "[0, 0]", "[1, 1]" 684 | ] 685 | ) 686 | 687 | assert result.exit_code == 0 688 | 689 | # --waypoint-snapping 1 --waypoint-snapping 1 690 | 691 | responses.add( 692 | method=responses.GET, 693 | url="https://api.mapbox.com/directions/v5" + 694 | "/mapbox/driving" + 695 | ENCODED_COORDS + 696 | "?access_token=test-token" + 697 | "&alternatives=true" + 698 | "&geometries=geojson" + 699 | "&steps=true" + 700 | "&continue_straight=true" + 701 | "&radiuses=1%3B1", 702 | match_querystring=True, 703 | body=GEOJSON_BODY, 704 | status=200 705 | ) 706 | 707 | runner = CliRunner() 708 | 709 | result = runner.invoke( 710 | main_group, 711 | [ 712 | "--access-token", "test-token", 713 | "directions", 714 | "--waypoint-snapping", "1", 715 | "--waypoint-snapping", "1", 716 | "[0, 0]", "[1, 1]" 717 | ] 718 | ) 719 | 720 | assert result.exit_code == 0 721 | 722 | # --waypoint-snapping unlimited --waypoint-snapping unlimited 723 | 724 | responses.add( 725 | method=responses.GET, 726 | url="https://api.mapbox.com/directions/v5" + 727 | "/mapbox/driving" + 728 | ENCODED_COORDS + 729 | "?access_token=test-token" + 730 | "&alternatives=true" + 731 | "&geometries=geojson" + 732 | "&steps=true" + 733 | "&continue_straight=true" + 734 | "&radiuses=unlimited%3Bunlimited", 735 | match_querystring=True, 736 | body=GEOJSON_BODY, 737 | status=200 738 | ) 739 | 740 | runner = CliRunner() 741 | 742 | result = runner.invoke( 743 | main_group, 744 | [ 745 | "--access-token", "test-token", 746 | "directions", 747 | "--waypoint-snapping", "unlimited", 748 | "--waypoint-snapping", "unlimited", 749 | "[0, 0]", "[1, 1]" 750 | ] 751 | ) 752 | 753 | assert result.exit_code == 0 754 | 755 | # --waypoint-snapping 1,1,1 --waypoint-snapping 1 756 | 757 | responses.add( 758 | method=responses.GET, 759 | url="https://api.mapbox.com/directions/v5" + 760 | "/mapbox/driving" + 761 | ENCODED_COORDS + 762 | "?access_token=test-token" + 763 | "&alternatives=true" + 764 | "&geometries=geojson" + 765 | "&steps=true" + 766 | "&continue_straight=true" + 767 | "&bearings=1%2C1%3B" + 768 | "&radiuses=1%3B1", 769 | match_querystring=True, 770 | body=GEOJSON_BODY, 771 | status=200 772 | ) 773 | 774 | runner = CliRunner() 775 | 776 | result = runner.invoke( 777 | main_group, 778 | [ 779 | "--access-token", "test-token", 780 | "directions", 781 | "--waypoint-snapping", "1,1,1", 782 | "--waypoint-snapping", "1", 783 | "[0, 0]", "[1, 1]" 784 | ] 785 | ) 786 | 787 | assert result.exit_code == 0 788 | 789 | # --waypoint-snapping 1,1,1 --waypoint-snapping unlimited 790 | 791 | responses.add( 792 | method=responses.GET, 793 | url="https://api.mapbox.com/directions/v5" + 794 | "/mapbox/driving" + 795 | ENCODED_COORDS + 796 | "?access_token=test-token" + 797 | "&alternatives=true" + 798 | "&geometries=geojson" + 799 | "&steps=true" + 800 | "&continue_straight=true" + 801 | "&bearings=1%2C1%3B" + 802 | "&radiuses=1%3Bunlimited", 803 | match_querystring=True, 804 | body=GEOJSON_BODY, 805 | status=200 806 | ) 807 | 808 | runner = CliRunner() 809 | 810 | result = runner.invoke( 811 | main_group, 812 | [ 813 | "--access-token", "test-token", 814 | "directions", 815 | "--waypoint-snapping", "1,1,1", 816 | "--waypoint-snapping", "unlimited", 817 | "[0, 0]", "[1, 1]" 818 | ] 819 | ) 820 | 821 | assert result.exit_code == 0 822 | 823 | # --waypoint-snapping 1 --waypoint-snapping unlimited 824 | 825 | responses.add( 826 | method=responses.GET, 827 | url="https://api.mapbox.com/directions/v5" + 828 | "/mapbox/driving" + 829 | ENCODED_COORDS + 830 | "?access_token=test-token" + 831 | "&alternatives=true" + 832 | "&geometries=geojson" + 833 | "&steps=true" + 834 | "&continue_straight=true" + 835 | "&radiuses=1%3Bunlimited", 836 | match_querystring=True, 837 | body=GEOJSON_BODY, 838 | status=200 839 | ) 840 | 841 | runner = CliRunner() 842 | 843 | result = runner.invoke( 844 | main_group, 845 | [ 846 | "--access-token", "test-token", 847 | "directions", 848 | "--waypoint-snapping", "1", 849 | "--waypoint-snapping", "unlimited", 850 | "[0, 0]", "[1, 1]" 851 | ] 852 | ) 853 | 854 | assert result.exit_code == 0 855 | 856 | 857 | @responses.activate 858 | def test_cli_directions_with_annotations(): 859 | # --annotations duration 860 | 861 | responses.add( 862 | method=responses.GET, 863 | url="https://api.mapbox.com/directions/v5" + 864 | "/mapbox/driving" + 865 | ENCODED_COORDS + 866 | "?access_token=test-token" + 867 | "&alternatives=true" + 868 | "&geometries=geojson" + 869 | "&steps=true" + 870 | "&continue_straight=true" + 871 | "&annotations=duration", 872 | match_querystring=True, 873 | body=GEOJSON_BODY, 874 | status=200 875 | ) 876 | 877 | runner = CliRunner() 878 | 879 | result = runner.invoke( 880 | main_group, 881 | [ 882 | "--access-token", "test-token", 883 | "directions", 884 | "--annotations", "duration", 885 | "[0, 0]", "[1, 1]" 886 | ] 887 | ) 888 | 889 | assert result.exit_code == 0 890 | 891 | # --annotations distance 892 | 893 | responses.add( 894 | method=responses.GET, 895 | url="https://api.mapbox.com/directions/v5" + 896 | "/mapbox/driving" + 897 | ENCODED_COORDS + 898 | "?access_token=test-token" + 899 | "&alternatives=true" + 900 | "&geometries=geojson" + 901 | "&steps=true" + 902 | "&continue_straight=true" + 903 | "&annotations=distance", 904 | match_querystring=True, 905 | body=GEOJSON_BODY, 906 | status=200 907 | ) 908 | 909 | runner = CliRunner() 910 | 911 | result = runner.invoke( 912 | main_group, 913 | [ 914 | "--access-token", "test-token", 915 | "directions", 916 | "--annotations", "distance", 917 | "[0, 0]", "[1, 1]" 918 | ] 919 | ) 920 | 921 | assert result.exit_code == 0 922 | 923 | # --annotations speed 924 | 925 | responses.add( 926 | method=responses.GET, 927 | url="https://api.mapbox.com/directions/v5" + 928 | "/mapbox/driving" + 929 | ENCODED_COORDS + 930 | "?access_token=test-token" + 931 | "&alternatives=true" + 932 | "&geometries=geojson" + 933 | "&steps=true" + 934 | "&continue_straight=true" + 935 | "&annotations=speed", 936 | match_querystring=True, 937 | body=GEOJSON_BODY, 938 | status=200 939 | ) 940 | 941 | runner = CliRunner() 942 | 943 | result = runner.invoke( 944 | main_group, 945 | [ 946 | "--access-token", "test-token", 947 | "directions", 948 | "--annotations", "speed", 949 | "[0, 0]", "[1, 1]" 950 | ] 951 | ) 952 | 953 | assert result.exit_code == 0 954 | 955 | # --annotations duration,distance,speed 956 | 957 | responses.add( 958 | method=responses.GET, 959 | url="https://api.mapbox.com/directions/v5" + 960 | "/mapbox/driving" + 961 | ENCODED_COORDS + 962 | "?access_token=test-token" + 963 | "&alternatives=true" + 964 | "&geometries=geojson" + 965 | "&steps=true" + 966 | "&continue_straight=true" + 967 | "&annotations=duration%2Cdistance%2Cspeed", 968 | match_querystring=True, 969 | body=GEOJSON_BODY, 970 | status=200 971 | ) 972 | 973 | runner = CliRunner() 974 | 975 | result = runner.invoke( 976 | main_group, 977 | [ 978 | "--access-token", "test-token", 979 | "directions", 980 | "--annotations", "duration,distance,speed", 981 | "[0, 0]", "[1, 1]" 982 | ] 983 | ) 984 | 985 | assert result.exit_code == 0 986 | 987 | 988 | @responses.activate 989 | def test_cli_directions_with_language(): 990 | # --language en 991 | 992 | responses.add( 993 | method=responses.GET, 994 | url="https://api.mapbox.com/directions/v5" + 995 | "/mapbox/driving" + 996 | ENCODED_COORDS + 997 | "?access_token=test-token" + 998 | "&alternatives=true" + 999 | "&geometries=geojson" + 1000 | "&steps=true" + 1001 | "&continue_straight=true" + 1002 | "&language=en", 1003 | match_querystring=True, 1004 | body=GEOJSON_BODY, 1005 | status=200 1006 | ) 1007 | 1008 | runner = CliRunner() 1009 | 1010 | result = runner.invoke( 1011 | main_group, 1012 | [ 1013 | "--access-token", "test-token", 1014 | "directions", 1015 | "--language", "en", 1016 | "[0, 0]", "[1, 1]" 1017 | ] 1018 | ) 1019 | 1020 | assert result.exit_code == 0 1021 | 1022 | 1023 | @responses.activate 1024 | def test_cli_directions_stdout(): 1025 | responses.add( 1026 | method=responses.GET, 1027 | url="https://api.mapbox.com/directions/v5" + 1028 | "/mapbox/driving" + 1029 | ENCODED_COORDS + 1030 | "?access_token=test-token" + 1031 | "&alternatives=true" + 1032 | "&geometries=geojson" + 1033 | "&steps=true" + 1034 | "&continue_straight=true", 1035 | match_querystring=True, 1036 | body=GEOJSON_BODY, 1037 | status=200 1038 | ) 1039 | 1040 | runner = CliRunner() 1041 | 1042 | result = runner.invoke( 1043 | main_group, 1044 | [ 1045 | "--access-token", "test-token", 1046 | "directions", 1047 | "--output", "-", 1048 | "[0, 0]", "[1, 1]" 1049 | ] 1050 | ) 1051 | 1052 | assert result.exit_code == 0 1053 | 1054 | 1055 | @responses.activate 1056 | def test_cli_directions_file_output(): 1057 | responses.add( 1058 | method=responses.GET, 1059 | url="https://api.mapbox.com/directions/v5" + 1060 | "/mapbox/driving" + 1061 | ENCODED_COORDS + 1062 | "?access_token=test-token" + 1063 | "&alternatives=true" + 1064 | "&geometries=geojson" + 1065 | "&steps=true" + 1066 | "&continue_straight=true", 1067 | match_querystring=True, 1068 | body=GEOJSON_BODY, 1069 | status=200 1070 | ) 1071 | 1072 | runner = CliRunner() 1073 | 1074 | result = runner.invoke( 1075 | main_group, 1076 | [ 1077 | "--access-token", "test-token", 1078 | "directions", 1079 | "--output", OUTPUT_FILE, 1080 | "[0, 0]", "[1, 1]" 1081 | ] 1082 | ) 1083 | 1084 | assert result.exit_code == 0 1085 | assert os.stat(OUTPUT_FILE) 1086 | os.remove(OUTPUT_FILE) 1087 | 1088 | 1089 | @pytest.mark.parametrize("input_snap,expected", [ 1090 | ("1,1,1", [(1, 1, 1)]), 1091 | ("1", [1]), 1092 | ("unlimited", ["unlimited"]), 1093 | ]) 1094 | def test_waypoint_callback(input_snap, expected): 1095 | wpt = waypoint_snapping_callback(None, None, [input_snap]) 1096 | assert wpt == expected 1097 | 1098 | wpt = waypoint_snapping_callback(None, None, (u"1", u"unlimited")) 1099 | assert wpt == [1, "unlimited"] 1100 | -------------------------------------------------------------------------------- /tests/test_geocoding.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from click.testing import CliRunner 4 | from mapbox.errors import ValidationError 5 | from mock import patch 6 | import responses 7 | 8 | from mapboxcli.scripts.cli import main_group 9 | 10 | 11 | @responses.activate 12 | def test_cli_geocode_fwd(): 13 | 14 | responses.add( 15 | responses.GET, 16 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json?access_token=bogus', 17 | match_querystring=True, 18 | body='{"query": ["1600", "pennsylvania", "ave", "nw"]}', status=200, 19 | content_type='application/json') 20 | 21 | runner = CliRunner() 22 | result = runner.invoke( 23 | main_group, 24 | ['--access-token', 'bogus', 'geocoding', '--forward', '1600 pennsylvania ave nw'], 25 | catch_exceptions=False) 26 | print(result.output) 27 | print(result.exception) 28 | print(result.exc_info) 29 | assert result.exit_code == 0 30 | assert result.output == '{"query": ["1600", "pennsylvania", "ave", "nw"]}\n' 31 | 32 | @responses.activate 33 | def test_cli_geocode_fwd_bbox(): 34 | 35 | responses.add( 36 | responses.GET, 37 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json?access_token=bogus&bbox=-78.3284%2C38.6039%2C-78.0428%2C38.7841', 38 | match_querystring=True, 39 | body='{"query": ["1600", "pennsylvania", "ave", "nw"]}', status=200, 40 | content_type='application/json') 41 | 42 | runner = CliRunner() 43 | result = runner.invoke( 44 | main_group, 45 | ['--access-token', 'bogus', 'geocoding', '--forward', '1600 pennsylvania ave nw', '--bbox', '[-78.3284,38.6039,-78.0428,38.7841]'], 46 | catch_exceptions=False) 47 | print(result.output) 48 | print(result.exception) 49 | print(result.exc_info) 50 | assert result.exit_code == 0 51 | assert result.output == '{"query": ["1600", "pennsylvania", "ave", "nw"]}\n' 52 | 53 | 54 | @responses.activate 55 | def test_cli_geocode_fwd_env_token(): 56 | 57 | responses.add( 58 | responses.GET, 59 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json?access_token=bogus', 60 | match_querystring=True, 61 | body='{"query": ["1600", "pennsylvania", "ave", "nw"]}', status=200, 62 | content_type='application/json') 63 | 64 | runner = CliRunner() 65 | result = runner.invoke( 66 | main_group, 67 | ['geocoding', '--forward', '1600 pennsylvania ave nw'], 68 | env={'MAPBOX_ACCESS_TOKEN': 'bogus'}) 69 | assert result.exit_code == 0 70 | assert result.output == '{"query": ["1600", "pennsylvania", "ave", "nw"]}\n' 71 | 72 | 73 | @responses.activate 74 | def test_cli_geocode_reverse(): 75 | 76 | lon, lat = -77.4371, 37.5227 77 | body = json.dumps({"query": [lon, lat]}) 78 | 79 | responses.add( 80 | responses.GET, 81 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json?access_token=pk.test'.format(lon, lat), 82 | match_querystring=True, 83 | body=body, 84 | status=200, 85 | content_type='application/json') 86 | 87 | runner = CliRunner() 88 | result = runner.invoke( 89 | main_group, 90 | ['--access-token', 'pk.test', 'geocoding', '--reverse'], 91 | input='{0},{1}'.format(lon, lat)) 92 | assert result.exit_code == 0 93 | assert result.output.strip() == body 94 | 95 | 96 | @responses.activate 97 | def test_cli_geocode_reverse_env_token(): 98 | 99 | lon, lat = -77.4371, 37.5227 100 | body = json.dumps({"query": [lon, lat]}) 101 | 102 | responses.add( 103 | responses.GET, 104 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json?access_token=bogus'.format(lon, lat), 105 | match_querystring=True, 106 | body=body, 107 | status=200, 108 | content_type='application/json') 109 | 110 | runner = CliRunner() 111 | result = runner.invoke( 112 | main_group, 113 | ['geocoding', '--reverse'], 114 | input='{0},{1}'.format(lon, lat), 115 | env={'MAPBOX_ACCESS_TOKEN': 'bogus'}) 116 | assert result.exit_code == 0 117 | assert result.output.strip() == body 118 | 119 | 120 | @responses.activate 121 | def test_cli_geocode_unauthorized(): 122 | 123 | responses.add( 124 | responses.GET, 125 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json', 126 | body='{"message":"Not Authorized - Invalid Token"}', status=401, 127 | content_type='application/json') 128 | 129 | runner = CliRunner() 130 | result = runner.invoke(main_group, ['geocoding', '--forward', 131 | '1600 pennsylvania ave nw']) 132 | assert result.exit_code == 1 133 | assert result.output == 'Error: {"message":"Not Authorized - Invalid Token"}\n' 134 | 135 | 136 | @responses.activate 137 | def test_cli_geocode_rev_unauthorized(): 138 | 139 | lon, lat = -77.4371, 37.5227 140 | 141 | responses.add( 142 | responses.GET, 143 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json'.format(lon, lat), 144 | body='{"message":"Not Authorized - Invalid Token"}', status=401, 145 | content_type='application/json') 146 | 147 | runner = CliRunner() 148 | result = runner.invoke( 149 | main_group, 150 | ['geocoding', '--reverse'], 151 | input='{0},{1}'.format(lon, lat)) 152 | assert result.exit_code == 1 153 | assert result.output == 'Error: {"message":"Not Authorized - Invalid Token"}\n' 154 | 155 | 156 | @responses.activate 157 | def test_cli_geocode_fwd_headers(): 158 | 159 | responses.add( 160 | responses.GET, 161 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json', 162 | body='{"query": ["1600", "pennsylvania", "ave", "nw"]}', status=200, 163 | content_type='application/json') 164 | 165 | runner = CliRunner() 166 | result = runner.invoke( 167 | main_group, 168 | ['geocoding', '-i', '--forward', '1600 pennsylvania ave nw']) 169 | assert result.exit_code == 0 170 | assert result.output.startswith('Content-Type') 171 | 172 | 173 | @responses.activate 174 | def test_cli_geocode_rev_headers(): 175 | 176 | lon, lat = -77.4371, 37.5227 177 | body = json.dumps({"query": [lon, lat]}) 178 | 179 | responses.add( 180 | responses.GET, 181 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json'.format(lon, lat), 182 | body=body, 183 | status=200, 184 | content_type='application/json') 185 | 186 | runner = CliRunner() 187 | result = runner.invoke( 188 | main_group, 189 | ['geocoding', '-i', '--reverse'], 190 | input='{0},{1}'.format(lon, lat)) 191 | assert result.exit_code == 0 192 | assert result.output.startswith('Content-Type') 193 | 194 | 195 | def test_cli_geocode_fwd_bad_place(): 196 | runner = CliRunner() 197 | 198 | result = runner.invoke( 199 | main_group, 200 | ['geocoding', '-t', 'spaceship'], 201 | input='Millennium Falcon') 202 | assert result.exit_code == 2 203 | 204 | 205 | def test_cli_geocode_rev_bad_place(): 206 | runner = CliRunner() 207 | 208 | lon, lat = -77.4371, 37.5227 209 | result = runner.invoke( 210 | main_group, 211 | ['geocoding', '-t', 'spaceship', '--reverse'], 212 | input='{0},{1}'.format(lon, lat)) 213 | assert result.exit_code == 2 214 | 215 | 216 | @patch('mapboxcli.scripts.geocoding.Geocoder') 217 | def test_cli_geocode_validation_error_handling(Geocoder): 218 | """ValidationError errors are handled""" 219 | Geocoder.return_value.place_types.return_value = {'country': 'country'} 220 | Geocoder.return_value.reverse.side_effect = ValidationError("Invalid") 221 | 222 | runner = CliRunner() 223 | lon, lat = -77.4371, 91.0 224 | result = runner.invoke( 225 | main_group, 226 | ['geocoding', '-t', 'country', '--reverse'], 227 | input='{0},{1}'.format(lon, lat)) 228 | assert result.exit_code == 2 229 | assert "Error: Invalid value" in result.output 230 | 231 | 232 | def test_cli_geocode_bad_dataset(): 233 | runner = CliRunner() 234 | 235 | result = runner.invoke( 236 | main_group, 237 | ['geocoding', '-d', 'mapbox.spaceships'], 238 | input='Millennium Falcon') 239 | assert result.exit_code == 2 240 | assert "Invalid value" in result.output 241 | 242 | 243 | def test_cli_geocode_invalid_country(): 244 | runner = CliRunner() 245 | 246 | result = runner.invoke( 247 | main_group, 248 | ['geocoding', '--country', 'US,Tatooine'], 249 | input='Millennium Falcon') 250 | assert result.exit_code == 2 251 | assert "Invalid value" in result.output 252 | 253 | 254 | @responses.activate 255 | def test_cli_geocode_fwd_limit(): 256 | 257 | responses.add( 258 | responses.GET, 259 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json?access_token=bogus&limit=2', 260 | match_querystring=True, 261 | body='{"features": [{"name": "first"}, {"name": "second"}]}', status=200, 262 | content_type='application/json') 263 | 264 | runner = CliRunner() 265 | result = runner.invoke( 266 | main_group, 267 | ['--access-token', 'bogus', 'geocoding', '--limit', '2', 268 | '--forward', '1600 pennsylvania ave nw'], 269 | catch_exceptions=False) 270 | assert result.exit_code == 0 271 | assert result.output == '{"features": [{"name": "first"}, {"name": "second"}]}\n' 272 | 273 | 274 | @responses.activate 275 | def test_cli_geocode_reverse_limit(): 276 | 277 | lon, lat = -77.4371, 37.5227 278 | res = {"query": [lon, lat], 279 | "features": [{"name": "first"}]} 280 | body = json.dumps(res) 281 | 282 | responses.add( 283 | responses.GET, 284 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json?limit=1&types=country&access_token=pk.test'.format(lon, lat), 285 | match_querystring=True, 286 | body=body, 287 | status=200, 288 | content_type='application/json') 289 | 290 | runner = CliRunner() 291 | result = runner.invoke( 292 | main_group, 293 | ['--access-token', 'pk.test', 'geocoding', '--reverse', 294 | '--limit', '1', '--place-type', 'country'], 295 | input='{0},{1}'.format(lon, lat)) 296 | assert result.exit_code == 0 297 | assert result.output.strip() == body 298 | 299 | 300 | @responses.activate 301 | def test_cli_geocode_reverse_features(): 302 | 303 | lon, lat = -77.4371, 37.5227 304 | res = {"query": [lon, lat], 305 | "features": [{"name": "first"}, {"name": "second"}]} 306 | body = json.dumps(res) 307 | 308 | responses.add( 309 | responses.GET, 310 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/{0},{1}.json?access_token=pk.test'.format(lon, lat), 311 | match_querystring=True, 312 | body=body, 313 | status=200, 314 | content_type='application/json') 315 | 316 | runner = CliRunner() 317 | result = runner.invoke( 318 | main_group, 319 | ['--access-token', 'pk.test', 'geocoding', '--reverse', '--features'], 320 | input='{0},{1}'.format(lon, lat)) 321 | assert result.exit_code == 0 322 | assert result.output == '{"name": "first"}\n{"name": "second"}\n' 323 | 324 | 325 | @responses.activate 326 | def test_cli_geocode_fwd_features(): 327 | 328 | responses.add( 329 | responses.GET, 330 | 'https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%20nw.json?access_token=bogus&limit=2', 331 | match_querystring=True, 332 | body='{"features": [{"name": "first"}, {"name": "second"}]}', status=200, 333 | content_type='application/json') 334 | 335 | runner = CliRunner() 336 | result = runner.invoke( 337 | main_group, 338 | ['--access-token', 'bogus', 'geocoding', '--limit', '2', '--features', 339 | '--forward', '1600 pennsylvania ave nw'], 340 | catch_exceptions=False) 341 | assert result.exit_code == 0 342 | assert result.output == '{"name": "first"}\n{"name": "second"}\n' 343 | -------------------------------------------------------------------------------- /tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | from mapboxcli.scripts.geocoding import coords_from_query, iter_query 2 | 3 | 4 | def test_iter_query_string(): 5 | assert iter_query("lolwut") == ["lolwut"] 6 | 7 | 8 | def test_iter_query_file(tmpdir): 9 | filename = str(tmpdir.join('test.txt')) 10 | with open(filename, 'w') as f: 11 | f.write("lolwut") 12 | assert iter_query(filename) == ["lolwut"] 13 | 14 | 15 | def test_coords_from_query_json(): 16 | assert coords_from_query("[-100, 40]") == (-100, 40) 17 | 18 | 19 | def test_coords_from_query_csv(): 20 | assert coords_from_query("-100, 40") == (-100, 40) 21 | 22 | def test_coords_from_query_csv_ws(): 23 | assert coords_from_query("-100, \n \t 40") == (-100, 40) 24 | 25 | def test_coords_from_query_csv_no_ws(): 26 | assert coords_from_query("-100,40") == (-100, 40) 27 | 28 | 29 | def test_coords_from_query_ws(): 30 | assert coords_from_query("-100 40") == (-100, 40) 31 | -------------------------------------------------------------------------------- /tests/test_mapmatching.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | import responses 3 | 4 | from mapboxcli.scripts.cli import main_group 5 | 6 | 7 | @responses.activate 8 | def test_cli_mapmatching(): 9 | 10 | responses.add( 11 | responses.POST, 12 | 'https://api.mapbox.com/matching/v4/mapbox.cycling.json?gps_precision=4&access_token=bogus', 13 | match_querystring=True, 14 | body="", status=200, 15 | content_type='application/json') 16 | 17 | runner = CliRunner() 18 | result = runner.invoke( 19 | main_group, 20 | ['--access-token', 'bogus', 21 | "mapmatching", 22 | "--gps-precision", "4", 23 | "--profile", "mapbox.cycling", 24 | "tests/line_feature.geojson"]) 25 | assert result.exit_code == 0 26 | 27 | def test_cli_mapmatching_invalid(): 28 | 29 | runner = CliRunner() 30 | result = runner.invoke( 31 | main_group, 32 | ['--access-token', 'bogus', 33 | "mapmatching", 34 | "--gps-precision", "4", 35 | "--profile", "mapbox.cycling", 36 | "tests/twopoints.geojson"]) 37 | assert result.exit_code != 0 38 | -------------------------------------------------------------------------------- /tests/test_static.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | import responses 3 | 4 | from mapboxcli.scripts.cli import main_group 5 | 6 | 7 | @responses.activate 8 | def test_cli_static(): 9 | 10 | responses.add( 11 | responses.GET, 12 | 'https://api.mapbox.com/v4/mapbox.satellite/-61.7,12.1,12/600x600.png256?access_token=bogus', 13 | match_querystring=True, 14 | body='.PNG...', 15 | status=200, 16 | content_type='image/png') 17 | 18 | runner = CliRunner() 19 | result = runner.invoke( 20 | main_group, 21 | ['--access-token', 'bogus', 22 | 'staticmap', 23 | '--lon', '-61.7', 24 | '--lat', '12.1', 25 | '--zoom', '12', 26 | 'mapbox.satellite', 27 | '/dev/null']) 28 | 29 | assert result.exit_code == 0 30 | 31 | 32 | @responses.activate 33 | def test_cli_static_features_stdin(): 34 | 35 | responses.add( 36 | responses.GET, 37 | 'https://api.mapbox.com/v4/mapbox.satellite/geojson(%7B%22features%22%3A%5B%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-122.7282%2C45.5801%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%220%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%2C%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-121.3153%2C44.0582%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%221%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%5D%2C%22type%22%3A%22FeatureCollection%22%7D)/-61.7,12.1,12/600x600.png256?access_token=bogus', 38 | match_querystring=True, 39 | body='.PNG...', 40 | status=200, 41 | content_type='image/png') 42 | 43 | # alternate ordering for py26 44 | responses.add( 45 | responses.GET, 46 | 'https://api.mapbox.com/v4/mapbox.satellite/geojson(%7B%22features%22%3A%5B%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-122.7282%2C45.580100000000002%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%220%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%2C%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-121.31529999999999%2C44.058199999999999%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%221%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%5D%2C%22type%22%3A%22FeatureCollection%22%7D)/-61.7,12.1,12/600x600.png256?access_token=bogus', 47 | match_querystring=True, 48 | body='.PNG...', 49 | status=200, 50 | content_type='image/png') 51 | 52 | with open('tests/twopoints_seq.geojson', 'r') as src: 53 | stdin = src.read() 54 | 55 | runner = CliRunner() 56 | result = runner.invoke( 57 | main_group, 58 | ['--access-token', 'bogus', 59 | 'staticmap', 60 | '--features', '-', 61 | '--lon', '-61.7', 62 | '--lat', '12.1', 63 | '--zoom', '12', 64 | 'mapbox.satellite', 65 | '/dev/null'], 66 | input=stdin) 67 | 68 | assert result.exit_code == 0 69 | 70 | 71 | def test_cli_bad_size(): 72 | with open('tests/twopoints_seq.geojson', 'r') as src: 73 | stdin = src.read() 74 | 75 | runner = CliRunner() 76 | result = runner.invoke( 77 | main_group, 78 | ['--access-token', 'bogus', 79 | 'staticmap', 80 | '--features', '-', 81 | '--size', '2000', '2000', 82 | '--zoom', '12', 83 | 'mapbox.satellite', 84 | '/dev/null'], 85 | input=stdin) 86 | 87 | assert result.exit_code == 2 88 | 89 | 90 | @responses.activate 91 | def test_cli_static_unauthorized(): 92 | responses.add( 93 | responses.GET, 94 | 'https://api.mapbox.com/v4/mapbox.satellite/-61.7,12.1,12/600x600.png256?access_token=INVALID', 95 | match_querystring=True, 96 | body='{"message":"Not Authorized - Invalid Token"}', status=401, 97 | content_type='application/json') 98 | 99 | runner = CliRunner() 100 | result = runner.invoke( 101 | main_group, 102 | ['--access-token', 'INVALID', 103 | 'staticmap', 104 | '--lon', '-61.7', 105 | '--lat', '12.1', 106 | '--zoom', '12', 107 | 'mapbox.satellite', 108 | '/dev/null']) 109 | 110 | assert result.exit_code == 1 111 | assert result.output == 'Error: {"message":"Not Authorized - Invalid Token"}\n' 112 | 113 | 114 | @responses.activate 115 | def test_cli_static_features_file(): 116 | 117 | responses.add( 118 | responses.GET, 119 | 'https://api.mapbox.com/v4/mapbox.satellite/geojson(%7B%22features%22%3A%5B%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-122.7282%2C45.5801%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%220%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%2C%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-121.3153%2C44.0582%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%221%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%5D%2C%22type%22%3A%22FeatureCollection%22%7D)/-61.7,12.1,12/600x600.png256?access_token=bogus', 120 | match_querystring=True, 121 | body='.PNG...', 122 | status=200, 123 | content_type='image/png') 124 | 125 | # alternate ordering for py26 126 | responses.add( 127 | responses.GET, 128 | 'https://api.mapbox.com/v4/mapbox.satellite/geojson(%7B%22features%22%3A%5B%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-122.7282%2C45.580100000000002%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%220%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%2C%7B%22geometry%22%3A%7B%22coordinates%22%3A%5B-121.31529999999999%2C44.058199999999999%5D%2C%22type%22%3A%22Point%22%7D%2C%22id%22%3A%221%22%2C%22properties%22%3A%7B%7D%2C%22type%22%3A%22Feature%22%7D%5D%2C%22type%22%3A%22FeatureCollection%22%7D)/-61.7,12.1,12/600x600.png256?access_token=bogus', 129 | match_querystring=True, 130 | body='.PNG...', 131 | status=200, 132 | content_type='image/png') 133 | 134 | 135 | runner = CliRunner() 136 | result = runner.invoke( 137 | main_group, 138 | ['--access-token', 'bogus', 139 | 'staticmap', 140 | '--features', 'tests/twopoints_seq.geojson', 141 | '--lon', '-61.7', 142 | '--lat', '12.1', 143 | '--zoom', '12', 144 | 'mapbox.satellite', 145 | '/dev/null']) 146 | 147 | assert result.exit_code == 0 148 | -------------------------------------------------------------------------------- /tests/test_uploads.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from click.testing import CliRunner 4 | import responses 5 | 6 | from mapboxcli.scripts.cli import main_group 7 | import mapbox 8 | 9 | 10 | username = 'testuser' 11 | access_token = 'pk.{0}.test'.format( 12 | base64.b64encode(b'{"u":"testuser"}').decode('utf-8')) 13 | 14 | 15 | upload_response_body = """ 16 | {{"progress": 0, 17 | "modified": "date.test", 18 | "error": null, 19 | "tileset": "{username}.test1", 20 | "complete": false, 21 | "owner": "{username}", 22 | "created": "date.test", 23 | "id": "id.test", 24 | "name": null}}""".format(username=username) 25 | 26 | 27 | class MockSession(object): 28 | """ Mocks a boto3 session, 29 | specifically for the purposes of an s3 key put 30 | """ 31 | def __init__(self, *args, **kwargs): 32 | self.bucket = None 33 | self.key = None 34 | self.body = None 35 | self.resource_name = None 36 | 37 | def Bucket(self, bucket): 38 | self.bucket = bucket 39 | return self 40 | 41 | def resource(self, name): 42 | self.resource_name = name 43 | return self 44 | 45 | def Object(self, bucket, key): 46 | assert self.resource_name == 's3' 47 | self.bucket = bucket 48 | self.key = key 49 | return self 50 | 51 | def put(self, Body): 52 | assert self.bucket 53 | assert self.key 54 | self.body = Body 55 | return True 56 | 57 | def upload_file(self, src, dst): 58 | assert self.bucket 59 | self.key = dst 60 | return True 61 | 62 | def upload_fileobj(self, data, key, Callback=None): 63 | self.data = data 64 | self.key = key 65 | self.Callback = Callback 66 | 67 | bytes_read = data.read(8192) 68 | if bytes_read and self.Callback: 69 | self.Callback(len(bytes_read)) 70 | while bytes_read: 71 | bytes_read = data.read(8192) 72 | if bytes_read and self.Callback: 73 | self.Callback(len(bytes_read)) 74 | 75 | 76 | @responses.activate 77 | def test_cli_upload(monkeypatch): 78 | 79 | monkeypatch.setattr(mapbox.services.uploads, 'boto3_session', MockSession) 80 | 81 | # Credentials 82 | query_body = """ 83 | {{"key": "_pending/{username}/key.test", 84 | "accessKeyId": "ak.test", 85 | "bucket": "tilestream-tilesets-production", 86 | "url": "https://tilestream-tilesets-production.s3.amazonaws.com/_pending/{username}/key.test", 87 | "secretAccessKey": "sak.test", 88 | "sessionToken": "st.test"}}""".format(username=username) 89 | 90 | responses.add( 91 | responses.POST, 92 | 'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token={1}'.format( 93 | username, access_token), 94 | match_querystring=True, 95 | body=query_body, status=200, 96 | content_type='application/json') 97 | 98 | responses.add( 99 | responses.POST, 100 | 'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token), 101 | match_querystring=True, 102 | body=upload_response_body, status=201, 103 | content_type='application/json') 104 | 105 | runner = CliRunner() 106 | result = runner.invoke( 107 | main_group, 108 | ['--access-token', access_token, 'upload', username + '.test-data', 109 | 'tests/twopoints.geojson']) 110 | assert result.exit_code == 0 111 | assert "Uploading data source" in result.output 112 | 113 | 114 | @responses.activate 115 | def test_cli_create_aws(monkeypatch): 116 | """Skip upload and do it all in the 'cloud'""" 117 | 118 | monkeypatch.setattr(mapbox.services.uploads, 'boto3_session', MockSession) 119 | 120 | responses.add( 121 | responses.POST, 122 | 'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token), 123 | match_querystring=True, 124 | body=upload_response_body, status=201, 125 | content_type='application/json') 126 | 127 | runner = CliRunner() 128 | result = runner.invoke( 129 | main_group, 130 | ['--access-token', access_token, 'upload', username + '.test-data', 131 | 'https://s3.amazonaws.com/mapbox/rasterio/RGB.byte.tif']) 132 | assert result.exit_code == 0 133 | assert '"tileset": "testuser.test1"' in result.output 134 | 135 | 136 | @responses.activate 137 | def test_cli_upload_unknown_error(monkeypatch): 138 | 139 | monkeypatch.setattr(mapbox.services.uploads, 'boto3_session', MockSession) 140 | 141 | # Credentials 142 | query_body = """ 143 | {{"key": "_pending/{username}/key.test", 144 | "accessKeyId": "ak.test", 145 | "bucket": "tilestream-tilesets-production", 146 | "url": "https://tilestream-tilesets-production.s3.amazonaws.com/_pending/{username}/key.test", 147 | "secretAccessKey": "sak.test", 148 | "sessionToken": "st.test"}}""".format(username=username) 149 | 150 | responses.add( 151 | responses.POST, 152 | 'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token={1}'.format( 153 | username, access_token), 154 | match_querystring=True, 155 | body=query_body, status=200, 156 | content_type='application/json') 157 | 158 | responses.add( 159 | responses.POST, 160 | 'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token), 161 | match_querystring=True, 162 | body='{"message":"Something went wrong"}', status=500, 163 | content_type='application/json') 164 | 165 | runner = CliRunner() 166 | result = runner.invoke( 167 | main_group, 168 | ['--access-token', access_token, 'upload', username + '.test-data', 169 | 'tests/twopoints.geojson']) 170 | assert result.exit_code == 1 171 | assert result.output.endswith('Error: {"message":"Something went wrong"}\n') 172 | 173 | 174 | @responses.activate 175 | def test_cli_upload_doesnotexist(monkeypatch): 176 | 177 | monkeypatch.setattr(mapbox.services.uploads, 'boto3_session', MockSession) 178 | 179 | # Credentials 180 | query_body = """ 181 | {{"key": "_pending/{username}/key.test", 182 | "accessKeyId": "ak.test", 183 | "bucket": "tilestream-tilesets-production", 184 | "url": "https://tilestream-tilesets-production.s3.amazonaws.com/_pending/{username}/key.test", 185 | "secretAccessKey": "sak.test", 186 | "sessionToken": "st.test"}}""".format(username=username) 187 | 188 | responses.add( 189 | responses.POST, 190 | 'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token={1}'.format( 191 | username, access_token), 192 | match_querystring=True, 193 | body=query_body, status=200, 194 | content_type='application/json') 195 | 196 | runner = CliRunner() 197 | result = runner.invoke( 198 | main_group, 199 | ['--access-token', access_token, 200 | 'upload', 201 | 'tests/doesnotexist.gml', 202 | username + 'test-data']) 203 | assert result.exit_code == 2 204 | 205 | 206 | @responses.activate 207 | def test_cli_upload_stdin(monkeypatch): 208 | 209 | monkeypatch.setattr(mapbox.services.uploads, 'boto3_session', MockSession) 210 | 211 | # Credentials 212 | query_body = """ 213 | {{"key": "_pending/{username}/key.test", 214 | "accessKeyId": "ak.test", 215 | "bucket": "tilestream-tilesets-production", 216 | "url": "https://tilestream-tilesets-production.s3.amazonaws.com/_pending/{username}/key.test", 217 | "secretAccessKey": "sak.test", 218 | "sessionToken": "st.test"}}""".format(username=username) 219 | 220 | responses.add( 221 | responses.POST, 222 | 'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token={1}'.format( 223 | username, access_token), 224 | match_querystring=True, 225 | body=query_body, status=200, 226 | content_type='application/json') 227 | 228 | responses.add( 229 | responses.POST, 230 | 'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token), 231 | match_querystring=True, 232 | body=upload_response_body, status=201, 233 | content_type='application/json') 234 | 235 | runner = CliRunner() 236 | result = runner.invoke( 237 | main_group, 238 | ['--access-token', access_token, 'upload', username + '.test-data'], 239 | input='{"type":"FeatureCollection","features":[]}') 240 | assert result.exit_code == 0 241 | 242 | 243 | def test_too_many_args(): 244 | runner = CliRunner() 245 | result = runner.invoke( 246 | main_group, 247 | ['upload', 248 | username + '.test-data', 249 | 'tests/twopoints.geojson', 250 | 'useless-arg']) 251 | 252 | assert result.exit_code == 2 253 | -------------------------------------------------------------------------------- /tests/twopoints.geojson: -------------------------------------------------------------------------------- 1 | {"features": [{"bbox": [-122.9292140099711, 45.37948199034149, -122.44106199104115, 45.858097009742835], "center": [-122.7282, 45.5801], "context": [{"id": "postcode.2503633822", "text": "97203"}, {"id": "region.3470299826", "text": "Oregon"}, {"id": "country.4150104525", "short_code": "us", "text": "United States"}], "geometry": {"coordinates": [-122.7282, 45.5801], "type": "Point"}, "id": "place.42767", "place_name": "Portland, Oregon, United States", "properties": {}, "relevance": 0.999, "text": "Portland", "type": "Feature"}, {"bbox": [-121.9779540096568, 43.74737999114854, -120.74788099000016, 44.32812500969035], "center": [-121.3153, 44.0582], "context": [{"id": "postcode.3332732485", "text": "97701"}, {"id": "region.3470299826", "text": "Oregon"}, {"id": "country.4150104525", "short_code": "us", "text": "United States"}], "geometry": {"coordinates": [-121.3153, 44.0582], "type": "Point"}, "id": "place.3965", "place_name": "Bend, Oregon, United States", "properties": {}, "relevance": 0.999, "text": "Bend", "type": "Feature"}], "type": "FeatureCollection"} 2 | -------------------------------------------------------------------------------- /tests/twopoints_seq.geojson: -------------------------------------------------------------------------------- 1 | {"geometry": {"coordinates": [-122.7282, 45.5801], "type": "Point"}, "id": "0", "properties": {}, "type": "Feature"} 2 | {"geometry": {"coordinates": [-121.3153, 44.0582], "type": "Point"}, "id": "1", "properties": {}, "type": "Feature"} 3 | -------------------------------------------------------------------------------- /tests/twopoints_seqrs.geojson: -------------------------------------------------------------------------------- 1 | {"geometry": {"coordinates": [-122.7282, 45.5801], "type": "Point"}, "id": "0", "properties": {}, "type": "Feature"} 2 | {"geometry": {"coordinates": [-121.3153, 44.0582], "type": "Point"}, "id": "1", "properties": {}, "type": "Feature"} 3 | --------------------------------------------------------------------------------