├── .eslintignore ├── .eslintrc.json ├── .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 ├── release-please-config.json ├── stale.yml ├── sync-repo-settings.yaml └── workflows │ ├── dependabot.yml │ ├── docs.yml │ ├── release-please.yml │ └── test.yml ├── .gitignore ├── .releaserc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── e2e ├── client.test.ts ├── compression.test.ts ├── directions.test.ts ├── distance.test.ts ├── elevation.test.ts ├── geocode │ ├── geocode.test.ts │ └── reversegeocode.test.ts ├── geolocation.test.ts ├── places │ ├── autocomplete.test.ts │ ├── details.test.ts │ ├── findplacefromtext.test.ts │ ├── photo.test.ts │ ├── placesnearby.test.ts │ ├── queryautocomplete.test.ts │ └── textsearch.test.ts ├── roads │ ├── nearestroads.test.ts │ └── snaptoroads.test.ts └── timezone.test.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── adapter.test.ts ├── adapter.ts ├── client.test.ts ├── client.ts ├── common.ts ├── directions.test.ts ├── directions.ts ├── distance.test.ts ├── distance.ts ├── elevation.test.ts ├── elevation.ts ├── geocode │ ├── geocode.test.ts │ ├── geocode.ts │ ├── reversegeocode.test.ts │ └── reversegeocode.ts ├── geolocate.test.ts ├── geolocate.ts ├── index.test.ts ├── index.ts ├── places │ ├── autocomplete.test.ts │ ├── autocomplete.ts │ ├── details.test.ts │ ├── details.ts │ ├── findplacefromtext.test.ts │ ├── findplacefromtext.ts │ ├── photo.test.ts │ ├── photo.ts │ ├── placesnearby.test.ts │ ├── placesnearby.ts │ ├── queryautocomplete.test.ts │ ├── queryautocomplete.ts │ ├── textsearch.test.ts │ └── textsearch.ts ├── roads │ ├── nearestroads.test.ts │ ├── nearestroads.ts │ ├── snaptoroads.test.ts │ └── snaptoroads.ts ├── serialize.test.ts ├── serialize.ts ├── timezone.test.ts ├── timezone.ts ├── util.test.ts └── util.ts ├── test-module-loading.sh ├── tsconfig.json ├── typedoc.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | coverage/ 3 | dist/ 4 | docs/ 5 | lib/ 6 | node_modules/ 7 | public/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 3 | "parserOptions": { 4 | "ecmaVersion": 12, 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["jest"], 8 | "rules": { 9 | "no-var": 2, 10 | "prefer-arrow-callback": 2 11 | }, 12 | "overrides": [ 13 | { 14 | "files": ["*.ts", "*.tsx"], 15 | "parser": "@typescript-eslint/parser", 16 | "plugins": ["@typescript-eslint"], 17 | "extends": ["plugin:@typescript-eslint/recommended"], 18 | "rules": { 19 | "@typescript-eslint/ban-ts-comment": 0, 20 | "@typescript-eslint/ban-types": 1, 21 | "@typescript-eslint/no-empty-function": 1, 22 | "@typescript-eslint/no-var-requires": 0, 23 | "@typescript-eslint/member-ordering": 1 24 | } 25 | } 26 | ], 27 | "env": { 28 | "browser": false, 29 | "node": true, 30 | "es6": true, 31 | "jest/globals": true 32 | }, 33 | "globals": { "google": "readonly" } 34 | } 35 | -------------------------------------------------------------------------------- /.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 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 | version: 2 16 | updates: 17 | - package-ecosystem: "npm" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /.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/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bump-minor-pre-major": true 3 | } 4 | -------------------------------------------------------------------------------- /.github/stale.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 | # 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 | test: 23 | uses: ./.github/workflows/test.yml 24 | dependabot: 25 | needs: test 26 | runs-on: ubuntu-latest 27 | if: ${{ github.actor == 'dependabot[bot]' }} 28 | env: 29 | PR_URL: ${{github.event.pull_request.html_url}} 30 | GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} 31 | steps: 32 | - name: approve 33 | run: gh pr review --approve "$PR_URL" 34 | - name: merge 35 | run: gh pr merge --auto --squash --delete-branch "$PR_URL" 36 | -------------------------------------------------------------------------------- /.github/workflows/docs.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: Docs 16 | on: 17 | push: 18 | branches: 19 | - master 20 | jobs: 21 | docs: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Setup node 26 | uses: actions/setup-node@v4 27 | with: 28 | node_version: 22 29 | cache: npm 30 | cache-dependency-path: package-lock.json 31 | - run: npm ci 32 | - run: npm run docs 33 | - run: touch docs/.nojekyll 34 | - name: GitHub Pages 35 | if: success() 36 | uses: crazy-max/ghaction-github-pages@v1.3.0 37 | with: 38 | target_branch: gh-pages 39 | build_dir: docs 40 | committer_name: googlemaps-bot 41 | committer_email: googlemaps-bot@google.com 42 | commit_message: "chore: publish docs" 43 | keep_history: true 44 | env: 45 | GITHUB_PAT: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 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 | ## Runs the release-please action for all new pushes to the main branch. 16 | ## This will create new release-PRs, create GitHub releases and update 17 | ## the CHANGELOG.md. 18 | 19 | on: 20 | push: 21 | branches: [master] 22 | 23 | permissions: 24 | contents: write 25 | pull-requests: write 26 | 27 | name: Release Please 28 | 29 | jobs: 30 | release-please: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - id: release 34 | name: Release Please 35 | uses: googleapis/release-please-action@v4 36 | 37 | with: 38 | release-type: node 39 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 40 | package-name: "@googlemaps/google-maps-services-js" 41 | config-file: .github/release-please-config.json 42 | 43 | # Everything below is for NPM publishing when a release is cut. 44 | # Note the "if" statement on all commands to make sure that publishing 45 | # only happens when a release is cut. 46 | 47 | - if: ${{ steps.release.outputs.release_created }} 48 | name: Checkout 49 | uses: actions/checkout@v4 50 | 51 | - if: ${{ steps.release.outputs.release_created }} 52 | name: Setup Node for Dependency Installation 53 | uses: actions/setup-node@v4 54 | with: 55 | node-version: 22 56 | cache: npm 57 | 58 | - if: ${{ steps.release.outputs.release_created }} 59 | name: Install Dependencies 60 | run: npm ci 61 | 62 | # Now configure node with the registry used for publishing 63 | - if: ${{ steps.release.outputs.release_created }} 64 | name: Setup Node for Publishing 65 | uses: actions/setup-node@v4 66 | with: 67 | node-version: 20 68 | registry-url: "https://wombat-dressing-room.appspot.com/" 69 | 70 | - if: ${{ steps.release.outputs.release_created }} 71 | name: Publish 72 | # npm publish will trigger the build via the prepack hook 73 | run: npm publish 74 | env: 75 | NODE_AUTH_TOKEN: ${{ secrets.NPM_WOMBAT_TOKEN }} 76 | -------------------------------------------------------------------------------- /.github/workflows/test.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: Test 16 | 17 | on: 18 | - push 19 | - pull_request 20 | - workflow_call 21 | 22 | jobs: 23 | matrix: 24 | runs-on: ubuntu-latest 25 | 26 | strategy: 27 | matrix: 28 | node-version: [18.x, 20.x, 22.x] 29 | 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | - name: Use Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | cache: npm 38 | - name: Install Dependencies 39 | run: npm ci 40 | - name: Run Tests 41 | run: npm test 42 | - name: Collect Coverage Data 43 | uses: codecov/codecov-action@v1 44 | - if: github.event_name == 'push' && github.actor != 'dependabot[bot]' 45 | name: Run Extended Test Suite 46 | run: npm run test:all 47 | env: 48 | GOOGLE_MAPS_API_KEY: ${{ secrets.SYNCED_GOOGLE_MAPS_API_KEY_SERVICES }} 49 | 50 | test: 51 | needs: [matrix] 52 | runs-on: ubuntu-latest 53 | steps: 54 | - run: echo "Test matrix finished" 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | dist/ 5 | docs/ 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # JSDoc 31 | out 32 | 33 | # nyc 34 | .nyc_output 35 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | plugins: 3 | [ 4 | [ 5 | "@semantic-release/commit-analyzer", 6 | { releaseRules: [{ type: "docs", scope: "README", release: "patch" }] }, 7 | ], 8 | ["semantic-release-interval", { duration: 1, units: "week" }], 9 | [ 10 | "@semantic-release/release-notes-generator", 11 | { 12 | preset: "conventionalcommits", 13 | presetConfig: 14 | { 15 | types: 16 | [ 17 | { type: "feat", section: "Features" }, 18 | { type: "feature", section: "Features" }, 19 | { type: "fix", section: "Bug Fixes" }, 20 | { type: "perf", section: "Performance Improvements" }, 21 | { type: "revert", section: "Reverts" }, 22 | { type: "docs", section: "Documentation", hidden: false }, 23 | { type: "style", section: "Styles", hidden: false }, 24 | { 25 | type: "chore", 26 | section: "Miscellaneous Chores", 27 | hidden: false, 28 | }, 29 | { 30 | type: "refactor", 31 | section: "Code Refactoring", 32 | hidden: false, 33 | }, 34 | { type: "test", section: "Tests", hidden: false }, 35 | { type: "build", section: "Build System", hidden: false }, 36 | { 37 | type: "ci", 38 | section: "Continuous Integration", 39 | hidden: false, 40 | }, 41 | ], 42 | }, 43 | }, 44 | ], 45 | "@semantic-release/github", 46 | "@semantic-release/npm", 47 | "@semantic-release/git", 48 | ], 49 | options: { debug: true }, 50 | branches: 51 | [ 52 | "master", 53 | { name: "beta", prerelease: true }, 54 | { name: "alpha", prerelease: true }, 55 | ], 56 | } 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.4.1](https://github.com/googlemaps/google-maps-services-js/compare/v3.4.0...v3.4.1) (2025-03-28) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * trigger release-please ([#1337](https://github.com/googlemaps/google-maps-services-js/issues/1337)) ([1e7c9e6](https://github.com/googlemaps/google-maps-services-js/commit/1e7c9e6da0a8350421a558c027a6759daa8a4c50)) 9 | 10 | ## [3.4.0](https://github.com/googlemaps/google-maps-services-js/compare/v3.3.42...v3.4.0) (2024-04-09) 11 | 12 | 13 | ### Features 14 | 15 | * Added Address Descriptors to Geocoding response. ([#1187](https://github.com/googlemaps/google-maps-services-js/issues/1187)) ([47db368](https://github.com/googlemaps/google-maps-services-js/commit/47db36856c2235433d01b1d82d6965cdef07b6b6)) 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement]( 6 | https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use Github pull requests for this purpose. 22 | 23 | ### The small print 24 | Contributions made by corporations are covered by a different agreement than 25 | the one above, the 26 | [Software Grant and Corporate Contributor License Agreement]( 27 | https://cla.developers.google.com/about/google-corporate). 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /e2e/client.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | Client, 19 | defaultAxiosInstance, 20 | defaultHttpsAgent, 21 | type ElevationResponse, 22 | } from "../src"; 23 | import { AxiosError } from "axios"; 24 | 25 | test("client should work with defaults", async () => { 26 | const location = { lat: 10, lng: 20 }; 27 | 28 | const params = { 29 | locations: [location, location], 30 | key: process.env.GOOGLE_MAPS_API_KEY, 31 | }; 32 | const client = new Client(); 33 | const r = await client.elevation({ params: params }); 34 | 35 | expect(r.data.results.length).toEqual(2); 36 | }); 37 | 38 | test("client should work with modified config", async () => { 39 | const location = { lat: 10, lng: 20 }; 40 | 41 | const params = { 42 | locations: [location, location], 43 | key: process.env.GOOGLE_MAPS_API_KEY, 44 | }; 45 | const client = new Client({ 46 | config: { timeout: 30000, httpsAgent: defaultHttpsAgent }, 47 | }); 48 | const r = await client.elevation({ params: params }); 49 | 50 | expect(r.data.results.length).toEqual(2); 51 | }); 52 | 53 | test("client should work with instance", async () => { 54 | const location = { lat: 10, lng: 20 }; 55 | 56 | const params = { 57 | locations: [location, location], 58 | key: process.env.GOOGLE_MAPS_API_KEY, 59 | }; 60 | const client = new Client({ axiosInstance: defaultAxiosInstance }); 61 | const r = await client.elevation({ params: params }); 62 | 63 | expect(r.data.results.length).toEqual(2); 64 | }); 65 | 66 | test("readme example using client", async () => { 67 | const { Client, Status } = require("../src"); 68 | const client = new Client({}); 69 | 70 | await client 71 | .elevation({ 72 | params: { 73 | locations: [{ lat: 45, lng: -110 }], 74 | key: process.env.GOOGLE_MAPS_API_KEY, 75 | }, 76 | timeout: 1000, // milliseconds 77 | }) 78 | .then((r) => { 79 | expect(r.data.status).toEqual(Status.OK); 80 | expect(r.data.results[0].elevation).toBeGreaterThan(2000); 81 | expect(r.data.results[0].elevation).toBeLessThan(3000); 82 | }) 83 | .catch((e) => { 84 | console.log(e); 85 | throw "Should not error"; 86 | }); 87 | }); 88 | 89 | test("readme example using client fails correctly", async () => { 90 | const { Client, Status } = require("../src"); 91 | const client = new Client({}); 92 | 93 | await client 94 | .elevation({ 95 | params: { 96 | locations: [{ lat: 45, lng: -110 }], 97 | key: "invalid key", 98 | }, 99 | timeout: 1000, // milliseconds 100 | }) 101 | .catch((e: AxiosError) => { 102 | const response = e.response as ElevationResponse; 103 | expect(response.status).toEqual(403); 104 | expect(response.data.status).toEqual(Status.REQUEST_DENIED); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /e2e/compression.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { Status } from "../src/common"; 18 | import { directions } from "../src/directions"; 19 | 20 | test("server responds with compressed content", async () => { 21 | const params = { 22 | origin: "Seattle, WA", 23 | destination: "San Francisco, CA", 24 | waypoints: [{ lat: 40, lng: -120 }], 25 | key: process.env.GOOGLE_MAPS_API_KEY, 26 | }; 27 | 28 | // Use of directions here is entirely arbitrary and any API that supports 29 | // result compression could be substituted. 30 | 31 | const r = await directions({ params: params }); 32 | expect(r.data.status).toEqual(Status.OK); 33 | 34 | // Axios removes conent-encoding from the response header set once it takes 35 | // care of piping the stream through a zlib unzip instance. So verifying 36 | // that the server responds with a compressed response must be done via the 37 | // raw headers. 38 | 39 | const { rawHeaders } = r.request.res; 40 | const contentEncodingIndex = rawHeaders.findIndex( 41 | (i) => i.toLowerCase() === "content-encoding" 42 | ); 43 | 44 | expect(contentEncodingIndex).not.toBe(-1); 45 | expect(rawHeaders[contentEncodingIndex + 1]).toBe("gzip"); 46 | expect(r.headers["Content-Encoding"]).toBeUndefined(); 47 | }); 48 | -------------------------------------------------------------------------------- /e2e/directions.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { Status } from "../src/common"; 18 | import { decodePath } from "../src/util"; 19 | import { directions } from "../src/directions"; 20 | 21 | test("directions should get correct result", async () => { 22 | const params = { 23 | origin: "Adelaide,SA", 24 | destination: "Adelaide,SA", 25 | waypoints: [ 26 | "Barossa+Valley,SA", 27 | "Clare,SA", 28 | "Connawarra,SA", 29 | "McLaren+Vale,SA", 30 | ], 31 | optimize: true, 32 | departure_time: "now" as const, 33 | key: process.env.GOOGLE_MAPS_API_KEY, 34 | }; 35 | const r = await directions({ params: params }); 36 | expect(r.request.path).toMatch("waypoints=optimize%3Atrue|Barossa"); 37 | expect(r.data.status).toEqual(Status.OK); 38 | expect(r.data.routes[0].legs[0].distance.value).toBeGreaterThan(0); 39 | expect( 40 | decodePath(r.data.routes[0].overview_polyline.points)[0].lat 41 | ).toBeDefined(); 42 | expect( 43 | decodePath(r.data.routes[0].legs[0].steps[0].polyline.points)[0].lat 44 | ).toBeDefined(); 45 | }); 46 | -------------------------------------------------------------------------------- /e2e/distance.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { distancematrix } from "../src/distance"; 18 | 19 | test("distance should return correct result", async () => { 20 | const params = { 21 | origins: ["Seattle, WA"], 22 | destinations: ["San Francisco, CA", "New York, NY"], 23 | key: process.env.GOOGLE_MAPS_API_KEY, 24 | }; 25 | 26 | const r = await distancematrix({ params: params }); 27 | const matrix = r.data.rows; 28 | 29 | expect(matrix.length).toEqual(1); 30 | expect(matrix[0].elements.length).toEqual(2); 31 | }); 32 | 33 | test("distance between two latlngs", async () => { 34 | const params = { 35 | origins: [{ lat: 22.2805459, lng: 70.8011263 }], 36 | destinations: [{ lat: 22.2970336, lng: 70.8148468 }], 37 | key: process.env.GOOGLE_MAPS_API_KEY, 38 | }; 39 | 40 | const r = await distancematrix({ params: params }); 41 | 42 | const matrix = r.data.rows; 43 | 44 | expect(matrix[0].elements[0].distance.value).toBeGreaterThan(0); 45 | expect(matrix[0].elements[0].duration.value).toBeGreaterThan(0); 46 | }); 47 | -------------------------------------------------------------------------------- /e2e/elevation.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { elevation } from "../src/elevation"; 18 | 19 | test("elevation should return correct result", async () => { 20 | const location = { lat: 10, lng: 20 }; 21 | 22 | const params = { 23 | locations: [location, location], 24 | key: process.env.GOOGLE_MAPS_API_KEY, 25 | }; 26 | 27 | const r = await elevation({ params: params }); 28 | 29 | expect(r.data.results.length).toEqual(2); 30 | }); 31 | 32 | test("elevation should return correct result with path params", async () => { 33 | const path = [ 34 | { lat: 35, lng: -110 }, 35 | { lat: 45, lng: -110 }, 36 | ]; 37 | 38 | const params = { 39 | path: path, 40 | samples: 10, 41 | key: process.env.GOOGLE_MAPS_API_KEY, 42 | }; 43 | 44 | const r = await elevation({ params: params }); 45 | 46 | expect(r.data.results.length).toEqual(10); 47 | }); 48 | -------------------------------------------------------------------------------- /e2e/geocode/geocode.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { geocode } from "../../src/geocode/geocode"; 18 | 19 | test("geocode should call axios correctly", async () => { 20 | const params = { 21 | address: "Seattle", 22 | components: { country: "us" }, 23 | bounds: { 24 | northeast: { lat: 50, lng: -110 }, 25 | southwest: { lat: 35, lng: -130 }, 26 | }, 27 | key: process.env.GOOGLE_MAPS_API_KEY, 28 | }; 29 | 30 | const r = await geocode({ params: params }); 31 | expect(r.data.results[0].place_id).toBeDefined(); 32 | }); 33 | -------------------------------------------------------------------------------- /e2e/geocode/reversegeocode.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { reverseGeocode } from "../../src/geocode/reversegeocode"; 18 | 19 | test("reverseGeocode should return correct response", async () => { 20 | const params = { 21 | latlng: { 22 | lat: 60.168997, 23 | lng: 24.9433353, 24 | }, 25 | key: process.env.GOOGLE_MAPS_API_KEY, 26 | }; 27 | const r = await reverseGeocode({ params: params }); 28 | expect(r.data.results.length).toBeTruthy(); 29 | }); 30 | 31 | test("reverseGeocode should return correct response using place_id", async () => { 32 | const params = { 33 | place_id: "ChIJKxDbe_lYwokRVf__s8CPn-o", 34 | key: process.env.GOOGLE_MAPS_API_KEY, 35 | }; 36 | const r = await reverseGeocode({ params: params }); 37 | expect(r.data.results.length).toBeTruthy(); 38 | }); 39 | 40 | 41 | test("reverseGeocode should return correct response when address descriptors are enabled", async () => { 42 | const params = { 43 | latlng: { 44 | lat: 28.650080, 45 | lng: 77.233172, 46 | }, 47 | enable_address_descriptor: true, 48 | key: process.env.GOOGLE_MAPS_API_KEY, 49 | }; 50 | const r = await reverseGeocode({ params: params }); 51 | console.log("Response data:", r.data); 52 | console.log("Address descriptor:", r.data.address_descriptor); 53 | expect(r.data.address_descriptor.landmarks.length > 0).toBeTruthy(); 54 | }); -------------------------------------------------------------------------------- /e2e/geolocation.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { geolocate } from "../src/geolocate"; 18 | 19 | test("geolocate should call axios correctly", async () => { 20 | const params = { key: process.env.GOOGLE_MAPS_API_KEY }; 21 | const data = { considerIp: false }; 22 | 23 | // geolocation depends upon the ip address of the client. 24 | // it is not deterministic and will return 404 for some IPs. 25 | try { 26 | const r = await geolocate({ params, data }); 27 | expect(r.data).toHaveProperty("location"); 28 | } catch (e) { 29 | expect(e.response.status).toEqual(404); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /e2e/places/autocomplete.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { placeAutocomplete } from "../../src/places/autocomplete"; 18 | 19 | test("autocomplete should return correct result", async () => { 20 | const params = { 21 | input: "Seattle, WA", 22 | sessiontoken: "123", 23 | key: process.env.GOOGLE_MAPS_API_KEY, 24 | }; 25 | const r = await placeAutocomplete({ params: params }); 26 | expect(r.data.predictions.length).toBeTruthy(); 27 | expect(Object.keys(r.data.predictions[0]).sort()).toMatchInlineSnapshot(` 28 | [ 29 | "description", 30 | "matched_substrings", 31 | "place_id", 32 | "reference", 33 | "structured_formatting", 34 | "terms", 35 | "types", 36 | ] 37 | `); 38 | }); 39 | -------------------------------------------------------------------------------- /e2e/places/details.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { placeDetails } from "../../src/places/details"; 18 | 19 | test("details should return correct result", async () => { 20 | const params = { 21 | place_id: "ChIJKxDbe_lYwokRVf__s8CPn-o", 22 | key: process.env.GOOGLE_MAPS_API_KEY, 23 | fields: ["place_id", "name", "formatted_address"], 24 | }; 25 | 26 | const r = await placeDetails({ params: params }); 27 | expect(r.data.result.place_id).toBeDefined(); 28 | expect(Object.keys(r.data.result).sort()).toEqual(params.fields.sort()); 29 | }); 30 | 31 | test("details should return all fields", async () => { 32 | const params = { 33 | place_id: "ChIJKxDbe_lYwokRVf__s8CPn-o", 34 | key: process.env.GOOGLE_MAPS_API_KEY, 35 | }; 36 | 37 | const r = await placeDetails({ params: params }); 38 | 39 | expect(r.data.result.address_components).toBeDefined(); 40 | expect(r.data.result.adr_address).toBeDefined(); 41 | expect(r.data.result.business_status).toBeDefined(); 42 | expect(r.data.result.formatted_address).toBeDefined(); 43 | expect(r.data.result.formatted_phone_number).toBeDefined(); 44 | expect(r.data.result.geometry).toBeDefined(); 45 | expect(r.data.result.icon).toBeDefined(); 46 | expect(r.data.result.international_phone_number).toBeDefined(); 47 | expect(r.data.result.name).toBeDefined(); 48 | expect(r.data.result.photos).toBeDefined(); 49 | expect(r.data.result.place_id).toBeDefined(); 50 | expect(r.data.result.plus_code).toBeDefined(); 51 | expect(r.data.result.rating).toBeDefined(); 52 | expect(r.data.result.reviews).toBeDefined(); 53 | expect(r.data.result.types).toBeDefined(); 54 | expect(r.data.result.url).toBeDefined(); 55 | expect(r.data.result.user_ratings_total).toBeDefined(); 56 | expect(r.data.result.utc_offset).toBeDefined(); 57 | expect(r.data.result.vicinity).toBeDefined(); 58 | expect(r.data.result.website).toBeDefined(); 59 | 60 | expect(r.data.result.reviews[0].author_name).toBeDefined(); 61 | expect(r.data.result.reviews[0].author_url).toBeDefined(); 62 | expect(r.data.result.reviews[0].language).toBeDefined(); 63 | expect(r.data.result.reviews[0].profile_photo_url).toBeDefined(); 64 | expect(r.data.result.reviews[0].rating).toBeDefined(); 65 | expect(r.data.result.reviews[0].relative_time_description).toBeDefined(); 66 | expect(r.data.result.reviews[0].text).toBeDefined(); 67 | expect(r.data.result.reviews[0].time).toBeDefined(); 68 | 69 | expect(r.data.result.photos[0].height).toBeDefined(); 70 | expect(r.data.result.photos[0].html_attributions).toBeDefined(); 71 | expect(r.data.result.photos[0].photo_reference).toBeDefined(); 72 | expect(r.data.result.photos[0].width).toBeDefined(); 73 | 74 | expect(Object.keys(r.data.result).sort()).toMatchInlineSnapshot(` 75 | [ 76 | "address_components", 77 | "adr_address", 78 | "business_status", 79 | "current_opening_hours", 80 | "editorial_summary", 81 | "formatted_address", 82 | "formatted_phone_number", 83 | "geometry", 84 | "icon", 85 | "icon_background_color", 86 | "icon_mask_base_uri", 87 | "international_phone_number", 88 | "name", 89 | "opening_hours", 90 | "photos", 91 | "place_id", 92 | "plus_code", 93 | "rating", 94 | "reference", 95 | "reviews", 96 | "types", 97 | "url", 98 | "user_ratings_total", 99 | "utc_offset", 100 | "vicinity", 101 | "website", 102 | "wheelchair_accessible_entrance", 103 | ] 104 | `); 105 | }); 106 | -------------------------------------------------------------------------------- /e2e/places/findplacefromtext.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { PlaceInputType, Status } from "../../src/common"; 18 | 19 | import { findPlaceFromText } from "../../src/places/findplacefromtext"; 20 | 21 | test("findPlaceFromText should return correct result", async () => { 22 | const params = { 23 | input: "Museum of Modern Art", 24 | inputtype: PlaceInputType.textQuery, 25 | key: process.env.GOOGLE_MAPS_API_KEY, 26 | fields: ["place_id", "name", "formatted_address"], 27 | }; 28 | 29 | const r = await findPlaceFromText({ params: params }); 30 | 31 | expect(r.data.status).toEqual(Status.OK); 32 | expect(Object.keys(r.data.candidates[0]).sort()).toEqual( 33 | params.fields.sort() 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /e2e/places/photo.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { placePhoto } from "../../src/places/photo"; 18 | import { placeDetails } from "../../src/places/details"; 19 | 20 | test("photo should return correct result", async () => { 21 | const placeDetailsResponse = await placeDetails({ 22 | params: { 23 | // The Museum of Modern Art, 11 W 53rd St, New York 24 | place_id: "ChIJKxDbe_lYwokRVf__s8CPn-o", 25 | key: process.env.GOOGLE_MAPS_API_KEY, 26 | fields: ["place_id", "photo"], 27 | }, 28 | }); 29 | 30 | const [photo] = placeDetailsResponse.data.result.photos; 31 | const { photo_reference: photoreference } = photo; 32 | 33 | const params = { 34 | photoreference, 35 | maxwidth: 1000, 36 | maxheight: 1000, 37 | key: process.env.GOOGLE_MAPS_API_KEY, 38 | }; 39 | const r = await placePhoto({ params: params, responseType: "arraybuffer" }); 40 | 41 | expect(r.status).toEqual(200); 42 | expect(r.headers["content-type"]).toEqual("image/jpeg"); 43 | }); 44 | -------------------------------------------------------------------------------- /e2e/places/placesnearby.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { placesNearby } from "../../src/places/placesnearby"; 18 | 19 | test("placesNearby should return correct result", async () => { 20 | const params = { 21 | location: { lat: 35, lng: -110 }, 22 | radius: 500000, 23 | key: process.env.GOOGLE_MAPS_API_KEY, 24 | }; 25 | const r = await placesNearby({ params: params }); 26 | expect(r.data.results.length).toBeTruthy(); 27 | expect(Object.keys(r.data.results[0]).sort()).toMatchInlineSnapshot(` 28 | [ 29 | "geometry", 30 | "icon", 31 | "icon_background_color", 32 | "icon_mask_base_uri", 33 | "name", 34 | "photos", 35 | "place_id", 36 | "reference", 37 | "scope", 38 | "types", 39 | "vicinity", 40 | ] 41 | `); 42 | }); 43 | -------------------------------------------------------------------------------- /e2e/places/queryautocomplete.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { placeQueryAutocomplete } from "../../src/places/queryautocomplete"; 18 | 19 | test("query autocomplete should return correct result", async () => { 20 | const params = { 21 | input: "Seattle", 22 | sessiontoken: "asdf", 23 | key: process.env.GOOGLE_MAPS_API_KEY, 24 | }; 25 | 26 | const r = await placeQueryAutocomplete({ params: params }); 27 | expect(r.data.predictions.length).toBeTruthy(); 28 | expect(r.data.predictions[0].terms.length).toBeTruthy(); 29 | expect(Object.keys(r.data.predictions[0]).sort()).toMatchInlineSnapshot(` 30 | [ 31 | "description", 32 | "matched_substrings", 33 | "place_id", 34 | "reference", 35 | "structured_formatting", 36 | "terms", 37 | "types", 38 | ] 39 | `); 40 | }); 41 | -------------------------------------------------------------------------------- /e2e/places/textsearch.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { textSearch } from "../../src/places/textsearch"; 18 | 19 | test("textsearch should return correct result", async () => { 20 | const params = { 21 | query: "Seattle", 22 | key: process.env.GOOGLE_MAPS_API_KEY, 23 | }; 24 | 25 | const r = await textSearch({ params: params }); 26 | 27 | expect(r.data.results.length).toBeTruthy(); 28 | expect(Object.keys(r.data.results[0]).sort()).toMatchInlineSnapshot(` 29 | [ 30 | "formatted_address", 31 | "geometry", 32 | "icon", 33 | "icon_background_color", 34 | "icon_mask_base_uri", 35 | "name", 36 | "photos", 37 | "place_id", 38 | "reference", 39 | "types", 40 | ] 41 | `); 42 | }); 43 | -------------------------------------------------------------------------------- /e2e/roads/nearestroads.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { nearestRoads } from "../../src/roads/nearestroads"; 18 | import { LatLng } from "../../src/common"; 19 | 20 | test("nearestRoads should return correct response", async () => { 21 | const params = { 22 | points: [[60.17088, 24.942795] as LatLng], 23 | key: process.env.GOOGLE_MAPS_API_KEY, 24 | }; 25 | 26 | const r = await nearestRoads({ params: params }); 27 | expect(r.data.snappedPoints.length).toEqual(1); 28 | }); 29 | -------------------------------------------------------------------------------- /e2e/roads/snaptoroads.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { snapToRoads } from "../../src/roads/snaptoroads"; 18 | import { LatLng } from "../../src/common"; 19 | 20 | test("snapToRoads should have corect result", async () => { 21 | const params = { 22 | path: [ 23 | [60.17088, 24.942795] as LatLng, 24 | [60.170879, 24.942796] as LatLng, 25 | [60.170877, 24.942796] as LatLng, 26 | ], 27 | interpolate: false, 28 | key: process.env.GOOGLE_MAPS_API_KEY, 29 | }; 30 | 31 | const r = await snapToRoads({ params: params }); 32 | 33 | expect(r.data.snappedPoints.length).toEqual(3); 34 | }); 35 | -------------------------------------------------------------------------------- /e2e/timezone.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { Language, Status } from "../src/common"; 18 | 19 | import { timezone } from "../src/timezone"; 20 | 21 | test("elevation should get an ok response", async () => { 22 | const params = { 23 | location: "30, 50", 24 | timestamp: new Date(), 25 | language: Language.en, 26 | key: process.env.GOOGLE_MAPS_API_KEY, 27 | }; 28 | const r = await timezone({ params: params }); 29 | 30 | expect(r.data.status).toEqual(Status.OK); 31 | }); 32 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | module.exports = { 18 | roots: ["/src/", "/e2e/"], 19 | preset: "ts-jest/presets/js-with-ts", 20 | testEnvironment: "node", 21 | // some dependencies are esm and need to be compiled to work in jest 22 | transformIgnorePatterns: [ 23 | "/node_modules/(?!(axios|retry-axios|query-string|decode-uri-component|split-on-first|filter-obj)/)", 24 | ], 25 | collectCoverageFrom: ["src/**/([a-zA-Z_]*).{js,ts}", "!**/*.test.{js,ts}"], 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@googlemaps/google-maps-services-js", 3 | "version": "3.4.1", 4 | "description": "Node.js client library for Google Maps API Web Services", 5 | "keywords": [ 6 | "google", 7 | "maps", 8 | "googlemaps", 9 | "geo", 10 | "geocode", 11 | "timezone", 12 | "api", 13 | "client", 14 | "roads", 15 | "directions", 16 | "navigation" 17 | ], 18 | "homepage": "https://github.com/googlemaps/google-maps-services-js", 19 | "bugs": { 20 | "url": "https://github.com/googlemaps/google-maps-services-js/issues" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/googlemaps/google-maps-services-js.git" 25 | }, 26 | "license": "Apache-2.0", 27 | "author": "Google Inc.", 28 | "contributors": [ 29 | { 30 | "name": "Justin Poehnelt", 31 | "email": "jpoehnelt@google.com" 32 | } 33 | ], 34 | "main": "./dist/index.js", 35 | "files": [ 36 | "dist", 37 | "src" 38 | ], 39 | "scripts": { 40 | "build": "tsc", 41 | "docs": "rm -rf docs/ && typedoc src/index.ts", 42 | "prepack": "npm run build", 43 | "pretest": "npm run build", 44 | "test": "jest --runInBand --collectCoverage ./src/* && npm run test:loading", 45 | "test:loading": "./test-module-loading.sh", 46 | "test:e2e": "jest --runInBand ./e2e/*", 47 | "test:all": "jest --runInBand", 48 | "format": "eslint . --fix", 49 | "lint": "eslint ." 50 | }, 51 | "dependencies": { 52 | "@googlemaps/url-signature": "^1.0.4", 53 | "agentkeepalive": "^4.1.0", 54 | "axios": "^1.5.1", 55 | "query-string": "<8.x", 56 | "retry-axios": "<3.x" 57 | }, 58 | "devDependencies": { 59 | "@types/jest": "^29.5.5", 60 | "@types/node": "^22.1.0", 61 | "@typescript-eslint/eslint-plugin": "^7.0.0", 62 | "@typescript-eslint/parser": "^6.7.4", 63 | "eslint": "^8.51.0", 64 | "eslint-config-prettier": "^10.0.1", 65 | "eslint-plugin-jest": "^28.5.0", 66 | "eslint-plugin-prettier": "^5.0.0", 67 | "jest": "^29.7.0", 68 | "nock": "^14.0.2", 69 | "prettier": "^3.0.3", 70 | "ts-jest": "^29.1.1", 71 | "typedoc": "^0.28.1", 72 | "typescript": "^5.2.2" 73 | }, 74 | "publishConfig": { 75 | "registry": "https://wombat-dressing-room.appspot.com", 76 | "access": "public" 77 | }, 78 | "prettier": { 79 | "trailingComma": "es5" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/adapter.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import nock from "nock"; 18 | 19 | import { AxiosResponse } from "axios"; 20 | import { Client } from "./client"; 21 | import { Status } from "./common"; 22 | import { statusToCode } from "./adapter"; 23 | 24 | beforeAll(() => { 25 | nock.disableNetConnect(); 26 | }); 27 | 28 | afterAll(() => { 29 | nock.cleanAll(); 30 | nock.enableNetConnect(); 31 | }); 32 | 33 | test("httpadapter rejects Status.NOT_FOUND", async () => { 34 | nock("https://maps.googleapis.com") 35 | .get( 36 | "/maps/api/place/details/json?fields=place_id,name&key=foo&place_id=notarealid" 37 | ) 38 | .reply(200, JSON.stringify({ status: Status.NOT_FOUND }), { 39 | "Content-Type": "application/json", 40 | }); 41 | 42 | const client = new Client(); 43 | 44 | const params = { 45 | place_id: "notarealid", 46 | key: "foo", 47 | fields: ["place_id", "name"], 48 | }; 49 | 50 | await expect(client.placeDetails({ params: params })).rejects.toEqual( 51 | Error("Request failed with status code 404") 52 | ); 53 | }); 54 | 55 | test("httpadapter resolves Status.OK", async () => { 56 | const response = { status: Status.OK }; 57 | 58 | nock("https://maps.googleapis.com") 59 | .get( 60 | "/maps/api/place/details/json?fields=place_id,name&key=foo&place_id=notarealid" 61 | ) 62 | .reply(200, JSON.stringify(response), { 63 | "Content-Type": "application/json", 64 | }); 65 | 66 | const client = new Client(); 67 | 68 | const params = { 69 | place_id: "notarealid", 70 | key: "foo", 71 | fields: ["place_id", "name"], 72 | }; 73 | 74 | const r: AxiosResponse = await client.placeDetails({ params: params }); 75 | expect(r.data).toEqual(response); 76 | }); 77 | 78 | test("statusToCode returns correct value", () => { 79 | expect(statusToCode(Status.OK)).toEqual(200); 80 | expect(statusToCode(Status.ZERO_RESULTS)).toEqual(200); 81 | expect(statusToCode(Status.INVALID_REQUEST)).toEqual(400); 82 | expect(statusToCode(Status.MAX_ROUTE_LENGTH_EXCEEDED)).toEqual(400); 83 | expect(statusToCode(Status.MAX_WAYPOINTS_EXCEEDED)).toEqual(400); 84 | expect(statusToCode(Status.REQUEST_DENIED)).toEqual(403); 85 | expect(statusToCode(Status.NOT_FOUND)).toEqual(404); 86 | expect(statusToCode(Status.OVER_DAILY_LIMIT)).toEqual(429); 87 | expect(statusToCode(Status.OVER_QUERY_LIMIT)).toEqual(429); 88 | expect(statusToCode(Status.UNKNOWN_ERROR)).toEqual(500); 89 | expect(statusToCode("foo" as Status)).toEqual(200); 90 | }); 91 | -------------------------------------------------------------------------------- /src/adapter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { Status } from "./common"; 18 | 19 | import axios from "axios"; 20 | import type { AxiosResponse } from "axios"; 21 | 22 | export function statusToCode(status: Status): number { 23 | switch (status) { 24 | case Status.OK: 25 | case Status.ZERO_RESULTS: { 26 | return 200; 27 | } 28 | case Status.INVALID_REQUEST: 29 | case Status.MAX_ROUTE_LENGTH_EXCEEDED: 30 | case Status.MAX_WAYPOINTS_EXCEEDED: { 31 | return 400; 32 | } 33 | case Status.REQUEST_DENIED: { 34 | return 403; 35 | } 36 | case Status.NOT_FOUND: { 37 | return 404; 38 | } 39 | case Status.OVER_DAILY_LIMIT: 40 | case Status.OVER_QUERY_LIMIT: { 41 | return 429; 42 | } 43 | case Status.UNKNOWN_ERROR: { 44 | return 500; 45 | } 46 | default: { 47 | return 200; 48 | } 49 | } 50 | } 51 | 52 | function settle(resolve, reject, response) { 53 | const validateStatus = response.config.validateStatus; 54 | if (!response.status || !validateStatus || validateStatus(response.status)) { 55 | resolve(response); 56 | } else { 57 | reject( 58 | new axios.AxiosError( 59 | "Request failed with status code " + response.status, 60 | [axios.AxiosError.ERR_BAD_REQUEST, axios.AxiosError.ERR_BAD_RESPONSE][ 61 | Math.floor(response.status / 100) - 4 62 | ], 63 | response.config, 64 | response.request, 65 | response 66 | ) 67 | ); 68 | } 69 | } 70 | 71 | export const customAdapter = axios.getAdapter((config) => { 72 | const httpAdapter = axios.getAdapter("http"); 73 | 74 | return new Promise((resolve, reject) => { 75 | httpAdapter(config) 76 | .then((r: AxiosResponse) => { 77 | // unfortunately data is transformed after the adapter 78 | let data = r.data; 79 | if (config.transformResponse) { 80 | const t = Array.isArray(config.transformResponse) 81 | ? config.transformResponse 82 | : [config.transformResponse]; 83 | for (const fn of t) { 84 | data = fn.call(config, data, r.headers, r.status); 85 | } 86 | } 87 | 88 | if (r.status === 200 && data.status) { 89 | r.status = statusToCode(data.status); 90 | } 91 | 92 | settle(resolve, reject, r); 93 | }) 94 | .catch(reject); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/client.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | Client, 19 | userAgent, 20 | acceptEncoding, 21 | DirectionsRequest, 22 | DistanceMatrixRequest, 23 | ElevationRequest, 24 | TimeZoneRequest, 25 | GeolocateRequest, 26 | GeocodeRequest, 27 | ReverseGeocodeRequest, 28 | PlaceAutocompleteRequest, 29 | PlaceDetailsRequest, 30 | FindPlaceFromTextRequest, 31 | PlacePhotoRequest, 32 | PlacesNearbyRequest, 33 | PlaceQueryAutocompleteRequest, 34 | TextSearchRequest, 35 | NearestRoadsRequest, 36 | SnapToRoadsRequest, 37 | X_GOOG_MAPS_EXPERIENCE_ID, 38 | defaultAxiosInstance, 39 | } from "./client"; 40 | 41 | import axios from "axios"; 42 | 43 | test("client can be instantiated", () => { 44 | const client = new Client({}); 45 | expect(client["axiosInstance"]).toBeDefined(); 46 | }); 47 | 48 | test("client can be instantiated with axiosInstance", () => { 49 | const client = new Client({ axiosInstance: axios.create({}) }); 50 | expect(client["axiosInstance"]).toBeDefined(); 51 | }); 52 | 53 | test("client can be instantiated with axiosInstance has correct defaults", () => { 54 | const client = new Client({ axiosInstance: axios.create({}) }); 55 | expect(client["axiosInstance"].defaults.headers["User-Agent"]).toEqual( 56 | userAgent 57 | ); 58 | expect(client["axiosInstance"].defaults.headers["Accept-Encoding"]).toBe( 59 | acceptEncoding 60 | ); 61 | expect(client["axiosInstance"].defaults.timeout).toEqual( 62 | axios.defaults.timeout 63 | ); 64 | }); 65 | 66 | test("client instantiated with custom instance and config throws error", () => { 67 | expect(() => { 68 | new Client({ 69 | axiosInstance: defaultAxiosInstance, 70 | config: { timeout: 10000 }, 71 | }); 72 | }).toThrowError(); 73 | }); 74 | 75 | test("client can be instantiated with header options", () => { 76 | const client = new Client({ config: { headers: { "x-foo": "bar" } } }); 77 | expect(client["axiosInstance"]).toBeDefined(); 78 | expect(client["axiosInstance"].defaults.headers["x-foo"]).toEqual("bar"); 79 | expect(client["axiosInstance"].defaults.headers["User-Agent"]).toEqual( 80 | userAgent 81 | ); 82 | expect(client["axiosInstance"].defaults.headers["Accept-Encoding"]).toBe( 83 | acceptEncoding 84 | ); 85 | }); 86 | 87 | test("client can be override Accept-Encoding with header options", () => { 88 | const client = new Client({ 89 | config: { headers: { "x-foo": "bar", "Accept-Encoding": "identity" } }, 90 | }); 91 | expect(client["axiosInstance"]).toBeDefined(); 92 | expect(client["axiosInstance"].defaults.headers["x-foo"]).toEqual("bar"); 93 | expect(client["axiosInstance"].defaults.headers["User-Agent"]).toEqual( 94 | userAgent 95 | ); 96 | expect(client["axiosInstance"].defaults.headers["Accept-Encoding"]).toBe( 97 | "identity" 98 | ); 99 | }); 100 | 101 | test("client can be instantiated without header options", () => { 102 | const client = new Client({ config: { timeout: 1234 } }); 103 | expect(client["axiosInstance"]).toBeDefined(); 104 | expect(client["axiosInstance"].defaults.timeout).toEqual(1234); 105 | expect(client["axiosInstance"].defaults.headers["User-Agent"]).toEqual( 106 | userAgent 107 | ); 108 | expect(client["axiosInstance"].defaults.headers["Accept-Encoding"]).toBe( 109 | acceptEncoding 110 | ); 111 | }); 112 | 113 | test("client can be instantiated with experienceId", () => { 114 | const client = new Client({ experienceId: ["foo", "bar"] }); 115 | expect( 116 | client["axiosInstance"].defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID] 117 | ).toEqual("foo,bar"); 118 | }); 119 | 120 | test("getExperienceId returns correct value", () => { 121 | const ids = ["foo", "bar"]; 122 | const client = new Client({ experienceId: ids }); 123 | expect(client.getExperienceId()).toEqual(ids); 124 | }); 125 | 126 | test("clearExperienceIdHeader removes value and header from defaults", () => { 127 | const client = new Client({}); 128 | client["axiosInstance"].defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID] = "foo"; 129 | client.clearExperienceId(); 130 | expect( 131 | client["axiosInstance"].defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID] 132 | ).toBeUndefined(); 133 | expect(client["experienceId"]).toBeNull(); 134 | }); 135 | 136 | test("setExperienceId sets value and header", () => { 137 | const client = new Client({}); 138 | const ids = ["foo", "bar"]; 139 | client.setExperienceId(...ids); 140 | expect( 141 | client["axiosInstance"].defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID] 142 | ).toEqual("foo,bar"); 143 | expect(client["experienceId"]).toEqual(ids); 144 | }); 145 | 146 | describe("client wraps all functions correctly", () => { 147 | const client = new Client({}); 148 | 149 | afterEach(() => { 150 | jest.clearAllMocks(); 151 | }); 152 | 153 | test("client wraps directions correctly", () => { 154 | const directions = require("./directions"); 155 | const mock = (directions.directions = jest.fn()); 156 | client.directions({} as DirectionsRequest); 157 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 158 | }); 159 | 160 | test("client wraps distancematrix correctly", () => { 161 | const distance = require("./distance"); 162 | const mock = (distance.distancematrix = jest.fn()); 163 | client.distancematrix({} as DistanceMatrixRequest); 164 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 165 | }); 166 | 167 | test("client wraps elevation correctly", () => { 168 | const elevation = require("./elevation"); 169 | const mock = (elevation.elevation = jest.fn()); 170 | client.elevation({} as ElevationRequest); 171 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 172 | }); 173 | 174 | test("client wraps timezone correctly", () => { 175 | const timezone = require("./timezone"); 176 | const mock = (timezone.timezone = jest.fn()); 177 | client.timezone({} as TimeZoneRequest); 178 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 179 | }); 180 | 181 | test("client wraps geolocate correctly", () => { 182 | const geolocate = require("./geolocate"); 183 | const mock = (geolocate.geolocate = jest.fn()); 184 | client.geolocate({} as GeolocateRequest); 185 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 186 | }); 187 | 188 | test("client wraps geocode correctly", () => { 189 | const geocode = require("./geocode/geocode"); 190 | const mock = (geocode.geocode = jest.fn()); 191 | client.geocode({} as GeocodeRequest); 192 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 193 | }); 194 | 195 | test("client wraps reverseGeocode correctly", () => { 196 | const reverseGeocode = require("./geocode/reversegeocode"); 197 | const mock = (reverseGeocode.reverseGeocode = jest.fn()); 198 | client.reverseGeocode({} as ReverseGeocodeRequest); 199 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 200 | }); 201 | 202 | test("client wraps placeAutocomplete correctly", () => { 203 | const placeAutocomplete = require("./places/autocomplete"); 204 | const mock = (placeAutocomplete.placeAutocomplete = jest.fn()); 205 | client.placeAutocomplete({} as PlaceAutocompleteRequest); 206 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 207 | }); 208 | 209 | test("client wraps placeDetails correctly", () => { 210 | const placeDetails = require("./places/details"); 211 | const mock = (placeDetails.placeDetails = jest.fn()); 212 | client.placeDetails({} as PlaceDetailsRequest); 213 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 214 | }); 215 | 216 | test("client wraps findPlaceFromText correctly", () => { 217 | const findPlaceFromText = require("./places/findplacefromtext"); 218 | const mock = (findPlaceFromText.findPlaceFromText = jest.fn()); 219 | client.findPlaceFromText({} as FindPlaceFromTextRequest); 220 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 221 | }); 222 | 223 | test("client wraps placePhoto correctly", () => { 224 | const placePhoto = require("./places/photo"); 225 | const mock = (placePhoto.placePhoto = jest.fn()); 226 | client.placePhoto({} as PlacePhotoRequest); 227 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 228 | }); 229 | 230 | test("client wraps placesNearby correctly", () => { 231 | const placesNearby = require("./places/placesnearby"); 232 | const mock = (placesNearby.placesNearby = jest.fn()); 233 | client.placesNearby({} as PlacesNearbyRequest); 234 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 235 | }); 236 | 237 | test("client wraps placeQueryAutocomplete correctly", () => { 238 | const placeQueryAutocomplete = require("./places/queryautocomplete"); 239 | const mock = (placeQueryAutocomplete.placeQueryAutocomplete = jest.fn()); 240 | client.placeQueryAutocomplete({} as PlaceQueryAutocompleteRequest); 241 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 242 | }); 243 | 244 | test("client wraps textSearch correctly", () => { 245 | const textSearch = require("./places/textsearch"); 246 | const mock = (textSearch.textSearch = jest.fn()); 247 | client.textSearch({} as TextSearchRequest); 248 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 249 | }); 250 | 251 | test("client wraps nearestRoads correctly", () => { 252 | const nearestRoads = require("./roads/nearestroads"); 253 | const mock = (nearestRoads.nearestRoads = jest.fn()); 254 | client.nearestRoads({} as NearestRoadsRequest); 255 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 256 | }); 257 | 258 | test("client wraps snapToRoads correctly", () => { 259 | const snapToRoads = require("./roads/snaptoroads"); 260 | const mock = (snapToRoads.snapToRoads = jest.fn()); 261 | client.snapToRoads({} as SnapToRoadsRequest); 262 | expect(mock).toBeCalledWith({}, client["axiosInstance"]); 263 | }); 264 | }); 265 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import * as rax from "retry-axios"; 18 | 19 | import { 20 | DirectionsRequest, 21 | DirectionsResponse, 22 | directions, 23 | } from "./directions"; 24 | import { 25 | DistanceMatrixRequest, 26 | DistanceMatrixResponse, 27 | distancematrix, 28 | } from "./distance"; 29 | import { ElevationRequest, ElevationResponse, elevation } from "./elevation"; 30 | import { 31 | FindPlaceFromTextRequest, 32 | FindPlaceFromTextResponse, 33 | findPlaceFromText, 34 | } from "./places/findplacefromtext"; 35 | import { GeocodeRequest, GeocodeResponse, geocode } from "./geocode/geocode"; 36 | import { GeolocateRequest, GeolocateResponse, geolocate } from "./geolocate"; 37 | import { 38 | NearestRoadsRequest, 39 | NearestRoadsResponse, 40 | nearestRoads, 41 | } from "./roads/nearestroads"; 42 | import { 43 | PlaceAutocompleteRequest, 44 | PlaceAutocompleteResponse, 45 | placeAutocomplete, 46 | } from "./places/autocomplete"; 47 | import { 48 | PlaceDetailsRequest, 49 | PlaceDetailsResponse, 50 | placeDetails, 51 | } from "./places/details"; 52 | import { 53 | PlacePhotoRequest, 54 | PlacePhotoResponse, 55 | placePhoto, 56 | } from "./places/photo"; 57 | import { 58 | PlaceQueryAutocompleteRequest, 59 | PlaceQueryAutocompleteResponse, 60 | placeQueryAutocomplete, 61 | } from "./places/queryautocomplete"; 62 | import { 63 | PlacesNearbyRequest, 64 | PlacesNearbyResponse, 65 | placesNearby, 66 | } from "./places/placesnearby"; 67 | import { 68 | ReverseGeocodeRequest, 69 | ReverseGeocodeResponse, 70 | reverseGeocode, 71 | } from "./geocode/reversegeocode"; 72 | import { 73 | SnapToRoadsRequest, 74 | SnapToRoadsResponse, 75 | snapToRoads, 76 | } from "./roads/snaptoroads"; 77 | import { 78 | TextSearchRequest, 79 | TextSearchResponse, 80 | textSearch, 81 | } from "./places/textsearch"; 82 | import { TimeZoneRequest, TimeZoneResponse, timezone } from "./timezone"; 83 | import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; 84 | 85 | import { HttpsAgent } from "agentkeepalive"; 86 | import { customAdapter } from "./adapter"; 87 | 88 | // Cannot be `import` as it's not under TS root dir 89 | export const version = require("../package.json").version; 90 | export const defaultHttpsAgent = new HttpsAgent({ keepAlive: true }); 91 | export const defaultTimeout = 10000; 92 | export const userAgent = `google-maps-services-node-${version}`; 93 | export const acceptEncoding = "gzip"; 94 | export const X_GOOG_MAPS_EXPERIENCE_ID = "X-GOOG-MAPS-EXPERIENCE-ID"; 95 | 96 | const defaultConfig: AxiosRequestConfig = { 97 | timeout: defaultTimeout, 98 | httpsAgent: defaultHttpsAgent, 99 | adapter: customAdapter, 100 | headers: { 101 | "User-Agent": userAgent, 102 | "Accept-Encoding": acceptEncoding, 103 | }, 104 | }; 105 | 106 | export const defaultAxiosInstance = axios.create(defaultConfig); 107 | rax.attach(defaultAxiosInstance); 108 | 109 | export type Config = { 110 | raxConfig?: rax.RetryConfig; 111 | } & AxiosRequestConfig; 112 | 113 | export interface ClientOptions { 114 | /** AxiosInstance to be used by client. Provide one of axiosInstance or config. */ 115 | axiosInstance?: AxiosInstance; 116 | /** Config used to create AxiosInstance. Provide one of axiosInstance or config. */ 117 | config?: Config; 118 | experienceId?: string[]; 119 | } 120 | /** 121 | * Client is a light wrapper around API methods providing shared configuration for Axios 122 | * settings such as retry logic using the default retry-axios settings and gzip encoding. 123 | * 124 | * ### Instantiate with defaults 125 | * ``` 126 | * const client = Client() 127 | * ``` 128 | * 129 | * ### Instantiate with config 130 | * ``` 131 | * const client = Client({config}) 132 | * ``` 133 | * 134 | * ### Instantiate with axiosInstance **Advanced** 135 | * ``` 136 | * const axiosInstance = axios.create(config) 137 | * const client = Client({axiosInstance}) 138 | * ``` 139 | */ 140 | export class Client { 141 | private readonly axiosInstance: AxiosInstance; 142 | private experienceId: string[]; 143 | 144 | constructor({ axiosInstance, config, experienceId }: ClientOptions = {}) { 145 | if (axiosInstance && config) { 146 | throw new Error("Provide one of axiosInstance or config."); 147 | } 148 | 149 | if (axiosInstance) { 150 | this.axiosInstance = axiosInstance; 151 | this.axiosInstance.defaults.headers = { 152 | ...defaultConfig.headers, 153 | ...this.axiosInstance.defaults.headers, 154 | }; 155 | } else if (config) { 156 | config = { ...defaultConfig, ...config }; 157 | config.headers = { ...defaultConfig.headers, ...(config.headers || {}) }; 158 | this.axiosInstance = axios.create(config); 159 | rax.attach(this.axiosInstance); 160 | } else { 161 | this.axiosInstance = defaultAxiosInstance; 162 | } 163 | 164 | if (experienceId) { 165 | this.setExperienceId(...experienceId); 166 | } 167 | } 168 | 169 | setExperienceId(...ids: string[]) { 170 | this.experienceId = ids; 171 | this.axiosInstance.defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID] = 172 | ids.join(","); 173 | } 174 | 175 | clearExperienceId() { 176 | this.experienceId = null; 177 | delete this.axiosInstance.defaults.headers[X_GOOG_MAPS_EXPERIENCE_ID]; 178 | } 179 | 180 | getExperienceId(): string[] { 181 | return this.experienceId; 182 | } 183 | 184 | directions(request: DirectionsRequest): Promise { 185 | return directions(request, this.axiosInstance); 186 | } 187 | 188 | distancematrix( 189 | request: DistanceMatrixRequest 190 | ): Promise { 191 | return distancematrix(request, this.axiosInstance); 192 | } 193 | 194 | elevation(request: ElevationRequest): Promise { 195 | return elevation(request, this.axiosInstance); 196 | } 197 | 198 | timezone(request: TimeZoneRequest): Promise { 199 | return timezone(request, this.axiosInstance); 200 | } 201 | geolocate(request: GeolocateRequest): Promise { 202 | return geolocate(request, this.axiosInstance); 203 | } 204 | /** 205 | * An example use of this function. 206 | * 207 | * ```javascript 208 | * import { Client } from '@googlemaps/google-maps-services-js'; 209 | * 210 | * const args = { 211 | * params: { 212 | * key: '', 213 | * address: 'Perth 4WD & Commercial Centre', 214 | * } 215 | * }; 216 | * const client = new Client(); 217 | * client.geocode(args).then(gcResponse => { 218 | * const str = JSON.stringify(gcResponse.data.results[0]); 219 | * console.log(`First result is: ${str}`); 220 | * }); 221 | * ``` 222 | */ 223 | geocode(request: GeocodeRequest): Promise { 224 | return geocode(request, this.axiosInstance); 225 | } 226 | 227 | reverseGeocode( 228 | request: ReverseGeocodeRequest 229 | ): Promise { 230 | return reverseGeocode(request, this.axiosInstance); 231 | } 232 | 233 | placeAutocomplete( 234 | request: PlaceAutocompleteRequest 235 | ): Promise { 236 | return placeAutocomplete(request, this.axiosInstance); 237 | } 238 | 239 | placeDetails(request: PlaceDetailsRequest): Promise { 240 | return placeDetails(request, this.axiosInstance); 241 | } 242 | 243 | findPlaceFromText( 244 | request: FindPlaceFromTextRequest 245 | ): Promise { 246 | return findPlaceFromText(request, this.axiosInstance); 247 | } 248 | 249 | placePhoto(request: PlacePhotoRequest): Promise { 250 | return placePhoto(request, this.axiosInstance); 251 | } 252 | 253 | placesNearby(request: PlacesNearbyRequest): Promise { 254 | return placesNearby(request, this.axiosInstance); 255 | } 256 | 257 | placeQueryAutocomplete( 258 | request: PlaceQueryAutocompleteRequest 259 | ): Promise { 260 | return placeQueryAutocomplete(request, this.axiosInstance); 261 | } 262 | 263 | textSearch(request: TextSearchRequest): Promise { 264 | return textSearch(request, this.axiosInstance); 265 | } 266 | nearestRoads(request: NearestRoadsRequest): Promise { 267 | return nearestRoads(request, this.axiosInstance); 268 | } 269 | snapToRoads(request: SnapToRoadsRequest): Promise { 270 | return snapToRoads(request, this.axiosInstance); 271 | } 272 | } 273 | 274 | export { 275 | DirectionsRequest, 276 | DirectionsResponse, 277 | DistanceMatrixRequest, 278 | DistanceMatrixResponse, 279 | ElevationRequest, 280 | ElevationResponse, 281 | FindPlaceFromTextRequest, 282 | FindPlaceFromTextResponse, 283 | GeolocateRequest, 284 | GeocodeRequest, 285 | GeocodeResponse, 286 | GeolocateResponse, 287 | NearestRoadsRequest, 288 | NearestRoadsResponse, 289 | PlaceAutocompleteRequest, 290 | PlaceAutocompleteResponse, 291 | PlaceDetailsRequest, 292 | PlaceDetailsResponse, 293 | PlacePhotoRequest, 294 | PlacePhotoResponse, 295 | PlaceQueryAutocompleteRequest, 296 | PlaceQueryAutocompleteResponse, 297 | PlacesNearbyRequest, 298 | PlacesNearbyResponse, 299 | ReverseGeocodeRequest, 300 | ReverseGeocodeResponse, 301 | SnapToRoadsRequest, 302 | SnapToRoadsResponse, 303 | TextSearchRequest, 304 | TextSearchResponse, 305 | TimeZoneRequest, 306 | TimeZoneResponse, 307 | }; 308 | -------------------------------------------------------------------------------- /src/directions.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { directions, defaultParamsSerializer, defaultUrl } from "./directions"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("elevation should call axios correctly", () => { 29 | const params = { 30 | origin: "Seattle, WA", 31 | destination: "San Francisco, CA", 32 | key: "foo", 33 | }; 34 | 35 | directions({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | 46 | test("serializer should transform correctly", () => { 47 | const params = { 48 | origin: "Seattle, WA", 49 | destination: "San Francisco, CA", 50 | key: "foo", 51 | }; 52 | 53 | expect(defaultParamsSerializer(params)).toEqual( 54 | "destination=San%20Francisco%2C%20CA&key=foo&origin=Seattle%2C%20WA" 55 | ); 56 | }); 57 | -------------------------------------------------------------------------------- /src/distance.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | distancematrix, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./distance"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("elevation should call axios correctly", () => { 33 | const params = { 34 | origins: ["Seattle, WA"], 35 | destinations: ["San Francisco, CA", "New York, NY"], 36 | key: "foo", 37 | }; 38 | 39 | distancematrix({ params: params }, mockedAxios); 40 | 41 | expect(mockedAxios).toHaveBeenCalledTimes(1); 42 | expect(mockedAxios).toHaveBeenCalledWith({ 43 | method: "get", 44 | params: params, 45 | paramsSerializer: defaultParamsSerializer, 46 | url: defaultUrl, 47 | }); 48 | }); 49 | 50 | test("serializer should transform correctly", () => { 51 | const params = { 52 | origins: ["Seattle, WA"], 53 | destinations: ["San Francisco, CA", "New York, NY"], 54 | key: "foo", 55 | }; 56 | 57 | expect(defaultParamsSerializer(params)).toEqual( 58 | "destinations=San%20Francisco%2C%20CA|New%20York%2C%20NY&key=foo&origins=Seattle%2C%20WA" 59 | ); 60 | }); 61 | -------------------------------------------------------------------------------- /src/distance.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { 19 | DistanceMatrixRow, 20 | LatLng, 21 | RequestParams, 22 | ResponseData, 23 | TrafficModel, 24 | TransitMode, 25 | TransitRoutingPreference, 26 | TravelMode, 27 | TravelRestriction, 28 | UnitSystem, 29 | } from "./common"; 30 | import { latLngToString, serializer, toTimestamp } from "./serialize"; 31 | 32 | import { defaultAxiosInstance } from "./client"; 33 | 34 | export interface DistanceMatrixRequest extends Partial { 35 | params: { 36 | /** 37 | * The starting point for calculating travel distance and time. 38 | * You can supply one or more locations separated by the pipe character (`|`), in the form of an address, latitude/longitude coordinates, 39 | * or a place ID: 40 | * - If you pass an address, the service geocodes the string and converts it to a latitude/longitude coordinate to calculate distance. 41 | * This coordinate may be different from that returned by the Geocoding API, for example a building entrance rather than its center. 42 | * 43 | * `origins=Bobcaygeon+ON|24+Sussex+Drive+Ottawa+ON` 44 | * 45 | * - If you pass latitude/longitude coordinates, they are used unchanged to calculate distance. 46 | * Ensure that no space exists between the latitude and longitude values. 47 | * 48 | * `origins=41.43206,-81.38992|-33.86748,151.20699` 49 | * 50 | * - If you supply a place ID, you must prefix it with `place_id:`. 51 | * You can only specify a place ID if the request includes an API key or a Google Maps APIs Premium Plan client ID. 52 | * You can retrieve place IDs from the Geocoding API and the Places SDK (including Place Autocomplete). 53 | * 54 | * `origins=place_id:ChIJ3S-JXmauEmsRUcIaWtf4MzE` 55 | * 56 | * - Alternatively, you can supply an encoded set of coordinates using the 57 | * [Encoded Polyline Algorithm](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). 58 | * This is particularly useful if you have a large number of origin points, because the URL is significantly shorter when using 59 | * an encoded polyline. 60 | * 61 | * - Encoded polylines must be prefixed with `enc:` and followed by a colon (`:`). For example: `origins=enc:gfo}EtohhU:` 62 | * - You can also include multiple encoded polylines, separated by the pipe character (`|`). 63 | * For example: `origins=enc:wc~oAwquwMdlTxiKtqLyiK:|enc:c~vnAamswMvlTor@tjGi}L:|enc:udymA{~bxM:` 64 | */ 65 | origins: LatLng[]; 66 | /** 67 | * One or more locations to use as the finishing point for calculating travel distance and time. 68 | * The options for the destinations parameter are the same as for the origins parameter, described above. 69 | */ 70 | destinations: LatLng[]; 71 | /** 72 | * Specifies the mode of transport to use when calculating distance. 73 | * Valid values and other request details are specified in the Travel Modes section of this document. 74 | * 75 | * @default TravelMode.driving 76 | */ 77 | mode?: TravelMode; 78 | /** 79 | * The language in which to return results. 80 | * - If `language` is not supplied, the API attempts to use the preferred language as specified in the `Accept-Language` header, 81 | * or the native language of the domain from which the request is sent. 82 | * - The API does its best to provide a street address that is readable for both the user and locals. To achieve that goal, 83 | * it returns street addresses in the local language, transliterated to a script readable by the user if necessary, 84 | * observing the preferred language. All other addresses are returned in the preferred language. 85 | * Address components are all returned in the same language, which is chosen from the first component. 86 | * - If a name is not available in the preferred language, the API uses the closest match. 87 | * - The preferred language has a small influence on the set of results that the API chooses to return, 88 | * and the order in which they are returned. The geocoder interprets abbreviations differently depending on language, 89 | * such as the abbreviations for street types, or synonyms that may be valid in one language but not in another. 90 | * For example, utca and tér are synonyms for street in Hungarian. 91 | */ 92 | language?: string; 93 | /** 94 | * The region code, specified as a [ccTLD](https://en.wikipedia.org/wiki/CcTLD) (country code top-level domain) two-character value. 95 | * Most ccTLD codes are identical to ISO 3166-1 codes, with some exceptions. 96 | * This parameter will only influence, not fully restrict, results from the geocoder. 97 | * If more relevant results exist outside of the specified region, they may be included. 98 | */ 99 | region?: string; 100 | /** 101 | * Introduces restrictions to the route. Valid values are specified in the Restrictions section of this document. 102 | * Only one restriction can be specified. 103 | */ 104 | avoid?: TravelRestriction[]; 105 | /** Specifies the unit system to use when expressing distance as text. */ 106 | units?: UnitSystem; 107 | /** 108 | * Specifies the desired time of arrival for transit requests, in seconds since midnight, January 1, 1970 UTC. 109 | * You can specify either `departure_time` or `arrival_time`, but not both. 110 | * Note that `arrival_time` must be specified as an integer. 111 | */ 112 | arrival_time?: Date | number; 113 | /** 114 | * The desired time of departure. You can specify the time as an integer in seconds since midnight, January 1, 1970 UTC. 115 | * Alternatively, you can specify a value of now, which sets the departure time to the current time (correct to the nearest second). 116 | * 117 | * The departure time may be specified in two cases: 118 | * 119 | * - For requests where the travel mode is transit: You can optionally specify one of `departure_time` or `arrival_time`. 120 | * If neither time is specified, the `departure_time` defaults to now (that is, the departure time defaults to the current time). 121 | * 122 | * - For requests where the travel mode is driving: You can specify the `departure_time` to receive a route and trip duration 123 | * (response field: `duration_in_traffic`) that take traffic conditions into account. 124 | * This option is only available if the request contains a valid API key, or a valid 125 | * Google Maps APIs Premium Plan client ID and signature. 126 | * The `departure_time` must be set to the current time or some time in the future. It cannot be in the past. 127 | * 128 | * **Note:** Distance Matrix requests specifying `departure_time` when `mode=driving` are limited 129 | * to a maximum of 100 elements per request. The number of origins times the number of destinations defines the number of elements. 130 | */ 131 | departure_time?: Date | number; 132 | /** 133 | * Specifies the assumptions to use when calculating time in traffic. 134 | * This setting affects the value returned in the `duration_in_traffic` field in the response, 135 | * which contains the predicted time in traffic based on historical averages. 136 | * The `traffic_model` parameter may only be specified for requests where the travel mode is `driving`, 137 | * and where the request includes a `departure_time`, and only if the request includes an API key or 138 | * a Google Maps APIs Premium Plan client ID. 139 | * 140 | * @default TrafficModel.best_guess 141 | */ 142 | traffic_model?: TrafficModel; 143 | /** Specifies one or more preferred modes of transit. This parameter may only be specified for requests where the `mode` is `transit`. */ 144 | transit_mode?: TransitMode[]; 145 | /** 146 | * Specifies preferences for transit requests. Using this parameter, you can bias the options returned, 147 | * rather than accepting the default best route chosen by the API. 148 | * This parameter may only be specified for requests where the `mode` is `transit`. 149 | */ 150 | transit_routing_preference?: TransitRoutingPreference; 151 | } & RequestParams; 152 | } 153 | 154 | export interface DistanceMatrixResponseData extends ResponseData { 155 | origin_addresses: string[]; 156 | /** 157 | * contains an array of addresses as returned by the API from your original request. 158 | * As with origin_addresses, these are localized if appropriate. 159 | */ 160 | destination_addresses: string[]; 161 | /** contains an array of elements, which in turn each contain a status, duration, and distance element. */ 162 | rows: DistanceMatrixRow[]; 163 | } 164 | 165 | export interface DistanceMatrixResponse extends AxiosResponse { 166 | data: DistanceMatrixResponseData; 167 | } 168 | 169 | export const defaultUrl = 170 | "https://maps.googleapis.com/maps/api/distancematrix/json"; 171 | 172 | export const defaultParamsSerializer = serializer( 173 | { 174 | origins: (o) => o.map(latLngToString), 175 | destinations: (o) => o.map(latLngToString), 176 | arrival_time: toTimestamp, 177 | departure_time: toTimestamp, 178 | }, 179 | defaultUrl 180 | ); 181 | 182 | export function distancematrix( 183 | { 184 | params, 185 | method = "get", 186 | url = defaultUrl, 187 | paramsSerializer = defaultParamsSerializer, 188 | ...config 189 | }: DistanceMatrixRequest, 190 | axiosInstance: AxiosInstance = defaultAxiosInstance 191 | ): Promise { 192 | return axiosInstance({ 193 | params, 194 | method, 195 | url, 196 | paramsSerializer, 197 | ...config, 198 | }) as Promise; 199 | } 200 | -------------------------------------------------------------------------------- /src/elevation.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { elevation, defaultParamsSerializer, defaultUrl } from "./elevation"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("elevation should call axios correctly with location params", () => { 29 | const params = { locations: ["10,20"], key: "foo" }; 30 | 31 | elevation({ params: params }, mockedAxios); 32 | 33 | expect(mockedAxios).toHaveBeenCalledTimes(1); 34 | expect(mockedAxios).toHaveBeenCalledWith({ 35 | method: "get", 36 | params: params, 37 | paramsSerializer: defaultParamsSerializer, 38 | url: defaultUrl, 39 | }); 40 | }); 41 | 42 | test("elevation should call axios correctly with path params", () => { 43 | const params = { 44 | path: [ 45 | { lat: 35, lng: -110 }, 46 | { lat: 45, lng: -110 }, 47 | ], 48 | samples: 10, 49 | key: "foo", 50 | }; 51 | 52 | elevation({ params: params }, mockedAxios); 53 | 54 | expect(mockedAxios).toHaveBeenCalledTimes(1); 55 | expect(mockedAxios).toHaveBeenCalledWith({ 56 | method: "get", 57 | params: params, 58 | paramsSerializer: defaultParamsSerializer, 59 | url: defaultUrl, 60 | }); 61 | }); 62 | 63 | test("serializer should transform correctly", () => { 64 | const params = { 65 | path: [ 66 | { lat: 35, lng: -110 }, 67 | { lat: 45, lng: -110 }, 68 | ], 69 | samples: 10, 70 | key: "foo", 71 | }; 72 | 73 | expect(defaultParamsSerializer(params)).toEqual( 74 | "key=foo&path=35%2C-110|45%2C-110&samples=10" 75 | ); 76 | }); 77 | -------------------------------------------------------------------------------- /src/elevation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLng, LatLngLiteral, ResponseData, RequestParams } from "./common"; 18 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 19 | import { defaultAxiosInstance } from "./client"; 20 | import { serializer, latLngToString } from "./serialize"; 21 | 22 | export interface PositionalElevationParams { 23 | /** 24 | * defines the location(s) on the earth from which to return elevation data. 25 | * This parameter takes either a single location as a comma-separated {latitude,longitude} pair (e.g. "40.714728,-73.998672") 26 | * or multiple latitude/longitude pairs passed as an array or as an encoded polyline. 27 | */ 28 | locations: LatLng[]; 29 | } 30 | 31 | export interface SampledPathElevationParams { 32 | /** 33 | * defines a path on the earth for which to return elevation data. This parameter defines a 34 | * set of two or more ordered pairs defining a path along the surface of the earth. This 35 | * parameter must be used in conjunction with the samples parameter described below. 36 | */ 37 | path: LatLng[]; 38 | /** 39 | * specifies the number of sample points along a path for which to return elevation data. 40 | * The samples parameter divides the given path into an ordered set of equidistant points 41 | * along the path. 42 | */ 43 | samples: number; 44 | } 45 | 46 | export interface ElevationRequest extends Partial { 47 | params: (PositionalElevationParams | SampledPathElevationParams) & 48 | RequestParams; 49 | } 50 | export interface ElevationResponseData extends ResponseData { 51 | results: { 52 | /** 53 | * A `location` element (containing `lat` and `lng` elements) of the position for which elevation data is being computed. 54 | * Note that for path requests, the set of `location` elements will contain the sampled points along the path. 55 | */ 56 | location: LatLngLiteral; 57 | /** An `elevation` element indicating the elevation of the location in meters. */ 58 | elevation: number; 59 | /** 60 | * A `resolution` value, indicating the maximum distance between data points from which the elevation was interpolated, in meters. 61 | * This property will be missing if the resolution is not known. 62 | * Note that elevation data becomes more coarse (larger `resolution` values) when multiple points are passed. 63 | * To obtain the most accurate elevation value for a point, it should be queried independently. 64 | */ 65 | resolution: number; 66 | }[]; 67 | } 68 | 69 | export interface ElevationResponse extends AxiosResponse { 70 | data: ElevationResponseData; 71 | } 72 | 73 | export const defaultUrl = "https://maps.googleapis.com/maps/api/elevation/json"; 74 | 75 | export const defaultParamsSerializer = serializer( 76 | { 77 | locations: (o) => o.map(latLngToString), 78 | path: (o) => o.map(latLngToString), 79 | }, 80 | defaultUrl 81 | ); 82 | 83 | export function elevation( 84 | { 85 | params, 86 | method = "get", 87 | url = defaultUrl, 88 | paramsSerializer = defaultParamsSerializer, 89 | ...config 90 | }: ElevationRequest, 91 | axiosInstance: AxiosInstance = defaultAxiosInstance 92 | ): Promise { 93 | return axiosInstance({ 94 | params, 95 | method, 96 | url, 97 | paramsSerializer, 98 | ...config, 99 | }) as Promise; 100 | } 101 | -------------------------------------------------------------------------------- /src/geocode/geocode.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { geocode, defaultParamsSerializer, defaultUrl } from "./geocode"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("geocode should call axios correctly", () => { 29 | const params = { address: "Seattle", key: "foo", components: "country:us" }; 30 | 31 | geocode({ params: params }, mockedAxios); 32 | 33 | expect(mockedAxios).toHaveBeenCalledTimes(1); 34 | expect(mockedAxios).toHaveBeenCalledWith({ 35 | method: "get", 36 | params: params, 37 | paramsSerializer: defaultParamsSerializer, 38 | url: defaultUrl, 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/geocode/geocode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | LatLngBounds, 19 | GeocodeResult, 20 | ResponseData, 21 | RequestParams, 22 | } from "../common"; 23 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 24 | import { defaultAxiosInstance } from "../client"; 25 | import { serializer, latLngBoundsToString, objectToString } from "../serialize"; 26 | 27 | export const defaultUrl = "https://maps.googleapis.com/maps/api/geocode/json"; 28 | 29 | export interface GeocodeComponents { 30 | /** matches `postal_code` and `postal_code_prefix`. */ 31 | postal_code?: string; 32 | /** 33 | * matches a country name or a two letter [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code. 34 | * **Note:** The API follows the ISO standard for defining countries, and the filtering works best when using 35 | * the corresponding ISO code of the country 36 | */ 37 | country?: string; 38 | /** matches the long or short name of a route. */ 39 | route?: string; 40 | /** matches against `locality` and `sublocality` types. */ 41 | locality?: string; 42 | /** matches all the administrative_area levels. */ 43 | administrative_area?: string; 44 | } 45 | 46 | export interface GeocodeRequest extends Partial { 47 | params: { 48 | /** 49 | * The place_id that you want to geocode. You can retrieve this information from Places API for example. 50 | */ 51 | place_id?: string; 52 | /** 53 | * The street address that you want to geocode, in the format used by the national postal service of the country concerned. 54 | * Additional address elements such as business names and unit, suite or floor numbers should be avoided. 55 | */ 56 | address?: string; 57 | /** 58 | * The bounding box of the viewport within which to bias geocode results more prominently. 59 | * This parameter will only influence, not fully restrict, results from the geocoder. 60 | */ 61 | bounds?: string | LatLngBounds; 62 | /** 63 | * The language in which to return results. 64 | * - If `language` is not supplied, the geocoder attempts to use the preferred language as specified in the `Accept-Language` header, 65 | * or the native language of the domain from which the request is sent. 66 | * - The geocoder does its best to provide a street address that is readable for both the user and locals. 67 | * To achieve that goal, it returns street addresses in the local language, transliterated to a script readable 68 | * by the user if necessary, observing the preferred language. All other addresses are returned in the preferred language. 69 | * Address components are all returned in the same language, which is chosen from the first component. 70 | * - If a name is not available in the preferred language, the geocoder uses the closest match. 71 | * - The preferred language has a small influence on the set of results that the API chooses to return, 72 | * and the order in which they are returned. The geocoder interprets abbreviations differently depending on language, 73 | * such as the abbreviations for street types, or synonyms that may be valid in one language but not in another. 74 | * For example, utca and tér are synonyms for street in Hungarian. 75 | */ 76 | language?: string; 77 | /** 78 | * The region code, specified as a ccTLD ("top-level domain") two-character value. 79 | * This parameter will only influence, not fully restrict, results from the geocoder. 80 | */ 81 | region?: string; 82 | /** 83 | * A components filter with elements separated by a pipe (`|`). 84 | * The components filter is *required* if the request doesn't include an `address`. 85 | * Each element in the components filter consists of a `component:value` pair, and fully restricts the results from the geocoder. 86 | */ 87 | components?: string | GeocodeComponents; 88 | } & RequestParams; 89 | } 90 | 91 | export interface GeocodeResponseData extends ResponseData { 92 | /** 93 | * contains an array of geocoded address information and geometry information. 94 | * 95 | * Generally, only one entry in the `"results"` array is returned for address lookups,though the geocoder may return several results 96 | * when address queries are ambiguous. 97 | */ 98 | results: GeocodeResult[]; 99 | } 100 | 101 | export interface GeocodeResponse extends AxiosResponse { 102 | data: GeocodeResponseData; 103 | } 104 | 105 | export const defaultParamsSerializer = serializer( 106 | { 107 | bounds: latLngBoundsToString, 108 | components: objectToString, 109 | }, 110 | defaultUrl 111 | ); 112 | 113 | export function geocode( 114 | { 115 | params, 116 | method = "get", 117 | url = defaultUrl, 118 | paramsSerializer = defaultParamsSerializer, 119 | ...config 120 | }: GeocodeRequest, 121 | axiosInstance: AxiosInstance = defaultAxiosInstance 122 | ): Promise { 123 | return axiosInstance({ 124 | params, 125 | method, 126 | url, 127 | paramsSerializer, 128 | ...config, 129 | }) as Promise; 130 | } 131 | -------------------------------------------------------------------------------- /src/geocode/reversegeocode.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | reverseGeocode, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./reversegeocode"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("reverseGeocode should call axios correctly", () => { 33 | const params = { 34 | latlng: { 35 | lat: 60.168997, 36 | lng: 24.9433353, 37 | }, 38 | key: "foo", 39 | enable_address_descriptor: true 40 | }; 41 | 42 | reverseGeocode({ params: params }, mockedAxios); 43 | 44 | expect(mockedAxios).toHaveBeenCalledTimes(1); 45 | expect(mockedAxios).toHaveBeenCalledWith({ 46 | method: "get", 47 | params: params, 48 | paramsSerializer: defaultParamsSerializer, 49 | url: defaultUrl, 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/geocode/reversegeocode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AddressType, Language, LatLng, RequestParams } from "../common"; 18 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 19 | import { GeocodeResult, ResponseData, AddressDescriptor } from "../common"; 20 | import { latLngToString, enableAddressDescriptorToString, serializer } from "../serialize"; 21 | 22 | import { defaultAxiosInstance } from "../client"; 23 | 24 | /** 25 | * If both `result_type` and `location_type` filters are present then the API returns only those results that match both the 26 | * `result_type` and the `location_type` values. If none of the filter values are acceptable, the API returns `ZERO_RESULTS`. 27 | */ 28 | export enum ReverseGeocodingLocationType { 29 | /** returns only the addresses for which Google has location information accurate down to street address precision. */ 30 | ROOFTOP = "ROOFTOP", 31 | /** 32 | * returns only the addresses that reflect an approximation (usually on a road) interpolated between two precise points 33 | * (such as intersections). An interpolated range generally indicates that rooftop geocodes are unavailable for a street address. 34 | */ 35 | RANGE_INTERPOLATED = "RANGE_INTERPOLATED", 36 | /** returns only geometric centers of a location such as a polyline (for example, a street) or polygon (region). */ 37 | GEOMETRIC_CENTER = "GEOMETRIC_CENTER", 38 | /** returns only the addresses that are characterized as approximate. */ 39 | APPROXIMATE = "APPROXIMATE", 40 | } 41 | 42 | export interface ReverseGeocodeRequest extends Partial { 43 | params: { 44 | /** The latitude and longitude values specifying the location for which you wish to obtain the closest, human-readable address. */ 45 | latlng?: LatLng; 46 | /** 47 | * The place ID of the place for which you wish to obtain the human-readable address. 48 | * The place ID is a unique identifier that can be used with other Google APIs. 49 | * For example, you can use the `placeID` returned by the Roads API to get the address for a snapped point. 50 | * The place ID may only be specified if the request includes an API key or a Google Maps APIs Premium Plan client ID. 51 | */ 52 | place_id?: string; 53 | /** 54 | * The language in which to return results. 55 | * - Google often updates the supported languages, so this list may not be exhaustive. 56 | * - If `language` is not supplied, the geocoder attempts to use the preferred language as specified in the 57 | * `Accept-Language` header, or the native language of the domain from which the request is sent. 58 | * - The geocoder does its best to provide a street address that is readable for both the user and locals. 59 | * To achieve that goal, it returns street addresses in the local language, transliterated to a script readable by the user 60 | * if necessary, observing the preferred language. All other addresses are returned in the preferred language. 61 | * Address components are all returned in the same language, which is chosen from the first component. 62 | * - If a name is not available in the preferred language, the geocoder uses the closest match. 63 | */ 64 | language?: Language; 65 | /** 66 | * A filter of one or more address types, separated by a pipe (`|`). 67 | * If the parameter contains multiple address types, the API returns all addresses that match any of the types. 68 | * A note about processing: The `result_type` parameter does not restrict the search to the specified address type(s). 69 | * Rather, the `result_type` acts as a post-search filter: the API fetches all results for the specified `latlng`, 70 | * then discards those results that do not match the specified address type(s). 71 | * Note: This parameter is available only for requests that include an API key or a client ID. 72 | */ 73 | result_type?: AddressType[]; 74 | /** 75 | * A filter of one or more location types, separated by a pipe (`|`). 76 | * If the parameter contains multiple location types, the API returns all addresses that match any of the types. 77 | * A note about processing: The `location_type` parameter does not restrict the search to the specified location type(s). 78 | * Rather, the `location_type` acts as a post-search filter: the API fetches all results for the specified `latlng`, 79 | * then discards those results that do not match the specified location type(s). 80 | * Note: This parameter is available only for requests that include an API key or a client ID. 81 | */ 82 | location_type?: ReverseGeocodingLocationType[]; 83 | /** 84 | * Determines whether the address descriptor is returned in the response. 85 | */ 86 | enable_address_descriptor?: boolean; 87 | } & RequestParams; 88 | } 89 | 90 | export interface ReverseGeocodeResponseData extends ResponseData { 91 | /** 92 | * contains an array of geocoded address information and geometry information. 93 | * 94 | * Generally, only one entry in the `"results"` array is returned for address lookups,though the geocoder may return several results 95 | * when address queries are ambiguous. 96 | */ 97 | results: GeocodeResult[]; 98 | /** 99 | * The Address Descriptor for the target. 100 | */ 101 | address_descriptor: AddressDescriptor; 102 | } 103 | 104 | export interface ReverseGeocodeResponse extends AxiosResponse { 105 | data: ReverseGeocodeResponseData; 106 | } 107 | 108 | export const defaultUrl = "https://maps.googleapis.com/maps/api/geocode/json"; 109 | 110 | export const defaultParamsSerializer = serializer( 111 | { 112 | latlng: latLngToString, 113 | enable_address_descriptor: enableAddressDescriptorToString 114 | }, 115 | defaultUrl 116 | ); 117 | 118 | export function reverseGeocode( 119 | { 120 | params, 121 | method = "get", 122 | url = defaultUrl, 123 | paramsSerializer = defaultParamsSerializer, 124 | ...config 125 | }: ReverseGeocodeRequest, 126 | axiosInstance: AxiosInstance = defaultAxiosInstance 127 | ): Promise { 128 | return axiosInstance({ 129 | params, 130 | method, 131 | url, 132 | paramsSerializer, 133 | ...config, 134 | }) as Promise; 135 | } 136 | -------------------------------------------------------------------------------- /src/geolocate.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { geolocate, defaultUrl } from "./geolocate"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("elevation should call axios correctly", () => { 29 | const params = { key: "foo" }; 30 | const data = { considerIp: false }; 31 | geolocate({ params: params, data: data }, mockedAxios); 32 | 33 | expect(mockedAxios).toHaveBeenCalledTimes(1); 34 | expect(mockedAxios).toHaveBeenCalledWith({ 35 | method: "post", 36 | params: params, 37 | data: data, 38 | url: defaultUrl, 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/geolocate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { 19 | CellTower, 20 | LatLngLiteral, 21 | RadioType, 22 | RequestParams, 23 | ResponseData, 24 | WifiAccessPoint, 25 | } from "./common"; 26 | 27 | import { defaultAxiosInstance } from "./client"; 28 | 29 | export interface GeolocateRequest extends Partial { 30 | data: { 31 | /** The mobile country code (MCC) for the device's home network. */ 32 | homeMobileCountryCode?: number; 33 | /** The mobile network code (MNC) for the device's home network. */ 34 | homeMobileNetworkCode?: number; 35 | /** The mobile radio type. While this field is optional, it should be included if a value is available, for more accurate results. */ 36 | radioType?: RadioType; 37 | /** The carrier name. */ 38 | carrier?: string; 39 | /** 40 | * Specifies whether to fall back to IP geolocation if wifi and cell tower signals are not available. 41 | * Defaults to `true`. Set `considerIp` to `false` to disable fall back. 42 | */ 43 | considerIp?: boolean; 44 | /** An array of cell tower objects. */ 45 | cellTowers?: CellTower[]; 46 | /** An array of WiFi access point objects. */ 47 | wifiAccessPoints?: WifiAccessPoint[]; 48 | }; 49 | params: RequestParams; 50 | } 51 | 52 | export interface GeolocateResponseData extends ResponseData { 53 | /** The user's estimated latitude and longitude, in degrees. Contains one `lat` and one `lng` subfield. */ 54 | location: LatLngLiteral; 55 | /** The accuracy of the estimated location, in meters. This represents the radius of a circle around the given location. */ 56 | accuracy: number; 57 | } 58 | export interface GeolocateResponseSuccess extends AxiosResponse { 59 | data: GeolocateResponseData; 60 | } 61 | 62 | /** 63 | * In the case of an error, a standard format error response body will be returned 64 | * and the HTTP status code will be set to an error status. 65 | */ 66 | export interface GeolocateResponseError extends AxiosResponse { 67 | data: { 68 | error: { 69 | /** This is the same as the HTTP status of the response. */ 70 | code: number; 71 | /** A short description of the error. */ 72 | message: string; 73 | /** 74 | * A list of errors which occurred. Each error contains an identifier for the type of error (the `reason`) 75 | * and a short description (the `message`). 76 | */ 77 | errors: { 78 | domain: string; 79 | reason: GeolocateErrorReason; 80 | message: string; 81 | }[]; 82 | }; 83 | }; 84 | } 85 | 86 | export enum GeolocateErrorReason { 87 | /** 88 | * You have exceeded your daily limit. 89 | * Domain: usageLimits 90 | * Code: 403 91 | */ 92 | dailyLimitExceeded = "dailyLimitExceeded", 93 | /** 94 | * Your API key is not valid for the Geolocate API. Please ensure that you've included the entire key, 95 | * and that you've either purchased the API or have enabled billing and activated the API to obtain the free quota. 96 | * Domain: usageLimits 97 | * Code: 400 98 | */ 99 | keyInvalid = "keyInvalid", 100 | /** 101 | * You have exceeded the requests per second per user limit that you configured in the Google Cloud Platform Console. 102 | * This limit should be configured to prevent a single or small group of users from exhausting your daily quota, 103 | * while still allowing reasonable access to all users. 104 | * Domain: usageLimits 105 | * Code: 403 106 | */ 107 | userRateLimitExceeded = "userRateLimitExceeded", 108 | /** 109 | * The request was valid, but no results were returned. 110 | * Domain: geolocation 111 | * Code: 404 112 | */ 113 | notFound = "notFound", 114 | /** 115 | * The request body is not valid JSON. Refer to the Request Body section for details on each field. 116 | * Domain: global 117 | * Code: 400 118 | */ 119 | parseError = "parseError", 120 | } 121 | 122 | export type GeolocateResponse = 123 | | GeolocateResponseSuccess 124 | | GeolocateResponseError; 125 | 126 | export const defaultUrl = "https://www.googleapis.com/geolocation/v1/geolocate"; 127 | 128 | export function geolocate( 129 | { params, method = "post", url = defaultUrl, ...config }: GeolocateRequest, 130 | axiosInstance: AxiosInstance = defaultAxiosInstance 131 | ): Promise { 132 | return axiosInstance({ 133 | params, 134 | method, 135 | url, 136 | ...config, 137 | }) as Promise; 138 | } 139 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { version } from "."; 18 | 19 | test("version exists and is string", () => { 20 | expect(typeof version).toBe("string"); 21 | }); 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | export { 18 | DirectionsRequest, 19 | DirectionsResponse, 20 | DirectionsResponseData, 21 | } from "./directions"; 22 | export { 23 | DistanceMatrixRequest, 24 | DistanceMatrixResponse, 25 | DistanceMatrixResponseData, 26 | } from "./distance"; 27 | export { 28 | ElevationRequest, 29 | ElevationResponse, 30 | PositionalElevationParams, 31 | SampledPathElevationParams, 32 | ElevationResponseData, 33 | } from "./elevation"; 34 | export { 35 | FindPlaceFromTextRequest, 36 | FindPlaceFromTextResponse, 37 | FindPlaceFromTextResponseData, 38 | } from "./places/findplacefromtext"; 39 | export { 40 | GeocodeRequest, 41 | GeocodeResponse, 42 | GeocodeResponseData, 43 | GeocodeComponents, 44 | } from "./geocode/geocode"; 45 | export { 46 | GeolocateRequest, 47 | GeolocateResponse, 48 | GeolocateResponseSuccess, 49 | GeolocateResponseError, 50 | GeolocateResponseData, 51 | GeolocateErrorReason, 52 | } from "./geolocate"; 53 | export { 54 | NearestRoadsRequest, 55 | NearestRoadsResponse, 56 | } from "./roads/nearestroads"; 57 | export { 58 | PlaceAutocompleteRequest, 59 | PlaceAutocompleteResponse, 60 | PlaceAutocompleteResult, 61 | PlaceAutocompleteType, 62 | PlaceAutocompleteResponseData, 63 | } from "./places/autocomplete"; 64 | export { 65 | PlaceDetailsRequest, 66 | PlaceDetailsResponse, 67 | PlaceDetailsResponseData, 68 | } from "./places/details"; 69 | export { PlacePhotoRequest, PlacePhotoResponse } from "./places/photo"; 70 | export { 71 | PlaceQueryAutocompleteRequest, 72 | PlaceQueryAutocompleteResponse, 73 | PlaceQueryAutocompleteResponseData, 74 | PlaceQueryAutocompletePrediction, 75 | } from "./places/queryautocomplete"; 76 | export { 77 | PlacesNearbyRequest, 78 | PlacesNearbyResponse, 79 | PlacesNearbyResponseData, 80 | PlacesNearbyRanking, 81 | } from "./places/placesnearby"; 82 | export { 83 | ReverseGeocodeRequest, 84 | ReverseGeocodeResponse, 85 | ReverseGeocodeResponseData, 86 | ReverseGeocodingLocationType, 87 | } from "./geocode/reversegeocode"; 88 | export { SnapToRoadsRequest, SnapToRoadsResponse } from "./roads/snaptoroads"; 89 | export { 90 | TextSearchRequest, 91 | TextSearchResponse, 92 | TextSearchResponseData, 93 | } from "./places/textsearch"; 94 | export { 95 | TimeZoneRequest, 96 | TimeZoneResponse, 97 | TimeZoneResponseData, 98 | } from "./timezone"; 99 | 100 | export * from "./common"; 101 | export * from "./client"; 102 | -------------------------------------------------------------------------------- /src/places/autocomplete.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | placeAutocomplete, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./autocomplete"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("autocomplete should call axios correctly", () => { 33 | const params = { input: "Seattle", sessiontoken: "asdf", key: "foo" }; 34 | 35 | placeAutocomplete({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/places/autocomplete.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | AddressType, 19 | LatLng, 20 | PredictionSubstring, 21 | PredictionTerm, 22 | RequestParams, 23 | ResponseData, 24 | StructuredFormatting, 25 | } from "../common"; 26 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 27 | import { latLngToString, serializer } from "../serialize"; 28 | 29 | import { defaultAxiosInstance } from "../client"; 30 | 31 | export enum PlaceAutocompleteType { 32 | /** 33 | * instructs the Place Autocomplete service to return only geocoding results, rather than business results. 34 | * Generally, you use this request to disambiguate results where the location specified may be indeterminate. 35 | */ 36 | geocode = "geocode", 37 | /** 38 | * instructs the Place Autocomplete service to return only geocoding results with a precise address. 39 | * Generally, you use this request when you know the user will be looking for a fully specified address. 40 | */ 41 | address = "address", 42 | /** instructs the Place Autocomplete service to return only business results. */ 43 | establishment = "establishment", 44 | /** 45 | * the `(regions)` type collection instructs the Places service to return any result matching the following types: 46 | * - `locality` 47 | * - `sublocality` 48 | * - `postal_code` 49 | * - `country` 50 | * - `administrative_area_level_1` 51 | * - `administrative_area_level_2` 52 | */ 53 | regions = "(regions)", 54 | /** the (cities) type collection instructs the Places service to return results that match `locality` or `administrative_area_level_3`. */ 55 | cities = "(cities)", 56 | } 57 | 58 | export interface PlaceAutocompleteRequest extends Partial { 59 | params: { 60 | /** 61 | * The text string on which to search. The Place Autocomplete service will return candidate matches 62 | * based on this string and order results based on their perceived relevance. 63 | */ 64 | input: string; 65 | /** 66 | * A random string which identifies an autocomplete 67 | * [session](https://developers.google.com/places/web-service/autocomplete#session_tokens) for billing purposes. 68 | * If this parameter is omitted from an autocomplete request, the request is billed independently 69 | */ 70 | sessiontoken?: string; 71 | /** 72 | * The position, in the input term, of the last character that the service uses to match predictions. 73 | * For example, if the input is 'Google' and the `offset` is 3, the service will match on 'Goo'. 74 | * The string determined by the `offset` is matched against the first word in the input term only. 75 | * For example, if the input term is 'Google abc' and the offset is 3, the service will attempt to match against 'Goo abc'. 76 | * If no `offset` is supplied, the service will use the whole term. 77 | * The `offset` should generally be set to the position of the text caret. 78 | */ 79 | offset?: number; 80 | /** 81 | * The origin point from which to calculate straight-line distance to the destination (returned as distance_meters). 82 | * If this value is omitted, straight-line distance will not be returned. 83 | */ 84 | origin?: LatLng; 85 | /** The point around which you wish to retrieve place information. */ 86 | location?: LatLng; 87 | /** 88 | * The distance (in meters) within which to return place results. Note that setting a radius biases results to the indicated area, 89 | * but may not fully restrict results to the specified area. 90 | */ 91 | radius?: number; 92 | /** 93 | * The language code, indicating in which language the results should be returned, if possible. 94 | * Searches are also biased to the selected language; results in the selected language may be given a higher ranking. 95 | * See the list of supported languages and their codes. 96 | * Note that we often update supported languages so this list may not be exhaustive. 97 | * If language is not supplied, the Place Autocomplete service will attempt to use the native language 98 | * of the domain from which the request is sent. 99 | */ 100 | language?: string; 101 | /** The types of place results to return. */ 102 | types?: PlaceAutocompleteType; 103 | /** 104 | * A grouping of places to which you would like to restrict your results. 105 | * Currently, you can use `components` to filter by up to 5 countries. 106 | * Countries must be passed as a two character, ISO 3166-1 Alpha-2 compatible country code. 107 | * For example: `components=country:fr` would restrict your results to places within France. 108 | * Multiple countries must be passed as multiple `country:XX` filters, with the pipe character (`|`) as a separator. 109 | * For example: `components=country:us|country:pr|country:vi|country:gu|country:mp` would restrict your results 110 | * to places within the United States and its unincorporated organized territories. 111 | */ 112 | components?: string[]; 113 | /** 114 | * Returns only those places that are strictly within the region defined by `location` and `radius`. 115 | * This is a restriction, rather than a bias, meaning that results outside this region 116 | * will not be returned even if they match the user input. 117 | */ 118 | strictbounds?: boolean; 119 | } & RequestParams; 120 | } 121 | 122 | export interface PlaceAutocompleteResult { 123 | /** 124 | * contains the human-readable name for the returned result. 125 | * For `establishment` results, this is usually the business name. 126 | */ 127 | description: string; 128 | /** 129 | * contains an integer indicating the straight-line distance between the predicted place, and the specified origin point, in meters. 130 | * This field is only returned when the origin point is specified in the request. 131 | * This field is not returned in predictions of type route. 132 | */ 133 | distance_meters?: number; 134 | /** 135 | * is a textual identifier that uniquely identifies a place. 136 | * To retrieve information about the place, pass this identifier in the `placeId` field of a Places API request. 137 | */ 138 | place_id: string; 139 | /** 140 | * contains an array of terms identifying each section of the returned description 141 | * (a section of the description is generally terminated with a comma). 142 | */ 143 | terms: PredictionTerm[]; 144 | /** 145 | * contains an array of types that apply to this place. 146 | * For example: `[ "political", "locality" ]` or `[ "establishment", "geocode" ]`. 147 | */ 148 | types: AddressType[]; 149 | /** 150 | * contains an array with `offset` value and `length`. These describe the location of 151 | * the entered term in the prediction result text, so that the term can be highlighted if desired. 152 | */ 153 | matched_substrings: PredictionSubstring[]; 154 | /** contains details on the prediction. */ 155 | structured_formatting: StructuredFormatting; 156 | } 157 | 158 | export interface PlaceAutocompleteResponseData extends ResponseData { 159 | /** 160 | * contains an array of places, with information about the place. 161 | * See [Place Autocomplete Results](https://developers.google.com/places/web-service/autocomplete#place_autocomplete_results) 162 | * for information about these results. The Places API returns up to 5 results. 163 | */ 164 | predictions: PlaceAutocompleteResult[]; 165 | } 166 | 167 | export interface PlaceAutocompleteResponse extends AxiosResponse { 168 | data: PlaceAutocompleteResponseData; 169 | } 170 | 171 | export const defaultUrl = 172 | "https://maps.googleapis.com/maps/api/place/autocomplete/json"; 173 | 174 | export const defaultParamsSerializer = serializer( 175 | { 176 | location: latLngToString, 177 | origin: latLngToString, 178 | }, 179 | defaultUrl 180 | ); 181 | 182 | export function placeAutocomplete( 183 | { 184 | params, 185 | method = "get", 186 | url = defaultUrl, 187 | paramsSerializer = defaultParamsSerializer, 188 | ...config 189 | }: PlaceAutocompleteRequest, 190 | axiosInstance: AxiosInstance = defaultAxiosInstance 191 | ): Promise { 192 | return axiosInstance({ 193 | params, 194 | method, 195 | url, 196 | paramsSerializer, 197 | ...config, 198 | }) as Promise; 199 | } 200 | -------------------------------------------------------------------------------- /src/places/details.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { placeDetails, defaultParamsSerializer, defaultUrl } from "./details"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("autocomplete should call axios correctly", () => { 29 | const params = { 30 | place_id: "notarealid", 31 | key: "foo", 32 | fields: ["place_id", "name"], 33 | }; 34 | 35 | placeDetails({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/places/details.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { Language, Place, RequestParams, ResponseData } from "../common"; 19 | 20 | import { defaultAxiosInstance } from "../client"; 21 | import { serializer } from "../serialize"; 22 | 23 | export interface PlaceDetailsRequest extends Partial { 24 | params: { 25 | /** A textual identifier that uniquely identifies a place, returned from a Place Search. */ 26 | place_id: string; 27 | /** 28 | * The language code, indicating in which language the results should be returned, if possible. 29 | * Note that some fields may not be available in the requested language. 30 | * Note that we often update supported languages so this list may not be exhaustive. 31 | */ 32 | language?: Language; 33 | /** 34 | * The region code, specified as a ccTLD (country code top-level domain) two-character value. 35 | * Most ccTLD codes are identical to ISO 3166-1 codes, with some exceptions. 36 | * This parameter will only influence, not fully restrict, results. 37 | * If more relevant results exist outside of the specified region, they may be included. 38 | * When this parameter is used, the country name is omitted from the resulting `formatted_address` 39 | * for results in the specified region. 40 | */ 41 | region?: string; 42 | /** 43 | * A random string which identifies an autocomplete session for billing purposes. 44 | * Use this for Place Details requests that are called following an autocomplete request in the same user session 45 | */ 46 | sessiontoken?: string; 47 | /** 48 | * One or more fields, specifying the types of place data to return, separated by a comma. 49 | * 50 | * **Warning: If you do not specify at least one field with a request, or if you omit the **fields** 51 | * parameter from a request, ALL possible fields will be returned, and you will be billed accordingly. 52 | * This applies only to Place Details requests. 53 | */ 54 | fields?: string[]; 55 | } & RequestParams; 56 | } 57 | export interface PlaceDetailsResponseData extends ResponseData { 58 | result: Place; 59 | /** contains a set of attributions about this listing which must be displayed to the user. */ 60 | html_attributions: string[]; 61 | } 62 | 63 | export interface PlaceDetailsResponse extends AxiosResponse { 64 | data: PlaceDetailsResponseData; 65 | } 66 | 67 | export const defaultUrl = 68 | "https://maps.googleapis.com/maps/api/place/details/json"; 69 | 70 | export const defaultParamsSerializer = serializer({}, defaultUrl, { 71 | arrayFormat: "comma", 72 | }); 73 | 74 | export function placeDetails( 75 | { 76 | params, 77 | method = "get", 78 | url = defaultUrl, 79 | paramsSerializer = defaultParamsSerializer, 80 | ...config 81 | }: PlaceDetailsRequest, 82 | axiosInstance: AxiosInstance = defaultAxiosInstance 83 | ): Promise { 84 | return axiosInstance({ 85 | params, 86 | method, 87 | url, 88 | paramsSerializer, 89 | ...config, 90 | }) as Promise; 91 | } 92 | -------------------------------------------------------------------------------- /src/places/findplacefromtext.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | findPlaceFromText, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./findplacefromtext"; 23 | import { PlaceInputType } from "../common"; 24 | 25 | jest.mock("axios"); 26 | 27 | const mockedAxios = axios as jest.Mocked; 28 | 29 | afterEach(() => { 30 | jest.clearAllMocks(); 31 | }); 32 | 33 | test("autocomplete should call axios correctly", () => { 34 | const params = { 35 | input: "google", 36 | inputtype: PlaceInputType.textQuery, 37 | key: "foo", 38 | fields: ["place_id", "name"], 39 | }; 40 | 41 | findPlaceFromText({ params: params }, mockedAxios); 42 | 43 | expect(mockedAxios).toHaveBeenCalledTimes(1); 44 | expect(mockedAxios).toHaveBeenCalledWith({ 45 | method: "get", 46 | params: params, 47 | paramsSerializer: defaultParamsSerializer, 48 | url: defaultUrl, 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/places/findplacefromtext.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | Language, 19 | ResponseData, 20 | Place, 21 | PlaceInputType, 22 | RequestParams, 23 | } from "../common"; 24 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 25 | import { defaultAxiosInstance } from "../client"; 26 | import { serializer } from "../serialize"; 27 | 28 | export interface FindPlaceFromTextRequest extends Partial { 29 | params: { 30 | /** The text input specifying which place to search for (for example, a name, address, or phone number). */ 31 | input: string; 32 | /** The type of input. This can be one of either `textQuery` or `phoneNumber`. */ 33 | inputtype: PlaceInputType; 34 | /** 35 | * The language code, indicating in which language the results should be returned, if possible. 36 | * Searches are also biased to the selected language; results in the selected language may be given a higher ranking 37 | */ 38 | language?: Language; 39 | /** 40 | * The fields specifying the types of place data to return. 41 | * 42 | * **Note:** If you omit the fields parameter from a Find Place request, only the place_id for the result will be returned. 43 | */ 44 | fields?: string[]; 45 | /** 46 | * Prefer results in a specified area, by specifying either a radius plus lat/lng, or two lat/lng pairs representing 47 | * the points of a rectangle. If this parameter is not specified, the API uses IP address biasing by default. 48 | */ 49 | locationbias?: string; 50 | } & RequestParams; 51 | } 52 | 53 | export interface FindPlaceFromTextResponseData extends ResponseData { 54 | candidates: Place[]; 55 | } 56 | 57 | export interface FindPlaceFromTextResponse extends AxiosResponse { 58 | data: FindPlaceFromTextResponseData; 59 | } 60 | 61 | export const defaultUrl = 62 | "https://maps.googleapis.com/maps/api/place/findplacefromtext/json"; 63 | 64 | export const defaultParamsSerializer = serializer({}, defaultUrl, { 65 | arrayFormat: "comma", 66 | }); 67 | 68 | export function findPlaceFromText( 69 | { 70 | params, 71 | method = "get", 72 | url = defaultUrl, 73 | paramsSerializer = defaultParamsSerializer, 74 | ...config 75 | }: FindPlaceFromTextRequest, 76 | axiosInstance: AxiosInstance = defaultAxiosInstance 77 | ): Promise { 78 | return axiosInstance({ 79 | params, 80 | method, 81 | url, 82 | paramsSerializer, 83 | ...config, 84 | }) as Promise; 85 | } 86 | -------------------------------------------------------------------------------- /src/places/photo.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { placePhoto, defaultUrl, PlacePhotoRequest } from "./photo"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("photo should call axios correctly", () => { 29 | const params = { photoreference: "notaphotoreference", key: "foo" }; 30 | const responseType = "arraybuffer"; 31 | placePhoto({ params, responseType }, mockedAxios); 32 | 33 | expect(mockedAxios).toHaveBeenCalledTimes(1); 34 | expect(mockedAxios).toHaveBeenCalledWith({ 35 | method: "get", 36 | params, 37 | responseType, 38 | url: defaultUrl, 39 | }); 40 | }); 41 | 42 | test("photo should set default responseType", () => { 43 | const params = { photoreference: "notaphotoreference", key: "foo" }; 44 | placePhoto({ params } as PlacePhotoRequest, mockedAxios); 45 | 46 | expect(mockedAxios).toHaveBeenCalledTimes(1); 47 | expect(mockedAxios).toHaveBeenCalledWith({ 48 | method: "get", 49 | params, 50 | responseType: "arraybuffer", 51 | url: defaultUrl, 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/places/photo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { RequestParams } from "../common"; 19 | import { defaultAxiosInstance } from "../client"; 20 | 21 | export interface PlacePhotoRequest extends Partial { 22 | params: { 23 | /** 24 | * string identifier that uniquely identifies a photo. 25 | * Photo references are returned from either a Place Search or Place Details request. 26 | */ 27 | photoreference: string; 28 | /** 29 | * Specifies the maximum desired height or width, in pixels, of the image returned by the Place Photos service. 30 | * If the image is smaller than the values specified, the original image will be returned. 31 | * If the image is larger in either dimension, it will be scaled to match the smaller of the two dimensions, 32 | * restricted to its original aspect ratio. Both the `maxheight` and `maxwidth` properties accept an integer between 1 and 1600. 33 | */ 34 | maxwidth?: number; 35 | /** 36 | * Specifies the maximum desired height or width, in pixels, of the image returned by the Place Photos service. 37 | * If the image is smaller than the values specified, the original image will be returned. 38 | * If the image is larger in either dimension, it will be scaled to match the smaller of the two dimensions, 39 | * restricted to its original aspect ratio. Both the `maxheight` and `maxwidth` properties accept an integer between 1 and 1600. 40 | */ 41 | maxheight?: number; 42 | } & RequestParams; 43 | responseType: "arraybuffer" | "blob" | "stream"; 44 | } 45 | 46 | /** 47 | * The response of a successful Place Photo request will be an image. 48 | * The type of the image will depend upon the type of the originally submitted photo. 49 | * 50 | * If your request exceeds your available quota, the server will return an HTTP 403 status to indicate that the quota has been exceeded. 51 | * 52 | * If the server is unable to understand your request, it will return HTTP 400 status, which indicates an invalid request. 53 | * 54 | * The most common reasons why you might see an invalid request include: 55 | * - The submitted photo reference was incorrectly specified. 56 | * - Your request did not include either a `maxwidth` or `maxheight` parameter. 57 | */ 58 | export interface PlacePhotoResponse extends AxiosResponse {} 59 | 60 | export const defaultUrl = "https://maps.googleapis.com/maps/api/place/photo"; 61 | 62 | export function placePhoto( 63 | { 64 | params, 65 | method = "get", 66 | url = defaultUrl, 67 | responseType, 68 | ...config 69 | }: PlacePhotoRequest, 70 | axiosInstance: AxiosInstance = defaultAxiosInstance 71 | ): Promise { 72 | if (!responseType) { 73 | responseType = "arraybuffer"; 74 | } 75 | 76 | return axiosInstance({ 77 | params, 78 | method, 79 | url, 80 | responseType, 81 | ...config, 82 | }) as Promise; 83 | } 84 | -------------------------------------------------------------------------------- /src/places/placesnearby.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | PlacesNearbyRanking, 19 | defaultParamsSerializer, 20 | defaultUrl, 21 | placesNearby, 22 | } from "./placesnearby"; 23 | 24 | import axios from "axios"; 25 | 26 | jest.mock("axios"); 27 | 28 | const mockedAxios = axios as jest.Mocked; 29 | 30 | afterEach(() => { 31 | jest.clearAllMocks(); 32 | }); 33 | 34 | test("autocomplete should call axios correctly", () => { 35 | const params = { 36 | location: { lat: 35, lng: -110 }, 37 | key: "foo", 38 | ranking: PlacesNearbyRanking.distance, 39 | }; 40 | 41 | placesNearby({ params: params }, mockedAxios); 42 | 43 | expect(mockedAxios).toHaveBeenCalledTimes(1); 44 | expect(mockedAxios).toHaveBeenCalledWith({ 45 | method: "get", 46 | params: params, 47 | url: defaultUrl, 48 | paramsSerializer: defaultParamsSerializer, 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/places/placesnearby.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { 19 | Language, 20 | LatLng, 21 | Place, 22 | RequestParams, 23 | ResponseData, 24 | } from "../common"; 25 | import { latLngToString, serializer } from "../serialize"; 26 | 27 | import { defaultAxiosInstance } from "../client"; 28 | 29 | export enum PlacesNearbyRanking { 30 | /** 31 | * This option sorts results based on their importance. Ranking will favor prominent places within the specified area. 32 | * Prominence can be affected by a place's ranking in Google's index, global popularity, and other factors. 33 | */ 34 | prominence = "prominence", 35 | /** 36 | * This option biases search results in ascending order by their distance from the specified `location`. 37 | * When distance is specified, one or more of `keyword`, `name`, or `type` is required. 38 | */ 39 | distance = "distance", 40 | } 41 | 42 | export interface PlacesNearbyRequest extends Partial { 43 | params: { 44 | /** The latitude/longitude around which to retrieve place information. This must be specified as latitude,longitude. */ 45 | location: LatLng; 46 | /** 47 | * Defines the distance (in meters) within which to return place results. 48 | * The maximum allowed radius is 50 000 meters. 49 | * Note that `radius` must not be included if `rankby=distance` is specified. 50 | */ 51 | radius?: number; 52 | /** 53 | * A term to be matched against all content that Google has indexed for this place, including but not limited to 54 | * name, type, and address, as well as customer reviews and other third-party content. 55 | */ 56 | keyword?: string; 57 | /** 58 | * The language code, indicating in which language the results should be returned, if possible. 59 | * Note that we often update supported languages so this list may not be exhaustive. 60 | */ 61 | language?: Language; 62 | /** 63 | * Restricts results to only those places within the specified range. 64 | * Valid values range between 0 (most affordable) to 4 (most expensive), inclusive. 65 | * The exact amount indicated by a specific value will vary from region to region. 66 | */ 67 | minprice?: number; 68 | /** 69 | * Restricts results to only those places within the specified range. 70 | * Valid values range between 0 (most affordable) to 4 (most expensive), inclusive. 71 | * The exact amount indicated by a specific value will vary from region to region. 72 | */ 73 | maxprice?: number; 74 | /** 75 | * A term to be matched against all content that Google has indexed for this place. 76 | * Equivalent to `keyword`. The `name` field is no longer restricted to place names. 77 | * Values in this field are combined with values in the `keyword` field and passed as part of the same search string. 78 | * We recommend using only the `keyword` parameter for all search terms. 79 | */ 80 | name?: string; 81 | /** 82 | * Returns only those places that are open for business at the time the query is sent. 83 | * Places that do not specify opening hours in the Google Places database will not be returned if you include this parameter in your query. 84 | */ 85 | opennow?: boolean; 86 | /** 87 | * Specifies the order in which results are listed. 88 | * Note that `rankby` must not be included if `radius` is specified. 89 | * 90 | * @default PlacesNearbyRanking.prominence 91 | */ 92 | rankby?: PlacesNearbyRanking; 93 | /** 94 | * Restricts the results to places matching the specified type. 95 | * Only one type may be specified (if more than one type is provided, all types following the first entry are ignored). 96 | */ 97 | type?: string; 98 | /** 99 | * Returns the next 20 results from a previously run search. 100 | * Setting a pagetoken parameter will execute a search with the same parameters used previously — 101 | * all parameters other than pagetoken will be ignored. 102 | */ 103 | pagetoken?: string; 104 | } & RequestParams; 105 | } 106 | 107 | export interface PlacesNearbyResponseData extends ResponseData { 108 | results: Place[]; 109 | } 110 | 111 | export interface PlacesNearbyResponse extends AxiosResponse { 112 | data: PlacesNearbyResponseData; 113 | } 114 | 115 | export const defaultUrl = 116 | "https://maps.googleapis.com/maps/api/place/nearbysearch/json"; 117 | 118 | export const defaultParamsSerializer = serializer( 119 | { location: latLngToString }, 120 | defaultUrl 121 | ); 122 | 123 | export function placesNearby( 124 | { 125 | params, 126 | method = "get", 127 | url = defaultUrl, 128 | paramsSerializer = defaultParamsSerializer, 129 | ...config 130 | }: PlacesNearbyRequest, 131 | axiosInstance: AxiosInstance = defaultAxiosInstance 132 | ): Promise { 133 | return axiosInstance({ 134 | params, 135 | method, 136 | url, 137 | paramsSerializer, 138 | ...config, 139 | }) as Promise; 140 | } 141 | -------------------------------------------------------------------------------- /src/places/queryautocomplete.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | placeQueryAutocomplete, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./queryautocomplete"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("autocomplete should call axios correctly", () => { 33 | const params = { input: "Seattle", sessiontoken: "asdf", key: "foo" }; 34 | 35 | placeQueryAutocomplete({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/places/queryautocomplete.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | LatLng, 19 | Language, 20 | ResponseData, 21 | RequestParams, 22 | PredictionTerm, 23 | PredictionSubstring, 24 | StructuredFormatting, 25 | } from "../common"; 26 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 27 | import { defaultAxiosInstance } from "../client"; 28 | import { serializer, latLngToString } from "../serialize"; 29 | 30 | export interface PlaceQueryAutocompleteRequest 31 | extends Partial { 32 | params: { 33 | /** 34 | * The text string on which to search. 35 | * The Places service will return candidate matches based on this string and order results based on their perceived relevance. 36 | */ 37 | input: string; 38 | /** 39 | * The character position in the input term at which the service uses text for predictions. 40 | * For example, if the input is 'Googl' and the completion point is 3, the service will match on 'Goo'. 41 | * The offset should generally be set to the position of the text caret. 42 | * If no offset is supplied, the service will use the entire term. 43 | */ 44 | offset?: number; 45 | /** The point around which you wish to retrieve place information. Must be specified as latitude,longitude. */ 46 | location?: LatLng; 47 | /** 48 | * The distance (in meters) within which to return place results. 49 | * Note that setting a radius biases results to the indicated area, but may not fully restrict results to the specified area. 50 | */ 51 | radius?: number; 52 | /** 53 | * The language code, indicating in which language the results should be returned, if possible. 54 | * Searches are also biased to the selected language; results in the selected language may be given a higher ranking. 55 | * If language is not supplied, the Places service will attempt to use the native language of the domain from which the request is sent. 56 | */ 57 | language?: Language; 58 | } & RequestParams; 59 | } 60 | 61 | export interface PlaceQueryAutocompletePrediction { 62 | /** contains the human-readable name for the returned result. For establishment results, this is usually the business name. */ 63 | description: string; 64 | /** 65 | * contains an array of terms identifying each section of the returned description 66 | * (a section of the description is generally terminated with a comma). 67 | */ 68 | terms: PredictionTerm[]; 69 | /** 70 | * contains an `offset` value and a `length`. 71 | * These describe the location of the entered term in the prediction result text, so that the term can be highlighted if desired. 72 | */ 73 | matched_substrings: PredictionSubstring[]; 74 | structured_formatting?: StructuredFormatting[]; 75 | place_id?: string; 76 | types?: string[]; 77 | } 78 | 79 | export interface PlaceQueryAutocompleteResponseData extends ResponseData { 80 | /** 81 | * contains an array of places, with information about the place. 82 | * See [Place Autocomplete Results](https://developers.google.com/places/web-service/autocomplete#place_autocomplete_results) 83 | * for information about these results. The Places API returns up to 5 results. 84 | */ 85 | predictions: PlaceQueryAutocompletePrediction[]; 86 | } 87 | 88 | export interface PlaceQueryAutocompleteResponse extends AxiosResponse { 89 | data: PlaceQueryAutocompleteResponseData; 90 | } 91 | 92 | export const defaultUrl = 93 | "https://maps.googleapis.com/maps/api/place/queryautocomplete/json"; 94 | 95 | export const defaultParamsSerializer = serializer( 96 | { location: latLngToString }, 97 | defaultUrl 98 | ); 99 | 100 | export function placeQueryAutocomplete( 101 | { 102 | params, 103 | method = "get", 104 | url = defaultUrl, 105 | paramsSerializer = defaultParamsSerializer, 106 | ...config 107 | }: PlaceQueryAutocompleteRequest, 108 | axiosInstance: AxiosInstance = defaultAxiosInstance 109 | ): Promise { 110 | return axiosInstance({ 111 | params, 112 | method, 113 | url, 114 | paramsSerializer, 115 | ...config, 116 | }) as Promise; 117 | } 118 | -------------------------------------------------------------------------------- /src/places/textsearch.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { textSearch, defaultParamsSerializer, defaultUrl } from "./textsearch"; 19 | 20 | jest.mock("axios"); 21 | 22 | const mockedAxios = axios as jest.Mocked; 23 | 24 | afterEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | test("textsearch should call axios correctly", () => { 29 | const params = { query: "Seattle", key: "foo" }; 30 | 31 | textSearch({ params: params }, mockedAxios); 32 | 33 | expect(mockedAxios).toHaveBeenCalledTimes(1); 34 | expect(mockedAxios).toHaveBeenCalledWith({ 35 | method: "get", 36 | params: params, 37 | paramsSerializer: defaultParamsSerializer, 38 | url: defaultUrl, 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/places/textsearch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { 18 | ResponseData, 19 | LatLng, 20 | Language, 21 | PlaceType1, 22 | Place, 23 | RequestParams, 24 | } from "../common"; 25 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 26 | import { defaultAxiosInstance } from "../client"; 27 | import { serializer, latLngToString } from "../serialize"; 28 | 29 | export interface TextSearchRequest extends Partial { 30 | params: { 31 | /** 32 | * The text string on which to search, for example: "restaurant" or "123 Main Street". 33 | * The Google Places service will return candidate matches based on this string and order the results 34 | * based on their perceived relevance. This parameter becomes optional if the `type` parameter 35 | * is also used in the search request. 36 | */ 37 | query: string; 38 | /** 39 | * The region code, specified as a ccTLD (country code top-level domain) two-character value. 40 | * Most ccTLD codes are identical to ISO 3166-1 codes, with some exceptions. 41 | * This parameter will only influence, not fully restrict, search results. 42 | * If more relevant results exist outside of the specified region, they may be included. 43 | * When this parameter is used, the country name is omitted from the resulting `formatted_address` 44 | * for results in the specified region. 45 | */ 46 | region?: string; 47 | /** 48 | * The latitude/longitude around which to retrieve place information. 49 | * This must be specified as latitude,longitude. If you specify a location parameter, 50 | * you must also specify a radius parameter. 51 | */ 52 | location?: LatLng; 53 | /** 54 | * Defines the distance (in meters) within which to bias place results. 55 | * The maximum allowed radius is 50 000 meters. 56 | * Results inside of this region will be ranked higher than results outside of the search circle; 57 | * however, prominent results from outside of the search radius may be included. 58 | */ 59 | radius?: number; 60 | /** 61 | * The language code, indicating in which language the results should be returned, if possible. 62 | * Note that we often update supported languages so this list may not be exhaustive 63 | */ 64 | language?: Language; 65 | /** 66 | * Restricts results to only those places within the specified price level. 67 | * Valid values are in the range from 0 (most affordable) to 4 (most expensive), inclusive. 68 | * The exact amount indicated by a specific value will vary from region to region. 69 | */ 70 | minprice?: number; 71 | /** 72 | * Restricts results to only those places within the specified price level. 73 | * Valid values are in the range from 0 (most affordable) to 4 (most expensive), inclusive. 74 | * The exact amount indicated by a specific value will vary from region to region. 75 | */ 76 | maxprice?: number; 77 | /** 78 | * Returns only those places that are open for business at the time the query is sent. 79 | * Places that do not specify opening hours in the Google Places database will not be returned 80 | * if you include this parameter in your query. 81 | */ 82 | opennow?: boolean; 83 | /** 84 | * Returns the next 20 results from a previously run search. 85 | * Setting a `pagetoken` parameter will execute a search with the same parameters used previously — 86 | * all parameters other than `pagetoken` will be ignored. 87 | */ 88 | pagetoken?: string; 89 | /** 90 | * Restricts the results to places matching the specified type. 91 | * Only one type may be specified (if more than one type is provided, all types following the first entry are ignored). 92 | */ 93 | type?: PlaceType1; 94 | } & RequestParams; 95 | } 96 | 97 | export interface TextSearchResponseData extends ResponseData { 98 | results: Place[]; 99 | } 100 | 101 | export interface TextSearchResponse extends AxiosResponse { 102 | data: TextSearchResponseData; 103 | } 104 | 105 | export const defaultUrl = 106 | "https://maps.googleapis.com/maps/api/place/textsearch/json"; 107 | 108 | export const defaultParamsSerializer = serializer( 109 | { location: latLngToString }, 110 | defaultUrl 111 | ); 112 | 113 | export function textSearch( 114 | { 115 | params, 116 | method = "get", 117 | url = defaultUrl, 118 | paramsSerializer = defaultParamsSerializer, 119 | ...config 120 | }: TextSearchRequest, 121 | axiosInstance: AxiosInstance = defaultAxiosInstance 122 | ): Promise { 123 | return axiosInstance({ 124 | params, 125 | method, 126 | url, 127 | paramsSerializer, 128 | ...config, 129 | }) as Promise; 130 | } 131 | -------------------------------------------------------------------------------- /src/roads/nearestroads.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | nearestRoads, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./nearestroads"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("nearestRoads should call axios correctly", () => { 33 | const params = { points: ["0,0"], key: "foo" }; 34 | 35 | nearestRoads({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | 46 | test("serializer should transform correctly", () => { 47 | const params = { points: ["0,0"], key: "foo" }; 48 | 49 | expect(defaultParamsSerializer(params)).toEqual("key=foo&points=0%2C0"); 50 | }); 51 | -------------------------------------------------------------------------------- /src/roads/nearestroads.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLng, SnappedPoint, RequestParams } from "../common"; 18 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 19 | import { defaultAxiosInstance } from "../client"; 20 | import { serializer, latLngToString } from "../serialize"; 21 | 22 | export interface NearestRoadsRequest extends Partial { 23 | params: { 24 | /** 25 | * A list of latitude/longitude pairs. Latitude and longitude values should be separated by commas. 26 | * Coordinates should be separated by the pipe character: "|". 27 | * For example: `points=60.170880,24.942795|60.170879,24.942796|60.170877,24.942796`. 28 | */ 29 | points: LatLng[]; 30 | } & RequestParams; 31 | } 32 | 33 | export interface NearestRoadsResponse extends AxiosResponse { 34 | data: { 35 | /** An array of snapped points. */ 36 | snappedPoints: SnappedPoint[]; 37 | }; 38 | } 39 | 40 | export const defaultUrl = "https://roads.googleapis.com/v1/nearestRoads"; 41 | export const defaultParamsSerializer = serializer( 42 | { 43 | points: (o) => o.map((latLng) => latLngToString(latLng)), 44 | }, 45 | defaultUrl 46 | ); 47 | 48 | export function nearestRoads( 49 | { 50 | params, 51 | method = "get", 52 | url = defaultUrl, 53 | paramsSerializer = defaultParamsSerializer, 54 | ...config 55 | }: NearestRoadsRequest, 56 | axiosInstance: AxiosInstance = defaultAxiosInstance 57 | ): Promise { 58 | return axiosInstance({ 59 | params, 60 | method, 61 | url, 62 | paramsSerializer, 63 | ...config, 64 | }) as Promise; 65 | } 66 | -------------------------------------------------------------------------------- /src/roads/snaptoroads.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import axios from "axios"; 18 | import { 19 | snapToRoads, 20 | defaultParamsSerializer, 21 | defaultUrl, 22 | } from "./snaptoroads"; 23 | 24 | jest.mock("axios"); 25 | 26 | const mockedAxios = axios as jest.Mocked; 27 | 28 | afterEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test("snapToRoads should call axios correctly", () => { 33 | const params = { path: ["0,0"], key: "foo" }; 34 | 35 | snapToRoads({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | 46 | test("serializer should transform correctly", () => { 47 | const params = { path: ["0,0"], key: "foo" }; 48 | 49 | expect(defaultParamsSerializer(params)).toEqual("key=foo&path=0%2C0"); 50 | }); 51 | -------------------------------------------------------------------------------- /src/roads/snaptoroads.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLng, SnappedPoint, RequestParams } from "../common"; 18 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 19 | import { defaultAxiosInstance } from "../client"; 20 | import { serializer, latLngToString } from "../serialize"; 21 | 22 | export interface SnapToRoadsRequest extends Partial { 23 | params: { 24 | /** 25 | * The path to be snapped. The `path` parameter accepts a list of latitude/longitude pairs. 26 | * Latitude and longitude values should be separated by commas. Coordinates should be separated by the pipe character: `"|"`. 27 | * For example: `path=60.170880,24.942795|60.170879,24.942796|60.170877,24.942796`. 28 | * 29 | * **Note:** The snapping algorithm works best for points that are not too far apart. 30 | * If you observe odd snapping behavior, try creating paths that have points closer together. 31 | * To ensure the best snap-to-road quality, you should aim to provide paths on which consecutive pairs 32 | * of points are within 300m of each other. This will also help in handling any isolated, long jumps between 33 | * consecutive points caused by GPS signal loss, or noise. 34 | */ 35 | path: LatLng[]; 36 | /** 37 | * Whether to interpolate a path to include all points forming the full road-geometry. 38 | * When true, additional interpolated points will also be returned, resulting in a path that smoothly follows 39 | * the geometry of the road, even around corners and through tunnels. 40 | * Interpolated paths will most likely contain more points than the original path. 41 | * 42 | * @default false 43 | */ 44 | interpolate?: boolean; 45 | } & RequestParams; 46 | } 47 | 48 | export interface SnapToRoadsResponse extends AxiosResponse { 49 | data: { 50 | /** An array of snapped points. */ 51 | snappedPoints: SnappedPoint[]; 52 | }; 53 | } 54 | export const defaultUrl = "https://roads.googleapis.com/v1/snapToRoads"; 55 | export const defaultParamsSerializer = serializer( 56 | { 57 | path: (o) => o.map(latLngToString), 58 | }, 59 | defaultUrl 60 | ); 61 | 62 | export function snapToRoads( 63 | { 64 | params, 65 | method = "get", 66 | url = defaultUrl, 67 | paramsSerializer = defaultParamsSerializer, 68 | ...config 69 | }: SnapToRoadsRequest, 70 | axiosInstance: AxiosInstance = defaultAxiosInstance 71 | ): Promise { 72 | return axiosInstance({ 73 | params, 74 | method, 75 | url, 76 | paramsSerializer, 77 | ...config, 78 | }) as Promise; 79 | } 80 | -------------------------------------------------------------------------------- /src/serialize.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLng, LatLngLiteral } from "./common"; 18 | import { 19 | createPremiumPlanQueryString, 20 | latLngArrayToStringMaybeEncoded, 21 | latLngBoundsToString, 22 | latLngToString, 23 | objectToString, 24 | serializer, 25 | toLatLngLiteral, 26 | toTimestamp, 27 | } from "./serialize"; 28 | 29 | test("latLngToString is correct", () => { 30 | expect(latLngToString("")).toBe(""); 31 | expect(latLngToString("10,20")).toBe("10,20"); 32 | expect(latLngToString([10, 20])).toBe("10,20"); 33 | expect(latLngToString({ lat: 10, lng: 20 })).toBe("10,20"); 34 | expect(latLngToString({ latitude: 10, longitude: 20 })).toBe("10,20"); 35 | expect(() => { 36 | latLngToString({} as LatLngLiteral); 37 | }).toThrow(TypeError); 38 | }); 39 | 40 | test("latLngBoundsToString is correct", () => { 41 | expect(latLngBoundsToString("")).toBe(""); 42 | expect( 43 | latLngBoundsToString({ 44 | southwest: { lat: 1, lng: 2 }, 45 | northeast: { lat: 3, lng: 4 }, 46 | }) 47 | ).toBe("1,2|3,4"); 48 | }); 49 | 50 | test("serializer", () => { 51 | expect( 52 | serializer({ quz: (o) => o }, "http://mock.url")({ foo: ["bar"] }) 53 | ).toBe("foo=bar"); 54 | expect( 55 | serializer( 56 | { 57 | foo: (o) => o.map((latLng: LatLng) => latLngToString(latLng)), 58 | }, 59 | "http://mock.url" 60 | )({ 61 | foo: [ 62 | [0, 1], 63 | [2, 3], 64 | ], 65 | }) 66 | ).toBe("foo=0%2C1|2%2C3"); 67 | }); 68 | 69 | test("serializer should not mutate params", () => { 70 | const location = { lat: 0, lng: 1 }; 71 | const params = { 72 | location, 73 | }; 74 | 75 | serializer({ location: latLngToString }, "http://mock.url")(params); 76 | expect(params.location).toBe(location); 77 | }); 78 | 79 | test("serializer should return pipe joined arrays by default", () => { 80 | expect(serializer({}, "http://mock.url")({ foo: ["b", "a", "r"] })).toBe( 81 | "foo=b|a|r" 82 | ); 83 | }); 84 | 85 | test("serializer creates premium plan query string if premium plan params are included", () => { 86 | const params = { 87 | avoid: "ferries", 88 | destination: { 89 | lat: "38.8977", 90 | lng: "-77.0365", 91 | }, 92 | mode: "driving", 93 | origin: { 94 | lat: "33.8121", 95 | lng: "-117.9190", 96 | }, 97 | units: "imperial", 98 | client_id: "testClient", 99 | client_secret: "testClientSecret", 100 | }; 101 | 102 | expect( 103 | serializer( 104 | { 105 | origin: latLngToString, 106 | destination: latLngToString, 107 | }, 108 | "https://test.url/maps/api/directions/json" 109 | )(params) 110 | ).toEqual( 111 | "avoid=ferries&client=testClient&destination=38.8977%2C-77.0365&mode=driving&origin=33.8121%2C-117.9190&units=imperial&signature=YRJoTd6ohbpsR14WkWv3S7H6MqU=" 112 | ); 113 | }); 114 | 115 | test("objectToString", () => { 116 | expect(objectToString("foo")).toBe("foo"); 117 | expect(objectToString({ c: "c", a: "a", b: "b" })).toBe("a:a|b:b|c:c"); 118 | }); 119 | 120 | test("latLngArrayToStringMaybeEncoded", () => { 121 | expect(latLngArrayToStringMaybeEncoded("0,0")).toEqual("0,0"); 122 | expect(latLngArrayToStringMaybeEncoded([[0, 0]])).toEqual("0,0"); 123 | expect( 124 | latLngArrayToStringMaybeEncoded([ 125 | [40.714728, -73.998672], 126 | [-34.397, 150.644], 127 | ]) 128 | ).toEqual("enc:abowFtzsbMhgmiMuobzi@"); 129 | }); 130 | 131 | test("toLatLngLiteral", () => { 132 | expect(toLatLngLiteral("0,1")).toEqual({ lat: 0, lng: 1 }); 133 | expect(toLatLngLiteral([0, 1])).toEqual({ lat: 0, lng: 1 }); 134 | expect(toLatLngLiteral({ lat: 0, lng: 1 })).toEqual({ 135 | lat: 0, 136 | lng: 1, 137 | }); 138 | expect(toLatLngLiteral({ latitude: 0, longitude: 1 })).toEqual({ 139 | lat: 0, 140 | lng: 1, 141 | }); 142 | expect(() => { 143 | toLatLngLiteral({} as LatLngLiteral); 144 | }).toThrow(TypeError); 145 | }); 146 | 147 | test("toTimestamp", () => { 148 | expect(toTimestamp(100)).toEqual(100); 149 | 150 | const dt = new Date(); 151 | const seconds = Math.round(Number(dt) / 1000); 152 | expect(toTimestamp(dt)).toEqual(seconds); 153 | expect(toTimestamp("now")).toEqual("now"); 154 | 155 | expect(toTimestamp(new Date("2022-06-22T09:03:33.430Z"))).toEqual(1655888613); 156 | }); 157 | 158 | test("createPremiumPlanQueryString", () => { 159 | const serializedParams = { 160 | avoid: "ferries", 161 | destination: "38.8977,-77.0365", 162 | mode: "driving", 163 | origin: "33.8121,-117.9190", 164 | units: "imperial", 165 | client_id: "testClient", 166 | client_secret: "testClientSecret", 167 | }; 168 | const queryStringOptions = { 169 | arrayFormat: "separator", 170 | arrayFormatSeparator: "|", 171 | }; 172 | const baseUrl = "https://test.url/maps/api/directions/json"; 173 | 174 | expect( 175 | createPremiumPlanQueryString(serializedParams, queryStringOptions, baseUrl) 176 | ).toEqual( 177 | "avoid=ferries&client=testClient&destination=38.8977%2C-77.0365&mode=driving&origin=33.8121%2C-117.9190&units=imperial&signature=YRJoTd6ohbpsR14WkWv3S7H6MqU=" 178 | ); 179 | }); 180 | -------------------------------------------------------------------------------- /src/serialize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLng, LatLngBounds, LatLngLiteral } from "./common"; 18 | 19 | import { encodePath } from "./util"; 20 | import { createSignature } from "@googlemaps/url-signature"; 21 | import queryString from "query-string"; 22 | 23 | const qs = queryString.stringify; 24 | 25 | const separator = "|"; 26 | 27 | export function latLngToString(o: LatLng) { 28 | if (typeof o === "string") { 29 | return o; 30 | } else if (Array.isArray(o) && o.length === 2) { 31 | // no transformation 32 | } else if ("lat" in o && "lng" in o) { 33 | o = [o.lat, o.lng]; 34 | } else if ("latitude" in o && "longitude" in o) { 35 | o = [o.latitude, o.longitude]; 36 | } else { 37 | throw new TypeError(); 38 | } 39 | 40 | return o 41 | .map((x) => { 42 | return x.toString(); 43 | }) 44 | .join(","); 45 | } 46 | 47 | export function enableAddressDescriptorToString(o: boolean) { 48 | if (typeof o === "string") { 49 | return o; 50 | } else if (o == true) { 51 | return "True"; 52 | } else { 53 | return "False"; 54 | } 55 | } 56 | 57 | export function objectToString(o: string | object): string { 58 | if (typeof o === "string") { 59 | return o; 60 | } else { 61 | const keys = Object.keys(o); 62 | keys.sort(); 63 | return keys.map((k) => k + ":" + o[k]).join(separator); 64 | } 65 | } 66 | 67 | export function latLngBoundsToString(latLngBounds: string | LatLngBounds) { 68 | if (typeof latLngBounds === "string") { 69 | return latLngBounds; 70 | } else { 71 | return ( 72 | latLngToString(latLngBounds.southwest) + 73 | separator + 74 | latLngToString(latLngBounds.northeast) 75 | ); 76 | } 77 | } 78 | 79 | export function toLatLngLiteral(o: LatLng): LatLngLiteral { 80 | if (typeof o === "string") { 81 | const parts = o.split(",").map(Number); 82 | return { lat: parts[0], lng: parts[1] }; 83 | } else if (Array.isArray(o) && o.length === 2) { 84 | const parts = o.map(Number); 85 | return { lat: parts[0], lng: parts[1] }; 86 | } else if ("lat" in o && "lng" in o) { 87 | return o; 88 | } else if ("latitude" in o && "longitude" in o) { 89 | return { lat: o.latitude, lng: o.longitude }; 90 | } else { 91 | throw new TypeError(); 92 | } 93 | } 94 | 95 | export function latLngArrayToStringMaybeEncoded(o: string | LatLng[]): string { 96 | if (typeof o === "string") { 97 | return o; 98 | } 99 | 100 | const concatenated = o.map(latLngToString).join(separator); 101 | const encoded = `enc:${encodePath(o.map(toLatLngLiteral))}`; 102 | 103 | if (encoded.length < concatenated.length) { 104 | return encoded; 105 | } 106 | 107 | return concatenated; 108 | } 109 | 110 | export type serializerFunction = (any) => string | number | boolean; 111 | export type serializerFormat = { [key: string]: serializerFunction }; 112 | 113 | export function serializer( 114 | format: serializerFormat, 115 | baseUrl: string, 116 | queryStringOptions: object = { 117 | arrayFormat: "separator", 118 | arrayFormatSeparator: separator, 119 | } 120 | ) { 121 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 122 | return (params: Record) => { 123 | // avoid mutating params 124 | const serializedParams = { ...params }; 125 | 126 | for (const key of Object.keys(format)) { 127 | if (key in serializedParams) { 128 | serializedParams[key] = format[key](serializedParams[key]); 129 | } 130 | } 131 | 132 | if ( 133 | "client_id" in serializedParams && 134 | "client_secret" in serializedParams 135 | ) { 136 | // Special case to handle premium plan signature 137 | return createPremiumPlanQueryString( 138 | serializedParams, 139 | queryStringOptions, 140 | baseUrl 141 | ); 142 | } 143 | 144 | return qs(serializedParams, queryStringOptions); 145 | }; 146 | } 147 | 148 | export function toTimestamp(o: "now" | number | Date): number | "now" { 149 | if (o === "now") { 150 | return o; 151 | } 152 | if (o instanceof Date) { 153 | return Math.round(Number(o) / 1000); 154 | } 155 | return o; 156 | } 157 | 158 | export function createPremiumPlanQueryString( 159 | serializedParams: { [key: string]: string }, 160 | queryStringOptions: object, 161 | baseUrl: string 162 | ): string { 163 | serializedParams.client = serializedParams.client_id; 164 | const clientSecret = serializedParams.client_secret; 165 | delete serializedParams.client_id; 166 | delete serializedParams.client_secret; 167 | 168 | const partialQueryString = qs(serializedParams, queryStringOptions); 169 | const unsignedUrl = `${baseUrl}?${partialQueryString}`; 170 | const signature = createSignature(unsignedUrl, clientSecret); 171 | 172 | // The signature must come last 173 | return `${partialQueryString}&signature=${signature}`; 174 | } 175 | -------------------------------------------------------------------------------- /src/timezone.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { defaultParamsSerializer, defaultUrl, timezone } from "./timezone"; 18 | 19 | import axios from "axios"; 20 | 21 | jest.mock("axios"); 22 | 23 | const mockedAxios = axios as jest.Mocked; 24 | 25 | afterEach(() => { 26 | jest.clearAllMocks(); 27 | }); 28 | 29 | test("elevation should call axios correctly", () => { 30 | const params = { 31 | location: { lat: 35, lng: -110 }, 32 | timestamp: 999999999, 33 | key: "foo", 34 | }; 35 | timezone({ params: params }, mockedAxios); 36 | 37 | expect(mockedAxios).toHaveBeenCalledTimes(1); 38 | expect(mockedAxios).toHaveBeenCalledWith({ 39 | method: "get", 40 | params: params, 41 | paramsSerializer: defaultParamsSerializer, 42 | url: defaultUrl, 43 | }); 44 | }); 45 | 46 | test("serializer should handle date object", () => { 47 | const dt = new Date(); 48 | expect(defaultParamsSerializer({ timestamp: dt })).toEqual( 49 | `timestamp=${Math.round(Number(dt) / 1000)}` 50 | ); 51 | }); 52 | -------------------------------------------------------------------------------- /src/timezone.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 18 | import { Language, LatLng, RequestParams, ResponseData } from "./common"; 19 | import { latLngToString, serializer, toTimestamp } from "./serialize"; 20 | 21 | import { defaultAxiosInstance } from "./client"; 22 | 23 | export interface TimeZoneRequest extends Partial { 24 | params: { 25 | /** a comma-separated `lat,lng` tuple (eg. `location=-33.86,151.20`), representing the location to look up. */ 26 | location: LatLng; 27 | /** 28 | * specifies the desired time as seconds since midnight, January 1, 1970 UTC. 29 | * The Time Zone API uses the timestamp to determine whether or not Daylight Savings should be applied, 30 | * based on the time zone of the location. Note that the API does not take historical time zones into account. 31 | * That is, if you specify a past timestamp, the API does not take into account the possibility that 32 | * the location was previously in a different time zone. 33 | */ 34 | timestamp: Date | number; 35 | /** 36 | * The language in which to return results. 37 | * Note that we often update supported languages so this list may not be exhaustive. 38 | * 39 | * @default Language.English 40 | */ 41 | language?: Language; 42 | } & RequestParams; 43 | } 44 | 45 | export interface TimeZoneResponseData extends ResponseData { 46 | /** 47 | * the offset for daylight-savings time in seconds. 48 | * This will be zero if the time zone is not in Daylight Savings Time during the specified `timestamp`. 49 | */ 50 | dstOffset: number; 51 | /** the offset from UTC (in seconds) for the given location. This does not take into effect daylight savings. */ 52 | rawOffset: number; 53 | /** 54 | * a string containing the ID of the time zone, such as "America/Los_Angeles" or "Australia/Sydney". 55 | * These IDs are defined by [Unicode Common Locale Data Repository (CLDR) project](http://cldr.unicode.org/), 56 | * and currently available in file [timezone.xml](http://unicode.org/repos/cldr/trunk/common/bcp47/timezone.xml). 57 | * When a timezone has several IDs, the canonical one is returned. In timezone.xml, this is the first alias of each timezone. 58 | * For example, "Asia/Calcutta" is returned, not "Asia/Kolkata". 59 | */ 60 | timeZoneId: string; 61 | /** 62 | * a string containing the long form name of the time zone. 63 | * This field will be localized if the `language` parameter is set. 64 | * eg. "Pacific Daylight Time" or "Australian Eastern Daylight Time" 65 | */ 66 | timeZoneName: string; 67 | } 68 | 69 | export interface TimeZoneResponse extends AxiosResponse { 70 | data: TimeZoneResponseData; 71 | } 72 | 73 | export const defaultUrl = "https://maps.googleapis.com/maps/api/timezone/json"; 74 | export const defaultParamsSerializer = serializer( 75 | { 76 | timestamp: toTimestamp, 77 | location: latLngToString, 78 | }, 79 | defaultUrl 80 | ); 81 | export function timezone( 82 | { 83 | params, 84 | method = "get", 85 | url = defaultUrl, 86 | paramsSerializer = defaultParamsSerializer, 87 | ...config 88 | }: TimeZoneRequest, 89 | axiosInstance: AxiosInstance = defaultAxiosInstance 90 | ): Promise { 91 | return axiosInstance({ 92 | params, 93 | method, 94 | url, 95 | paramsSerializer, 96 | ...config, 97 | }) as Promise; 98 | } 99 | -------------------------------------------------------------------------------- /src/util.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { encodePath, decodePath } from "./util"; 18 | 19 | describe("polyline encoding and decoding is correct", () => { 20 | const encoded = 21 | "gcneIpgxzRcDnBoBlEHzKjBbHlG`@`IkDxIiKhKoMaLwTwHeIqHuAyGXeB~Ew@fFjAtIzExF"; 22 | const decoded = [ 23 | { 24 | lat: 53.489320000000006, 25 | lng: -104.16777, 26 | }, 27 | { 28 | lat: 53.490140000000004, 29 | lng: -104.16833000000001, 30 | }, 31 | { 32 | lat: 53.490700000000004, 33 | lng: -104.16936000000001, 34 | }, 35 | { 36 | lat: 53.49065, 37 | lng: -104.17142000000001, 38 | }, 39 | { 40 | lat: 53.49011, 41 | lng: -104.17288, 42 | }, 43 | { 44 | lat: 53.488760000000006, 45 | lng: -104.17305, 46 | }, 47 | { 48 | lat: 53.48715000000001, 49 | lng: -104.17219000000001, 50 | }, 51 | { 52 | lat: 53.485420000000005, 53 | lng: -104.17022000000001, 54 | }, 55 | { 56 | lat: 53.483450000000005, 57 | lng: -104.1679, 58 | }, 59 | { 60 | lat: 53.48554000000001, 61 | lng: -104.16442, 62 | }, 63 | { 64 | lat: 53.487100000000005, 65 | lng: -104.16279000000002, 66 | }, 67 | { 68 | lat: 53.48863000000001, 69 | lng: -104.16236, 70 | }, 71 | { 72 | lat: 53.49004000000001, 73 | lng: -104.16249, 74 | }, 75 | { 76 | lat: 53.490550000000006, 77 | lng: -104.16361, 78 | }, 79 | { 80 | lat: 53.49083, 81 | lng: -104.16477, 82 | }, 83 | { 84 | lat: 53.49045, 85 | lng: -104.16648, 86 | }, 87 | { 88 | lat: 53.48935, 89 | lng: -104.16773, 90 | }, 91 | ]; 92 | 93 | test("encodePath is correct", () => { 94 | expect(encodePath(decoded)).toEqual(encoded); 95 | expect(encodePath([])).toEqual(""); 96 | }); 97 | 98 | test("decodePath is correct", () => { 99 | expect(decodePath(encoded)).toEqual(decoded); 100 | expect(decodePath("")).toEqual([]); 101 | }); 102 | 103 | test("roundtrip", () => { 104 | expect(encodePath(decodePath(encoded))).toEqual(encoded); 105 | expect(decodePath(encodePath(decoded))).toEqual(decoded); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | import { LatLngLiteral } from "./common"; 18 | 19 | /** 20 | * Polyline encodes an array of LatLng objects. 21 | * 22 | * See {@link https://developers.google.com/maps/documentation/utilities/polylinealgorithm} 23 | * 24 | */ 25 | export function encodePath(path: LatLngLiteral[]): string { 26 | const result: string[] = []; 27 | let start: [number, number] = [0, 0]; 28 | let end: [number, number]; 29 | 30 | const encodePart = function (part: number) { 31 | part = part < 0 ? ~(part << 1) : part << 1; 32 | while (part >= 0x20) { 33 | result.push(String.fromCharCode((0x20 | (part & 0x1f)) + 63)); 34 | part >>= 5; 35 | } 36 | result.push(String.fromCharCode(part + 63)); 37 | }; 38 | 39 | for (let i = 0, I = path.length || 0; i < I; ++i) { 40 | end = [Math.round(path[i].lat * 1e5), Math.round(path[i].lng * 1e5)]; 41 | encodePart(end[0] - start[0]); // lat 42 | encodePart(end[1] - start[1]); // lng 43 | start = end; 44 | } 45 | 46 | return result.join(""); 47 | } 48 | 49 | /** 50 | * Decodes a polyline encoded string. 51 | * 52 | * See {@link https://developers.google.com/maps/documentation/utilities/polylinealgorithm} 53 | */ 54 | export function decodePath(encodedPath: string): LatLngLiteral[] { 55 | const len: number = encodedPath.length || 0; 56 | const path = new Array(Math.floor(encodedPath.length / 2)); 57 | let index: number = 0; 58 | let lat: number = 0; 59 | let lng: number = 0; 60 | let pointIndex: number; 61 | 62 | for (pointIndex = 0; index < len; ++pointIndex) { 63 | let result: number = 1; 64 | let shift: number = 0; 65 | let b: number; 66 | do { 67 | b = encodedPath.charCodeAt(index++) - 63 - 1; 68 | result += b << shift; 69 | shift += 5; 70 | } while (b >= 0x1f); 71 | lat += result & 1 ? ~(result >> 1) : result >> 1; 72 | 73 | result = 1; 74 | shift = 0; 75 | do { 76 | b = encodedPath.charCodeAt(index++) - 63 - 1; 77 | result += b << shift; 78 | shift += 5; 79 | } while (b >= 0x1f); 80 | lng += result & 1 ? ~(result >> 1) : result >> 1; 81 | 82 | path[pointIndex] = { lat: lat * 1e-5, lng: lng * 1e-5 }; 83 | } 84 | path.length = pointIndex; 85 | 86 | return path; 87 | } 88 | -------------------------------------------------------------------------------- /test-module-loading.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | failed=0 4 | 5 | cjsCode='const {Client} = require("./dist/"); new Client();' 6 | esmCode='import {Client} from "./dist/index.js"; new Client();' 7 | 8 | echo "test loading as commonJS..." 9 | if ! node -e "${cjsCode}" ; then 10 | failed=1 11 | echo 'Loading package as commomJS failed' >&2 12 | fi 13 | 14 | echo "test loading as ESM..." 15 | if ! node --input-type module -e "${esmCode}" ; then 16 | failed=1 17 | echo 'Loading package as ESM failed' >&2 18 | fi 19 | 20 | if [ $failed -eq 1 ] ; then exit 1 ; fi 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "allowJs": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "declarationDir": "./dist", 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"] 14 | }, 15 | "include": [ 16 | "src/**/*.ts" 17 | ], 18 | "exclude": [ 19 | "node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /typedoc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | module.exports = { 18 | out: "docs", 19 | exclude: ["**/node_modules/**", "**/*.spec.ts", "**/*.test.ts", "dist"], 20 | name: "Google Maps Services Node Client", 21 | readme: "./README.md", 22 | }; 23 | --------------------------------------------------------------------------------