├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── dependabot.yml ├── pull_request_template.md ├── scripts │ ├── distribution.sh │ └── install.sh ├── stale.yml ├── sync-repo-settings.yaml └── workflows │ ├── dependabot.yml │ ├── docs.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .releaserc ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIB.md ├── CONTRIBUTORS ├── LICENSE ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── coverage.xml ├── docs ├── conf.py └── index.rst ├── googlemaps ├── __init__.py ├── addressvalidation.py ├── client.py ├── convert.py ├── directions.py ├── distance_matrix.py ├── elevation.py ├── exceptions.py ├── geocoding.py ├── geolocation.py ├── maps.py ├── places.py ├── roads.py └── timezone.py ├── noxfile.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── test_addressvalidation.py ├── test_client.py ├── test_convert.py ├── test_directions.py ├── test_distance_matrix.py ├── test_elevation.py ├── test_geocoding.py ├── test_geolocation.py ├── test_maps.py ├── test_places.py ├── test_roads.py └── test_timezone.py └── text.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 16 | 17 | .github/ @googlemaps/admin 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'type: bug, triage me' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thanks for stopping by to let us know something could be better! 11 | 12 | --- 13 | **PLEASE READ** 14 | 15 | If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. 16 | 17 | Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). 18 | 19 | If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). 20 | 21 | Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. 22 | 23 | --- 24 | 25 | Please be sure to include as much information as possible: 26 | 27 | #### Environment details 28 | 29 | 1. Specify the API at the beginning of the title (for example, "Places: ...") 30 | 2. OS type and version 31 | 3. Library version and other environment information 32 | 33 | #### Steps to reproduce 34 | 35 | 1. ? 36 | 37 | #### Code example 38 | 39 | ```python 40 | # example 41 | ``` 42 | 43 | #### Stack trace 44 | ``` 45 | # example 46 | ``` 47 | 48 | Following these steps will guarantee the quickest resolution possible. 49 | 50 | Thanks! 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this library 4 | title: '' 5 | labels: 'type: feature request, triage me' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thanks for stopping by to let us know something could be better! 11 | 12 | --- 13 | **PLEASE READ** 14 | 15 | If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. 16 | 17 | Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). 18 | 19 | If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). 20 | 21 | Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. 22 | 23 | --- 24 | 25 | **Is your feature request related to a problem? Please describe.** 26 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 27 | 28 | **Describe the solution you'd like** 29 | A clear and concise description of what you want to happen. 30 | 31 | **Describe alternatives you've considered** 32 | A clear and concise description of any alternative solutions or features you've considered. 33 | 34 | **Additional context** 35 | Add any other context or screenshots about the feature request here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: If you have a support contract with Google, please create an issue in the Google 4 | Cloud Support console. 5 | title: '' 6 | labels: 'triage me, type: question' 7 | assignees: '' 8 | 9 | --- 10 | 11 | **PLEASE READ** 12 | 13 | If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. 14 | 15 | Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). 16 | 17 | If your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker). 18 | 19 | Check for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag. 20 | 21 | --- 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull request 3 | about: Create a pull request 4 | label: 'triage me' 5 | --- 6 | Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 7 | - [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 8 | - [ ] Ensure the tests and linter pass 9 | - [ ] Code coverage does not decrease (if any source code was changed) 10 | - [ ] Appropriate docs were updated (if necessary) 11 | 12 | Fixes # 🦕 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: 2 16 | updates: 17 | - package-ecosystem: pip 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | open-pull-requests-limit: 10 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thank you for opening a Pull Request! 2 | 3 | --- 4 | 5 | Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 6 | - [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 7 | - [ ] Ensure the tests and linter pass 8 | - [ ] Code coverage does not decrease (if any source code was changed) 9 | - [ ] Appropriate docs were updated (if necessary) 10 | 11 | Fixes # 🦕 12 | -------------------------------------------------------------------------------- /.github/scripts/distribution.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | rm -rf dist 18 | 19 | python setup.py sdist 20 | pip install $(find dist -name googlemaps-*.tar.gz) 21 | -------------------------------------------------------------------------------- /.github/scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | set -exo pipefail 18 | 19 | if ! python3 -m pip --version; then 20 | curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 21 | sudo python3 get-pip.py 22 | sudo python3 -m pip install --upgrade setuptools 23 | sudo python3 -m pip install nox twine 24 | else 25 | sudo python3 -m pip install --upgrade setuptools 26 | python3 -m pip install nox 27 | python3 -m pip install --prefer-binary twine 28 | fi 29 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Configuration for probot-stale - https://github.com/probot/stale 16 | 17 | # Number of days of inactivity before an Issue or Pull Request becomes stale 18 | daysUntilStale: 120 19 | 20 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 21 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 22 | daysUntilClose: 180 23 | 24 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 25 | onlyLabels: [] 26 | 27 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 28 | exemptLabels: 29 | - pinned 30 | - "type: bug" 31 | 32 | # Set to true to ignore issues in a project (defaults to false) 33 | exemptProjects: false 34 | 35 | # Set to true to ignore issues in a milestone (defaults to false) 36 | exemptMilestones: false 37 | 38 | # Set to true to ignore issues with an assignee (defaults to false) 39 | exemptAssignees: false 40 | 41 | # Label to use when marking as stale 42 | staleLabel: "stale" 43 | 44 | # Comment to post when marking as stale. Set to `false` to disable 45 | markComment: > 46 | This issue has been automatically marked as stale because it has not had 47 | recent activity. Please comment here if it is still valid so that we can 48 | reprioritize. Thank you! 49 | 50 | # Comment to post when removing the stale label. 51 | # unmarkComment: > 52 | # Your comment here. 53 | 54 | # Comment to post when closing a stale Issue or Pull Request. 55 | closeComment: > 56 | Closing this. Please reopen if you believe it should be addressed. Thank you for your contribution. 57 | 58 | # Limit the number of actions per hour, from 1-30. Default is 30 59 | limitPerRun: 10 60 | 61 | # Limit to only `issues` or `pulls` 62 | only: issues 63 | 64 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 65 | # pulls: 66 | # daysUntilStale: 30 67 | # markComment: > 68 | # This pull request has been automatically marked as stale because it has not had 69 | # recent activity. It will be closed if no further activity occurs. Thank you 70 | # for your contributions. 71 | 72 | # issues: 73 | # exemptLabels: 74 | # - confirmed 75 | -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings 16 | 17 | rebaseMergeAllowed: true 18 | squashMergeAllowed: true 19 | mergeCommitAllowed: false 20 | deleteBranchOnMerge: true 21 | branchProtectionRules: 22 | - pattern: main 23 | isAdminEnforced: false 24 | requiresStrictStatusChecks: false 25 | requiredStatusCheckContexts: 26 | - 'cla/google' 27 | - 'test' 28 | - 'snippet-bot check' 29 | - 'header-check' 30 | requiredApprovingReviewCount: 1 31 | requiresCodeOwnerReviews: true 32 | - pattern: master 33 | isAdminEnforced: false 34 | requiresStrictStatusChecks: false 35 | requiredStatusCheckContexts: 36 | - 'cla/google' 37 | - 'test' 38 | - 'snippet-bot check' 39 | - 'header-check' 40 | requiredApprovingReviewCount: 1 41 | requiresCodeOwnerReviews: true 42 | permissionRules: 43 | - team: admin 44 | permission: admin 45 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Dependabot 16 | on: pull_request 17 | 18 | permissions: 19 | contents: write 20 | 21 | jobs: 22 | dependabot: 23 | runs-on: ubuntu-latest 24 | if: ${{ github.actor == 'dependabot[bot]' }} 25 | env: 26 | PR_URL: ${{github.event.pull_request.html_url}} 27 | GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} 28 | steps: 29 | - name: approve 30 | run: gh pr review --approve "$PR_URL" 31 | - name: merge 32 | run: gh pr merge --auto --squash --delete-branch "$PR_URL" 33 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # A workflow that pushes artifacts to Sonatype 16 | name: Publish 17 | 18 | on: 19 | push: 20 | tags: 21 | - '*' 22 | repository_dispatch: 23 | types: [docs] 24 | 25 | jobs: 26 | docs: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | 33 | - name: Setup Python 34 | uses: "actions/setup-python@v3" 35 | with: 36 | python-version: "3.9" 37 | 38 | - name: Install dependencies 39 | run: ./.github/scripts/install.sh 40 | 41 | - name: Generate docs 42 | run: python3 -m nox --session docs 43 | 44 | - name: Update gh-pages branch with docs 45 | run: | 46 | echo "Creating tar for generated docs" 47 | cd ./docs/_build/html && tar cvf ~/docs.tar . 48 | 49 | echo "Unpacking tar into gh-pages branch" 50 | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* 51 | cd $GITHUB_WORKSPACE && git checkout gh-pages && tar xvf ~/docs.tar 52 | 53 | - name: PR Changes 54 | uses: peter-evans/create-pull-request@v4 55 | with: 56 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 57 | commit-message: 'docs: Update docs' 58 | committer: googlemaps-bot 59 | author: googlemaps-bot 60 | title: 'docs: Update docs' 61 | body: | 62 | Updated GitHub pages with latest from `python3 -m nox --session docs`. 63 | branch: googlemaps-bot/update_gh_pages 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Release 16 | on: 17 | push: 18 | branches: [ master ] 19 | jobs: 20 | release: 21 | runs-on: ubuntu-latest 22 | env: 23 | PYTHONDONTWRITEBYTECODE: 1 24 | steps: 25 | - name: Setup Python 26 | uses: "actions/setup-python@v3" 27 | with: 28 | python-version: "3.9" 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | with: 32 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 33 | - name: Install dependencies 34 | run: ./.github/scripts/install.sh 35 | - name: Run distribution 36 | run: python3 -m nox -e distribution 37 | - name: Cleanup old dist 38 | run: rm -rf googlemaps-* dist/ 39 | - name: Semantic Release 40 | uses: cycjimmy/semantic-release-action@v3 41 | with: 42 | semantic_version: 19 43 | extra_plugins: | 44 | "@semantic-release/commit-analyzer" 45 | "@semantic-release/release-notes-generator" 46 | "@google/semantic-release-replace-plugin" 47 | "@semantic-release/exec" 48 | "@semantic-release/git" 49 | "@semantic-release/github" 50 | env: 51 | GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 52 | TWINE_USERNAME: __token__ 53 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 54 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # A workflow that runs tests on every new pull request 16 | name: Run tests 17 | 18 | on: 19 | repository_dispatch: 20 | types: [test] 21 | pull_request: 22 | branches: ['*'] 23 | push: 24 | branches: ['*'] 25 | 26 | jobs: 27 | matrix: 28 | name: "Run tests on Python ${{ matrix.python-version }}" 29 | runs-on: ubuntu-latest 30 | strategy: 31 | matrix: 32 | python-version: ["3.7", "3.8", "3.9", "3.10"] 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v3 36 | 37 | - name: Setup Python 38 | uses: "actions/setup-python@v3" 39 | with: 40 | python-version: "${{ matrix.python-version }}" 41 | 42 | - name: Install dependencies 43 | run: ./.github/scripts/install.sh 44 | 45 | - name: Run tests 46 | run: | 47 | python3 -m nox --session "tests-${{ matrix.python-version }}" 48 | python3 -m nox -e distribution 49 | test: 50 | name: Wait for matrix to finish 51 | needs: [matrix] 52 | runs-on: ubuntu-latest 53 | steps: 54 | - run: | 55 | echo "Test matrix finished"; 56 | exit 0; 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.pyc 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | .DS_Store? 9 | ._* 10 | .Spotlight-V100 11 | .Trashes 12 | ehthumbs.db 13 | Thumbs.db 14 | 15 | # Sphinx documentation # 16 | ######################## 17 | docs/_build/ 18 | generated_docs/ 19 | 20 | # Release generated files # 21 | ########################### 22 | .eggs/ 23 | build/ 24 | dist/ 25 | 26 | # vim 27 | *.swp 28 | 29 | # python testing things etc 30 | .coverage 31 | .nox 32 | env 33 | googlemaps.egg-info 34 | *.egg 35 | .vscode/ 36 | .idea/ 37 | index.py 38 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | branches: 2 | - master 3 | plugins: 4 | - "@semantic-release/commit-analyzer" 5 | - "@semantic-release/release-notes-generator" 6 | - - "@google/semantic-release-replace-plugin" 7 | - replacements: 8 | - files: 9 | - "./googlemaps/__init__.py" 10 | from: "__version__ = \".*\"" 11 | to: "__version__ = \"${nextRelease.version}\"" 12 | - files: 13 | - "./setup.py" 14 | from: 'version=".*"' 15 | to: 'version="${nextRelease.version}"' 16 | - [ "@semantic-release/exec", { publishCmd: "python3 setup.py sdist && python3 -m twine upload dist/*" }] 17 | - - "@semantic-release/git" 18 | - assets: 19 | - "./googlemaps/__init__.py" 20 | - "./setup.py" 21 | - "@semantic-release/github" 22 | options: 23 | debug: true 24 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Python Client for Google Maps Services authors 2 | # for copyright purposes. This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | # Please keep the list sorted. 10 | 11 | Google Inc. 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [v4.2.0] 5 | ### Added 6 | - Add support for Maps Static API (#344) 7 | 8 | ## [v4.1.0] 9 | ### Added 10 | - Adding support for passing in `experience_id` to Client class (#338) 11 | 12 | ## [v4.0.0] 13 | ### Changed 14 | - Python 2 is no longer supported 15 | - Removed place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. 16 | 17 | ## [v3.1.4] 18 | ### Changed 19 | - `APIError.__str__` should always return a str (#328) 20 | 21 | ## [v3.1.3] 22 | ### Changed 23 | - deprecation warning for place fields: `alt_id`, `id`, `reference`, and `scope`. Read more about this at https://developers.google.com/maps/deprecations. 24 | 25 | ## [v3.1.2] 26 | ### Added 27 | - Tests for distribution tar as part of CI 28 | - Support for subfields such as `geometry/location` and `geometry/viewport` in Places. 29 | 30 | ## [v3.1.1] 31 | ### Changed 32 | - Added changelog to manifest 33 | 34 | ## [v3.1.0] 35 | ### Changed 36 | - Switched build system to use [nox](https://nox.thea.codes/en/stable/), pytest, and codecov. Added Python 3.7 to test framework. 37 | - Set precision of truncated latitude and longitude floats [to 8 decimals](https://github.com/googlemaps/google-maps-services-python/pull/301) instead of 6. 38 | - Minimum version of requests increased. 39 | - Session token parameter [added](https://github.com/googlemaps/google-maps-services-python/pull/244) to `place()`. 40 | - Fixed issue where headers in `request_kwargs` were being overridden. 41 | ### Added 42 | - Automation for PyPi uploads. 43 | - Long description to package. 44 | - Added tests to manifest and tarball. 45 | ### Removed 46 | - Removed places `places_autocomplete_session_token` which can be replaced with `uuid.uuid4().hex`. 47 | - Removed deprecated `places_radar`. 48 | 49 | 50 | **Note:** Start of changelog is 2019-08-27, [v3.0.2]. 51 | 52 | [Unreleased]: https://github.com/googlemaps/google-maps-services-python/compare/4.2.0...HEAD 53 | [v4.2.0]: https://github.com/googlemaps/google-maps-services-python/compare/4.1.0...4.2.0 54 | [v4.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/4.0.0...4.1.0 55 | [v4.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.4...4.0.0 56 | [v3.1.4]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.3...3.1.4 57 | [v3.1.3]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.2...3.1.3 58 | [v3.1.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.1...3.1.2 59 | [v3.1.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.1.0...3.1.1 60 | [v3.1.0]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.2...3.1.0 61 | [v3.0.2]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.1...3.0.2 62 | [v3.0.1]: https://github.com/googlemaps/google-maps-services-python/compare/3.0.0...3.0.1 63 | [v3.0.0]: https://github.com/googlemaps/google-maps-services-python/compare/2.5.1...3.0.0 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Google Open Source Community Guidelines 2 | 3 | At Google, we recognize and celebrate the creativity and collaboration of open 4 | source contributors and the diversity of skills, experiences, cultures, and 5 | opinions they bring to the projects and communities they participate in. 6 | 7 | Every one of Google's open source projects and communities are inclusive 8 | environments, based on treating all individuals respectfully, regardless of 9 | gender identity and expression, sexual orientation, disabilities, 10 | neurodiversity, physical appearance, body size, ethnicity, nationality, race, 11 | age, religion, or similar personal characteristic. 12 | 13 | We value diverse opinions, but we value respectful behavior more. 14 | 15 | Respectful behavior includes: 16 | 17 | * Being considerate, kind, constructive, and helpful. 18 | * Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or 19 | physically threatening behavior, speech, and imagery. 20 | * Not engaging in unwanted physical contact. 21 | 22 | Some Google open source projects [may adopt][] an explicit project code of 23 | conduct, which may have additional detailed expectations for participants. Most 24 | of those projects will use our [modified Contributor Covenant][]. 25 | 26 | [may adopt]: https://opensource.google/docs/releasing/preparing/#conduct 27 | [modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ 28 | 29 | ## Resolve peacefully 30 | 31 | We do not believe that all conflict is necessarily bad; healthy debate and 32 | disagreement often yields positive results. However, it is never okay to be 33 | disrespectful. 34 | 35 | If you see someone behaving disrespectfully, you are encouraged to address the 36 | behavior directly with those involved. Many issues can be resolved quickly and 37 | easily, and this gives people more control over the outcome of their dispute. 38 | If you are unable to resolve the matter for any reason, or if the behavior is 39 | threatening or harassing, report it. We are dedicated to providing an 40 | environment where participants feel welcome and safe. 41 | 42 | ## Reporting problems 43 | 44 | Some Google open source projects may adopt a project-specific code of conduct. 45 | In those cases, a Google employee will be identified as the Project Steward, 46 | who will receive and handle reports of code of conduct violations. In the event 47 | that a project hasn’t identified a Project Steward, you can report problems by 48 | emailing opensource@google.com. 49 | 50 | We will investigate every complaint, but you may not receive a direct response. 51 | We will use our discretion in determining when and how to follow up on reported 52 | incidents, which may range from not taking action to permanent expulsion from 53 | the project and project-sponsored spaces. We will notify the accused of the 54 | report and provide them an opportunity to discuss it before any action is 55 | taken. The identity of the reporter will be omitted from the details of the 56 | report supplied to the accused. In potentially harmful situations, such as 57 | ongoing harassment or threats to anyone's safety, we may take action without 58 | notice. 59 | 60 | *This document was adapted from the [IndieWeb Code of Conduct][] and can also 61 | be found at .* 62 | 63 | [IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct 64 | -------------------------------------------------------------------------------- /CONTRIB.md: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | Want to help out? That's awesome! 5 | 6 | The library is open source and lives on GitHub at: 7 | https://github.com/googlemaps/google-maps-services-python. 8 | Open an issue or fork the library and submit a pull request. 9 | 10 | Keep in mind that before we can accept any pull requests we have to jump 11 | through a couple of legal hurdles, primarily a Contributor License Agreement 12 | (CLA): 13 | 14 | - **If you are an individual writing original source code** 15 | and you're sure you own the intellectual property, 16 | then you'll need to sign an 17 | [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). 18 | - **If you work for a company that wants to allow you to contribute your work**, 19 | then you'll need to sign a 20 | [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html) 21 | 22 | Follow either of the two links above to access the appropriate CLA and 23 | instructions for how to sign and return it. Once we receive it, we'll be able 24 | to accept your pull requests. 25 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who have contributed to the project. The 2 | # copyright is held by those individuals or organizations in the AUTHORS file. 3 | # 4 | # Names should be added to this file like so: 5 | # Name 6 | 7 | # Please keep the list sorted by first name. 8 | 9 | Brett Morgan 10 | Chris Broadfoot 11 | Dave Holmes 12 | Luke Mahe 13 | Mark McDonald 14 | Sam Thorogood 15 | Sean Wohltman 16 | Stephen McDonald 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md LICENSE README.md 2 | global-exclude __pycache__ 3 | global-exclude *.py[co] 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python Client for Google Maps Services 2 | ==================================== 3 | 4 | ![Test](https://github.com/googlemaps/google-maps-services-js/workflows/Test/badge.svg) 5 | ![Release](https://github.com/googlemaps/google-maps-services-js/workflows/Release/badge.svg) 6 | [![codecov](https://codecov.io/gh/googlemaps/google-maps-services-python/branch/master/graph/badge.svg)](https://codecov.io/gh/googlemaps/google-maps-services-python) 7 | [![PyPI version](https://badge.fury.io/py/googlemaps.svg)](https://badge.fury.io/py/googlemaps) 8 | ![PyPI - Downloads](https://img.shields.io/pypi/dd/googlemaps) 9 | ![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/google-maps-services-python) 10 | 11 | ## Description 12 | 13 | Use Python? Want to geocode something? Looking for directions? 14 | Maybe matrices of directions? This library brings the Google Maps Platform Web 15 | Services to your Python application. 16 | 17 | The Python Client for Google Maps Services is a Python Client library for the following Google Maps 18 | APIs: 19 | 20 | - Directions API 21 | - Distance Matrix API 22 | - Elevation API 23 | - Geocoding API 24 | - Geolocation API 25 | - Time Zone API 26 | - Roads API 27 | - Places API 28 | - Maps Static API 29 | - Address Validation API 30 | 31 | Keep in mind that the same [terms and conditions](https://developers.google.com/maps/terms) apply 32 | to usage of the APIs when they're accessed through this library. 33 | 34 | ## Support 35 | 36 | This library is community supported. We're comfortable enough with the stability and features of 37 | the library that we want you to build real production applications on it. We will try to support, 38 | through Stack Overflow, the public and protected surface of the library and maintain backwards 39 | compatibility in the future; however, while the library is in version 0.x, we reserve the right 40 | to make backwards-incompatible changes. If we do remove some functionality (typically because 41 | better functionality exists or if the feature proved infeasible), our intention is to deprecate 42 | and give developers a year to update their code. 43 | 44 | If you find a bug, or have a feature suggestion, please log an issue. If you'd like to 45 | contribute, please read contribute. 46 | 47 | ## Requirements 48 | 49 | - Python 3.5 or later. 50 | - A Google Maps API key. 51 | 52 | ## API Keys 53 | 54 | Each Google Maps Web Service request requires an API key or client ID. API keys 55 | are generated in the 'Credentials' page of the 'APIs & Services' tab of [Google Cloud console](https://console.cloud.google.com/apis/credentials). 56 | 57 | For even more information on getting started with Google Maps Platform and generating/restricting an API key, see [Get Started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started) in our docs. 58 | 59 | **Important:** This key should be kept secret on your server. 60 | 61 | ## Installation 62 | 63 | $ pip install -U googlemaps 64 | 65 | Note that you will need requests 2.4.0 or higher if you want to specify connect/read timeouts. 66 | 67 | ## Usage 68 | 69 | This example uses the Geocoding API and the Directions API with an API key: 70 | 71 | ```python 72 | import googlemaps 73 | from datetime import datetime 74 | 75 | gmaps = googlemaps.Client(key='Add Your Key here') 76 | 77 | # Geocoding an address 78 | geocode_result = gmaps.geocode('1600 Amphitheatre Parkway, Mountain View, CA') 79 | 80 | # Look up an address with reverse geocoding 81 | reverse_geocode_result = gmaps.reverse_geocode((40.714224, -73.961452)) 82 | 83 | # Request directions via public transit 84 | now = datetime.now() 85 | directions_result = gmaps.directions("Sydney Town Hall", 86 | "Parramatta, NSW", 87 | mode="transit", 88 | departure_time=now) 89 | 90 | # Validate an address with address validation 91 | addressvalidation_result = gmaps.addressvalidation(['1600 Amphitheatre Pk'], 92 | regionCode='US', 93 | locality='Mountain View', 94 | enableUspsCass=True) 95 | 96 | # Get an Address Descriptor of a location in the reverse geocoding response 97 | address_descriptor_result = gmaps.reverse_geocode((40.714224, -73.961452), enable_address_descriptor=True) 98 | 99 | ``` 100 | 101 | For more usage examples, check out [the tests](https://github.com/googlemaps/google-maps-services-python/tree/master/tests). 102 | 103 | ## Features 104 | 105 | ### Retry on Failure 106 | 107 | Automatically retry when intermittent failures occur. That is, when any of the retriable 5xx errors 108 | are returned from the API. 109 | 110 | 111 | ## Building the Project 112 | 113 | 114 | # Installing nox 115 | $ pip install nox 116 | 117 | # Running tests 118 | $ nox 119 | 120 | # Generating documentation 121 | $ nox -e docs 122 | 123 | # Copy docs to gh-pages 124 | $ nox -e docs && mv docs/_build/html generated_docs && git clean -Xdi && git checkout gh-pages 125 | 126 | ## Documentation & resources 127 | 128 | [Documentation for the `google-maps-services-python` library](https://googlemaps.github.io/google-maps-services-python/docs/index.html) 129 | 130 | ### Getting started 131 | - [Get Started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started) 132 | - [Generating/restricting an API key](https://developers.google.com/maps/gmp-get-started#api-key) 133 | - [Authenticating with a client ID](https://developers.google.com/maps/documentation/directions/get-api-key#client-id) 134 | 135 | ### API docs 136 | - [Google Maps Platform web services](https://developers.google.com/maps/apis-by-platform#web_service_apis) 137 | - [Directions API](https://developers.google.com/maps/documentation/directions/) 138 | - [Distance Matrix API](https://developers.google.com/maps/documentation/distancematrix/) 139 | - [Elevation API](https://developers.google.com/maps/documentation/elevation/) 140 | - [Geocoding API](https://developers.google.com/maps/documentation/geocoding/) 141 | - [Geolocation API](https://developers.google.com/maps/documentation/geolocation/) 142 | - [Time Zone API](https://developers.google.com/maps/documentation/timezone/) 143 | - [Roads API](https://developers.google.com/maps/documentation/roads/) 144 | - [Places API](https://developers.google.com/places/) 145 | - [Maps Static API](https://developers.google.com/maps/documentation/maps-static/) 146 | 147 | ### Support 148 | - [Report an issue](https://github.com/googlemaps/google-maps-services-python/issues) 149 | - [Contribute](https://github.com/googlemaps/google-maps-services-python/blob/master/CONTRIB.md) 150 | - [StackOverflow](http://stackoverflow.com/questions/tagged/google-maps) 151 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Report a security issue 2 | 3 | To report a security issue, please use https://g.co/vulnz. We use 4 | https://g.co/vulnz for our intake, and do coordination and disclosure here on 5 | GitHub (including using GitHub Security Advisory). The Google Security Team will 6 | respond within 5 working days of your report on g.co/vulnz. 7 | 8 | To contact us about other bugs, please open an issue on GitHub. 9 | 10 | > **Note**: This file is synchronized from the https://github.com/googlemaps/.github repository. 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # -*- coding: utf-8 -*- 16 | # 17 | # Maps API documentation build configuration file, created by 18 | # sphinx-quickstart on Tue Sep 2 13:42:20 2014. 19 | # 20 | # This file is execfile()d with the current directory set to its containing dir. 21 | # 22 | # Note that not all possible configuration values are present in this 23 | # autogenerated file. 24 | # 25 | # All configuration values have a default; values that are commented out 26 | # serve to show the default. 27 | 28 | import sys, os 29 | 30 | # If extensions (or modules to document with autodoc) are in another directory, 31 | # add these directories to sys.path here. If the directory is relative to the 32 | # documentation root, use os.path.abspath to make it absolute, like shown here. 33 | sys.path.insert(0, os.path.abspath('..')) 34 | 35 | # -- General configuration ----------------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | #needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be extensions 41 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 42 | extensions = ['sphinx.ext.autodoc'] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | #source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | from datetime import datetime 58 | project = u'Python Client for Google Maps Services' 59 | copyright = u'%s, Google Inc.' % datetime.now().year 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | from googlemaps import __version__ 67 | version = __version__ 68 | # The full version, including alpha/beta/rc tags. 69 | release = version 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | #language = None 74 | 75 | # There are two options for replacing |today|: either, you set today to some 76 | # non-false value, then it is used: 77 | #today = '' 78 | # Else, today_fmt is used as the format for a strftime call. 79 | #today_fmt = '%B %d, %Y' 80 | 81 | # List of patterns, relative to source directory, that match files and 82 | # directories to ignore when looking for source files. 83 | exclude_patterns = ['_build'] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | 106 | # -- Options for HTML output --------------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | html_theme = 'default' 111 | 112 | # Theme options are theme-specific and customize the look and feel of a theme 113 | # further. For a list of options available for each theme, see the 114 | # documentation. 115 | #html_theme_options = {} 116 | 117 | # Add any paths that contain custom themes here, relative to this directory. 118 | #html_theme_path = [] 119 | 120 | # The name for this set of Sphinx documents. If None, it defaults to 121 | # " v documentation". 122 | #html_title = None 123 | 124 | # A shorter title for the navigation bar. Default is the same as html_title. 125 | #html_short_title = None 126 | 127 | # The name of an image file (relative to this directory) to place at the top 128 | # of the sidebar. 129 | #html_logo = None 130 | 131 | # The name of an image file (within the static path) to use as favicon of the 132 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 133 | # pixels large. 134 | #html_favicon = None 135 | 136 | # Add any paths that contain custom static files (such as style sheets) here, 137 | # relative to this directory. They are copied after the builtin static files, 138 | # so a file named "default.css" will overwrite the builtin "default.css". 139 | html_static_path = [] 140 | 141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 142 | # using the given strftime format. 143 | #html_last_updated_fmt = '%b %d, %Y' 144 | 145 | # If true, SmartyPants will be used to convert quotes and dashes to 146 | # typographically correct entities. 147 | #html_use_smartypants = True 148 | 149 | # Custom sidebar templates, maps document names to template names. 150 | #html_sidebars = {} 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | #html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | #html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | #html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | #html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | #html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | #html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | #html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | #html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | #html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = 'MapsAPIdoc' 184 | 185 | # Ensure class constructors are added to the docs. 186 | autoclass_content = 'both' 187 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Python Client for Google Maps Services 2 | ====================================== 3 | 4 | :mod:`googlemaps` Module 5 | ------------------------ 6 | 7 | .. automodule:: googlemaps 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`googlemaps.exceptions` Module 13 | ----------------------------------- 14 | 15 | .. automodule:: googlemaps.exceptions 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /googlemaps/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | __version__ = "4.10.0" 19 | 20 | from googlemaps.client import Client 21 | from googlemaps import exceptions 22 | 23 | 24 | __all__ = ["Client", "exceptions"] 25 | -------------------------------------------------------------------------------- /googlemaps/addressvalidation.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Address Validation API.""" 19 | from googlemaps import exceptions 20 | 21 | 22 | _ADDRESSVALIDATION_BASE_URL = "https://addressvalidation.googleapis.com" 23 | 24 | 25 | def _addressvalidation_extract(response): 26 | """ 27 | Mimics the exception handling logic in ``client._get_body``, but 28 | for addressvalidation which uses a different response format. 29 | """ 30 | body = response.json() 31 | return body 32 | 33 | # if response.status_code in (200, 404): 34 | # return body 35 | 36 | # try: 37 | # error = body["error"]["errors"][0]["reason"] 38 | # except KeyError: 39 | # error = None 40 | 41 | # if response.status_code == 403: 42 | # raise exceptions._OverQueryLimit(response.status_code, error) 43 | # else: 44 | # raise exceptions.ApiError(response.status_code, error) 45 | 46 | 47 | def addressvalidation(client, addressLines, regionCode=None , locality=None, enableUspsCass=None): 48 | """ 49 | The Google Maps Address Validation API returns a verification of an address 50 | See https://developers.google.com/maps/documentation/address-validation/overview 51 | request must include parameters below. 52 | :param addressLines: The address to validate 53 | :type addressLines: array 54 | :param regionCode: (optional) The country code 55 | :type regionCode: string 56 | :param locality: (optional) Restrict to a locality, ie:Mountain View 57 | :type locality: string 58 | :param enableUspsCass For the "US" and "PR" regions only, you can optionally enable the Coding Accuracy Support System (CASS) from the United States Postal Service (USPS) 59 | :type locality: boolean 60 | """ 61 | 62 | params = { 63 | "address":{ 64 | "addressLines": addressLines 65 | } 66 | } 67 | 68 | if regionCode is not None: 69 | params["address"]["regionCode"] = regionCode 70 | 71 | if locality is not None: 72 | params["address"]["locality"] = locality 73 | 74 | if enableUspsCass is not False or enableUspsCass is not None: 75 | params["enableUspsCass"] = enableUspsCass 76 | 77 | return client._request("/v1:validateAddress", {}, # No GET params 78 | base_url=_ADDRESSVALIDATION_BASE_URL, 79 | extract_body=_addressvalidation_extract, 80 | post_json=params) 81 | -------------------------------------------------------------------------------- /googlemaps/convert.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Converts Python types to string representations suitable for Maps API server. 19 | 20 | For example: 21 | 22 | sydney = { 23 | "lat" : -33.8674869, 24 | "lng" : 151.2069902 25 | } 26 | 27 | convert.latlng(sydney) 28 | # '-33.8674869,151.2069902' 29 | """ 30 | 31 | 32 | def format_float(arg): 33 | """Formats a float value to be as short as possible. 34 | 35 | Truncates float to 8 decimal places and trims extraneous 36 | trailing zeros and period to give API args the best 37 | possible chance of fitting within 2000 char URL length 38 | restrictions. 39 | 40 | For example: 41 | 42 | format_float(40) -> "40" 43 | format_float(40.0) -> "40" 44 | format_float(40.1) -> "40.1" 45 | format_float(40.001) -> "40.001" 46 | format_float(40.0010) -> "40.001" 47 | format_float(40.000000001) -> "40" 48 | format_float(40.000000009) -> "40.00000001" 49 | 50 | :param arg: The lat or lng float. 51 | :type arg: float 52 | 53 | :rtype: string 54 | """ 55 | return ("%.8f" % float(arg)).rstrip("0").rstrip(".") 56 | 57 | 58 | def latlng(arg): 59 | """Converts a lat/lon pair to a comma-separated string. 60 | 61 | For example: 62 | 63 | sydney = { 64 | "lat" : -33.8674869, 65 | "lng" : 151.2069902 66 | } 67 | 68 | convert.latlng(sydney) 69 | # '-33.8674869,151.2069902' 70 | 71 | For convenience, also accepts lat/lon pair as a string, in 72 | which case it's returned unchanged. 73 | 74 | :param arg: The lat/lon pair. 75 | :type arg: string or dict or list or tuple 76 | """ 77 | if is_string(arg): 78 | return arg 79 | 80 | normalized = normalize_lat_lng(arg) 81 | return "%s,%s" % (format_float(normalized[0]), format_float(normalized[1])) 82 | 83 | 84 | def normalize_lat_lng(arg): 85 | """Take the various lat/lng representations and return a tuple. 86 | 87 | Accepts various representations: 88 | 1) dict with two entries - "lat" and "lng" 89 | 2) list or tuple - e.g. (-33, 151) or [-33, 151] 90 | 91 | :param arg: The lat/lng pair. 92 | :type arg: dict or list or tuple 93 | 94 | :rtype: tuple (lat, lng) 95 | """ 96 | if isinstance(arg, dict): 97 | if "lat" in arg and "lng" in arg: 98 | return arg["lat"], arg["lng"] 99 | if "latitude" in arg and "longitude" in arg: 100 | return arg["latitude"], arg["longitude"] 101 | 102 | # List or tuple. 103 | if _is_list(arg): 104 | return arg[0], arg[1] 105 | 106 | raise TypeError( 107 | "Expected a lat/lng dict or tuple, " 108 | "but got %s" % type(arg).__name__) 109 | 110 | 111 | def location_list(arg): 112 | """Joins a list of locations into a pipe separated string, handling 113 | the various formats supported for lat/lng values. 114 | 115 | For example: 116 | p = [{"lat" : -33.867486, "lng" : 151.206990}, "Sydney"] 117 | convert.waypoint(p) 118 | # '-33.867486,151.206990|Sydney' 119 | 120 | :param arg: The lat/lng list. 121 | :type arg: list 122 | 123 | :rtype: string 124 | """ 125 | if isinstance(arg, tuple): 126 | # Handle the single-tuple lat/lng case. 127 | return latlng(arg) 128 | else: 129 | return "|".join([latlng(location) for location in as_list(arg)]) 130 | 131 | 132 | def join_list(sep, arg): 133 | """If arg is list-like, then joins it with sep. 134 | 135 | :param sep: Separator string. 136 | :type sep: string 137 | 138 | :param arg: Value to coerce into a list. 139 | :type arg: string or list of strings 140 | 141 | :rtype: string 142 | """ 143 | return sep.join(as_list(arg)) 144 | 145 | 146 | def as_list(arg): 147 | """Coerces arg into a list. If arg is already list-like, returns arg. 148 | Otherwise, returns a one-element list containing arg. 149 | 150 | :rtype: list 151 | """ 152 | if _is_list(arg): 153 | return arg 154 | return [arg] 155 | 156 | 157 | def _is_list(arg): 158 | """Checks if arg is list-like. This excludes strings and dicts.""" 159 | if isinstance(arg, dict): 160 | return False 161 | if isinstance(arg, str): # Python 3-only, as str has __iter__ 162 | return False 163 | return _has_method(arg, "__getitem__") if not _has_method(arg, "strip") else _has_method(arg, "__iter__") 164 | 165 | 166 | def is_string(val): 167 | """Determines whether the passed value is a string, safe for 2/3.""" 168 | try: 169 | basestring 170 | except NameError: 171 | return isinstance(val, str) 172 | return isinstance(val, basestring) 173 | 174 | 175 | def time(arg): 176 | """Converts the value into a unix time (seconds since unix epoch). 177 | 178 | For example: 179 | convert.time(datetime.now()) 180 | # '1409810596' 181 | 182 | :param arg: The time. 183 | :type arg: datetime.datetime or int 184 | """ 185 | # handle datetime instances. 186 | if _has_method(arg, "timestamp"): 187 | arg = arg.timestamp() 188 | 189 | if isinstance(arg, float): 190 | arg = int(arg) 191 | 192 | return str(arg) 193 | 194 | 195 | def _has_method(arg, method): 196 | """Returns true if the given object has a method with the given name. 197 | 198 | :param arg: the object 199 | 200 | :param method: the method name 201 | :type method: string 202 | 203 | :rtype: bool 204 | """ 205 | return hasattr(arg, method) and callable(getattr(arg, method)) 206 | 207 | 208 | def components(arg): 209 | """Converts a dict of components to the format expected by the Google Maps 210 | server. 211 | 212 | For example: 213 | c = {"country": "US", "postal_code": "94043"} 214 | convert.components(c) 215 | # 'country:US|postal_code:94043' 216 | 217 | :param arg: The component filter. 218 | :type arg: dict 219 | 220 | :rtype: basestring 221 | """ 222 | 223 | # Components may have multiple values per type, here we 224 | # expand them into individual key/value items, eg: 225 | # {"country": ["US", "AU"], "foo": 1} -> "country:AU", "country:US", "foo:1" 226 | def expand(arg): 227 | for k, v in arg.items(): 228 | for item in as_list(v): 229 | yield "%s:%s" % (k, item) 230 | 231 | if isinstance(arg, dict): 232 | return "|".join(sorted(expand(arg))) 233 | 234 | raise TypeError( 235 | "Expected a dict for components, " 236 | "but got %s" % type(arg).__name__) 237 | 238 | 239 | def bounds(arg): 240 | """Converts a lat/lon bounds to a comma- and pipe-separated string. 241 | 242 | Accepts two representations: 243 | 1) string: pipe-separated pair of comma-separated lat/lon pairs. 244 | 2) dict with two entries - "southwest" and "northeast". See convert.latlng 245 | for information on how these can be represented. 246 | 247 | For example: 248 | 249 | sydney_bounds = { 250 | "northeast" : { 251 | "lat" : -33.4245981, 252 | "lng" : 151.3426361 253 | }, 254 | "southwest" : { 255 | "lat" : -34.1692489, 256 | "lng" : 150.502229 257 | } 258 | } 259 | 260 | convert.bounds(sydney_bounds) 261 | # '-34.169249,150.502229|-33.424598,151.342636' 262 | 263 | :param arg: The bounds. 264 | :type arg: dict 265 | """ 266 | 267 | if is_string(arg) and arg.count("|") == 1 and arg.count(",") == 2: 268 | return arg 269 | elif isinstance(arg, dict): 270 | if "southwest" in arg and "northeast" in arg: 271 | return "%s|%s" % (latlng(arg["southwest"]), 272 | latlng(arg["northeast"])) 273 | 274 | raise TypeError( 275 | "Expected a bounds (southwest/northeast) dict, " 276 | "but got %s" % type(arg).__name__) 277 | 278 | 279 | def size(arg): 280 | if isinstance(arg, int): 281 | return "%sx%s" % (arg, arg) 282 | elif _is_list(arg): 283 | return "%sx%s" % (arg[0], arg[1]) 284 | 285 | raise TypeError( 286 | "Expected a size int or list, " 287 | "but got %s" % type(arg).__name__) 288 | 289 | 290 | def decode_polyline(polyline): 291 | """Decodes a Polyline string into a list of lat/lng dicts. 292 | 293 | See the developer docs for a detailed description of this encoding: 294 | https://developers.google.com/maps/documentation/utilities/polylinealgorithm 295 | 296 | :param polyline: An encoded polyline 297 | :type polyline: string 298 | 299 | :rtype: list of dicts with lat/lng keys 300 | """ 301 | points = [] 302 | index = lat = lng = 0 303 | 304 | while index < len(polyline): 305 | result = 1 306 | shift = 0 307 | while True: 308 | b = ord(polyline[index]) - 63 - 1 309 | index += 1 310 | result += b << shift 311 | shift += 5 312 | if b < 0x1f: 313 | break 314 | lat += (~result >> 1) if (result & 1) != 0 else (result >> 1) 315 | 316 | result = 1 317 | shift = 0 318 | while True: 319 | b = ord(polyline[index]) - 63 - 1 320 | index += 1 321 | result += b << shift 322 | shift += 5 323 | if b < 0x1f: 324 | break 325 | lng += ~(result >> 1) if (result & 1) != 0 else (result >> 1) 326 | 327 | points.append({"lat": lat * 1e-5, "lng": lng * 1e-5}) 328 | 329 | return points 330 | 331 | 332 | def encode_polyline(points): 333 | """Encodes a list of points into a polyline string. 334 | 335 | See the developer docs for a detailed description of this encoding: 336 | https://developers.google.com/maps/documentation/utilities/polylinealgorithm 337 | 338 | :param points: a list of lat/lng pairs 339 | :type points: list of dicts or tuples 340 | 341 | :rtype: string 342 | """ 343 | last_lat = last_lng = 0 344 | result = "" 345 | 346 | for point in points: 347 | ll = normalize_lat_lng(point) 348 | lat = int(round(ll[0] * 1e5)) 349 | lng = int(round(ll[1] * 1e5)) 350 | d_lat = lat - last_lat 351 | d_lng = lng - last_lng 352 | 353 | for v in [d_lat, d_lng]: 354 | v = ~(v << 1) if v < 0 else v << 1 355 | while v >= 0x20: 356 | result += (chr((0x20 | (v & 0x1f)) + 63)) 357 | v >>= 5 358 | result += (chr(v + 63)) 359 | 360 | last_lat = lat 361 | last_lng = lng 362 | 363 | return result 364 | 365 | 366 | def shortest_path(locations): 367 | """Returns the shortest representation of the given locations. 368 | 369 | The Elevations API limits requests to 2000 characters, and accepts 370 | multiple locations either as pipe-delimited lat/lng values, or 371 | an encoded polyline, so we determine which is shortest and use it. 372 | 373 | :param locations: The lat/lng list. 374 | :type locations: list 375 | 376 | :rtype: string 377 | """ 378 | if isinstance(locations, tuple): 379 | # Handle the single-tuple lat/lng case. 380 | locations = [locations] 381 | encoded = "enc:%s" % encode_polyline(locations) 382 | unencoded = location_list(locations) 383 | if len(encoded) < len(unencoded): 384 | return encoded 385 | else: 386 | return unencoded 387 | -------------------------------------------------------------------------------- /googlemaps/directions.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Directions API.""" 19 | 20 | from googlemaps import convert 21 | 22 | 23 | def directions(client, origin, destination, 24 | mode=None, waypoints=None, alternatives=False, avoid=None, 25 | language=None, units=None, region=None, departure_time=None, 26 | arrival_time=None, optimize_waypoints=False, transit_mode=None, 27 | transit_routing_preference=None, traffic_model=None): 28 | """Get directions between an origin point and a destination point. 29 | 30 | :param origin: The address or latitude/longitude value from which you wish 31 | to calculate directions. 32 | :type origin: string, dict, list, or tuple 33 | 34 | :param destination: The address or latitude/longitude value from which 35 | you wish to calculate directions. You can use a place_id as destination 36 | by putting 'place_id:' as a prefix in the passing parameter. 37 | :type destination: string, dict, list, or tuple 38 | 39 | :param mode: Specifies the mode of transport to use when calculating 40 | directions. One of "driving", "walking", "bicycling" or "transit" 41 | :type mode: string 42 | 43 | :param waypoints: Specifies an array of waypoints. Waypoints alter a 44 | route by routing it through the specified location(s). To influence 45 | route without adding stop prefix the waypoint with `via`, similar to 46 | `waypoints = ["via:San Francisco", "via:Mountain View"]`. 47 | :type waypoints: a single location, or a list of locations, where a 48 | location is a string, dict, list, or tuple 49 | 50 | :param alternatives: If True, more than one route may be returned in the 51 | response. 52 | :type alternatives: bool 53 | 54 | :param avoid: Indicates that the calculated route(s) should avoid the 55 | indicated features. 56 | :type avoid: list or string 57 | 58 | :param language: The language in which to return results. 59 | :type language: string 60 | 61 | :param units: Specifies the unit system to use when displaying results. 62 | "metric" or "imperial" 63 | :type units: string 64 | 65 | :param region: The region code, specified as a ccTLD ("top-level domain" 66 | two-character value. 67 | :type region: string 68 | 69 | :param departure_time: Specifies the desired time of departure. 70 | :type departure_time: int or datetime.datetime 71 | 72 | :param arrival_time: Specifies the desired time of arrival for transit 73 | directions. Note: you can't specify both departure_time and 74 | arrival_time. 75 | :type arrival_time: int or datetime.datetime 76 | 77 | :param optimize_waypoints: Optimize the provided route by rearranging the 78 | waypoints in a more efficient order. 79 | :type optimize_waypoints: bool 80 | 81 | :param transit_mode: Specifies one or more preferred modes of transit. 82 | This parameter may only be specified for requests where the mode is 83 | transit. Valid values are "bus", "subway", "train", "tram", "rail". 84 | "rail" is equivalent to ["train", "tram", "subway"]. 85 | :type transit_mode: string or list of strings 86 | 87 | :param transit_routing_preference: Specifies preferences for transit 88 | requests. Valid values are "less_walking" or "fewer_transfers" 89 | :type transit_routing_preference: string 90 | 91 | :param traffic_model: Specifies the predictive travel time model to use. 92 | Valid values are "best_guess" or "optimistic" or "pessimistic". 93 | The traffic_model parameter may only be specified for requests where 94 | the travel mode is driving, and where the request includes a 95 | departure_time. 96 | :type units: string 97 | 98 | :rtype: list of routes 99 | """ 100 | 101 | params = { 102 | "origin": convert.latlng(origin), 103 | "destination": convert.latlng(destination) 104 | } 105 | 106 | if mode: 107 | # NOTE(broady): the mode parameter is not validated by the Maps API 108 | # server. Check here to prevent silent failures. 109 | if mode not in ["driving", "walking", "bicycling", "transit"]: 110 | raise ValueError("Invalid travel mode.") 111 | params["mode"] = mode 112 | 113 | if waypoints: 114 | waypoints = convert.location_list(waypoints) 115 | if optimize_waypoints: 116 | waypoints = "optimize:true|" + waypoints 117 | params["waypoints"] = waypoints 118 | 119 | if alternatives: 120 | params["alternatives"] = "true" 121 | 122 | if avoid: 123 | params["avoid"] = convert.join_list("|", avoid) 124 | 125 | if language: 126 | params["language"] = language 127 | 128 | if units: 129 | params["units"] = units 130 | 131 | if region: 132 | params["region"] = region 133 | 134 | if departure_time: 135 | params["departure_time"] = convert.time(departure_time) 136 | 137 | if arrival_time: 138 | params["arrival_time"] = convert.time(arrival_time) 139 | 140 | if departure_time and arrival_time: 141 | raise ValueError("Should not specify both departure_time and" 142 | "arrival_time.") 143 | 144 | if transit_mode: 145 | params["transit_mode"] = convert.join_list("|", transit_mode) 146 | 147 | if transit_routing_preference: 148 | params["transit_routing_preference"] = transit_routing_preference 149 | 150 | if traffic_model: 151 | params["traffic_model"] = traffic_model 152 | 153 | return client._request("/maps/api/directions/json", params).get("routes", []) 154 | -------------------------------------------------------------------------------- /googlemaps/distance_matrix.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Distance Matrix API.""" 19 | 20 | from googlemaps import convert 21 | 22 | 23 | def distance_matrix(client, origins, destinations, 24 | mode=None, language=None, avoid=None, units=None, 25 | departure_time=None, arrival_time=None, transit_mode=None, 26 | transit_routing_preference=None, traffic_model=None, region=None): 27 | """ Gets travel distance and time for a matrix of origins and destinations. 28 | 29 | :param origins: One or more addresses, Place IDs, and/or latitude/longitude 30 | values, from which to calculate distance and time. Each Place ID string 31 | must be prepended with 'place_id:'. If you pass an address as a string, 32 | the service will geocode the string and convert it to a 33 | latitude/longitude coordinate to calculate directions. 34 | :type origins: a single location, or a list of locations, where a 35 | location is a string, dict, list, or tuple 36 | 37 | :param destinations: One or more addresses, Place IDs, and/or lat/lng values 38 | , to which to calculate distance and time. Each Place ID string must be 39 | prepended with 'place_id:'. If you pass an address as a string, the 40 | service will geocode the string and convert it to a latitude/longitude 41 | coordinate to calculate directions. 42 | :type destinations: a single location, or a list of locations, where a 43 | location is a string, dict, list, or tuple 44 | 45 | :param mode: Specifies the mode of transport to use when calculating 46 | directions. Valid values are "driving", "walking", "transit" or 47 | "bicycling". 48 | :type mode: string 49 | 50 | :param language: The language in which to return results. 51 | :type language: string 52 | 53 | :param avoid: Indicates that the calculated route(s) should avoid the 54 | indicated features. Valid values are "tolls", "highways" or "ferries". 55 | :type avoid: string 56 | 57 | :param units: Specifies the unit system to use when displaying results. 58 | Valid values are "metric" or "imperial". 59 | :type units: string 60 | 61 | :param departure_time: Specifies the desired time of departure. 62 | :type departure_time: int or datetime.datetime 63 | 64 | :param arrival_time: Specifies the desired time of arrival for transit 65 | directions. Note: you can't specify both departure_time and 66 | arrival_time. 67 | :type arrival_time: int or datetime.datetime 68 | 69 | :param transit_mode: Specifies one or more preferred modes of transit. 70 | This parameter may only be specified for requests where the mode is 71 | transit. Valid values are "bus", "subway", "train", "tram", "rail". 72 | "rail" is equivalent to ["train", "tram", "subway"]. 73 | :type transit_mode: string or list of strings 74 | 75 | :param transit_routing_preference: Specifies preferences for transit 76 | requests. Valid values are "less_walking" or "fewer_transfers". 77 | :type transit_routing_preference: string 78 | 79 | :param traffic_model: Specifies the predictive travel time model to use. 80 | Valid values are "best_guess" or "optimistic" or "pessimistic". 81 | The traffic_model parameter may only be specified for requests where 82 | the travel mode is driving, and where the request includes a 83 | departure_time. 84 | 85 | :param region: Specifies the prefered region the geocoder should search 86 | first, but it will not restrict the results to only this region. Valid 87 | values are a ccTLD code. 88 | :type region: string 89 | 90 | :rtype: matrix of distances. Results are returned in rows, each row 91 | containing one origin paired with each destination. 92 | """ 93 | 94 | params = { 95 | "origins": convert.location_list(origins), 96 | "destinations": convert.location_list(destinations) 97 | } 98 | 99 | if mode: 100 | # NOTE(broady): the mode parameter is not validated by the Maps API 101 | # server. Check here to prevent silent failures. 102 | if mode not in ["driving", "walking", "bicycling", "transit"]: 103 | raise ValueError("Invalid travel mode.") 104 | params["mode"] = mode 105 | 106 | if language: 107 | params["language"] = language 108 | 109 | if avoid: 110 | if avoid not in ["tolls", "highways", "ferries"]: 111 | raise ValueError("Invalid route restriction.") 112 | params["avoid"] = avoid 113 | 114 | if units: 115 | params["units"] = units 116 | 117 | if departure_time: 118 | params["departure_time"] = convert.time(departure_time) 119 | 120 | if arrival_time: 121 | params["arrival_time"] = convert.time(arrival_time) 122 | 123 | if departure_time and arrival_time: 124 | raise ValueError("Should not specify both departure_time and" 125 | "arrival_time.") 126 | 127 | if transit_mode: 128 | params["transit_mode"] = convert.join_list("|", transit_mode) 129 | 130 | if transit_routing_preference: 131 | params["transit_routing_preference"] = transit_routing_preference 132 | 133 | if traffic_model: 134 | params["traffic_model"] = traffic_model 135 | 136 | if region: 137 | params["region"] = region 138 | 139 | return client._request("/maps/api/distancematrix/json", params) 140 | -------------------------------------------------------------------------------- /googlemaps/elevation.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Elevation API.""" 19 | 20 | from googlemaps import convert 21 | 22 | 23 | def elevation(client, locations): 24 | """ 25 | Provides elevation data for locations provided on the surface of the 26 | earth, including depth locations on the ocean floor (which return negative 27 | values) 28 | 29 | :param locations: List of latitude/longitude values from which you wish 30 | to calculate elevation data. 31 | :type locations: a single location, or a list of locations, where a 32 | location is a string, dict, list, or tuple 33 | 34 | :rtype: list of elevation data responses 35 | """ 36 | params = {"locations": convert.shortest_path(locations)} 37 | return client._request("/maps/api/elevation/json", params).get("results", []) 38 | 39 | 40 | def elevation_along_path(client, path, samples): 41 | """ 42 | Provides elevation data sampled along a path on the surface of the earth. 43 | 44 | :param path: An encoded polyline string, or a list of latitude/longitude 45 | values from which you wish to calculate elevation data. 46 | :type path: string, dict, list, or tuple 47 | 48 | :param samples: The number of sample points along a path for which to 49 | return elevation data. 50 | :type samples: int 51 | 52 | :rtype: list of elevation data responses 53 | """ 54 | 55 | if type(path) is str: 56 | path = "enc:%s" % path 57 | else: 58 | path = convert.shortest_path(path) 59 | 60 | params = { 61 | "path": path, 62 | "samples": samples 63 | } 64 | 65 | return client._request("/maps/api/elevation/json", params).get("results", []) 66 | -------------------------------------------------------------------------------- /googlemaps/exceptions.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """ 19 | Defines exceptions that are thrown by the Google Maps client. 20 | """ 21 | 22 | class ApiError(Exception): 23 | """Represents an exception returned by the remote API.""" 24 | def __init__(self, status, message=None): 25 | self.status = status 26 | self.message = message 27 | 28 | def __str__(self): 29 | if self.message is None: 30 | return str(self.status) 31 | else: 32 | return "%s (%s)" % (self.status, self.message) 33 | 34 | class TransportError(Exception): 35 | """Something went wrong while trying to execute the request.""" 36 | 37 | def __init__(self, base_exception=None): 38 | self.base_exception = base_exception 39 | 40 | def __str__(self): 41 | if self.base_exception: 42 | return str(self.base_exception) 43 | 44 | return "An unknown error occurred." 45 | 46 | class HTTPError(TransportError): 47 | """An unexpected HTTP error occurred.""" 48 | def __init__(self, status_code): 49 | self.status_code = status_code 50 | 51 | def __str__(self): 52 | return "HTTP Error: %d" % self.status_code 53 | 54 | class Timeout(Exception): 55 | """The request timed out.""" 56 | pass 57 | 58 | class _RetriableRequest(Exception): 59 | """Signifies that the request can be retried.""" 60 | pass 61 | 62 | class _OverQueryLimit(ApiError, _RetriableRequest): 63 | """Signifies that the request failed because the client exceeded its query rate limit. 64 | 65 | Normally we treat this as a retriable condition, but we allow the calling code to specify that these requests should 66 | not be retried. 67 | """ 68 | pass 69 | -------------------------------------------------------------------------------- /googlemaps/geocoding.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Geocoding API.""" 19 | from googlemaps import convert 20 | 21 | 22 | def geocode(client, address=None, place_id=None, components=None, bounds=None, region=None, 23 | language=None): 24 | """ 25 | Geocoding is the process of converting addresses 26 | (like ``"1600 Amphitheatre Parkway, Mountain View, CA"``) into geographic 27 | coordinates (like latitude 37.423021 and longitude -122.083739), which you 28 | can use to place markers or position the map. 29 | 30 | :param address: The address to geocode. 31 | :type address: string 32 | 33 | :param place_id: A textual identifier that uniquely identifies a place, 34 | returned from a Places search. 35 | :type place_id: string 36 | 37 | :param components: A component filter for which you wish to obtain a 38 | geocode, for example: ``{'administrative_area': 'TX','country': 'US'}`` 39 | :type components: dict 40 | 41 | :param bounds: The bounding box of the viewport within which to bias geocode 42 | results more prominently. 43 | :type bounds: string or dict with northeast and southwest keys. 44 | 45 | :param region: The region code, specified as a ccTLD ("top-level domain") 46 | two-character value. 47 | :type region: string 48 | 49 | :param language: The language in which to return results. 50 | :type language: string 51 | 52 | :rtype: result dict with the following keys: 53 | status: status code 54 | results: list of geocoding results 55 | """ 56 | 57 | params = {} 58 | 59 | if address: 60 | params["address"] = address 61 | 62 | if place_id: 63 | params["place_id"] = place_id 64 | 65 | if components: 66 | params["components"] = convert.components(components) 67 | 68 | if bounds: 69 | params["bounds"] = convert.bounds(bounds) 70 | 71 | if region: 72 | params["region"] = region 73 | 74 | if language: 75 | params["language"] = language 76 | 77 | return client._request("/maps/api/geocode/json", params) 78 | 79 | 80 | def reverse_geocode(client, latlng, result_type=None, location_type=None, 81 | language=None, enable_address_descriptor=False): 82 | """ 83 | Reverse geocoding is the process of converting geographic coordinates into a 84 | human-readable address. 85 | 86 | :param latlng: The latitude/longitude value or place_id for which you wish 87 | to obtain the closest, human-readable address. 88 | :type latlng: string, dict, list, or tuple 89 | 90 | :param result_type: One or more address types to restrict results to. 91 | :type result_type: string or list of strings 92 | 93 | :param location_type: One or more location types to restrict results to. 94 | :type location_type: list of strings 95 | 96 | :param language: The language in which to return results. 97 | :type language: string 98 | 99 | :rtype: result dict with the following keys: 100 | status: status code 101 | results: list of reverse geocoding results 102 | address_descriptor: address descriptor for the target 103 | """ 104 | 105 | # Check if latlng param is a place_id string. 106 | # place_id strings do not contain commas; latlng strings do. 107 | if convert.is_string(latlng) and ',' not in latlng: 108 | params = {"place_id": latlng} 109 | else: 110 | params = {"latlng": convert.latlng(latlng)} 111 | 112 | if result_type: 113 | params["result_type"] = convert.join_list("|", result_type) 114 | 115 | if location_type: 116 | params["location_type"] = convert.join_list("|", location_type) 117 | 118 | if language: 119 | params["language"] = language 120 | 121 | if enable_address_descriptor: 122 | params["enable_address_descriptor"] = "true" 123 | 124 | return client._request("/maps/api/geocode/json", params) 125 | -------------------------------------------------------------------------------- /googlemaps/geolocation.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Geolocation API.""" 19 | from googlemaps import exceptions 20 | 21 | 22 | _GEOLOCATION_BASE_URL = "https://www.googleapis.com" 23 | 24 | 25 | def _geolocation_extract(response): 26 | """ 27 | Mimics the exception handling logic in ``client._get_body``, but 28 | for geolocation which uses a different response format. 29 | """ 30 | body = response.json() 31 | if response.status_code in (200, 404): 32 | return body 33 | 34 | try: 35 | error = body["error"]["errors"][0]["reason"] 36 | except KeyError: 37 | error = None 38 | 39 | if response.status_code == 403: 40 | raise exceptions._OverQueryLimit(response.status_code, error) 41 | else: 42 | raise exceptions.ApiError(response.status_code, error) 43 | 44 | 45 | def geolocate(client, home_mobile_country_code=None, 46 | home_mobile_network_code=None, radio_type=None, carrier=None, 47 | consider_ip=None, cell_towers=None, wifi_access_points=None): 48 | """ 49 | The Google Maps Geolocation API returns a location and accuracy 50 | radius based on information about cell towers and WiFi nodes given. 51 | 52 | See https://developers.google.com/maps/documentation/geolocation/intro 53 | for more info, including more detail for each parameter below. 54 | 55 | :param home_mobile_country_code: The mobile country code (MCC) for 56 | the device's home network. 57 | :type home_mobile_country_code: string 58 | 59 | :param home_mobile_network_code: The mobile network code (MCC) for 60 | the device's home network. 61 | :type home_mobile_network_code: string 62 | 63 | :param radio_type: The mobile radio type. Supported values are 64 | lte, gsm, cdma, and wcdma. While this field is optional, it 65 | should be included if a value is available, for more accurate 66 | results. 67 | :type radio_type: string 68 | 69 | :param carrier: The carrier name. 70 | :type carrier: string 71 | 72 | :param consider_ip: Specifies whether to fall back to IP geolocation 73 | if wifi and cell tower signals are not available. Note that the 74 | IP address in the request header may not be the IP of the device. 75 | :type consider_ip: bool 76 | 77 | :param cell_towers: A list of cell tower dicts. See 78 | https://developers.google.com/maps/documentation/geolocation/intro#cell_tower_object 79 | for more detail. 80 | :type cell_towers: list of dicts 81 | 82 | :param wifi_access_points: A list of WiFi access point dicts. See 83 | https://developers.google.com/maps/documentation/geolocation/intro#wifi_access_point_object 84 | for more detail. 85 | :type wifi_access_points: list of dicts 86 | """ 87 | 88 | params = {} 89 | if home_mobile_country_code is not None: 90 | params["homeMobileCountryCode"] = home_mobile_country_code 91 | if home_mobile_network_code is not None: 92 | params["homeMobileNetworkCode"] = home_mobile_network_code 93 | if radio_type is not None: 94 | params["radioType"] = radio_type 95 | if carrier is not None: 96 | params["carrier"] = carrier 97 | if consider_ip is not None: 98 | params["considerIp"] = consider_ip 99 | if cell_towers is not None: 100 | params["cellTowers"] = cell_towers 101 | if wifi_access_points is not None: 102 | params["wifiAccessPoints"] = wifi_access_points 103 | 104 | return client._request("/geolocation/v1/geolocate", {}, # No GET params 105 | base_url=_GEOLOCATION_BASE_URL, 106 | extract_body=_geolocation_extract, 107 | post_json=params) 108 | -------------------------------------------------------------------------------- /googlemaps/maps.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Static API.""" 19 | 20 | from googlemaps import convert 21 | 22 | 23 | MAPS_IMAGE_FORMATS = {'png8', 'png', 'png32', 'gif', 'jpg', 'jpg-baseline'} 24 | 25 | MAPS_MAP_TYPES = {'roadmap', 'satellite', 'terrain', 'hybrid'} 26 | 27 | class StaticMapParam: 28 | """Base class to handle parameters for Maps Static API.""" 29 | 30 | def __init__(self): 31 | self.params = [] 32 | 33 | def __str__(self): 34 | """Converts a list of parameters to the format expected by 35 | the Google Maps server. 36 | 37 | :rtype: str 38 | 39 | """ 40 | return convert.join_list('|', self.params) 41 | 42 | 43 | class StaticMapMarker(StaticMapParam): 44 | """Handles marker parameters for Maps Static API.""" 45 | 46 | def __init__(self, locations, 47 | size=None, color=None, label=None): 48 | """ 49 | :param locations: Specifies the locations of the markers on 50 | the map. 51 | :type locations: list 52 | 53 | :param size: Specifies the size of the marker. 54 | :type size: str 55 | 56 | :param color: Specifies a color of the marker. 57 | :type color: str 58 | 59 | :param label: Specifies a single uppercase alphanumeric 60 | character to be displaied on marker. 61 | :type label: str 62 | """ 63 | 64 | super(StaticMapMarker, self).__init__() 65 | 66 | if size: 67 | self.params.append("size:%s" % size) 68 | 69 | if color: 70 | self.params.append("color:%s" % color) 71 | 72 | if label: 73 | if len(label) != 1 or (label.isalpha() and not label.isupper()) or not label.isalnum(): 74 | raise ValueError("Marker label must be alphanumeric and uppercase.") 75 | self.params.append("label:%s" % label) 76 | 77 | self.params.append(convert.location_list(locations)) 78 | 79 | 80 | class StaticMapPath(StaticMapParam): 81 | """Handles path parameters for Maps Static API.""" 82 | 83 | def __init__(self, points, 84 | weight=None, color=None, 85 | fillcolor=None, geodesic=None): 86 | """ 87 | :param points: Specifies the point through which the path 88 | will be built. 89 | :type points: list 90 | 91 | :param weight: Specifies the thickness of the path in pixels. 92 | :type weight: int 93 | 94 | :param color: Specifies a color of the path. 95 | :type color: str 96 | 97 | :param fillcolor: Indicates both that the path marks off a 98 | polygonal area and specifies the fill color to use as an 99 | overlay within that area. 100 | :type fillcolor: str 101 | 102 | :param geodesic: Indicates that the requested path should be 103 | interpreted as a geodesic line that follows the curvature 104 | of the earth. 105 | :type geodesic: bool 106 | """ 107 | 108 | super(StaticMapPath, self).__init__() 109 | 110 | if weight: 111 | self.params.append("weight:%s" % weight) 112 | 113 | if color: 114 | self.params.append("color:%s" % color) 115 | 116 | if fillcolor: 117 | self.params.append("fillcolor:%s" % fillcolor) 118 | 119 | if geodesic: 120 | self.params.append("geodesic:%s" % geodesic) 121 | 122 | self.params.append(convert.location_list(points)) 123 | 124 | 125 | def static_map(client, size, 126 | center=None, zoom=None, scale=None, 127 | format=None, maptype=None, language=None, region=None, 128 | markers=None, path=None, visible=None, style=None): 129 | """ 130 | Downloads a map image from the Maps Static API. 131 | 132 | See https://developers.google.com/maps/documentation/maps-static/intro 133 | for more info, including more detail for each parameter below. 134 | 135 | :param size: Defines the rectangular dimensions of the map image. 136 | :type param: int or list 137 | 138 | :param center: Defines the center of the map, equidistant from all edges 139 | of the map. 140 | :type center: dict or list or string 141 | 142 | :param zoom: Defines the zoom level of the map, which determines the 143 | magnification level of the map. 144 | :type zoom: int 145 | 146 | :param scale: Affects the number of pixels that are returned. 147 | :type scale: int 148 | 149 | :param format: Defines the format of the resulting image. 150 | :type format: string 151 | 152 | :param maptype: defines the type of map to construct. There are several 153 | possible maptype values, including roadmap, satellite, hybrid, 154 | and terrain. 155 | :type maptype: string 156 | 157 | :param language: defines the language to use for display of labels on 158 | map tiles. 159 | :type language: string 160 | 161 | :param region: defines the appropriate borders to display, based on 162 | geo-political sensitivities. 163 | :type region: string 164 | 165 | :param markers: define one or more markers to attach to the image at 166 | specified locations. 167 | :type markers: StaticMapMarker 168 | 169 | :param path: defines a single path of two or more connected points to 170 | overlay on the image at specified locations. 171 | :type path: StaticMapPath 172 | 173 | :param visible: specifies one or more locations that should remain visible 174 | on the map, though no markers or other indicators will be displayed. 175 | :type visible: list of dict 176 | 177 | :param style: defines a custom style to alter the presentation of 178 | a specific feature (roads, parks, and other features) of the map. 179 | :type style: list of dict 180 | 181 | :rtype: iterator containing the raw image data, which typically can be 182 | used to save an image file locally. For example: 183 | 184 | .. code-block:: python 185 | 186 | f = open(local_filename, 'wb') 187 | for chunk in client.static_map(size=(400, 400), 188 | center=(52.520103, 13.404871), 189 | zoom=15): 190 | if chunk: 191 | f.write(chunk) 192 | f.close() 193 | """ 194 | 195 | params = {"size": convert.size(size)} 196 | 197 | if not markers: 198 | if not (center or zoom is not None): 199 | raise ValueError( 200 | "both center and zoom are required" 201 | "when markers is not specifed" 202 | ) 203 | 204 | if center: 205 | params["center"] = convert.latlng(center) 206 | 207 | if zoom is not None: 208 | params["zoom"] = zoom 209 | 210 | if scale is not None: 211 | params["scale"] = scale 212 | 213 | if format: 214 | if format not in MAPS_IMAGE_FORMATS: 215 | raise ValueError("Invalid image format") 216 | params['format'] = format 217 | 218 | if maptype: 219 | if maptype not in MAPS_MAP_TYPES: 220 | raise ValueError("Invalid maptype") 221 | params["maptype"] = maptype 222 | 223 | if language: 224 | params["language"] = language 225 | 226 | if region: 227 | params["region"] = region 228 | 229 | if markers: 230 | params["markers"] = markers 231 | 232 | if path: 233 | params["path"] = path 234 | 235 | if visible: 236 | params["visible"] = convert.location_list(visible) 237 | 238 | if style: 239 | params["style"] = convert.components(style) 240 | 241 | response = client._request( 242 | "/maps/api/staticmap", 243 | params, 244 | extract_body=lambda response: response, 245 | requests_kwargs={"stream": True}, 246 | ) 247 | return response.iter_content() 248 | -------------------------------------------------------------------------------- /googlemaps/roads.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Roads API.""" 19 | 20 | import googlemaps 21 | from googlemaps import convert 22 | 23 | 24 | _ROADS_BASE_URL = "https://roads.googleapis.com" 25 | 26 | 27 | def snap_to_roads(client, path, interpolate=False): 28 | """Snaps a path to the most likely roads travelled. 29 | 30 | Takes up to 100 GPS points collected along a route, and returns a similar 31 | set of data with the points snapped to the most likely roads the vehicle 32 | was traveling along. 33 | 34 | :param path: The path to be snapped. 35 | :type path: a single location, or a list of locations, where a 36 | location is a string, dict, list, or tuple 37 | 38 | :param interpolate: Whether to interpolate a path to include all points 39 | forming the full road-geometry. When true, additional interpolated 40 | points will also be returned, resulting in a path that smoothly follows 41 | the geometry of the road, even around corners and through tunnels. 42 | Interpolated paths may contain more points than the original path. 43 | :type interpolate: bool 44 | 45 | :rtype: A list of snapped points. 46 | """ 47 | 48 | params = {"path": convert.location_list(path)} 49 | 50 | if interpolate: 51 | params["interpolate"] = "true" 52 | 53 | return client._request("/v1/snapToRoads", params, 54 | base_url=_ROADS_BASE_URL, 55 | accepts_clientid=False, 56 | extract_body=_roads_extract).get("snappedPoints", []) 57 | 58 | def nearest_roads(client, points): 59 | """Find the closest road segments for each point 60 | 61 | Takes up to 100 independent coordinates, and returns the closest road 62 | segment for each point. The points passed do not need to be part of a 63 | continuous path. 64 | 65 | :param points: The points for which the nearest road segments are to be 66 | located. 67 | :type points: a single location, or a list of locations, where a 68 | location is a string, dict, list, or tuple 69 | 70 | :rtype: A list of snapped points. 71 | """ 72 | 73 | params = {"points": convert.location_list(points)} 74 | 75 | return client._request("/v1/nearestRoads", params, 76 | base_url=_ROADS_BASE_URL, 77 | accepts_clientid=False, 78 | extract_body=_roads_extract).get("snappedPoints", []) 79 | 80 | def speed_limits(client, place_ids): 81 | """Returns the posted speed limit (in km/h) for given road segments. 82 | 83 | :param place_ids: The Place ID of the road segment. Place IDs are returned 84 | by the snap_to_roads function. You can pass up to 100 Place IDs. 85 | :type place_ids: str or list 86 | 87 | :rtype: list of speed limits. 88 | """ 89 | 90 | params = [("placeId", place_id) for place_id in convert.as_list(place_ids)] 91 | 92 | return client._request("/v1/speedLimits", params, 93 | base_url=_ROADS_BASE_URL, 94 | accepts_clientid=False, 95 | extract_body=_roads_extract).get("speedLimits", []) 96 | 97 | 98 | def snapped_speed_limits(client, path): 99 | """Returns the posted speed limit (in km/h) for given road segments. 100 | 101 | The provided points will first be snapped to the most likely roads the 102 | vehicle was traveling along. 103 | 104 | :param path: The path of points to be snapped. 105 | :type path: a single location, or a list of locations, where a 106 | location is a string, dict, list, or tuple 107 | 108 | :rtype: dict with a list of speed limits and a list of the snapped points. 109 | """ 110 | 111 | params = {"path": convert.location_list(path)} 112 | 113 | return client._request("/v1/speedLimits", params, 114 | base_url=_ROADS_BASE_URL, 115 | accepts_clientid=False, 116 | extract_body=_roads_extract) 117 | 118 | 119 | def _roads_extract(resp): 120 | """Extracts a result from a Roads API HTTP response.""" 121 | 122 | try: 123 | j = resp.json() 124 | except: 125 | if resp.status_code != 200: 126 | raise googlemaps.exceptions.HTTPError(resp.status_code) 127 | 128 | raise googlemaps.exceptions.ApiError("UNKNOWN_ERROR", 129 | "Received a malformed response.") 130 | 131 | if "error" in j: 132 | error = j["error"] 133 | status = error["status"] 134 | 135 | if status == "RESOURCE_EXHAUSTED": 136 | raise googlemaps.exceptions._OverQueryLimit(status, 137 | error.get("message")) 138 | 139 | raise googlemaps.exceptions.ApiError(status, error.get("message")) 140 | 141 | if resp.status_code != 200: 142 | raise googlemaps.exceptions.HTTPError(resp.status_code) 143 | 144 | return j 145 | -------------------------------------------------------------------------------- /googlemaps/timezone.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Performs requests to the Google Maps Directions API.""" 19 | 20 | from googlemaps import convert 21 | 22 | from datetime import datetime 23 | 24 | 25 | def timezone(client, location, timestamp=None, language=None): 26 | """Get time zone for a location on the earth, as well as that location's 27 | time offset from UTC. 28 | 29 | :param location: The latitude/longitude value representing the location to 30 | look up. 31 | :type location: string, dict, list, or tuple 32 | 33 | :param timestamp: Timestamp specifies the desired time as seconds since 34 | midnight, January 1, 1970 UTC. The Time Zone API uses the timestamp to 35 | determine whether or not Daylight Savings should be applied. Times 36 | before 1970 can be expressed as negative values. Optional. Defaults to 37 | ``datetime.utcnow()``. 38 | :type timestamp: int or datetime.datetime 39 | 40 | :param language: The language in which to return results. 41 | :type language: string 42 | 43 | :rtype: dict 44 | """ 45 | 46 | params = { 47 | "location": convert.latlng(location), 48 | "timestamp": convert.time(timestamp or datetime.utcnow()) 49 | } 50 | 51 | if language: 52 | params["language"] = language 53 | 54 | return client._request( "/maps/api/timezone/json", params) 55 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import nox 16 | 17 | SUPPORTED_PY_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] 18 | 19 | 20 | def _install_dev_packages(session): 21 | session.install("-e", ".") 22 | 23 | 24 | def _install_test_dependencies(session): 25 | session.install("pytest") 26 | session.install("pytest-cov") 27 | session.install("responses") 28 | 29 | 30 | def _install_doc_dependencies(session): 31 | session.install("sphinx") 32 | 33 | 34 | @nox.session(python=SUPPORTED_PY_VERSIONS) 35 | def tests(session): 36 | _install_dev_packages(session) 37 | _install_test_dependencies(session) 38 | 39 | session.install("pytest") 40 | session.run("pytest") 41 | 42 | session.notify("cover") 43 | 44 | 45 | @nox.session 46 | def cover(session): 47 | """Coverage analysis.""" 48 | session.install("coverage") 49 | session.install("codecov") 50 | session.run("coverage", "report", "--show-missing") 51 | session.run("codecov") 52 | session.run("coverage", "erase") 53 | 54 | 55 | @nox.session(python="3.7") 56 | def docs(session): 57 | _install_dev_packages(session) 58 | _install_doc_dependencies(session) 59 | 60 | session.run("rm", "-rf", "docs/_build", external=True) 61 | 62 | sphinx_args = [ 63 | "-a", 64 | "-E", 65 | "-b", 66 | "html", 67 | "-d", 68 | "docs/_build/doctrees", 69 | "docs", 70 | "docs/_build/html", 71 | ] 72 | 73 | sphinx_cmd = "sphinx-build" 74 | 75 | session.run(sphinx_cmd, *sphinx_args) 76 | 77 | 78 | @nox.session() 79 | def distribution(session): 80 | session.run("bash", ".github/scripts/distribution.sh", external=True) 81 | session.run("python", "-c", "import googlemaps") 82 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = -rsxX --cov=googlemaps --cov-report= 3 | 4 | [coverage:run] 5 | omit = 6 | tests/* 7 | 8 | [coverage:report] 9 | exclude_lines = 10 | pragma: no cover 11 | def __repr__ 12 | raise NotImplementedError -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from setuptools import setup 16 | 17 | 18 | requirements = ["requests>=2.20.0,<3.0"] 19 | 20 | with open("README.md") as f: 21 | readme = f.read() 22 | 23 | with open("CHANGELOG.md") as f: 24 | changelog = f.read() 25 | 26 | 27 | setup( 28 | name="googlemaps", 29 | version="4.10.0", 30 | description="Python client library for Google Maps Platform", 31 | long_description=readme + changelog, 32 | long_description_content_type="text/markdown", 33 | scripts=[], 34 | url="https://github.com/googlemaps/google-maps-services-python", 35 | packages=["googlemaps"], 36 | license="Apache 2.0", 37 | platforms="Posix; MacOS X; Windows", 38 | setup_requires=requirements, 39 | install_requires=requirements, 40 | classifiers=[ 41 | "Development Status :: 4 - Beta", 42 | "Intended Audience :: Developers", 43 | "License :: OSI Approved :: Apache Software License", 44 | "Operating System :: OS Independent", 45 | "Programming Language :: Python :: 3.5", 46 | "Programming Language :: Python :: 3.6", 47 | "Programming Language :: Python :: 3.7", 48 | "Programming Language :: Python :: 3.8", 49 | "Topic :: Internet", 50 | ], 51 | python_requires='>=3.5' 52 | ) 53 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | import unittest 19 | import codecs 20 | 21 | from urllib.parse import urlparse, parse_qsl 22 | 23 | 24 | class TestCase(unittest.TestCase): 25 | def assertURLEqual(self, first, second, msg=None): 26 | """Check that two arguments are equivalent URLs. Ignores the order of 27 | query arguments. 28 | """ 29 | first_parsed = urlparse(first) 30 | second_parsed = urlparse(second) 31 | self.assertEqual(first_parsed[:3], second_parsed[:3], msg) 32 | 33 | first_qsl = sorted(parse_qsl(first_parsed.query)) 34 | second_qsl = sorted(parse_qsl(second_parsed.query)) 35 | self.assertEqual(first_qsl, second_qsl, msg) 36 | 37 | def u(self, string): 38 | """Create a unicode string, compatible across all versions of Python.""" 39 | # NOTE(cbro): Python 3-3.2 does not have the u'' syntax. 40 | return codecs.unicode_escape_decode(string)[0] 41 | -------------------------------------------------------------------------------- /tests/test_addressvalidation.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | # 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | # use this file except in compliance with the License. You may obtain a copy of 8 | # the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations under 16 | # the License. 17 | # 18 | 19 | """Tests for the addressvalidation module.""" 20 | 21 | import responses 22 | 23 | import googlemaps 24 | from . import TestCase 25 | 26 | 27 | class AddressValidationTest(TestCase): 28 | def setUp(self): 29 | self.key = "AIzaSyD_sJl0qMA65CYHMBokVfMNA7AKyt5ERYs" 30 | self.client = googlemaps.Client(self.key) 31 | 32 | @responses.activate 33 | def test_simple_addressvalidation(self): 34 | responses.add( 35 | responses.POST, 36 | "https://addressvalidation.googleapis.com/v1:validateAddress", 37 | body='{"address": {"regionCode": "US","locality": "Mountain View","addressLines": "1600 Amphitheatre Pkwy"},"enableUspsCass":true}', 38 | status=200, 39 | content_type="application/json", 40 | ) 41 | 42 | results = self.client.addressvalidation('1600 Amphitheatre Pk', regionCode='US', locality='Mountain View', enableUspsCass=True) 43 | 44 | self.assertEqual(1, len(responses.calls)) 45 | self.assertURLEqual( 46 | "https://addressvalidation.googleapis.com/v1:validateAddress?" "key=%s" % self.key, 47 | responses.calls[0].request.url, 48 | ) -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | 19 | """Tests for client module.""" 20 | 21 | import time 22 | 23 | import responses 24 | import requests 25 | import uuid 26 | 27 | import googlemaps 28 | import googlemaps.client as _client 29 | from . import TestCase 30 | from googlemaps.client import _X_GOOG_MAPS_EXPERIENCE_ID 31 | 32 | 33 | class ClientTest(TestCase): 34 | def test_no_api_key(self): 35 | with self.assertRaises(Exception): 36 | client = googlemaps.Client() 37 | client.directions("Sydney", "Melbourne") 38 | 39 | def test_invalid_api_key(self): 40 | with self.assertRaises(Exception): 41 | client = googlemaps.Client(key="Invalid key.") 42 | client.directions("Sydney", "Melbourne") 43 | 44 | def test_urlencode(self): 45 | # See GH #72. 46 | encoded_params = _client.urlencode_params([("address", "=Sydney ~")]) 47 | self.assertEqual("address=%3DSydney+~", encoded_params) 48 | 49 | @responses.activate 50 | def test_queries_per_second(self): 51 | # This test assumes that the time to run a mocked query is 52 | # relatively small, eg a few milliseconds. We define a rate of 53 | # 3 queries per second, and run double that, which should take at 54 | # least 1 second but no more than 2. 55 | queries_per_second = 3 56 | query_range = range(queries_per_second * 2) 57 | for _ in query_range: 58 | responses.add( 59 | responses.GET, 60 | "https://maps.googleapis.com/maps/api/geocode/json", 61 | body='{"status":"OK","results":[]}', 62 | status=200, 63 | content_type="application/json", 64 | ) 65 | client = googlemaps.Client( 66 | key="AIzaasdf", queries_per_second=queries_per_second 67 | ) 68 | start = time.time() 69 | for _ in query_range: 70 | client.geocode("Sesame St.") 71 | end = time.time() 72 | self.assertTrue(start + 1 < end < start + 2) 73 | 74 | @responses.activate 75 | def test_key_sent(self): 76 | responses.add( 77 | responses.GET, 78 | "https://maps.googleapis.com/maps/api/geocode/json", 79 | body='{"status":"OK","results":[]}', 80 | status=200, 81 | content_type="application/json", 82 | ) 83 | 84 | client = googlemaps.Client(key="AIzaasdf") 85 | client.geocode("Sesame St.") 86 | 87 | self.assertEqual(1, len(responses.calls)) 88 | self.assertURLEqual( 89 | "https://maps.googleapis.com/maps/api/geocode/json?" 90 | "key=AIzaasdf&address=Sesame+St.", 91 | responses.calls[0].request.url, 92 | ) 93 | 94 | @responses.activate 95 | def test_extra_params(self): 96 | responses.add( 97 | responses.GET, 98 | "https://maps.googleapis.com/maps/api/geocode/json", 99 | body='{"status":"OK","results":[]}', 100 | status=200, 101 | content_type="application/json", 102 | ) 103 | 104 | client = googlemaps.Client(key="AIzaasdf") 105 | client.geocode("Sesame St.", extra_params={"foo": "bar"}) 106 | 107 | self.assertEqual(1, len(responses.calls)) 108 | self.assertURLEqual( 109 | "https://maps.googleapis.com/maps/api/geocode/json?" 110 | "key=AIzaasdf&address=Sesame+St.&foo=bar", 111 | responses.calls[0].request.url, 112 | ) 113 | 114 | def test_hmac(self): 115 | """ 116 | From http://en.wikipedia.org/wiki/Hash-based_message_authentication_code 117 | 118 | HMAC_SHA1("key", "The quick brown fox jumps over the lazy dog") 119 | = 0xde7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 120 | """ 121 | 122 | message = "The quick brown fox jumps over the lazy dog" 123 | key = "a2V5" # "key" -> base64 124 | signature = "3nybhbi3iqa8ino29wqQcBydtNk=" 125 | 126 | self.assertEqual(signature, _client.sign_hmac(key, message)) 127 | 128 | @responses.activate 129 | def test_url_signed(self): 130 | responses.add( 131 | responses.GET, 132 | "https://maps.googleapis.com/maps/api/geocode/json", 133 | body='{"status":"OK","results":[]}', 134 | status=200, 135 | content_type="application/json", 136 | ) 137 | 138 | client = googlemaps.Client(client_id="foo", client_secret="a2V5") 139 | client.geocode("Sesame St.") 140 | 141 | self.assertEqual(1, len(responses.calls)) 142 | 143 | # Check ordering of parameters. 144 | self.assertIn( 145 | "address=Sesame+St.&client=foo&signature", responses.calls[0].request.url 146 | ) 147 | self.assertURLEqual( 148 | "https://maps.googleapis.com/maps/api/geocode/json?" 149 | "address=Sesame+St.&client=foo&" 150 | "signature=fxbWUIcNPZSekVOhp2ul9LW5TpY=", 151 | responses.calls[0].request.url, 152 | ) 153 | 154 | @responses.activate 155 | def test_ua_sent(self): 156 | responses.add( 157 | responses.GET, 158 | "https://maps.googleapis.com/maps/api/geocode/json", 159 | body='{"status":"OK","results":[]}', 160 | status=200, 161 | content_type="application/json", 162 | ) 163 | 164 | client = googlemaps.Client(key="AIzaasdf") 165 | client.geocode("Sesame St.") 166 | 167 | self.assertEqual(1, len(responses.calls)) 168 | user_agent = responses.calls[0].request.headers["User-Agent"] 169 | self.assertTrue(user_agent.startswith("GoogleGeoApiClientPython")) 170 | 171 | @responses.activate 172 | def test_retry(self): 173 | class request_callback: 174 | def __init__(self): 175 | self.first_req = True 176 | 177 | def __call__(self, req): 178 | if self.first_req: 179 | self.first_req = False 180 | return (200, {}, '{"status":"OVER_QUERY_LIMIT"}') 181 | return (200, {}, '{"status":"OK","results":[]}') 182 | 183 | responses.add_callback( 184 | responses.GET, 185 | "https://maps.googleapis.com/maps/api/geocode/json", 186 | content_type="application/json", 187 | callback=request_callback(), 188 | ) 189 | 190 | client = googlemaps.Client(key="AIzaasdf") 191 | client.geocode("Sesame St.") 192 | 193 | self.assertEqual(2, len(responses.calls)) 194 | self.assertEqual(responses.calls[0].request.url, responses.calls[1].request.url) 195 | 196 | @responses.activate 197 | def test_transport_error(self): 198 | responses.add( 199 | responses.GET, 200 | "https://maps.googleapis.com/maps/api/geocode/json", 201 | status=404, 202 | content_type="application/json", 203 | ) 204 | 205 | client = googlemaps.Client(key="AIzaasdf") 206 | with self.assertRaises(googlemaps.exceptions.HTTPError) as e: 207 | client.geocode("Foo") 208 | 209 | self.assertEqual(e.exception.status_code, 404) 210 | 211 | @responses.activate 212 | def test_host_override_on_init(self): 213 | responses.add( 214 | responses.GET, 215 | "https://foo.com/bar", 216 | body='{"status":"OK","results":[]}', 217 | status=200, 218 | content_type="application/json", 219 | ) 220 | 221 | client = googlemaps.Client(key="AIzaasdf", base_url="https://foo.com") 222 | client._get("/bar", {}) 223 | 224 | self.assertEqual(1, len(responses.calls)) 225 | 226 | @responses.activate 227 | def test_host_override_per_request(self): 228 | responses.add( 229 | responses.GET, 230 | "https://foo.com/bar", 231 | body='{"status":"OK","results":[]}', 232 | status=200, 233 | content_type="application/json", 234 | ) 235 | 236 | client = googlemaps.Client(key="AIzaasdf") 237 | client._get("/bar", {}, base_url="https://foo.com") 238 | 239 | self.assertEqual(1, len(responses.calls)) 240 | 241 | @responses.activate 242 | def test_custom_extract(self): 243 | def custom_extract(resp): 244 | return resp.json() 245 | 246 | responses.add( 247 | responses.GET, 248 | "https://maps.googleapis.com/bar", 249 | body='{"error":"errormessage"}', 250 | status=403, 251 | content_type="application/json", 252 | ) 253 | 254 | client = googlemaps.Client(key="AIzaasdf") 255 | b = client._get("/bar", {}, extract_body=custom_extract) 256 | self.assertEqual(1, len(responses.calls)) 257 | self.assertEqual("errormessage", b["error"]) 258 | 259 | @responses.activate 260 | def test_retry_intermittent(self): 261 | class request_callback: 262 | def __init__(self): 263 | self.first_req = True 264 | 265 | def __call__(self, req): 266 | if self.first_req: 267 | self.first_req = False 268 | return (500, {}, "Internal Server Error.") 269 | return (200, {}, '{"status":"OK","results":[]}') 270 | 271 | responses.add_callback( 272 | responses.GET, 273 | "https://maps.googleapis.com/maps/api/geocode/json", 274 | content_type="application/json", 275 | callback=request_callback(), 276 | ) 277 | 278 | client = googlemaps.Client(key="AIzaasdf") 279 | client.geocode("Sesame St.") 280 | 281 | self.assertEqual(2, len(responses.calls)) 282 | 283 | def test_invalid_channel(self): 284 | # Cf. limitations here: 285 | # https://developers.google.com/maps/premium/reports 286 | # /usage-reports#channels 287 | with self.assertRaises(ValueError): 288 | client = googlemaps.Client( 289 | client_id="foo", client_secret="a2V5", channel="auieauie$? " 290 | ) 291 | 292 | def test_auth_url_with_channel(self): 293 | client = googlemaps.Client( 294 | key="AIzaasdf", client_id="foo", client_secret="a2V5", channel="MyChannel_1" 295 | ) 296 | 297 | # Check ordering of parameters + signature. 298 | auth_url = client._generate_auth_url( 299 | "/test", {"param": "param"}, accepts_clientid=True 300 | ) 301 | self.assertEqual( 302 | auth_url, 303 | "/test?param=param" 304 | "&channel=MyChannel_1" 305 | "&client=foo" 306 | "&signature=OH18GuQto_mEpxj99UimKskvo4k=", 307 | ) 308 | 309 | # Check if added to requests to API with accepts_clientid=False 310 | auth_url = client._generate_auth_url( 311 | "/test", {"param": "param"}, accepts_clientid=False 312 | ) 313 | self.assertEqual(auth_url, "/test?param=param&key=AIzaasdf") 314 | 315 | def test_requests_version(self): 316 | client_args_timeout = { 317 | "key": "AIzaasdf", 318 | "client_id": "foo", 319 | "client_secret": "a2V5", 320 | "channel": "MyChannel_1", 321 | "connect_timeout": 5, 322 | "read_timeout": 5, 323 | } 324 | client_args = client_args_timeout.copy() 325 | del client_args["connect_timeout"] 326 | del client_args["read_timeout"] 327 | 328 | requests.__version__ = "2.3.0" 329 | with self.assertRaises(NotImplementedError): 330 | googlemaps.Client(**client_args_timeout) 331 | googlemaps.Client(**client_args) 332 | 333 | requests.__version__ = "2.4.0" 334 | googlemaps.Client(**client_args_timeout) 335 | googlemaps.Client(**client_args) 336 | 337 | def test_single_experience_id(self): 338 | experience_id1 = "Exp1" 339 | client = googlemaps.Client(key="AIzaasdf", experience_id=experience_id1) 340 | self.assertEqual(experience_id1, client.get_experience_id()) 341 | 342 | experience_id2 = "Exp2" 343 | client.set_experience_id(experience_id2) 344 | self.assertEqual(experience_id2, client.get_experience_id()) 345 | 346 | def test_multiple_experience_id(self): 347 | client = googlemaps.Client(key="AIzaasdf") 348 | 349 | experience_id1 = "Exp1" 350 | experience_id2 = "Exp2" 351 | client.set_experience_id(experience_id1, experience_id2) 352 | 353 | result = "%s,%s" % (experience_id1, experience_id2) 354 | self.assertEqual(result, client.get_experience_id()) 355 | 356 | def test_no_experience_id(self): 357 | client = googlemaps.Client(key="AIzaasdf") 358 | self.assertIsNone(client.get_experience_id()) 359 | 360 | def test_clearing_experience_id(self): 361 | client = googlemaps.Client(key="AIzaasdf") 362 | client.set_experience_id("ExpId") 363 | client.clear_experience_id() 364 | self.assertIsNone(client.get_experience_id()) 365 | 366 | def test_experience_id_sample(self): 367 | # [START maps_experience_id] 368 | experience_id = str(uuid.uuid4()) 369 | 370 | # instantiate client with experience id 371 | client = googlemaps.Client(key="AIza-Maps-API-Key", experience_id=experience_id) 372 | 373 | # clear the current experience id 374 | client.clear_experience_id() 375 | 376 | # set a new experience id 377 | other_experience_id = str(uuid.uuid4()) 378 | client.set_experience_id(experience_id, other_experience_id) 379 | 380 | # make API request, the client will set the header 381 | # X-GOOG-MAPS-EXPERIENCE-ID: experience_id,other_experience_id 382 | 383 | # get current experience id 384 | ids = client.get_experience_id() 385 | # [END maps_experience_id] 386 | 387 | result = "%s,%s" % (experience_id, other_experience_id) 388 | self.assertEqual(result, ids) 389 | 390 | @responses.activate 391 | def _perform_mock_request(self, experience_id=None): 392 | # Mock response 393 | responses.add( 394 | responses.GET, 395 | "https://maps.googleapis.com/maps/api/geocode/json", 396 | body='{"status":"OK","results":[]}', 397 | status=200, 398 | content_type="application/json", 399 | ) 400 | 401 | # Perform network call 402 | client = googlemaps.Client(key="AIzaasdf") 403 | client.set_experience_id(experience_id) 404 | client.geocode("Sesame St.") 405 | return responses.calls[0].request 406 | 407 | def test_experience_id_in_header(self): 408 | experience_id = "Exp1" 409 | request = self._perform_mock_request(experience_id) 410 | header_value = request.headers[_X_GOOG_MAPS_EXPERIENCE_ID] 411 | self.assertEqual(experience_id, header_value) 412 | 413 | def test_experience_id_no_in_header(self): 414 | request = self._perform_mock_request() 415 | self.assertIsNone(request.headers.get(_X_GOOG_MAPS_EXPERIENCE_ID)) 416 | 417 | @responses.activate 418 | def test_no_retry_over_query_limit(self): 419 | responses.add( 420 | responses.GET, 421 | "https://maps.googleapis.com/foo", 422 | body='{"status":"OVER_QUERY_LIMIT"}', 423 | status=200, 424 | content_type="application/json", 425 | ) 426 | 427 | client = googlemaps.Client(key="AIzaasdf", retry_over_query_limit=False) 428 | 429 | with self.assertRaises(googlemaps.exceptions.ApiError): 430 | client._request("/foo", {}) 431 | 432 | self.assertEqual(1, len(responses.calls)) 433 | -------------------------------------------------------------------------------- /tests/test_convert.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the convert module.""" 19 | 20 | import datetime 21 | import unittest 22 | import pytest 23 | 24 | from googlemaps import convert 25 | 26 | 27 | class ConvertTest(unittest.TestCase): 28 | def test_latlng(self): 29 | expected = "1,2" 30 | ll = {"lat": 1, "lng": 2} 31 | self.assertEqual(expected, convert.latlng(ll)) 32 | 33 | ll = [1, 2] 34 | self.assertEqual(expected, convert.latlng(ll)) 35 | 36 | ll = (1, 2) 37 | self.assertEqual(expected, convert.latlng(ll)) 38 | 39 | self.assertEqual(expected, convert.latlng(expected)) 40 | 41 | with self.assertRaises(TypeError): 42 | convert.latlng(1) 43 | 44 | def test_location_list(self): 45 | expected = "1,2|1,2" 46 | ll = [{"lat": 1, "lng": 2}, {"lat": 1, "lng": 2}] 47 | self.assertEqual(expected, convert.location_list(ll)) 48 | 49 | ll = [[1, 2], [1, 2]] 50 | self.assertEqual(expected, convert.location_list(ll)) 51 | 52 | ll = [(1, 2), [1, 2]] 53 | self.assertEqual(expected, convert.location_list(ll)) 54 | 55 | self.assertEqual(expected, convert.location_list(expected)) 56 | 57 | with self.assertRaises(TypeError): 58 | convert.latlng(1) 59 | 60 | def test_join_list(self): 61 | self.assertEqual("asdf", convert.join_list("|", "asdf")) 62 | 63 | self.assertEqual("1,2,A", convert.join_list(",", ["1", "2", "A"])) 64 | 65 | self.assertEqual("", convert.join_list(",", [])) 66 | 67 | self.assertEqual("a,B", convert.join_list(",", ("a", "B"))) 68 | 69 | def test_as_list(self): 70 | self.assertEqual([1], convert.as_list(1)) 71 | 72 | self.assertEqual([1, 2, 3], convert.as_list([1, 2, 3])) 73 | 74 | self.assertEqual(["string"], convert.as_list("string")) 75 | 76 | self.assertEqual((1, 2), convert.as_list((1, 2))) 77 | 78 | a_dict = {"a": 1} 79 | self.assertEqual([a_dict], convert.as_list(a_dict)) 80 | 81 | def test_time(self): 82 | self.assertEqual("1409810596", convert.time(1409810596)) 83 | 84 | dt = datetime.datetime.fromtimestamp(1409810596) 85 | self.assertEqual("1409810596", convert.time(dt)) 86 | 87 | def test_components(self): 88 | c = {"country": "US"} 89 | self.assertEqual("country:US", convert.components(c)) 90 | 91 | c = {"country": "US", "foo": 1} 92 | self.assertEqual("country:US|foo:1", convert.components(c)) 93 | 94 | c = {"country": ["US", "AU"], "foo": 1} 95 | self.assertEqual("country:AU|country:US|foo:1", convert.components(c)) 96 | 97 | with self.assertRaises(TypeError): 98 | convert.components("test") 99 | 100 | with self.assertRaises(TypeError): 101 | convert.components(1) 102 | 103 | with self.assertRaises(TypeError): 104 | convert.components(("c", "b")) 105 | 106 | def test_bounds(self): 107 | ne = {"lat": 1, "lng": 2} 108 | sw = (3, 4) 109 | b = {"northeast": ne, "southwest": sw} 110 | self.assertEqual("3,4|1,2", convert.bounds(b)) 111 | 112 | with self.assertRaises(TypeError): 113 | convert.bounds("test") 114 | 115 | def test_size(self): 116 | self.assertEqual("1x1", convert.size(1)) 117 | 118 | self.assertEqual("2x3", convert.size((2, 3))) 119 | 120 | with self.assertRaises(TypeError): 121 | convert.size("test") 122 | 123 | def test_polyline_decode(self): 124 | syd_mel_route = ( 125 | "rvumEis{y[`NsfA~tAbF`bEj^h{@{KlfA~eA~`AbmEghAt~D|e@j" 126 | "lRpO~yH_\\v}LjbBh~FdvCxu@`nCplDbcBf_B|wBhIfhCnqEb~D~" 127 | "jCn_EngApdEtoBbfClf@t_CzcCpoEr_Gz_DxmAphDjjBxqCviEf}" 128 | "B|pEvsEzbE~qGfpExjBlqCx}BvmLb`FbrQdpEvkAbjDllD|uDldD" 129 | "j`Ef|AzcEx_Gtm@vuI~xArwD`dArlFnhEzmHjtC~eDluAfkC|eAd" 130 | "hGpJh}N_mArrDlr@h|HzjDbsAvy@~~EdTxpJje@jlEltBboDjJdv" 131 | "KyZpzExrAxpHfg@pmJg[tgJuqBnlIarAh}DbN`hCeOf_IbxA~uFt" 132 | "|A|xEt_ArmBcN|sB|h@b_DjOzbJ{RlxCcfAp~AahAbqG~Gr}AerA" 133 | "`dCwlCbaFo]twKt{@bsG|}A~fDlvBvz@tw@rpD_r@rqB{PvbHek@" 134 | "vsHlh@ptNtm@fkD[~xFeEbyKnjDdyDbbBtuA|~Br|Gx_AfxCt}Cj" 135 | "nHv`Ew\\lnBdrBfqBraD|{BldBxpG|]jqC`mArcBv]rdAxgBzdEb" 136 | "{InaBzyC}AzaEaIvrCzcAzsCtfD~qGoPfeEh]h`BxiB`e@`kBxfA" 137 | "v^pyA`}BhkCdoCtrC~bCxhCbgEplKrk@tiAteBwAxbCwuAnnCc]b" 138 | "{FjrDdjGhhGzfCrlDruBzSrnGhvDhcFzw@n{@zxAf}Fd{IzaDnbD" 139 | "joAjqJjfDlbIlzAraBxrB}K~`GpuD~`BjmDhkBp{@r_AxCrnAjrC" 140 | "x`AzrBj{B|r@~qBbdAjtDnvCtNzpHxeApyC|GlfM`fHtMvqLjuEt" 141 | "lDvoFbnCt|@xmAvqBkGreFm~@hlHw|AltC}NtkGvhBfaJ|~@riAx" 142 | "uC~gErwCttCzjAdmGuF`iFv`AxsJftD|nDr_QtbMz_DheAf~Buy@" 143 | "rlC`i@d_CljC`gBr|H|nAf_Fh{G|mE~kAhgKviEpaQnu@zwAlrA`" 144 | "G~gFnvItz@j{Cng@j{D{]`tEftCdcIsPz{DddE~}PlnE|dJnzG`e" 145 | "G`mF|aJdqDvoAwWjzHv`H`wOtjGzeXhhBlxErfCf{BtsCjpEjtD|" 146 | "}Aja@xnAbdDt|ErMrdFh{CzgAnlCnr@`wEM~mE`bA`uD|MlwKxmB" 147 | "vuFlhB|sN`_@fvBp`CxhCt_@loDsS|eDlmChgFlqCbjCxk@vbGxm" 148 | "CjbMba@rpBaoClcCk_DhgEzYdzBl\\vsA_JfGztAbShkGtEhlDzh" 149 | "C~w@hnB{e@yF}`D`_Ayx@~vGqn@l}CafC" 150 | ) 151 | 152 | points = convert.decode_polyline(syd_mel_route) 153 | self.assertAlmostEqual(-33.86746, points[0]["lat"]) 154 | self.assertAlmostEqual(151.207090, points[0]["lng"]) 155 | self.assertAlmostEqual(-37.814130, points[-1]["lat"]) 156 | self.assertAlmostEqual(144.963180, points[-1]["lng"]) 157 | 158 | def test_polyline_round_trip(self): 159 | test_polyline = ( 160 | "gcneIpgxzRcDnBoBlEHzKjBbHlG`@`IkDxIi" 161 | "KhKoMaLwTwHeIqHuAyGXeB~Ew@fFjAtIzExF" 162 | ) 163 | 164 | points = convert.decode_polyline(test_polyline) 165 | actual_polyline = convert.encode_polyline(points) 166 | self.assertEqual(test_polyline, actual_polyline) 167 | 168 | 169 | @pytest.mark.parametrize( 170 | "value, expected", 171 | [ 172 | (40, "40"), 173 | (40.0, "40"), 174 | (40.1, "40.1"), 175 | (40.00000001, "40.00000001"), 176 | (40.000000009, "40.00000001"), 177 | (40.000000001, "40"), 178 | ], 179 | ) 180 | def test_format_float(value, expected): 181 | assert convert.format_float(value) == expected 182 | -------------------------------------------------------------------------------- /tests/test_directions.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the directions module.""" 19 | 20 | from datetime import datetime 21 | from datetime import timedelta 22 | import time 23 | 24 | import responses 25 | 26 | import googlemaps 27 | from . import TestCase 28 | 29 | 30 | class DirectionsTest(TestCase): 31 | def setUp(self): 32 | self.key = "AIzaasdf" 33 | self.client = googlemaps.Client(self.key) 34 | 35 | @responses.activate 36 | def test_simple_directions(self): 37 | responses.add( 38 | responses.GET, 39 | "https://maps.googleapis.com/maps/api/directions/json", 40 | body='{"status":"OK","routes":[]}', 41 | status=200, 42 | content_type="application/json", 43 | ) 44 | 45 | # Simplest directions request. Driving directions by default. 46 | routes = self.client.directions("Sydney", "Melbourne") 47 | 48 | self.assertEqual(1, len(responses.calls)) 49 | self.assertURLEqual( 50 | "https://maps.googleapis.com/maps/api/directions/json" 51 | "?origin=Sydney&destination=Melbourne&key=%s" % self.key, 52 | responses.calls[0].request.url, 53 | ) 54 | 55 | @responses.activate 56 | def test_complex_request(self): 57 | responses.add( 58 | responses.GET, 59 | "https://maps.googleapis.com/maps/api/directions/json", 60 | body='{"status":"OK","routes":[]}', 61 | status=200, 62 | content_type="application/json", 63 | ) 64 | 65 | routes = self.client.directions( 66 | "Sydney", 67 | "Melbourne", 68 | mode="bicycling", 69 | avoid=["highways", "tolls", "ferries"], 70 | units="metric", 71 | region="us", 72 | ) 73 | 74 | self.assertEqual(1, len(responses.calls)) 75 | self.assertURLEqual( 76 | "https://maps.googleapis.com/maps/api/directions/json?" 77 | "origin=Sydney&avoid=highways%%7Ctolls%%7Cferries&" 78 | "destination=Melbourne&mode=bicycling&key=%s" 79 | "&units=metric®ion=us" % self.key, 80 | responses.calls[0].request.url, 81 | ) 82 | 83 | def test_transit_without_time(self): 84 | # With mode of transit, we need a departure_time or an 85 | # arrival_time specified 86 | with self.assertRaises(googlemaps.exceptions.ApiError): 87 | self.client.directions( 88 | "Sydney Town Hall", "Parramatta, NSW", mode="transit" 89 | ) 90 | 91 | @responses.activate 92 | def test_transit_with_departure_time(self): 93 | responses.add( 94 | responses.GET, 95 | "https://maps.googleapis.com/maps/api/directions/json", 96 | body='{"status":"OK","routes":[]}', 97 | status=200, 98 | content_type="application/json", 99 | ) 100 | 101 | now = datetime.now() 102 | routes = self.client.directions( 103 | "Sydney Town Hall", 104 | "Parramatta, NSW", 105 | mode="transit", 106 | traffic_model="optimistic", 107 | departure_time=now, 108 | ) 109 | 110 | self.assertEqual(1, len(responses.calls)) 111 | self.assertURLEqual( 112 | "https://maps.googleapis.com/maps/api/directions/json?origin=" 113 | "Sydney+Town+Hall&key=%s&destination=Parramatta%%2C+NSW&" 114 | "mode=transit&departure_time=%d&traffic_model=optimistic" 115 | % (self.key, time.mktime(now.timetuple())), 116 | responses.calls[0].request.url, 117 | ) 118 | 119 | @responses.activate 120 | def test_transit_with_arrival_time(self): 121 | responses.add( 122 | responses.GET, 123 | "https://maps.googleapis.com/maps/api/directions/json", 124 | body='{"status":"OK","routes":[]}', 125 | status=200, 126 | content_type="application/json", 127 | ) 128 | 129 | an_hour_from_now = datetime.now() + timedelta(hours=1) 130 | routes = self.client.directions( 131 | "Sydney Town Hall", 132 | "Parramatta, NSW", 133 | mode="transit", 134 | arrival_time=an_hour_from_now, 135 | ) 136 | 137 | self.assertEqual(1, len(responses.calls)) 138 | self.assertURLEqual( 139 | "https://maps.googleapis.com/maps/api/directions/json?" 140 | "origin=Sydney+Town+Hall&arrival_time=%d&" 141 | "destination=Parramatta%%2C+NSW&mode=transit&key=%s" 142 | % (time.mktime(an_hour_from_now.timetuple()), self.key), 143 | responses.calls[0].request.url, 144 | ) 145 | 146 | def test_invalid_travel_mode(self): 147 | with self.assertRaises(ValueError): 148 | self.client.directions( 149 | "48 Pirrama Road, Pyrmont, NSW", "Sydney Town Hall", mode="crawling" 150 | ) 151 | 152 | @responses.activate 153 | def test_travel_mode_round_trip(self): 154 | responses.add( 155 | responses.GET, 156 | "https://maps.googleapis.com/maps/api/directions/json", 157 | body='{"status":"OK","routes":[]}', 158 | status=200, 159 | content_type="application/json", 160 | ) 161 | 162 | routes = self.client.directions( 163 | "Town Hall, Sydney", "Parramatta, NSW", mode="bicycling" 164 | ) 165 | 166 | self.assertEqual(1, len(responses.calls)) 167 | self.assertURLEqual( 168 | "https://maps.googleapis.com/maps/api/directions/json?" 169 | "origin=Town+Hall%%2C+Sydney&destination=Parramatta%%2C+NSW&" 170 | "mode=bicycling&key=%s" % self.key, 171 | responses.calls[0].request.url, 172 | ) 173 | 174 | @responses.activate 175 | def test_brooklyn_to_queens_by_transit(self): 176 | responses.add( 177 | responses.GET, 178 | "https://maps.googleapis.com/maps/api/directions/json", 179 | body='{"status":"OK","routes":[]}', 180 | status=200, 181 | content_type="application/json", 182 | ) 183 | 184 | now = datetime.now() 185 | routes = self.client.directions( 186 | "Brooklyn", "Queens", mode="transit", departure_time=now 187 | ) 188 | 189 | self.assertEqual(1, len(responses.calls)) 190 | self.assertURLEqual( 191 | "https://maps.googleapis.com/maps/api/directions/json?" 192 | "origin=Brooklyn&key=%s&destination=Queens&mode=transit&" 193 | "departure_time=%d" % (self.key, time.mktime(now.timetuple())), 194 | responses.calls[0].request.url, 195 | ) 196 | 197 | @responses.activate 198 | def test_boston_to_concord_via_charlestown_and_lexington(self): 199 | responses.add( 200 | responses.GET, 201 | "https://maps.googleapis.com/maps/api/directions/json", 202 | body='{"status":"OK","routes":[]}', 203 | status=200, 204 | content_type="application/json", 205 | ) 206 | 207 | routes = self.client.directions( 208 | "Boston, MA", "Concord, MA", waypoints=["Charlestown, MA", "Lexington, MA"] 209 | ) 210 | 211 | self.assertEqual(1, len(responses.calls)) 212 | self.assertURLEqual( 213 | "https://maps.googleapis.com/maps/api/directions/json?" 214 | "origin=Boston%%2C+MA&destination=Concord%%2C+MA&" 215 | "waypoints=Charlestown%%2C+MA%%7CLexington%%2C+MA&" 216 | "key=%s" % self.key, 217 | responses.calls[0].request.url, 218 | ) 219 | 220 | @responses.activate 221 | def test_adelaide_wine_tour(self): 222 | responses.add( 223 | responses.GET, 224 | "https://maps.googleapis.com/maps/api/directions/json", 225 | body='{"status":"OK","routes":[]}', 226 | status=200, 227 | content_type="application/json", 228 | ) 229 | 230 | routes = self.client.directions( 231 | "Adelaide, SA", 232 | "Adelaide, SA", 233 | waypoints=[ 234 | "Barossa Valley, SA", 235 | "Clare, SA", 236 | "Connawarra, SA", 237 | "McLaren Vale, SA", 238 | ], 239 | optimize_waypoints=True, 240 | ) 241 | 242 | self.assertEqual(1, len(responses.calls)) 243 | self.assertURLEqual( 244 | "https://maps.googleapis.com/maps/api/directions/json?" 245 | "origin=Adelaide%%2C+SA&destination=Adelaide%%2C+SA&" 246 | "waypoints=optimize%%3Atrue%%7CBarossa+Valley%%2C+" 247 | "SA%%7CClare%%2C+SA%%7CConnawarra%%2C+SA%%7CMcLaren+" 248 | "Vale%%2C+SA&key=%s" % self.key, 249 | responses.calls[0].request.url, 250 | ) 251 | 252 | @responses.activate 253 | def test_toledo_to_madrid_in_spain(self): 254 | responses.add( 255 | responses.GET, 256 | "https://maps.googleapis.com/maps/api/directions/json", 257 | body='{"status":"OK","routes":[]}', 258 | status=200, 259 | content_type="application/json", 260 | ) 261 | 262 | routes = self.client.directions("Toledo", "Madrid", region="es") 263 | 264 | self.assertEqual(1, len(responses.calls)) 265 | self.assertURLEqual( 266 | "https://maps.googleapis.com/maps/api/directions/json?" 267 | "origin=Toledo®ion=es&destination=Madrid&key=%s" % self.key, 268 | responses.calls[0].request.url, 269 | ) 270 | 271 | @responses.activate 272 | def test_zero_results_returns_response(self): 273 | responses.add( 274 | responses.GET, 275 | "https://maps.googleapis.com/maps/api/directions/json", 276 | body='{"status":"ZERO_RESULTS","routes":[]}', 277 | status=200, 278 | content_type="application/json", 279 | ) 280 | 281 | routes = self.client.directions("Toledo", "Madrid") 282 | self.assertIsNotNone(routes) 283 | self.assertEqual(0, len(routes)) 284 | 285 | @responses.activate 286 | def test_language_parameter(self): 287 | responses.add( 288 | responses.GET, 289 | "https://maps.googleapis.com/maps/api/directions/json", 290 | body='{"status":"OK","routes":[]}', 291 | status=200, 292 | content_type="application/json", 293 | ) 294 | 295 | routes = self.client.directions("Toledo", "Madrid", region="es", language="es") 296 | 297 | self.assertEqual(1, len(responses.calls)) 298 | self.assertURLEqual( 299 | "https://maps.googleapis.com/maps/api/directions/json?" 300 | "origin=Toledo®ion=es&destination=Madrid&key=%s&" 301 | "language=es" % self.key, 302 | responses.calls[0].request.url, 303 | ) 304 | 305 | @responses.activate 306 | def test_alternatives(self): 307 | responses.add( 308 | responses.GET, 309 | "https://maps.googleapis.com/maps/api/directions/json", 310 | body='{"status":"OK","routes":[]}', 311 | status=200, 312 | content_type="application/json", 313 | ) 314 | 315 | routes = self.client.directions( 316 | "Sydney Town Hall", "Parramatta Town Hall", alternatives=True 317 | ) 318 | 319 | self.assertEqual(1, len(responses.calls)) 320 | self.assertURLEqual( 321 | "https://maps.googleapis.com/maps/api/directions/json?" 322 | "origin=Sydney+Town+Hall&destination=Parramatta+Town+Hall&" 323 | "alternatives=true&key=%s" % self.key, 324 | responses.calls[0].request.url, 325 | ) 326 | -------------------------------------------------------------------------------- /tests/test_distance_matrix.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the distance matrix module.""" 19 | 20 | from datetime import datetime 21 | import time 22 | 23 | import responses 24 | 25 | import googlemaps 26 | from . import TestCase 27 | 28 | 29 | class DistanceMatrixTest(TestCase): 30 | def setUp(self): 31 | self.key = "AIzaasdf" 32 | self.client = googlemaps.Client(self.key) 33 | 34 | @responses.activate 35 | def test_basic_params(self): 36 | responses.add( 37 | responses.GET, 38 | "https://maps.googleapis.com/maps/api/distancematrix/json", 39 | body='{"status":"OK","rows":[]}', 40 | status=200, 41 | content_type="application/json", 42 | ) 43 | 44 | origins = [ 45 | "Perth, Australia", 46 | "Sydney, Australia", 47 | "Melbourne, Australia", 48 | "Adelaide, Australia", 49 | "Brisbane, Australia", 50 | "Darwin, Australia", 51 | "Hobart, Australia", 52 | "Canberra, Australia", 53 | ] 54 | destinations = [ 55 | "Uluru, Australia", 56 | "Kakadu, Australia", 57 | "Blue Mountains, Australia", 58 | "Bungle Bungles, Australia", 59 | "The Pinnacles, Australia", 60 | ] 61 | 62 | matrix = self.client.distance_matrix(origins, destinations) 63 | 64 | self.assertEqual(1, len(responses.calls)) 65 | self.assertURLEqual( 66 | "https://maps.googleapis.com/maps/api/distancematrix/json?" 67 | "key=%s&origins=Perth%%2C+Australia%%7CSydney%%2C+" 68 | "Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+" 69 | "Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+" 70 | "Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&" 71 | "destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C" 72 | "Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia" 73 | "%%7CThe+Pinnacles%%2C+Australia" % self.key, 74 | responses.calls[0].request.url, 75 | ) 76 | 77 | @responses.activate 78 | def test_mixed_params(self): 79 | responses.add( 80 | responses.GET, 81 | "https://maps.googleapis.com/maps/api/distancematrix/json", 82 | body='{"status":"OK","rows":[]}', 83 | status=200, 84 | content_type="application/json", 85 | ) 86 | 87 | origins = [ 88 | "Bobcaygeon ON", [41.43206, -81.38992], 89 | "place_id:ChIJ7cv00DwsDogRAMDACa2m4K8" 90 | ] 91 | destinations = [ 92 | (43.012486, -83.6964149), 93 | {"lat": 42.8863855, "lng": -78.8781627}, 94 | ] 95 | 96 | matrix = self.client.distance_matrix(origins, destinations) 97 | 98 | self.assertEqual(1, len(responses.calls)) 99 | self.assertURLEqual( 100 | "https://maps.googleapis.com/maps/api/distancematrix/json?" 101 | "key=%s&origins=Bobcaygeon+ON%%7C41.43206%%2C-81.38992%%7C" 102 | "place_id%%3AChIJ7cv00DwsDogRAMDACa2m4K8&" 103 | "destinations=43.012486%%2C-83.6964149%%7C42.8863855%%2C" 104 | "-78.8781627" % self.key, 105 | responses.calls[0].request.url, 106 | ) 107 | 108 | @responses.activate 109 | def test_all_params(self): 110 | responses.add( 111 | responses.GET, 112 | "https://maps.googleapis.com/maps/api/distancematrix/json", 113 | body='{"status":"OK","rows":[]}', 114 | status=200, 115 | content_type="application/json", 116 | ) 117 | 118 | origins = [ 119 | "Perth, Australia", 120 | "Sydney, Australia", 121 | "Melbourne, Australia", 122 | "Adelaide, Australia", 123 | "Brisbane, Australia", 124 | "Darwin, Australia", 125 | "Hobart, Australia", 126 | "Canberra, Australia", 127 | ] 128 | destinations = [ 129 | "Uluru, Australia", 130 | "Kakadu, Australia", 131 | "Blue Mountains, Australia", 132 | "Bungle Bungles, Australia", 133 | "The Pinnacles, Australia", 134 | ] 135 | 136 | now = datetime.now() 137 | matrix = self.client.distance_matrix( 138 | origins, 139 | destinations, 140 | mode="driving", 141 | language="en-AU", 142 | avoid="tolls", 143 | units="imperial", 144 | departure_time=now, 145 | traffic_model="optimistic", 146 | ) 147 | 148 | self.assertEqual(1, len(responses.calls)) 149 | self.assertURLEqual( 150 | "https://maps.googleapis.com/maps/api/distancematrix/json?" 151 | "origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7C" 152 | "Melbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7C" 153 | "Brisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+" 154 | "Australia%%7CCanberra%%2C+Australia&language=en-AU&" 155 | "avoid=tolls&mode=driving&key=%s&units=imperial&" 156 | "destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7C" 157 | "Blue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia" 158 | "%%7CThe+Pinnacles%%2C+Australia&departure_time=%d" 159 | "&traffic_model=optimistic" % (self.key, time.mktime(now.timetuple())), 160 | responses.calls[0].request.url, 161 | ) 162 | 163 | @responses.activate 164 | def test_lang_param(self): 165 | responses.add( 166 | responses.GET, 167 | "https://maps.googleapis.com/maps/api/distancematrix/json", 168 | body='{"status":"OK","rows":[]}', 169 | status=200, 170 | content_type="application/json", 171 | ) 172 | 173 | origins = ["Vancouver BC", "Seattle"] 174 | destinations = ["San Francisco", "Victoria BC"] 175 | 176 | matrix = self.client.distance_matrix( 177 | origins, destinations, language="fr-FR", mode="bicycling" 178 | ) 179 | 180 | self.assertEqual(1, len(responses.calls)) 181 | self.assertURLEqual( 182 | "https://maps.googleapis.com/maps/api/distancematrix/json?" 183 | "key=%s&language=fr-FR&mode=bicycling&" 184 | "origins=Vancouver+BC%%7CSeattle&" 185 | "destinations=San+Francisco%%7CVictoria+BC" % self.key, 186 | responses.calls[0].request.url, 187 | ) 188 | @responses.activate 189 | def test_place_id_param(self): 190 | responses.add( 191 | responses.GET, 192 | "https://maps.googleapis.com/maps/api/distancematrix/json", 193 | body='{"status":"OK","rows":[]}', 194 | status=200, 195 | content_type="application/json", 196 | ) 197 | 198 | origins = [ 199 | 'place_id:ChIJ7cv00DwsDogRAMDACa2m4K8', 200 | 'place_id:ChIJzxcfI6qAa4cR1jaKJ_j0jhE', 201 | ] 202 | destinations = [ 203 | 'place_id:ChIJPZDrEzLsZIgRoNrpodC5P30', 204 | 'place_id:ChIJjQmTaV0E9YgRC2MLmS_e_mY', 205 | ] 206 | 207 | matrix = self.client.distance_matrix(origins, destinations) 208 | 209 | self.assertEqual(1, len(responses.calls)) 210 | self.assertURLEqual( 211 | "https://maps.googleapis.com/maps/api/distancematrix/json?" 212 | "key=%s&" 213 | "origins=place_id%%3AChIJ7cv00DwsDogRAMDACa2m4K8%%7C" 214 | "place_id%%3AChIJzxcfI6qAa4cR1jaKJ_j0jhE&" 215 | "destinations=place_id%%3AChIJPZDrEzLsZIgRoNrpodC5P30%%7C" 216 | "place_id%%3AChIJjQmTaV0E9YgRC2MLmS_e_mY" % self.key, 217 | responses.calls[0].request.url, 218 | ) 219 | -------------------------------------------------------------------------------- /tests/test_elevation.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the elevation module.""" 19 | 20 | import datetime 21 | 22 | import responses 23 | 24 | import googlemaps 25 | from . import TestCase 26 | 27 | 28 | class ElevationTest(TestCase): 29 | def setUp(self): 30 | self.key = "AIzaasdf" 31 | self.client = googlemaps.Client(self.key) 32 | 33 | @responses.activate 34 | def test_elevation_single(self): 35 | responses.add( 36 | responses.GET, 37 | "https://maps.googleapis.com/maps/api/elevation/json", 38 | body='{"status":"OK","results":[]}', 39 | status=200, 40 | content_type="application/json", 41 | ) 42 | 43 | results = self.client.elevation((40.714728, -73.998672)) 44 | 45 | self.assertEqual(1, len(responses.calls)) 46 | self.assertURLEqual( 47 | "https://maps.googleapis.com/maps/api/elevation/json?" 48 | "locations=enc:abowFtzsbM&key=%s" % self.key, 49 | responses.calls[0].request.url, 50 | ) 51 | 52 | @responses.activate 53 | def test_elevation_single_list(self): 54 | responses.add( 55 | responses.GET, 56 | "https://maps.googleapis.com/maps/api/elevation/json", 57 | body='{"status":"OK","results":[]}', 58 | status=200, 59 | content_type="application/json", 60 | ) 61 | 62 | results = self.client.elevation([(40.714728, -73.998672)]) 63 | 64 | self.assertEqual(1, len(responses.calls)) 65 | self.assertURLEqual( 66 | "https://maps.googleapis.com/maps/api/elevation/json?" 67 | "locations=enc:abowFtzsbM&key=%s" % self.key, 68 | responses.calls[0].request.url, 69 | ) 70 | 71 | @responses.activate 72 | def test_elevation_multiple(self): 73 | responses.add( 74 | responses.GET, 75 | "https://maps.googleapis.com/maps/api/elevation/json", 76 | body='{"status":"OK","results":[]}', 77 | status=200, 78 | content_type="application/json", 79 | ) 80 | 81 | locations = [(40.714728, -73.998672), (-34.397, 150.644)] 82 | results = self.client.elevation(locations) 83 | 84 | self.assertEqual(1, len(responses.calls)) 85 | self.assertURLEqual( 86 | "https://maps.googleapis.com/maps/api/elevation/json?" 87 | "locations=enc:abowFtzsbMhgmiMuobzi@&key=%s" % self.key, 88 | responses.calls[0].request.url, 89 | ) 90 | 91 | def test_elevation_along_path_single(self): 92 | with self.assertRaises(googlemaps.exceptions.ApiError): 93 | results = self.client.elevation_along_path([(40.714728, -73.998672)], 5) 94 | 95 | @responses.activate 96 | def test_elevation_along_path(self): 97 | responses.add( 98 | responses.GET, 99 | "https://maps.googleapis.com/maps/api/elevation/json", 100 | body='{"status":"OK","results":[]}', 101 | status=200, 102 | content_type="application/json", 103 | ) 104 | 105 | path = [(40.714728, -73.998672), (-34.397, 150.644)] 106 | 107 | results = self.client.elevation_along_path(path, 5) 108 | 109 | self.assertEqual(1, len(responses.calls)) 110 | self.assertURLEqual( 111 | "https://maps.googleapis.com/maps/api/elevation/json?" 112 | "path=enc:abowFtzsbMhgmiMuobzi@&" 113 | "key=%s&samples=5" % self.key, 114 | responses.calls[0].request.url, 115 | ) 116 | 117 | @responses.activate 118 | def test_short_latlng(self): 119 | responses.add( 120 | responses.GET, 121 | "https://maps.googleapis.com/maps/api/elevation/json", 122 | body='{"status":"OK","results":[]}', 123 | status=200, 124 | content_type="application/json", 125 | ) 126 | 127 | results = self.client.elevation((40, -73)) 128 | 129 | self.assertEqual(1, len(responses.calls)) 130 | self.assertURLEqual( 131 | "https://maps.googleapis.com/maps/api/elevation/json?" 132 | "locations=40,-73&key=%s" % self.key, 133 | responses.calls[0].request.url, 134 | ) 135 | -------------------------------------------------------------------------------- /tests/test_geocoding.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # 3 | # Copyright 2014 Google Inc. All rights reserved. 4 | # 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | # use this file except in compliance with the License. You may obtain a copy of 8 | # the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations under 16 | # the License. 17 | # 18 | 19 | """Tests for the geocoding module.""" 20 | 21 | import datetime 22 | 23 | import responses 24 | 25 | import googlemaps 26 | from . import TestCase 27 | 28 | 29 | class GeocodingTest(TestCase): 30 | def setUp(self): 31 | self.key = "AIzaasdf" 32 | self.client = googlemaps.Client(self.key) 33 | 34 | @responses.activate 35 | def test_simple_geocode(self): 36 | responses.add( 37 | responses.GET, 38 | "https://maps.googleapis.com/maps/api/geocode/json", 39 | body='{"status":"OK","results":[]}', 40 | status=200, 41 | content_type="application/json", 42 | ) 43 | 44 | results = self.client.geocode("Sydney").get("results", []) 45 | 46 | self.assertEqual(1, len(responses.calls)) 47 | self.assertURLEqual( 48 | "https://maps.googleapis.com/maps/api/geocode/json?" 49 | "key=%s&address=Sydney" % self.key, 50 | responses.calls[0].request.url, 51 | ) 52 | 53 | @responses.activate 54 | def test_reverse_geocode(self): 55 | responses.add( 56 | responses.GET, 57 | "https://maps.googleapis.com/maps/api/geocode/json", 58 | body='{"status":"OK","results":[]}', 59 | status=200, 60 | content_type="application/json", 61 | ) 62 | 63 | results = self.client.reverse_geocode((-33.8674869, 151.2069902)).get("results", []) 64 | 65 | self.assertEqual(1, len(responses.calls)) 66 | self.assertURLEqual( 67 | "https://maps.googleapis.com/maps/api/geocode/json?" 68 | "latlng=-33.8674869,151.2069902&key=%s" % self.key, 69 | responses.calls[0].request.url, 70 | ) 71 | 72 | @responses.activate 73 | def test_geocoding_the_googleplex(self): 74 | responses.add( 75 | responses.GET, 76 | "https://maps.googleapis.com/maps/api/geocode/json", 77 | body='{"status":"OK","results":[]}', 78 | status=200, 79 | content_type="application/json", 80 | ) 81 | 82 | results = self.client.geocode("1600 Amphitheatre Parkway, " "Mountain View, CA").get("results", []) 83 | 84 | self.assertEqual(1, len(responses.calls)) 85 | self.assertURLEqual( 86 | "https://maps.googleapis.com/maps/api/geocode/json?" 87 | "key=%s&address=1600+Amphitheatre+Parkway%%2C+Mountain" 88 | "+View%%2C+CA" % self.key, 89 | responses.calls[0].request.url, 90 | ) 91 | 92 | @responses.activate 93 | def test_geocode_with_bounds(self): 94 | responses.add( 95 | responses.GET, 96 | "https://maps.googleapis.com/maps/api/geocode/json", 97 | body='{"status":"OK","results":[]}', 98 | status=200, 99 | content_type="application/json", 100 | ) 101 | 102 | results = self.client.geocode( 103 | "Winnetka", 104 | bounds={ 105 | "southwest": (34.172684, -118.604794), 106 | "northeast": (34.236144, -118.500938), 107 | }, 108 | ).get("results", []) 109 | 110 | self.assertEqual(1, len(responses.calls)) 111 | self.assertURLEqual( 112 | "https://maps.googleapis.com/maps/api/geocode/json?" 113 | "bounds=34.172684%%2C-118.604794%%7C34.236144%%2C" 114 | "-118.500938&key=%s&address=Winnetka" % self.key, 115 | responses.calls[0].request.url, 116 | ) 117 | 118 | @responses.activate 119 | def test_geocode_with_region_biasing(self): 120 | responses.add( 121 | responses.GET, 122 | "https://maps.googleapis.com/maps/api/geocode/json", 123 | body='{"status":"OK","results":[]}', 124 | status=200, 125 | content_type="application/json", 126 | ) 127 | 128 | results = self.client.geocode("Toledo", region="es").get("results", []) 129 | 130 | self.assertEqual(1, len(responses.calls)) 131 | self.assertURLEqual( 132 | "https://maps.googleapis.com/maps/api/geocode/json?" 133 | "region=es&key=%s&address=Toledo" % self.key, 134 | responses.calls[0].request.url, 135 | ) 136 | 137 | @responses.activate 138 | def test_geocode_with_component_filter(self): 139 | responses.add( 140 | responses.GET, 141 | "https://maps.googleapis.com/maps/api/geocode/json", 142 | body='{"status":"OK","results":[]}', 143 | status=200, 144 | content_type="application/json", 145 | ) 146 | 147 | results = self.client.geocode("santa cruz", components={"country": "ES"}).get("results", []) 148 | 149 | self.assertEqual(1, len(responses.calls)) 150 | self.assertURLEqual( 151 | "https://maps.googleapis.com/maps/api/geocode/json?" 152 | "key=%s&components=country%%3AES&address=santa+cruz" % self.key, 153 | responses.calls[0].request.url, 154 | ) 155 | 156 | @responses.activate 157 | def test_geocode_with_multiple_component_filters(self): 158 | responses.add( 159 | responses.GET, 160 | "https://maps.googleapis.com/maps/api/geocode/json", 161 | body='{"status":"OK","results":[]}', 162 | status=200, 163 | content_type="application/json", 164 | ) 165 | 166 | results = self.client.geocode( 167 | "Torun", components={"administrative_area": "TX", "country": "US"} 168 | ).get("results", []) 169 | 170 | self.assertEqual(1, len(responses.calls)) 171 | self.assertURLEqual( 172 | "https://maps.googleapis.com/maps/api/geocode/json?" 173 | "key=%s&components=administrative_area%%3ATX%%7C" 174 | "country%%3AUS&address=Torun" % self.key, 175 | responses.calls[0].request.url, 176 | ) 177 | 178 | @responses.activate 179 | def test_geocode_with_just_components(self): 180 | responses.add( 181 | responses.GET, 182 | "https://maps.googleapis.com/maps/api/geocode/json", 183 | body='{"status":"OK","results":[]}', 184 | status=200, 185 | content_type="application/json", 186 | ) 187 | 188 | results = self.client.geocode( 189 | components={ 190 | "route": "Annegatan", 191 | "administrative_area": "Helsinki", 192 | "country": "Finland", 193 | } 194 | ).get("results", []) 195 | 196 | self.assertEqual(1, len(responses.calls)) 197 | self.assertURLEqual( 198 | "https://maps.googleapis.com/maps/api/geocode/json?" 199 | "key=%s&components=administrative_area%%3AHelsinki" 200 | "%%7Ccountry%%3AFinland%%7Croute%%3AAnnegatan" % self.key, 201 | responses.calls[0].request.url, 202 | ) 203 | 204 | @responses.activate 205 | def test_geocode_place_id(self): 206 | responses.add( 207 | responses.GET, 208 | "https://maps.googleapis.com/maps/api/geocode/json", 209 | body='{"status":"OK","results":[]}', 210 | status=200, 211 | content_type="application/json", 212 | ) 213 | 214 | results = self.client.geocode(place_id="ChIJeRpOeF67j4AR9ydy_PIzPuM").get("results", []) 215 | 216 | self.assertEqual(1, len(responses.calls)) 217 | self.assertURLEqual( 218 | "https://maps.googleapis.com/maps/api/geocode/json?" 219 | "key=%s&place_id=ChIJeRpOeF67j4AR9ydy_PIzPuM" % self.key, 220 | responses.calls[0].request.url, 221 | ) 222 | 223 | @responses.activate 224 | def test_simple_reverse_geocode(self): 225 | responses.add( 226 | responses.GET, 227 | "https://maps.googleapis.com/maps/api/geocode/json", 228 | body='{"status":"OK","results":[]}', 229 | status=200, 230 | content_type="application/json", 231 | ) 232 | 233 | results = self.client.reverse_geocode((40.714224, -73.961452)).get("results", []) 234 | 235 | self.assertEqual(1, len(responses.calls)) 236 | self.assertURLEqual( 237 | "https://maps.googleapis.com/maps/api/geocode/json?" 238 | "latlng=40.714224%%2C-73.961452&key=%s" % self.key, 239 | responses.calls[0].request.url, 240 | ) 241 | 242 | @responses.activate 243 | def test_reverse_geocode_restricted_by_type(self): 244 | responses.add( 245 | responses.GET, 246 | "https://maps.googleapis.com/maps/api/geocode/json", 247 | body='{"status":"OK","results":[]}', 248 | status=200, 249 | content_type="application/json", 250 | ) 251 | 252 | results = self.client.reverse_geocode( 253 | (40.714224, -73.961452), 254 | location_type="ROOFTOP", 255 | result_type="street_address", 256 | ).get("results", []) 257 | 258 | self.assertEqual(1, len(responses.calls)) 259 | self.assertURLEqual( 260 | "https://maps.googleapis.com/maps/api/geocode/json?" 261 | "latlng=40.714224%%2C-73.961452&result_type=street_address&" 262 | "key=%s&location_type=ROOFTOP" % self.key, 263 | responses.calls[0].request.url, 264 | ) 265 | 266 | @responses.activate 267 | def test_reverse_geocode_multiple_location_types(self): 268 | responses.add( 269 | responses.GET, 270 | "https://maps.googleapis.com/maps/api/geocode/json", 271 | body='{"status":"OK","results":[]}', 272 | status=200, 273 | content_type="application/json", 274 | ) 275 | 276 | results = self.client.reverse_geocode( 277 | (40.714224, -73.961452), 278 | location_type=["ROOFTOP", "RANGE_INTERPOLATED"], 279 | result_type="street_address", 280 | ).get("results", []) 281 | 282 | self.assertEqual(1, len(responses.calls)) 283 | self.assertURLEqual( 284 | "https://maps.googleapis.com/maps/api/geocode/json?" 285 | "latlng=40.714224%%2C-73.961452&result_type=street_address&" 286 | "key=%s&location_type=ROOFTOP%%7CRANGE_INTERPOLATED" % self.key, 287 | responses.calls[0].request.url, 288 | ) 289 | 290 | @responses.activate 291 | def test_reverse_geocode_multiple_result_types(self): 292 | responses.add( 293 | responses.GET, 294 | "https://maps.googleapis.com/maps/api/geocode/json", 295 | body='{"status":"OK","results":[]}', 296 | status=200, 297 | content_type="application/json", 298 | ) 299 | 300 | results = self.client.reverse_geocode( 301 | (40.714224, -73.961452), 302 | location_type="ROOFTOP", 303 | result_type=["street_address", "route"], 304 | ).get("results", []) 305 | 306 | self.assertEqual(1, len(responses.calls)) 307 | self.assertURLEqual( 308 | "https://maps.googleapis.com/maps/api/geocode/json?" 309 | "latlng=40.714224%%2C-73.961452&result_type=street_address" 310 | "%%7Croute&key=%s&location_type=ROOFTOP" % self.key, 311 | responses.calls[0].request.url, 312 | ) 313 | 314 | @responses.activate 315 | def test_reverse_geocode_with_address_descriptors(self): 316 | responses.add( 317 | responses.GET, 318 | "https://maps.googleapis.com/maps/api/geocode/json", 319 | body='{"status":"OK","results":[], "address_descriptor":{ "landmarks": [ { "placeId": "id" } ] } }', 320 | status=200, 321 | content_type="application/json", 322 | ) 323 | 324 | response = self.client.reverse_geocode((-33.8674869, 151.2069902), enable_address_descriptor=True) 325 | 326 | address_descriptor = response.get("address_descriptor", []) 327 | 328 | self.assertEqual(1, len(address_descriptor["landmarks"])) 329 | self.assertEqual(1, len(responses.calls)) 330 | self.assertURLEqual( 331 | "https://maps.googleapis.com/maps/api/geocode/json?" 332 | "latlng=-33.8674869,151.2069902&enable_address_descriptor=true&key=%s" % self.key, 333 | responses.calls[0].request.url, 334 | ) 335 | 336 | @responses.activate 337 | def test_partial_match(self): 338 | responses.add( 339 | responses.GET, 340 | "https://maps.googleapis.com/maps/api/geocode/json", 341 | body='{"status":"OK","results":[]}', 342 | status=200, 343 | content_type="application/json", 344 | ) 345 | 346 | results = self.client.geocode("Pirrama Pyrmont").get("results", []) 347 | 348 | self.assertEqual(1, len(responses.calls)) 349 | self.assertURLEqual( 350 | "https://maps.googleapis.com/maps/api/geocode/json?" 351 | "key=%s&address=Pirrama+Pyrmont" % self.key, 352 | responses.calls[0].request.url, 353 | ) 354 | 355 | @responses.activate 356 | def test_utf_results(self): 357 | responses.add( 358 | responses.GET, 359 | "https://maps.googleapis.com/maps/api/geocode/json", 360 | body='{"status":"OK","results":[]}', 361 | status=200, 362 | content_type="application/json", 363 | ) 364 | 365 | results = self.client.geocode(components={"postal_code": "96766"}).get("results", []) 366 | 367 | self.assertEqual(1, len(responses.calls)) 368 | self.assertURLEqual( 369 | "https://maps.googleapis.com/maps/api/geocode/json?" 370 | "key=%s&components=postal_code%%3A96766" % self.key, 371 | responses.calls[0].request.url, 372 | ) 373 | 374 | @responses.activate 375 | def test_utf8_request(self): 376 | responses.add( 377 | responses.GET, 378 | "https://maps.googleapis.com/maps/api/geocode/json", 379 | body='{"status":"OK","results":[]}', 380 | status=200, 381 | content_type="application/json", 382 | ) 383 | 384 | self.client.geocode(self.u("\\u4e2d\\u56fd")).get("results", []) # China 385 | self.assertURLEqual( 386 | "https://maps.googleapis.com/maps/api/geocode/json?" 387 | "key=%s&address=%s" % (self.key, "%E4%B8%AD%E5%9B%BD"), 388 | responses.calls[0].request.url, 389 | ) 390 | -------------------------------------------------------------------------------- /tests/test_geolocation.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # 3 | # Copyright 2017 Google Inc. All rights reserved. 4 | # 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | # use this file except in compliance with the License. You may obtain a copy of 8 | # the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations under 16 | # the License. 17 | # 18 | 19 | """Tests for the geocolocation module.""" 20 | 21 | import responses 22 | 23 | import googlemaps 24 | from . import TestCase 25 | 26 | 27 | class GeolocationTest(TestCase): 28 | def setUp(self): 29 | self.key = "AIzaasdf" 30 | self.client = googlemaps.Client(self.key) 31 | 32 | @responses.activate 33 | def test_simple_geolocate(self): 34 | responses.add( 35 | responses.POST, 36 | "https://www.googleapis.com/geolocation/v1/geolocate", 37 | body='{"location": {"lat": 51.0,"lng": -0.1},"accuracy": 1200.4}', 38 | status=200, 39 | content_type="application/json", 40 | ) 41 | 42 | results = self.client.geolocate() 43 | 44 | self.assertEqual(1, len(responses.calls)) 45 | self.assertURLEqual( 46 | "https://www.googleapis.com/geolocation/v1/geolocate?" "key=%s" % self.key, 47 | responses.calls[0].request.url, 48 | ) 49 | -------------------------------------------------------------------------------- /tests/test_maps.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the maps module.""" 19 | 20 | from types import GeneratorType 21 | 22 | import responses 23 | 24 | import googlemaps 25 | from . import TestCase 26 | 27 | from googlemaps.maps import StaticMapMarker 28 | from googlemaps.maps import StaticMapPath 29 | 30 | 31 | class MapsTest(TestCase): 32 | def setUp(self): 33 | self.key = "AIzaasdf" 34 | self.client = googlemaps.Client(self.key) 35 | 36 | @responses.activate 37 | def test_static_map_marker(self): 38 | marker = StaticMapMarker( 39 | locations=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], 40 | size="small", 41 | color="blue", 42 | label="S", 43 | ) 44 | 45 | self.assertEqual( 46 | "size:small|color:blue|label:S|" "-33.867486,151.20699|Sydney", str(marker) 47 | ) 48 | 49 | with self.assertRaises(ValueError): 50 | StaticMapMarker(locations=["Sydney"], label="XS") 51 | 52 | self.assertEqual("label:1|Sydney", str(StaticMapMarker(locations=["Sydney"], label="1"))) 53 | 54 | @responses.activate 55 | def test_static_map_path(self): 56 | path = StaticMapPath( 57 | points=[{"lat": -33.867486, "lng": 151.206990}, "Sydney"], 58 | weight=5, 59 | color="red", 60 | geodesic=True, 61 | fillcolor="Red", 62 | ) 63 | 64 | self.assertEqual( 65 | "weight:5|color:red|fillcolor:Red|" 66 | "geodesic:True|" 67 | "-33.867486,151.20699|Sydney", 68 | str(path), 69 | ) 70 | 71 | @responses.activate 72 | def test_download(self): 73 | url = "https://maps.googleapis.com/maps/api/staticmap" 74 | responses.add(responses.GET, url, status=200) 75 | 76 | path = StaticMapPath( 77 | points=[(62.107733, -145.541936), "Delta+Junction,AK"], 78 | weight=5, 79 | color="red", 80 | ) 81 | 82 | m1 = StaticMapMarker( 83 | locations=[(62.107733, -145.541936)], color="blue", label="S" 84 | ) 85 | 86 | m2 = StaticMapMarker( 87 | locations=["Delta+Junction,AK"], size="tiny", color="green" 88 | ) 89 | 90 | m3 = StaticMapMarker( 91 | locations=["Tok,AK"], size="mid", color="0xFFFF00", label="C" 92 | ) 93 | 94 | response = self.client.static_map( 95 | size=(400, 400), 96 | zoom=6, 97 | center=(63.259591, -144.667969), 98 | maptype="hybrid", 99 | format="png", 100 | scale=2, 101 | visible=["Tok,AK"], 102 | path=path, 103 | markers=[m1, m2, m3], 104 | ) 105 | 106 | self.assertTrue(isinstance(response, GeneratorType)) 107 | self.assertEqual(1, len(responses.calls)) 108 | self.assertURLEqual( 109 | "%s?center=63.259591%%2C-144.667969&format=png&maptype=hybrid&" 110 | "markers=color%%3Ablue%%7Clabel%%3AS%%7C62.107733%%2C-145.541936&" 111 | "markers=size%%3Atiny%%7Ccolor%%3Agreen%%7CDelta%%2BJunction%%2CAK&" 112 | "markers=size%%3Amid%%7Ccolor%%3A0xFFFF00%%7Clabel%%3AC%%7CTok%%2CAK&" 113 | "path=weight%%3A5%%7Ccolor%%3Ared%%7C62.107733%%2C-145.541936%%7CDelta%%2BJunction%%2CAK&" 114 | "scale=2&size=400x400&visible=Tok%%2CAK&zoom=6&key=%s" % (url, self.key), 115 | responses.calls[0].request.url, 116 | ) 117 | 118 | with self.assertRaises(ValueError): 119 | self.client.static_map(size=(400, 400)) 120 | 121 | with self.assertRaises(ValueError): 122 | self.client.static_map( 123 | size=(400, 400), center=(63.259591, -144.667969), zoom=6, format="test" 124 | ) 125 | 126 | with self.assertRaises(ValueError): 127 | self.client.static_map( 128 | size=(400, 400), center=(63.259591, -144.667969), zoom=6, maptype="test" 129 | ) 130 | -------------------------------------------------------------------------------- /tests/test_places.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # 3 | # Copyright 2016 Google Inc. All rights reserved. 4 | # 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | # use this file except in compliance with the License. You may obtain a copy of 8 | # the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations under 16 | # the License. 17 | # 18 | 19 | """Tests for the places module.""" 20 | 21 | import uuid 22 | 23 | from types import GeneratorType 24 | 25 | import responses 26 | 27 | import googlemaps 28 | from . import TestCase 29 | 30 | 31 | class PlacesTest(TestCase): 32 | def setUp(self): 33 | self.key = "AIzaasdf" 34 | self.client = googlemaps.Client(self.key) 35 | self.location = (-33.86746, 151.207090) 36 | self.type = "liquor_store" 37 | self.language = "en-AU" 38 | self.region = "AU" 39 | self.radius = 100 40 | 41 | @responses.activate 42 | def test_places_find(self): 43 | url = "https://maps.googleapis.com/maps/api/place/findplacefromtext/json" 44 | responses.add( 45 | responses.GET, 46 | url, 47 | body='{"status": "OK", "candidates": []}', 48 | status=200, 49 | content_type="application/json", 50 | ) 51 | 52 | self.client.find_place( 53 | "restaurant", 54 | "textquery", 55 | fields=["business_status", "geometry/location", "place_id"], 56 | location_bias="point:90,90", 57 | language=self.language, 58 | ) 59 | 60 | self.assertEqual(1, len(responses.calls)) 61 | self.assertURLEqual( 62 | "%s?language=en-AU&inputtype=textquery&" 63 | "locationbias=point:90,90&input=restaurant" 64 | "&fields=business_status,geometry/location,place_id&key=%s" 65 | % (url, self.key), 66 | responses.calls[0].request.url, 67 | ) 68 | 69 | with self.assertRaises(ValueError): 70 | self.client.find_place("restaurant", "invalid") 71 | with self.assertRaises(ValueError): 72 | self.client.find_place( 73 | "restaurant", "textquery", fields=["geometry", "invalid"] 74 | ) 75 | with self.assertRaises(ValueError): 76 | self.client.find_place("restaurant", "textquery", location_bias="invalid") 77 | 78 | @responses.activate 79 | def test_places_text_search(self): 80 | url = "https://maps.googleapis.com/maps/api/place/textsearch/json" 81 | responses.add( 82 | responses.GET, 83 | url, 84 | body='{"status": "OK", "results": [], "html_attributions": []}', 85 | status=200, 86 | content_type="application/json", 87 | ) 88 | 89 | self.client.places( 90 | "restaurant", 91 | location=self.location, 92 | radius=self.radius, 93 | region=self.region, 94 | language=self.language, 95 | min_price=1, 96 | max_price=4, 97 | open_now=True, 98 | type=self.type, 99 | ) 100 | 101 | self.assertEqual(1, len(responses.calls)) 102 | self.assertURLEqual( 103 | "%s?language=en-AU&location=-33.86746%%2C151.20709&" 104 | "maxprice=4&minprice=1&opennow=true&query=restaurant&" 105 | "radius=100®ion=AU&type=liquor_store&key=%s" % (url, self.key), 106 | responses.calls[0].request.url, 107 | ) 108 | 109 | @responses.activate 110 | def test_places_nearby_search(self): 111 | url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" 112 | responses.add( 113 | responses.GET, 114 | url, 115 | body='{"status": "OK", "results": [], "html_attributions": []}', 116 | status=200, 117 | content_type="application/json", 118 | ) 119 | 120 | self.client.places_nearby( 121 | location=self.location, 122 | keyword="foo", 123 | language=self.language, 124 | min_price=1, 125 | max_price=4, 126 | name="bar", 127 | open_now=True, 128 | rank_by="distance", 129 | type=self.type, 130 | ) 131 | 132 | self.assertEqual(1, len(responses.calls)) 133 | self.assertURLEqual( 134 | "%s?keyword=foo&language=en-AU&location=-33.86746%%2C151.20709&" 135 | "maxprice=4&minprice=1&name=bar&opennow=true&rankby=distance&" 136 | "type=liquor_store&key=%s" % (url, self.key), 137 | responses.calls[0].request.url, 138 | ) 139 | 140 | with self.assertRaises(ValueError): 141 | self.client.places_nearby(radius=self.radius) 142 | with self.assertRaises(ValueError): 143 | self.client.places_nearby(self.location, rank_by="distance") 144 | 145 | with self.assertRaises(ValueError): 146 | self.client.places_nearby( 147 | location=self.location, 148 | rank_by="distance", 149 | keyword="foo", 150 | radius=self.radius, 151 | ) 152 | 153 | @responses.activate 154 | def test_place_detail(self): 155 | url = "https://maps.googleapis.com/maps/api/place/details/json" 156 | responses.add( 157 | responses.GET, 158 | url, 159 | body='{"status": "OK", "result": {}, "html_attributions": []}', 160 | status=200, 161 | content_type="application/json", 162 | ) 163 | 164 | self.client.place( 165 | "ChIJN1t_tDeuEmsRUsoyG83frY4", 166 | fields=["business_status", "geometry/location", 167 | "place_id", "reviews"], 168 | language=self.language, 169 | reviews_no_translations=True, 170 | reviews_sort="newest", 171 | ) 172 | 173 | self.assertEqual(1, len(responses.calls)) 174 | self.assertURLEqual( 175 | "%s?language=en-AU&placeid=ChIJN1t_tDeuEmsRUsoyG83frY4" 176 | "&reviews_no_translations=true&reviews_sort=newest" 177 | "&key=%s&fields=business_status,geometry/location,place_id,reviews" 178 | % (url, self.key), 179 | responses.calls[0].request.url, 180 | ) 181 | 182 | with self.assertRaises(ValueError): 183 | self.client.place( 184 | "ChIJN1t_tDeuEmsRUsoyG83frY4", fields=["geometry", "invalid"] 185 | ) 186 | 187 | @responses.activate 188 | def test_photo(self): 189 | url = "https://maps.googleapis.com/maps/api/place/photo" 190 | responses.add(responses.GET, url, status=200) 191 | 192 | ref = "CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0" 193 | response = self.client.places_photo(ref, max_width=100) 194 | 195 | self.assertTrue(isinstance(response, GeneratorType)) 196 | self.assertEqual(1, len(responses.calls)) 197 | self.assertURLEqual( 198 | "%s?maxwidth=100&photoreference=%s&key=%s" % (url, ref, self.key), 199 | responses.calls[0].request.url, 200 | ) 201 | 202 | @responses.activate 203 | def test_autocomplete(self): 204 | url = "https://maps.googleapis.com/maps/api/place/autocomplete/json" 205 | responses.add( 206 | responses.GET, 207 | url, 208 | body='{"status": "OK", "predictions": []}', 209 | status=200, 210 | content_type="application/json", 211 | ) 212 | 213 | session_token = uuid.uuid4().hex 214 | 215 | self.client.places_autocomplete( 216 | "Google", 217 | session_token=session_token, 218 | offset=3, 219 | origin=self.location, 220 | location=self.location, 221 | radius=self.radius, 222 | language=self.language, 223 | types="geocode", 224 | components={"country": "au"}, 225 | strict_bounds=True, 226 | ) 227 | 228 | self.assertEqual(1, len(responses.calls)) 229 | self.assertURLEqual( 230 | "%s?components=country%%3Aau&input=Google&language=en-AU&" 231 | "origin=-33.86746%%2C151.20709&" 232 | "location=-33.86746%%2C151.20709&offset=3&radius=100&" 233 | "strictbounds=true&types=geocode&key=%s&sessiontoken=%s" 234 | % (url, self.key, session_token), 235 | responses.calls[0].request.url, 236 | ) 237 | 238 | @responses.activate 239 | def test_autocomplete_query(self): 240 | url = "https://maps.googleapis.com/maps/api/place/queryautocomplete/json" 241 | responses.add( 242 | responses.GET, 243 | url, 244 | body='{"status": "OK", "predictions": []}', 245 | status=200, 246 | content_type="application/json", 247 | ) 248 | 249 | self.client.places_autocomplete_query("pizza near New York") 250 | 251 | self.assertEqual(1, len(responses.calls)) 252 | self.assertURLEqual( 253 | "%s?input=pizza+near+New+York&key=%s" % (url, self.key), 254 | responses.calls[0].request.url, 255 | ) 256 | -------------------------------------------------------------------------------- /tests/test_roads.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Google Inc. All rights reserved. 3 | # 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | # use this file except in compliance with the License. You may obtain a copy of 7 | # the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations under 15 | # the License. 16 | # 17 | 18 | """Tests for the roads module.""" 19 | 20 | 21 | import responses 22 | 23 | import googlemaps 24 | from . import TestCase 25 | 26 | 27 | class RoadsTest(TestCase): 28 | def setUp(self): 29 | self.key = "AIzaasdf" 30 | self.client = googlemaps.Client(self.key) 31 | 32 | @responses.activate 33 | def test_snap(self): 34 | responses.add( 35 | responses.GET, 36 | "https://roads.googleapis.com/v1/snapToRoads", 37 | body='{"snappedPoints":["foo"]}', 38 | status=200, 39 | content_type="application/json", 40 | ) 41 | 42 | results = self.client.snap_to_roads((40.714728, -73.998672)) 43 | self.assertEqual("foo", results[0]) 44 | 45 | self.assertEqual(1, len(responses.calls)) 46 | self.assertURLEqual( 47 | "https://roads.googleapis.com/v1/snapToRoads?" 48 | "path=40.714728%%2C-73.998672&key=%s" % self.key, 49 | responses.calls[0].request.url, 50 | ) 51 | 52 | @responses.activate 53 | def test_nearest_roads(self): 54 | responses.add( 55 | responses.GET, 56 | "https://roads.googleapis.com/v1/nearestRoads", 57 | body='{"snappedPoints":["foo"]}', 58 | status=200, 59 | content_type="application/json", 60 | ) 61 | 62 | results = self.client.nearest_roads((40.714728, -73.998672)) 63 | self.assertEqual("foo", results[0]) 64 | 65 | self.assertEqual(1, len(responses.calls)) 66 | self.assertURLEqual( 67 | "https://roads.googleapis.com/v1/nearestRoads?" 68 | "points=40.714728%%2C-73.998672&key=%s" % self.key, 69 | responses.calls[0].request.url, 70 | ) 71 | 72 | @responses.activate 73 | def test_path(self): 74 | responses.add( 75 | responses.GET, 76 | "https://roads.googleapis.com/v1/speedLimits", 77 | body='{"speedLimits":["foo"]}', 78 | status=200, 79 | content_type="application/json", 80 | ) 81 | 82 | results = self.client.snapped_speed_limits([(1, 2), (3, 4)]) 83 | self.assertEqual("foo", results["speedLimits"][0]) 84 | 85 | self.assertEqual(1, len(responses.calls)) 86 | self.assertURLEqual( 87 | "https://roads.googleapis.com/v1/speedLimits?" 88 | "path=1%%2C2|3%%2C4" 89 | "&key=%s" % self.key, 90 | responses.calls[0].request.url, 91 | ) 92 | 93 | @responses.activate 94 | def test_speedlimits(self): 95 | responses.add( 96 | responses.GET, 97 | "https://roads.googleapis.com/v1/speedLimits", 98 | body='{"speedLimits":["foo"]}', 99 | status=200, 100 | content_type="application/json", 101 | ) 102 | 103 | results = self.client.speed_limits("id1") 104 | self.assertEqual("foo", results[0]) 105 | self.assertEqual( 106 | "https://roads.googleapis.com/v1/speedLimits?" 107 | "placeId=id1&key=%s" % self.key, 108 | responses.calls[0].request.url, 109 | ) 110 | 111 | @responses.activate 112 | def test_speedlimits_multiple(self): 113 | responses.add( 114 | responses.GET, 115 | "https://roads.googleapis.com/v1/speedLimits", 116 | body='{"speedLimits":["foo"]}', 117 | status=200, 118 | content_type="application/json", 119 | ) 120 | 121 | results = self.client.speed_limits(["id1", "id2", "id3"]) 122 | self.assertEqual("foo", results[0]) 123 | self.assertEqual( 124 | "https://roads.googleapis.com/v1/speedLimits?" 125 | "placeId=id1&placeId=id2&placeId=id3" 126 | "&key=%s" % self.key, 127 | responses.calls[0].request.url, 128 | ) 129 | 130 | def test_clientid_not_accepted(self): 131 | client = googlemaps.Client(client_id="asdf", client_secret="asdf") 132 | 133 | with self.assertRaises(ValueError): 134 | client.speed_limits("foo") 135 | 136 | @responses.activate 137 | def test_retry(self): 138 | class request_callback: 139 | def __init__(self): 140 | self.first_req = True 141 | 142 | def __call__(self, req): 143 | if self.first_req: 144 | self.first_req = False 145 | return (500, {}, "Internal Server Error.") 146 | return (200, {}, '{"speedLimits":[]}') 147 | 148 | responses.add_callback( 149 | responses.GET, 150 | "https://roads.googleapis.com/v1/speedLimits", 151 | content_type="application/json", 152 | callback=request_callback(), 153 | ) 154 | 155 | self.client.speed_limits([]) 156 | 157 | self.assertEqual(2, len(responses.calls)) 158 | self.assertEqual(responses.calls[0].request.url, responses.calls[1].request.url) 159 | -------------------------------------------------------------------------------- /tests/test_timezone.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # 3 | # Copyright 2014 Google Inc. All rights reserved. 4 | # 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | # use this file except in compliance with the License. You may obtain a copy of 8 | # the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations under 16 | # the License. 17 | # 18 | 19 | """Tests for the timezone module.""" 20 | 21 | import datetime 22 | 23 | import responses 24 | from unittest import mock 25 | import googlemaps 26 | from . import TestCase 27 | 28 | 29 | class TimezoneTest(TestCase): 30 | def setUp(self): 31 | self.key = "AIzaasdf" 32 | self.client = googlemaps.Client(self.key) 33 | 34 | @responses.activate 35 | def test_los_angeles(self): 36 | responses.add( 37 | responses.GET, 38 | "https://maps.googleapis.com/maps/api/timezone/json", 39 | body='{"status":"OK"}', 40 | status=200, 41 | content_type="application/json", 42 | ) 43 | 44 | ts = 1331766000 45 | timezone = self.client.timezone((39.603481, -119.682251), ts) 46 | self.assertIsNotNone(timezone) 47 | 48 | self.assertEqual(1, len(responses.calls)) 49 | self.assertURLEqual( 50 | "https://maps.googleapis.com/maps/api/timezone/json" 51 | "?location=39.603481,-119.682251×tamp=%d" 52 | "&key=%s" % (ts, self.key), 53 | responses.calls[0].request.url, 54 | ) 55 | 56 | class MockDatetime: 57 | def now(self): 58 | return datetime.datetime.fromtimestamp(1608) 59 | 60 | utcnow = now 61 | 62 | @responses.activate 63 | @mock.patch("googlemaps.timezone.datetime", MockDatetime()) 64 | def test_los_angeles_with_no_timestamp(self): 65 | responses.add( 66 | responses.GET, 67 | "https://maps.googleapis.com/maps/api/timezone/json", 68 | body='{"status":"OK"}', 69 | status=200, 70 | content_type="application/json", 71 | ) 72 | 73 | timezone = self.client.timezone((39.603481, -119.682251)) 74 | self.assertIsNotNone(timezone) 75 | 76 | self.assertEqual(1, len(responses.calls)) 77 | self.assertURLEqual( 78 | "https://maps.googleapis.com/maps/api/timezone/json" 79 | "?location=39.603481,-119.682251×tamp=%d" 80 | "&key=%s" % (1608, self.key), 81 | responses.calls[0].request.url, 82 | ) 83 | -------------------------------------------------------------------------------- /text.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | queries_quota : int 4 | queries_per_second = 60 # None or 60 5 | queries_per_minute = None # None or 6000 6 | 7 | try: 8 | if (type(queries_per_second) == int and type(queries_per_minute) == int ): 9 | queries_quota = math.floor(min(queries_per_second, queries_per_minute/60)) 10 | elif (queries_per_second): 11 | queries_quota = math.floor(queries_per_second) 12 | elif (queries_per_minute): 13 | queries_quota = math.floor(queries_per_minute/60) 14 | else: 15 | print("MISSING VALID NUMBER for queries_per_second or queries_per_minute") 16 | print(queries_quota) 17 | 18 | except NameError: 19 | print("MISSING VALUE for queries_per_second or queries_per_minute") --------------------------------------------------------------------------------