├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── pull_request_template.md ├── stale.yml ├── sync-repo-settings.yaml └── workflows │ ├── dependabot.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .releaserc ├── AUTHORS ├── CODE_OF_CONDUCT.md ├── CONTRIB.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── SECURITY.md ├── addressdescriptor.go ├── client.go ├── client_test.go ├── directions.go ├── directions_test.go ├── distancematrix.go ├── distancematrix_test.go ├── doc.go ├── elevation.go ├── elevation_test.go ├── encoding.go ├── encoding_test.go ├── examples ├── directions │ └── cmdline │ │ └── main.go ├── distancematrix │ └── cmdline │ │ └── main.go ├── elevation │ └── cmdline │ │ └── main.go ├── geocoding │ └── cmdline │ │ └── main.go ├── places │ ├── findplacefromtext │ │ └── findplacefromtext.go │ ├── nearbysearch │ │ └── nearbysearch.go │ ├── photo │ │ └── photo.go │ ├── placeautocomplete │ │ └── placeautocomplete.go │ ├── placedetails │ │ └── placedetails.go │ ├── queryautocomplete │ │ └── queryautocomplete.go │ └── textsearch │ │ └── textsearch.go ├── roads │ ├── snaptoroad │ │ └── snaptoroad.go │ └── speedlimits │ │ └── speedlimits.go ├── staticmap │ └── cmdline │ │ └── main.go └── timezone │ └── cmdline │ └── main.go ├── geocoding.go ├── geocoding_test.go ├── geolocation.go ├── geolocation_test.go ├── go.mod ├── go.sum ├── internal ├── signer.go ├── signer_test.go ├── types.go └── types_test.go ├── latlng.go ├── latlng_test.go ├── metrics ├── metric.go ├── metric_test.go ├── opencensus.go └── opencensus_test.go ├── places.go ├── places_test.go ├── polyline.go ├── polyline_test.go ├── roads.go ├── roads_test.go ├── staticmap.go ├── staticmap_test.go ├── timezone.go ├── timezone_test.go ├── transport.go ├── transport_test.go └── types.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/ @googlemaps/admin 2 | -------------------------------------------------------------------------------- /.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/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/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 120 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 180 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - pinned 16 | - "type: bug" 17 | 18 | # Set to true to ignore issues in a project (defaults to false) 19 | exemptProjects: false 20 | 21 | # Set to true to ignore issues in a milestone (defaults to false) 22 | exemptMilestones: false 23 | 24 | # Set to true to ignore issues with an assignee (defaults to false) 25 | exemptAssignees: false 26 | 27 | # Label to use when marking as stale 28 | staleLabel: "stale" 29 | 30 | # Comment to post when marking as stale. Set to `false` to disable 31 | markComment: > 32 | This issue has been automatically marked as stale because it has not had 33 | recent activity. Please comment here if it is still valid so that we can 34 | reprioritize. Thank you! 35 | 36 | # Comment to post when removing the stale label. 37 | # unmarkComment: > 38 | # Your comment here. 39 | 40 | # Comment to post when closing a stale Issue or Pull Request. 41 | closeComment: > 42 | Closing this. Please reopen if you believe it should be addressed. Thank you for your contribution. 43 | 44 | # Limit the number of actions per hour, from 1-30. Default is 30 45 | limitPerRun: 10 46 | 47 | # Limit to only `issues` or `pulls` 48 | only: issues 49 | 50 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 51 | # pulls: 52 | # daysUntilStale: 30 53 | # markComment: > 54 | # This pull request has been automatically marked as stale because it has not had 55 | # recent activity. It will be closed if no further activity occurs. Thank you 56 | # for your contributions. 57 | 58 | # issues: 59 | # exemptLabels: 60 | # - confirmed 61 | -------------------------------------------------------------------------------- /.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/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Release 16 | on: 17 | push: 18 | branches: [ master ] 19 | jobs: 20 | release: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | with: 26 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 27 | 28 | - uses: actions/setup-node@v2 29 | with: 30 | node-version: '14' 31 | - name: Semantic Release 32 | uses: cycjimmy/semantic-release-action@v3.4.1 33 | with: 34 | extra_plugins: | 35 | "@semantic-release/commit-analyzer@8.0.1" 36 | "@semantic-release/release-notes-generator@9.0.3" 37 | "@semantic-release/git@9.0.1" 38 | "@semantic-release/github@7.2.3" 39 | env: 40 | GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 41 | -------------------------------------------------------------------------------- /.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 | on: [push, pull_request, workflow_call] 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/setup-go@v3 22 | with: 23 | go-version: 'stable' 24 | id: go 25 | - uses: actions/checkout@v3 26 | - run: go build -v ./... 27 | - run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 28 | - uses: codecov/codecov-action@v3 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | .idea 3 | .vscode 4 | coverage.txt -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | branches: 2 | - master 3 | plugins: 4 | - "@semantic-release/commit-analyzer" 5 | - "@semantic-release/release-notes-generator" 6 | - "@semantic-release/git" 7 | - "@semantic-release/github" 8 | options: 9 | debug: true 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Go Client for Google Maps Services authors 2 | # for copyright purposes. This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | # Please keep the list sorted. 10 | 11 | Google Inc. 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Google Open Source Community Guidelines 2 | 3 | At Google, we recognize and celebrate the creativity and collaboration of open 4 | source contributors and the diversity of skills, experiences, cultures, and 5 | opinions they bring to the projects and communities they participate in. 6 | 7 | Every one of Google's open source projects and communities are inclusive 8 | environments, based on treating all individuals respectfully, regardless of 9 | gender identity and expression, sexual orientation, disabilities, 10 | neurodiversity, physical appearance, body size, ethnicity, nationality, race, 11 | age, religion, or similar personal characteristic. 12 | 13 | We value diverse opinions, but we value respectful behavior more. 14 | 15 | Respectful behavior includes: 16 | 17 | * Being considerate, kind, constructive, and helpful. 18 | * Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or 19 | physically threatening behavior, speech, and imagery. 20 | * Not engaging in unwanted physical contact. 21 | 22 | Some Google open source projects [may adopt][] an explicit project code of 23 | conduct, which may have additional detailed expectations for participants. Most 24 | of those projects will use our [modified Contributor Covenant][]. 25 | 26 | [may adopt]: https://opensource.google/docs/releasing/preparing/#conduct 27 | [modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ 28 | 29 | ## Resolve peacefully 30 | 31 | We do not believe that all conflict is necessarily bad; healthy debate and 32 | disagreement often yields positive results. However, it is never okay to be 33 | disrespectful. 34 | 35 | If you see someone behaving disrespectfully, you are encouraged to address the 36 | behavior directly with those involved. Many issues can be resolved quickly and 37 | easily, and this gives people more control over the outcome of their dispute. 38 | If you are unable to resolve the matter for any reason, or if the behavior is 39 | threatening or harassing, report it. We are dedicated to providing an 40 | environment where participants feel welcome and safe. 41 | 42 | ## Reporting problems 43 | 44 | Some Google open source projects may adopt a project-specific code of conduct. 45 | In those cases, a Google employee will be identified as the Project Steward, 46 | who will receive and handle reports of code of conduct violations. In the event 47 | that a project hasn’t identified a Project Steward, you can report problems by 48 | emailing opensource@google.com. 49 | 50 | We will investigate every complaint, but you may not receive a direct response. 51 | We will use our discretion in determining when and how to follow up on reported 52 | incidents, which may range from not taking action to permanent expulsion from 53 | the project and project-sponsored spaces. We will notify the accused of the 54 | report and provide them an opportunity to discuss it before any action is 55 | taken. The identity of the reporter will be omitted from the details of the 56 | report supplied to the accused. In potentially harmful situations, such as 57 | ongoing harassment or threats to anyone's safety, we may take action without 58 | notice. 59 | 60 | *This document was adapted from the [IndieWeb Code of Conduct][] and can also 61 | be found at .* 62 | 63 | [IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct 64 | -------------------------------------------------------------------------------- /CONTRIB.md: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | Want to help out? That's awesome! 5 | 6 | The library is open source and lives on GitHub at: 7 | https://github.com/googlemaps/google-maps-services-go. 8 | Open an issue or fork the library and submit a pull request. 9 | 10 | Keep in mind that before we can accept any pull requests we have to jump 11 | through a couple of legal hurdles, primarily a Contributor License Agreement 12 | (CLA): 13 | 14 | - **If you are an individual writing original source code** 15 | and you're sure you own the intellectual property, 16 | then you'll need to sign an 17 | [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). 18 | - **If you work for a company that wants to allow you to contribute your work**, 19 | then you'll need to sign a 20 | [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html) 21 | 22 | Follow either of the two links above to access the appropriate CLA and 23 | instructions for how to sign and return it. Once we receive it, we'll be able 24 | to accept your pull requests. 25 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who have contributed to the project. The 2 | # copyright is held by those individuals or organizations in the AUTHORS file. 3 | # 4 | # Names should be added to this file like so: 5 | # Name 6 | 7 | # Please keep the list sorted by first name. 8 | 9 | Brett Morgan @domesticmouse 10 | Chris Broadfoot @broady 11 | James MacWhyte @jmacwhyte 12 | Sam Thorogood @samthor 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Client for Google Maps Services 2 | ================================== 3 | 4 | [![GoDoc](https://godoc.org/googlemaps.github.io/maps?status.svg)](https://godoc.org/googlemaps.github.io/maps) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/googlemaps/google-maps-services-go)](https://goreportcard.com/report/github.com/googlemaps/google-maps-services-go) 6 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/googlemaps/google-maps-services-go) 7 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 8 | 9 | ## Description 10 | 11 | Use Go? This library brings many [Google Maps Platform Web Services APIs] to your Go application. 12 | 13 | The Go Client for Google Maps Services is a Go Client library for the following Google Maps Platform 14 | APIs: 15 | 16 | - [Directions API] 17 | - [Distance Matrix API] 18 | - [Elevation API] 19 | - [Geocoding API] 20 | - [Places API] 21 | - [Roads API] 22 | - [Time Zone API] 23 | - [Maps Static API] 24 | 25 | > [!TIP] 26 | > See the [Google Maps Platform Cloud Client Library for Go](https://github.com/googleapis/google-cloud-go/tree/main/maps) for our newer APIs 27 | > including Address Validation API, Datasets API, Fleet Engine, new Places API, and Routes API. 28 | 29 | ## Requirements 30 | 31 | - Go 1.7 or later. 32 | - A Google Maps Platform [API key] from a project with the APIs below enabled. 33 | 34 | > [!IMPORTANT] 35 | > This key should be kept secret on your server. 36 | 37 | ## Installation 38 | 39 | To install the Go Client for Google Maps Services, please execute the following `go get` command. 40 | 41 | ```bash 42 | go get googlemaps.github.io/maps 43 | ``` 44 | 45 | ## Documentation 46 | 47 | View the [reference documentation](https://godoc.org/googlemaps.github.io/maps). 48 | 49 | Additional documentation about the APIs is available at: 50 | 51 | - [Directions API] 52 | - [Distance Matrix API] 53 | - [Elevation API] 54 | - [Geocoding API] 55 | - [Places API] 56 | - [Time Zone API] 57 | - [Roads API] 58 | - [Maps Static API] 59 | 60 | ## Usage 61 | 62 | Sample usage of the Directions API with an API key: 63 | 64 | ```go 65 | package main 66 | 67 | import ( 68 | "context" 69 | "log" 70 | 71 | "github.com/kr/pretty" 72 | "googlemaps.github.io/maps" 73 | ) 74 | 75 | func main() { 76 | c, err := maps.NewClient(maps.WithAPIKey("Insert-API-Key-Here")) 77 | if err != nil { 78 | log.Fatalf("fatal error: %s", err) 79 | } 80 | r := &maps.DirectionsRequest{ 81 | Origin: "Sydney", 82 | Destination: "Perth", 83 | } 84 | route, _, err := c.Directions(context.Background(), r) 85 | if err != nil { 86 | log.Fatalf("fatal error: %s", err) 87 | } 88 | 89 | pretty.Println(route) 90 | } 91 | ``` 92 | 93 | Sample usage of the Geocoding API with an API key to get an [Address Descriptor](https://developers.google.com/maps/documentation/geocoding/address-descriptors/requests-address-descriptors): 94 | 95 | ```go 96 | package main 97 | 98 | import ( 99 | "context" 100 | "log" 101 | 102 | "github.com/kr/pretty" 103 | "googlemaps.github.io/maps" 104 | ) 105 | 106 | func main() { 107 | c, err := maps.NewClient(maps.WithAPIKey("Insert-API-Key-Here")) 108 | if err != nil { 109 | log.Fatalf("fatal error: %s", err) 110 | } 111 | r := &maps.GeocodingRequest{ 112 | LatLng: &LatLng{Lat: 40.714224, Lng: -73.961452}, 113 | EnableAddressDescriptor: True 114 | } 115 | reverseGeocodingResponse, _, err := c.ReverseGeocode(context.Background(), r) 116 | if err != nil { 117 | log.Fatalf("fatal error: %s", err) 118 | } 119 | 120 | pretty.Println(reverseGeocodingResponse) 121 | } 122 | ``` 123 | 124 | ## Features 125 | 126 | ### Rate limiting 127 | 128 | Never sleep between requests again! By default, requests are sent at the expected rate limits for 129 | each web service, typically 50 queries per second for free users. If you want to speed up or slow 130 | down requests, you can do that too, using `maps.NewClient(maps.WithAPIKey(apiKey), maps.WithRateLimit(qps))`. 131 | 132 | ### Native types 133 | 134 | Native objects for each of the API responses. 135 | 136 | ### Monitoring 137 | 138 | It's possible to get metrics for status counts and latency histograms for monitoring. 139 | Use `maps.WithMetricReporter(metrics.OpenCensusReporter{})` to log metrics to OpenCensus, 140 | and `metrics.RegisterViews()` to make the metrics available to be exported. 141 | OpenCensus can export these metrics to a [variety of monitoring services](https://opencensus.io/exporters/). 142 | You can also implement your own metric reporter instead of using the provided one. 143 | 144 | ## Terms of Service 145 | 146 | This library uses Google Maps Platform services, and any use of Google Maps Platform is subject to the [Terms of Service](https://cloud.google.com/maps-platform/terms). 147 | 148 | For clarity, this library, and each underlying component, is not a Google Maps Platform Core Service. 149 | 150 | ## Support 151 | 152 | This library is offered via an open source license. It is not governed by the Google Maps Platform Support [Technical Support Services Guidelines](https://cloud.google.com/maps-platform/terms/tssg), the [SLA](https://cloud.google.com/maps-platform/terms/sla), or the [Deprecation Policy](https://cloud.google.com/maps-platform/terms) (however, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service). 153 | 154 | This library adheres to [semantic versioning](https://semver.org/) to indicate when backwards-incompatible changes are introduced. 155 | 156 | If you find a bug, or have a feature request, please [file an issue][issues] on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our [developer community channels](https://developers.google.com/maps/developer-community). If you'd like to contribute, please check the [Contributing guide][contrib]. 157 | 158 | You can also discuss this library on our [Discord server](https://discord.gg/hYsWbmk). 159 | 160 | [API key]: https://developers.google.com/maps/documentation/places/web-service/get-api-key 161 | 162 | [Google Maps Platform Web Services APIs]: https://developers.google.com/maps/apis-by-platform#web_service_apis 163 | [Directions API]: https://developers.google.com/maps/documentation/directions/ 164 | [Distance Matrix API]: https://developers.google.com/maps/documentation/distancematrix/ 165 | [Elevation API]: https://developers.google.com/maps/documentation/elevation/ 166 | [Geocoding API]: https://developers.google.com/maps/documentation/geocoding/ 167 | [Places API]: https://developers.google.com/places/web-service/ 168 | [Roads API]: https://developers.google.com/maps/documentation/roads/ 169 | [Time Zone API]: https://developers.google.com/maps/documentation/timezone/ 170 | [Maps Static API]: https://developers.google.com/maps/documentation/maps-static/ 171 | 172 | [issues]: https://github.com/googlemaps/google-maps-services-go/issues 173 | [contrib]: https://github.com/googlemaps/google-maps-services-go/blob/master/CONTRIB.md 174 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /addressdescriptor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | /** 22 | * An enum representing the relationship in space between the landmark and the target. 23 | */ 24 | type SpatialRelationship string 25 | 26 | const ( 27 | // This is the default relationship when nothing more specific below 28 | // applies. 29 | SPATIAL_RELATIONSHIP_NEAR SpatialRelationship = "NEAR" 30 | // The landmark has a spatial geometry and the target is within its 31 | // bounds. 32 | SPATIAL_RELATIONSHIP_WITHIN SpatialRelationship = "WITHIN" 33 | // The target is directly adjacent to the landmark or landmark's access 34 | // point. 35 | SPATIAL_RELATIONSHIP_BESIDE SpatialRelationship = "BESIDE" 36 | // The target is directly opposite the landmark on the other side of the 37 | // road. 38 | SPATIAL_RELATIONSHIP_ACROSS_THE_ROAD SpatialRelationship = "ACROSS_THE_ROAD" 39 | // On the same route as the landmark but not besides or across. 40 | SPATIAL_RELATIONSHIP_DOWN_THE_ROAD SpatialRelationship = "DOWN_THE_ROAD" 41 | // Not on the same route as the landmark but a single 'turn' away. 42 | SPATIAL_RELATIONSHIP_AROUND_THE_CORNER SpatialRelationship = "AROUND_THE_CORNER" 43 | // Close to the landmark's structure but further away from its access 44 | // point. 45 | SPATIAL_RELATIONSHIP_BEHIND SpatialRelationship = "BEHIND" 46 | ) 47 | 48 | // String method for formatted output 49 | func (sr SpatialRelationship) String() string { 50 | return string(sr) 51 | } 52 | 53 | /** 54 | * An enum representing the relationship in space between the area and the target. 55 | */ 56 | type Containment string 57 | 58 | const ( 59 | /** 60 | * Indicates an unknown containment returned by the server. 61 | */ 62 | CONTAINMENT_UNSPECIFIED Containment = "CONTAINMENT_UNSPECIFIED" 63 | /** The target location is within the area region, close to the center. */ 64 | CONTAINMENT_WITHIN Containment = "WITHIN" 65 | /** The target location is within the area region, close to the edge. */ 66 | CONTAINMENT_OUTSKIRTS Containment = "OUTSKIRTS" 67 | /** The target location is outside the area region, but close by. */ 68 | CONTAINMENT_NEAR Containment = "NEAR" 69 | ) 70 | 71 | // String method for formatted output 72 | func (c Containment) String() string { 73 | return string(c) 74 | } 75 | 76 | /** 77 | * Localized variant of a text in a particular language. 78 | */ 79 | type LocalizedText struct { 80 | // Localized string in the language corresponding to language_code below. 81 | Text string `json:"text"` 82 | // The text's BCP-47 language code, such as "en-US" or "sr-Latn". 83 | // 84 | // For more information, see 85 | // http://www.unicode.org/reports/tr35/#Unicode_locale_identifier. 86 | LanguageCode string `json:"language_code"` 87 | } 88 | 89 | // String method for formatted output 90 | func (lt LocalizedText) String() string { 91 | return fmt.Sprintf("(text=%s, languageCode=%s)", lt.Text, lt.LanguageCode) 92 | } 93 | 94 | // Landmarks that are useful at describing a location. 95 | type Landmark struct { 96 | // The Place ID of the underlying establishment serving as the landmark. 97 | // Can be used to resolve more information about the landmark through Place 98 | // Details or Place Id Lookup. 99 | PlaceID string `json:"place_id"` 100 | // The best name for the landmark. 101 | DisplayName LocalizedText `json:"display_name"` 102 | // One or more values indicating the type of the returned result. Please see Types 104 | // for more detail. 105 | Types []string `json:"types"` 106 | // Defines the spatial relationship between the target location and the 107 | // landmark. 108 | SpatialRelationship SpatialRelationship `json:"spatial_relationship"` 109 | // The straight line distance between the target location and one of the 110 | // landmark's access points. 111 | StraightLineDistanceMeters float32 `json:"straight_line_distance_meters"` 112 | // The travel distance along the road network between the target 113 | // location's closest point on a road, and the landmark's closest access 114 | // point on a road. This can be unpopulated if the landmark is disconnected 115 | // from the part of the road network the target is closest to OR if the 116 | // target location was not actually considered to be on the road network. 117 | TravelDistanceMeters float32 `json:"travel_distance_meters"` 118 | } 119 | 120 | // Precise regions that are useful at describing a location. 121 | type Area struct { 122 | // The Place ID of the underlying area feature. Can be used to 123 | // resolve more information about the area through Place Details or 124 | // Place Id Lookup. 125 | PlaceID string `json:"place_id"` 126 | // The best name for the area. 127 | DisplayName LocalizedText `json:"display_name"` 128 | /** 129 | * An enum representing the relationship in space between the area and the target. 130 | */ 131 | Containment Containment `json:"containment"` 132 | } 133 | 134 | /** 135 | * Represents a descriptor of an address. 136 | * 137 | *

Please see Address 139 | * Descriptors for more detail. 140 | */ 141 | type AddressDescriptor struct { 142 | // A ranked list of nearby landmarks. The most useful (recognizable and 143 | // nearby) landmarks are ranked first. 144 | Landmarks []Landmark `json:"landmarks"` 145 | // A ranked list of containing or adjacent areas. The most useful 146 | // (recognizable and precise) areas are ranked first. 147 | Areas []Area `json:"areas"` 148 | } -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/google/uuid" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestClientChannelIsConfigured(t *testing.T) { 28 | _, err := NewClient(WithAPIKey("AIza-Maps-API-Key"), WithChannel("Test-Channel")) 29 | if err != nil { 30 | t.Errorf("Unable to create client with channel") 31 | } 32 | } 33 | 34 | func TestClientWithExperienceId(t *testing.T) { 35 | ids := []string{"foo", "bar"} 36 | c, err := NewClient(WithAPIKey("AIza-Maps-API-Key"), WithExperienceId(ids...)) 37 | assert.Nil(t, err) 38 | assert.Equal(t, c.experienceId, ids) 39 | } 40 | 41 | func TestClientSetExperienceId(t *testing.T) { 42 | ids := []string{"foo", "bar"} 43 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key")) 44 | 45 | c.setExperienceId(ids...) 46 | assert.Equal(t, c.experienceId, ids) 47 | } 48 | 49 | func TestClientGetExperienceId(t *testing.T) { 50 | ids := []string{"foo", "bar"} 51 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key")) 52 | 53 | c.experienceId = ids 54 | assert.Equal(t, c.getExperienceId(), ids) 55 | } 56 | 57 | func TestClientClearExperienceId(t *testing.T) { 58 | ids := []string{"foo", "bar"} 59 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key")) 60 | 61 | c.experienceId = ids 62 | c.clearExperienceId() 63 | assert.Nil(t, c.experienceId) 64 | } 65 | 66 | func TestClientSetExperienceIdHeader(t *testing.T) { 67 | ids := []string{"foo", "bar"} 68 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key")) 69 | 70 | // slice has two elements 71 | c.experienceId = ids 72 | req, _ := http.NewRequest("GET", "/", nil) 73 | c.setExperienceIdHeader(context.Background(), req) 74 | assert.Equal(t, strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName)) 75 | 76 | // slice is nil 77 | c.experienceId = nil 78 | req, _ = http.NewRequest("GET", "/", nil) 79 | c.setExperienceIdHeader(context.Background(), req) 80 | assert.Equal(t, "", req.Header.Get(ExperienceIdHeaderName)) 81 | 82 | // slice is empty 83 | c.experienceId = []string{} 84 | req, _ = http.NewRequest("GET", "/", nil) 85 | c.setExperienceIdHeader(context.Background(), req) 86 | assert.Equal(t, "", req.Header.Get(ExperienceIdHeaderName)) 87 | 88 | var ctx context.Context 89 | // context has one element 90 | c.experienceId = []string{} 91 | ctx = context.Background() 92 | ctx = ExperienceIdContext(ctx, "foo") 93 | req, _ = http.NewRequest("GET", "/", nil) 94 | c.setExperienceIdHeader(ctx, req) 95 | assert.Equal(t, "foo", req.Header.Get(ExperienceIdHeaderName)) 96 | 97 | // context has two elements 98 | c.experienceId = []string{} 99 | ctx = context.Background() 100 | ctx = ExperienceIdContext(ctx, ids...) 101 | req, _ = http.NewRequest("GET", "/", nil) 102 | c.setExperienceIdHeader(ctx, req) 103 | assert.Equal(t, strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName)) 104 | 105 | // context has two elements and client has two elements 106 | c.experienceId = ids 107 | ctx = context.Background() 108 | ctx = ExperienceIdContext(ctx, ids...) 109 | req, _ = http.NewRequest("GET", "/", nil) 110 | c.setExperienceIdHeader(ctx, req) 111 | assert.Equal(t, strings.Join(ids, ",") + "," + strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName)) 112 | } 113 | 114 | func TestClientExperienceIdSample(t *testing.T) { 115 | // [START maps_experience_id] 116 | experienceId := uuid.New().String() 117 | 118 | // instantiate client with experience id 119 | client, _ := NewClient(WithAPIKey("AIza-Maps-API-Key"), WithExperienceId("foo")) 120 | 121 | // clear the current experience id 122 | client.clearExperienceId() 123 | 124 | // set a new experience id 125 | otherExperienceId := uuid.New().String() 126 | client.setExperienceId(experienceId, otherExperienceId) 127 | 128 | // make API request, the client will set the header 129 | // X-GOOG-MAPS-EXPERIENCE-ID: experienceId,otherExperienceId 130 | 131 | // get current experience id 132 | var ids []string 133 | ids = client.getExperienceId() 134 | // [END maps_experience_id] 135 | 136 | assert.Equal(t, ids, []string{experienceId, otherExperienceId}) 137 | } 138 | -------------------------------------------------------------------------------- /distancematrix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Distance Matrix API is available on 16 | // https://developers.google.com/maps/documentation/distancematrix/ 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "net/url" 24 | "strings" 25 | "time" 26 | ) 27 | 28 | var distanceMatrixAPI = &apiConfig{ 29 | host: "https://maps.googleapis.com", 30 | path: "/maps/api/distancematrix/json", 31 | acceptsClientID: true, 32 | acceptsSignature: false, 33 | } 34 | 35 | // DistanceMatrix makes a Distance Matrix API request 36 | func (c *Client) DistanceMatrix(ctx context.Context, r *DistanceMatrixRequest) (*DistanceMatrixResponse, error) { 37 | 38 | if len(r.Origins) == 0 { 39 | return nil, errors.New("maps: origins empty") 40 | } 41 | if len(r.Destinations) == 0 { 42 | return nil, errors.New("maps: destinations empty") 43 | } 44 | if r.DepartureTime != "" && r.ArrivalTime != "" { 45 | return nil, errors.New("maps: DepartureTime and ArrivalTime both specified") 46 | } 47 | if len(r.TransitMode) != 0 && r.Mode != TravelModeTransit { 48 | return nil, errors.New("maps: TransitMode specified while Mode != TravelModeTransit") 49 | } 50 | if r.TransitRoutingPreference != "" && r.Mode != TravelModeTransit { 51 | return nil, errors.New("maps: mode of transit '" + string(r.Mode) + "' invalid for TransitRoutingPreference") 52 | } 53 | if r.Mode == TravelModeTransit && r.TrafficModel != "" { 54 | return nil, errors.New("maps: cannot specify transit mode and traffic model together") 55 | } 56 | 57 | var response struct { 58 | commonResponse 59 | DistanceMatrixResponse 60 | } 61 | 62 | if err := c.getJSON(ctx, distanceMatrixAPI, r, &response); err != nil { 63 | return nil, err 64 | } 65 | 66 | if err := response.StatusError(); err != nil { 67 | return nil, err 68 | } 69 | 70 | return &response.DistanceMatrixResponse, nil 71 | } 72 | 73 | func (r *DistanceMatrixRequest) params() url.Values { 74 | q := make(url.Values) 75 | q.Set("origins", strings.Join(r.Origins, "|")) 76 | q.Set("destinations", strings.Join(r.Destinations, "|")) 77 | if r.Mode != "" { 78 | q.Set("mode", string(r.Mode)) 79 | } 80 | if r.Language != "" { 81 | q.Set("language", r.Language) 82 | } 83 | if r.Avoid != "" { 84 | q.Set("avoid", string(r.Avoid)) 85 | } 86 | if r.Units != "" { 87 | q.Set("units", string(r.Units)) 88 | } 89 | if r.DepartureTime != "" { 90 | q.Set("departure_time", r.DepartureTime) 91 | } 92 | if r.ArrivalTime != "" { 93 | q.Set("arrival_time", r.ArrivalTime) 94 | } 95 | if r.TrafficModel != "" { 96 | q.Set("traffic_model", string(r.TrafficModel)) 97 | } 98 | if len(r.TransitMode) != 0 { 99 | var transitMode []string 100 | for _, t := range r.TransitMode { 101 | transitMode = append(transitMode, string(t)) 102 | } 103 | q.Set("transit_mode", strings.Join(transitMode, "|")) 104 | } 105 | if r.TransitRoutingPreference != "" { 106 | q.Set("transit_routing_preference", string(r.TransitRoutingPreference)) 107 | } 108 | return q 109 | } 110 | 111 | // DistanceMatrixRequest is the request struct for Distance Matrix APi 112 | type DistanceMatrixRequest struct { 113 | // Origins is a list of addresses and/or textual latitude/longitude values 114 | // from which to calculate distance and time. Required. 115 | Origins []string 116 | // Destinations is a list of addresses and/or textual latitude/longitude values 117 | // to which to calculate distance and time. Required. 118 | Destinations []string 119 | // Mode specifies the mode of transport to use when calculating distance. 120 | // Valid values are `ModeDriving`, `ModeWalking`, `ModeBicycling` 121 | // and `ModeTransit`. Optional. 122 | Mode Mode 123 | // Language in which to return results. Optional. 124 | Language string 125 | // Avoid introduces restrictions to the route. Valid values are `AvoidTolls`, 126 | // `AvoidHighways` and `AvoidFerries`. Optional. 127 | Avoid Avoid 128 | // Units Specifies the unit system to use when expressing distance as text. 129 | // Valid values are `UnitsMetric` and `UnitsImperial`. Optional. 130 | Units Units 131 | // DepartureTime is the desired time of departure. You can specify the time as 132 | // an integer in seconds since midnight, January 1, 1970 UTC. Alternatively, 133 | // you can specify a value of `"now"``. Optional. 134 | DepartureTime string 135 | // ArrivalTime specifies the desired time of arrival for transit requests, 136 | // in seconds since midnight, January 1, 1970 UTC. You cannot specify 137 | // both `DepartureTime` and `ArrivalTime`. Optional. 138 | ArrivalTime string 139 | // TrafficModel determines the type of model that will be used when determining 140 | // travel time when using depature times in the future. Options are 141 | // `TrafficModelBestGuess`, `TrafficModelOptimistic`` or `TrafficModelPessimistic`. 142 | // Optional. Default is `TrafficModelBestGuess`` 143 | TrafficModel TrafficModel 144 | // TransitMode specifies one or more preferred modes of transit. This parameter 145 | // may only be specified for requests where the mode is `transit`. Valid values 146 | // are `TransitModeBus`, `TransitModeSubway`, `TransitModeTrain`, `TransitModeTram`, 147 | // and `TransitModeRail`. Optional. 148 | TransitMode []TransitMode 149 | // TransitRoutingPreference Specifies preferences for transit requests. Valid 150 | // values are `TransitRoutingPreferenceLessWalking` and 151 | // `TransitRoutingPreferenceFewerTransfers`. Optional. 152 | TransitRoutingPreference TransitRoutingPreference 153 | } 154 | 155 | // DistanceMatrixResponse represents a Distance Matrix API response. 156 | type DistanceMatrixResponse struct { 157 | 158 | // OriginAddresses contains an array of addresses as returned by the API from 159 | // your original request. 160 | OriginAddresses []string `json:"origin_addresses"` 161 | // DestinationAddresses contains an array of addresses as returned by the API 162 | // from your original request. 163 | DestinationAddresses []string `json:"destination_addresses"` 164 | // Rows contains an array of elements. 165 | Rows []DistanceMatrixElementsRow `json:"rows"` 166 | } 167 | 168 | // DistanceMatrixElementsRow is a row of distance elements. 169 | type DistanceMatrixElementsRow struct { 170 | Elements []*DistanceMatrixElement `json:"elements"` 171 | } 172 | 173 | // DistanceMatrixElement is the travel distance and time for a pair of origin 174 | // and destination. 175 | type DistanceMatrixElement struct { 176 | Status string `json:"status"` 177 | // Duration is the length of time it takes to travel this route. 178 | Duration time.Duration `json:"duration"` 179 | // DurationInTraffic is the length of time it takes to travel this route 180 | // considering traffic. 181 | DurationInTraffic time.Duration `json:"duration_in_traffic"` 182 | // Distance is the total distance of this route. 183 | Distance Distance `json:"distance"` 184 | } 185 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | /* 16 | Package maps provides a client library for the Google Maps Web Service APIs. 17 | Please see https://developers.google.com/maps/documentation/webservices/ for 18 | an overview of the Maps Web Service API suite. 19 | */ 20 | package maps // import "googlemaps.github.io/maps" 21 | -------------------------------------------------------------------------------- /elevation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Distance Matrix API is available on 16 | // https://developers.google.com/maps/documentation/distancematrix/ 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "net/url" 24 | "strconv" 25 | ) 26 | 27 | var elevationAPI = &apiConfig{ 28 | host: "https://maps.googleapis.com", 29 | path: "/maps/api/elevation/json", 30 | acceptsClientID: true, 31 | acceptsSignature: false, 32 | } 33 | 34 | // Elevation makes an Elevation API request 35 | func (c *Client) Elevation(ctx context.Context, r *ElevationRequest) ([]ElevationResult, error) { 36 | 37 | if len(r.Path) == 0 && len(r.Locations) == 0 { 38 | return nil, errors.New("maps: Path and Locations empty") 39 | } 40 | 41 | // Sampled path request 42 | if len(r.Path) > 0 && r.Samples == 0 { 43 | return nil, errors.New("maps: Samples empty") 44 | } 45 | 46 | var response struct { 47 | Results []ElevationResult `json:"results"` 48 | commonResponse 49 | } 50 | 51 | if err := c.getJSON(ctx, elevationAPI, r, &response); err != nil { 52 | return nil, err 53 | } 54 | 55 | if err := response.StatusError(); err != nil { 56 | return nil, err 57 | } 58 | 59 | return response.Results, nil 60 | } 61 | 62 | func (r *ElevationRequest) params() url.Values { 63 | q := make(url.Values) 64 | 65 | if len(r.Path) > 0 { 66 | q.Set("path", "enc:"+Encode(r.Path)) 67 | q.Set("samples", strconv.Itoa(r.Samples)) 68 | } 69 | 70 | if len(r.Locations) > 0 { 71 | q.Set("locations", "enc:"+Encode(r.Locations)) 72 | } 73 | 74 | return q 75 | } 76 | 77 | // ElevationRequest is the request structure for Elevation API. Either Locations or 78 | // Path must be set. 79 | type ElevationRequest struct { 80 | // Locations defines the location(s) on the earth from which to return elevation 81 | // data. 82 | Locations []LatLng 83 | // Path defines a path on the earth for which to return elevation data. 84 | Path []LatLng 85 | // Samples specifies the number of sample points along a path for which to return 86 | // elevation data. Required if Path is supplied. 87 | Samples int 88 | } 89 | 90 | // ElevationResult is a single elevation at a specific location 91 | type ElevationResult struct { 92 | // Location is the position for which elevation data is being computed. 93 | Location *LatLng `json:"location"` 94 | // Elevation indicates the elevation of the location in meters 95 | Elevation float64 `json:"elevation"` 96 | // Resolution indicates the maximum distance between data points from which the 97 | // elevation was interpolated, in meters. 98 | Resolution float64 `json:"resolution"` 99 | } 100 | -------------------------------------------------------------------------------- /elevation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "context" 19 | "reflect" 20 | "testing" 21 | ) 22 | 23 | func TestElevationDenver(t *testing.T) { 24 | 25 | // Elevation of Denver, the mile high city 26 | response := `{ 27 | "results" : [ 28 | { 29 | "elevation" : 1608.637939453125, 30 | "location" : { 31 | "lat" : 39.73915360, 32 | "lng" : -104.98470340 33 | }, 34 | "resolution" : 4.771975994110107 35 | } 36 | ], 37 | "status" : "OK" 38 | }` 39 | 40 | server := mockServer(200, response) 41 | defer server.Close() 42 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 43 | r := &ElevationRequest{ 44 | Locations: []LatLng{ 45 | { 46 | Lat: 39.73915360, 47 | Lng: -104.9847034, 48 | }, 49 | }, 50 | } 51 | 52 | resp, err := c.Elevation(context.Background(), r) 53 | 54 | if len(resp) != 1 { 55 | t.Errorf("Expected length of response is 1, was %+v", len(resp)) 56 | } 57 | if err != nil { 58 | t.Errorf("r.Get returned non nil error, was %+v", err) 59 | } 60 | 61 | correctResponse := ElevationResult{ 62 | Location: &LatLng{ 63 | Lat: 39.73915360, 64 | Lng: -104.98470340, 65 | }, 66 | Elevation: 1608.637939453125, 67 | Resolution: 4.771975994110107, 68 | } 69 | 70 | if !reflect.DeepEqual(resp[0], correctResponse) { 71 | t.Errorf("expected %+v, was %+v", correctResponse, resp[0]) 72 | } 73 | } 74 | 75 | func TestElevationSampledPath(t *testing.T) { 76 | 77 | response := `{ 78 | "results" : [ 79 | { 80 | "elevation" : 4411.941894531250, 81 | "location" : { 82 | "lat" : 36.5785810, 83 | "lng" : -118.2919940 84 | }, 85 | "resolution" : 19.08790397644043 86 | }, 87 | { 88 | "elevation" : 1381.861694335938, 89 | "location" : { 90 | "lat" : 36.41150289067028, 91 | "lng" : -117.5602607523847 92 | }, 93 | "resolution" : 19.08790397644043 94 | }, 95 | { 96 | "elevation" : -84.61699676513672, 97 | "location" : { 98 | "lat" : 36.239980, 99 | "lng" : -116.831710 100 | }, 101 | "resolution" : 19.08790397644043 102 | } 103 | ], 104 | "status" : "OK" 105 | }` 106 | 107 | server := mockServer(200, response) 108 | defer server.Close() 109 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 110 | r := &ElevationRequest{ 111 | Path: []LatLng{ 112 | {Lat: 36.578581, Lng: -118.291994}, 113 | {Lat: 36.23998, Lng: -116.83171}, 114 | }, 115 | Samples: 3, 116 | } 117 | 118 | resp, err := c.Elevation(context.Background(), r) 119 | 120 | if len(resp) != 3 { 121 | t.Errorf("Expected length of response is 3, was %+v", len(resp)) 122 | } 123 | if err != nil { 124 | t.Errorf("r.Get returned non nil error, was %+v", err) 125 | } 126 | 127 | correctResponse := ElevationResult{ 128 | Location: &LatLng{ 129 | Lat: 36.5785810, 130 | Lng: -118.2919940, 131 | }, 132 | Elevation: 4411.941894531250, 133 | Resolution: 19.08790397644043, 134 | } 135 | 136 | if !reflect.DeepEqual(resp[0], correctResponse) { 137 | t.Errorf("expected %+v, was %+v", correctResponse, resp[0]) 138 | } 139 | } 140 | 141 | func TestElevationNoPathOrLocations(t *testing.T) { 142 | c, _ := NewClient(WithAPIKey(apiKey)) 143 | r := &ElevationRequest{} 144 | 145 | if _, err := c.Elevation(context.Background(), r); err == nil { 146 | t.Errorf("Missing both Path and Locations should return error") 147 | } 148 | } 149 | 150 | func TestElevationPathWithNoSamples(t *testing.T) { 151 | c, _ := NewClient(WithAPIKey(apiKey)) 152 | r := &ElevationRequest{ 153 | Path: []LatLng{ 154 | {Lat: 36.578581, Lng: -118.291994}, 155 | {Lat: 36.23998, Lng: -116.83171}, 156 | }, 157 | } 158 | 159 | if _, err := c.Elevation(context.Background(), r); err == nil { 160 | t.Errorf("Missing both Path and Locations should return error") 161 | } 162 | } 163 | 164 | func TestElevationFailingServer(t *testing.T) { 165 | server := mockServer(500, `{"status" : "ERROR"}`) 166 | defer server.Close() 167 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 168 | r := &ElevationRequest{ 169 | Path: []LatLng{ 170 | {Lat: 36.578581, Lng: -118.291994}, 171 | {Lat: 36.23998, Lng: -116.83171}, 172 | }, 173 | Samples: 3, 174 | } 175 | 176 | if _, err := c.Elevation(context.Background(), r); err == nil { 177 | t.Errorf("Failing server should return error") 178 | } 179 | } 180 | 181 | func TestElevationCancelledContext(t *testing.T) { 182 | c, _ := NewClient(WithAPIKey(apiKey)) 183 | r := &ElevationRequest{ 184 | Path: []LatLng{ 185 | {Lat: 36.578581, Lng: -118.291994}, 186 | {Lat: 36.23998, Lng: -116.83171}, 187 | }, 188 | Samples: 3, 189 | } 190 | ctx, cancel := context.WithCancel(context.Background()) 191 | cancel() 192 | if _, err := c.Elevation(ctx, r); err == nil { 193 | t.Errorf("Cancelled context should return non-nil err") 194 | } 195 | } 196 | 197 | func TestElevationRequestURL(t *testing.T) { 198 | expectedQuery := "key=AIzaNotReallyAnAPIKey&locations=enc%3A_ibE_seK_seK_seK&path=enc%3A_qo%5D_%7Brc%40_seK_seK&samples=10" 199 | 200 | server := mockServerForQuery(expectedQuery, 200, `{"status":"OK"}"`) 201 | defer server.s.Close() 202 | 203 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.s.URL)) 204 | 205 | r := &ElevationRequest{ 206 | Locations: []LatLng{{1, 2}, {3, 4}}, 207 | Path: []LatLng{{5, 6}, {7, 8}}, 208 | Samples: 10, 209 | } 210 | 211 | _, err := c.Elevation(context.Background(), r) 212 | if err != nil { 213 | t.Errorf("Unexpected error in constructing request URL: %+v", err) 214 | } 215 | if server.successful != 1 { 216 | t.Errorf("Got URL(s) %v, want %s", server.failed, expectedQuery) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /encoding_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "encoding/json" 19 | "reflect" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | const ( 25 | jsonSnappedPoint = `{"originalIndex":null,"placeId":"helloPlace","location":{"latitude":-33.870315,"longitude":151.196532}}` 26 | ) 27 | 28 | func TestSnappedPoint(t *testing.T) { 29 | sp := SnappedPoint{ 30 | Location: LatLng{ 31 | Lat: -33.870315, 32 | Lng: 151.196532, 33 | }, 34 | PlaceID: "helloPlace", 35 | } 36 | 37 | bytes, err := json.Marshal(&sp) 38 | if err != nil { 39 | t.Errorf("expected ok encode of SnappedPoint, got: %v", err) 40 | } 41 | if string(bytes) != jsonSnappedPoint { 42 | t.Errorf("expected encoded snappedPoint, was: %v", string(bytes)) 43 | } 44 | 45 | var out SnappedPoint 46 | err = json.Unmarshal(bytes, &out) 47 | if err != nil { 48 | t.Errorf("expected ok decode of SnappedPoint, got: %v", err) 49 | } 50 | if !reflect.DeepEqual(out, sp) { 51 | t.Errorf("expected equal snappedPoint, was %+v expected %+v", out, sp) 52 | } 53 | } 54 | 55 | func TestDistanceMatrixElement_MarshalJSON(t *testing.T) { 56 | dme := &DistanceMatrixElement{ 57 | Duration: 1*time.Second, 58 | DurationInTraffic: 2*time.Second, 59 | } 60 | b, err := dme.MarshalJSON() 61 | if err != nil { 62 | t.Errorf("expected ok encode of DistanceMatrixElement, got: %v", err) 63 | } 64 | 65 | out := &DistanceMatrixElement{} 66 | err = out.UnmarshalJSON(b) 67 | if err != nil { 68 | t.Errorf("expected ok decode of DistanceMatrixElement, got: %v", err) 69 | } 70 | if !reflect.DeepEqual(dme, out) { 71 | t.Errorf("expected equal DistanceMatrixElement, was %+v expected %+v", out, dme) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/directions/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Directions API 16 | // Directions docs: https://developers.google.com/maps/documentation/directions/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | "time" 27 | 28 | "github.com/kr/pretty" 29 | "googlemaps.github.io/maps" 30 | ) 31 | 32 | var ( 33 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 34 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 35 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 36 | origin = flag.String("origin", "", "The address or textual latitude/longitude value from which you wish to calculate directions.") 37 | destination = flag.String("destination", "", "The address or textual latitude/longitude value from which you wish to calculate directions.") 38 | mode = flag.String("mode", "", "The travel mode for this directions request.") 39 | departureTime = flag.String("departure_time", "", "The depature time for transit mode directions request.") 40 | arrivalTime = flag.String("arrival_time", "", "The arrival time for transit mode directions request.") 41 | waypoints = flag.String("waypoints", "", "The waypoints for driving directions request, | separated.") 42 | alternatives = flag.Bool("alternatives", false, "Whether the Directions service may provide more than one route alternative in the response.") 43 | avoid = flag.String("avoid", "", "Indicates that the calculated route(s) should avoid the indicated features, | separated.") 44 | language = flag.String("language", "", "Specifies the language in which to return results.") 45 | units = flag.String("units", "", "Specifies the unit system to use when returning results.") 46 | region = flag.String("region", "", "Specifies the region code, specified as a ccTLD (\"top-level domain\") two-character value.") 47 | transitMode = flag.String("transit_mode", "", "Specifies one or more preferred modes of transit, | separated. This parameter may only be specified for transit directions.") 48 | transitRoutingPreference = flag.String("transit_routing_preference", "", "Specifies preferences for transit routes.") 49 | iterations = flag.Int("iterations", 1, "Number of times to make API request.") 50 | trafficModel = flag.String("traffic_model", "", "Specifies traffic prediction model when request future directions. Valid values are optimistic, best_guess, and pessimistic. Optional.") 51 | ) 52 | 53 | func usageAndExit(msg string) { 54 | fmt.Fprintln(os.Stderr, msg) 55 | fmt.Println("Flags:") 56 | flag.PrintDefaults() 57 | os.Exit(2) 58 | } 59 | 60 | func check(err error) { 61 | if err != nil { 62 | log.Fatalf("fatal error: %s", err) 63 | } 64 | } 65 | 66 | func main() { 67 | flag.Parse() 68 | 69 | var client *maps.Client 70 | var err error 71 | if *apiKey != "" { 72 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey), maps.WithRateLimit(2)) 73 | } else if *clientID != "" || *signature != "" { 74 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 75 | } else { 76 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 77 | } 78 | check(err) 79 | 80 | r := &maps.DirectionsRequest{ 81 | Origin: *origin, 82 | Destination: *destination, 83 | DepartureTime: *departureTime, 84 | ArrivalTime: *arrivalTime, 85 | Alternatives: *alternatives, 86 | Language: *language, 87 | Region: *region, 88 | } 89 | 90 | lookupMode(*mode, r) 91 | lookupUnits(*units, r) 92 | lookupTransitRoutingPreference(*transitRoutingPreference, r) 93 | lookupTrafficModel(*trafficModel, r) 94 | 95 | if *waypoints != "" { 96 | r.Waypoints = strings.Split(*waypoints, "|") 97 | } 98 | 99 | if *avoid != "" { 100 | for _, a := range strings.Split(*avoid, "|") { 101 | switch a { 102 | case "tolls": 103 | r.Avoid = append(r.Avoid, maps.AvoidTolls) 104 | case "highways": 105 | r.Avoid = append(r.Avoid, maps.AvoidHighways) 106 | case "ferries": 107 | r.Avoid = append(r.Avoid, maps.AvoidFerries) 108 | default: 109 | log.Fatalf("Unknown avoid restriction %s", a) 110 | } 111 | } 112 | } 113 | if *transitMode != "" { 114 | for _, t := range strings.Split(*transitMode, "|") { 115 | switch t { 116 | case "bus": 117 | r.TransitMode = append(r.TransitMode, maps.TransitModeBus) 118 | case "subway": 119 | r.TransitMode = append(r.TransitMode, maps.TransitModeSubway) 120 | case "train": 121 | r.TransitMode = append(r.TransitMode, maps.TransitModeTrain) 122 | case "tram": 123 | r.TransitMode = append(r.TransitMode, maps.TransitModeTram) 124 | case "rail": 125 | r.TransitMode = append(r.TransitMode, maps.TransitModeRail) 126 | } 127 | } 128 | } 129 | 130 | if *iterations == 1 { 131 | routes, waypoints, err := client.Directions(context.Background(), r) 132 | check(err) 133 | 134 | pretty.Println(waypoints) 135 | pretty.Println(routes) 136 | } else { 137 | done := make(chan iterationResult) 138 | for i := 0; i < *iterations; i++ { 139 | go func(i int) { 140 | startTime := time.Now() 141 | _, _, err := client.Directions(context.Background(), r) 142 | done <- iterationResult{ 143 | fmt.Sprintf("Iteration %2d: round trip %.2f seconds", i, float64(time.Now().Sub(startTime))/1000000000), 144 | err, 145 | } 146 | }(i) 147 | } 148 | 149 | for i := 0; i < *iterations; i++ { 150 | result := <-done 151 | if err != nil { 152 | fmt.Printf("error: %+v\n", result.err) 153 | } else { 154 | fmt.Println(result.result) 155 | } 156 | } 157 | } 158 | } 159 | 160 | type iterationResult struct { 161 | result string 162 | err error 163 | } 164 | 165 | func lookupMode(mode string, r *maps.DirectionsRequest) { 166 | switch mode { 167 | case "driving": 168 | r.Mode = maps.TravelModeDriving 169 | case "walking": 170 | r.Mode = maps.TravelModeWalking 171 | case "bicycling": 172 | r.Mode = maps.TravelModeBicycling 173 | case "transit": 174 | r.Mode = maps.TravelModeTransit 175 | case "": 176 | // ignore 177 | default: 178 | log.Fatalf("Unknown mode '%s'", mode) 179 | } 180 | } 181 | 182 | func lookupUnits(units string, r *maps.DirectionsRequest) { 183 | switch units { 184 | case "metric": 185 | r.Units = maps.UnitsMetric 186 | case "imperial": 187 | r.Units = maps.UnitsImperial 188 | case "": 189 | // ignore 190 | default: 191 | log.Fatalf("Unknown units '%s'", units) 192 | } 193 | } 194 | 195 | func lookupTransitRoutingPreference(transitRoutingPreference string, r *maps.DirectionsRequest) { 196 | switch transitRoutingPreference { 197 | case "fewer_transfers": 198 | r.TransitRoutingPreference = maps.TransitRoutingPreferenceFewerTransfers 199 | case "less_walking": 200 | r.TransitRoutingPreference = maps.TransitRoutingPreferenceLessWalking 201 | case "": 202 | // ignore 203 | default: 204 | log.Fatalf("Unknown transit routing preference %s", transitRoutingPreference) 205 | } 206 | } 207 | 208 | func lookupTrafficModel(trafficModel string, r *maps.DirectionsRequest) { 209 | switch trafficModel { 210 | case "optimistic": 211 | r.TrafficModel = maps.TrafficModelOptimistic 212 | case "best_guess": 213 | r.TrafficModel = maps.TrafficModelBestGuess 214 | case "pessimistic": 215 | r.TrafficModel = maps.TrafficModelPessimistic 216 | case "": 217 | // ignore 218 | default: 219 | log.Fatalf("Unknown traffic mode %s", trafficModel) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /examples/distancematrix/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for DistanceMatrix 16 | // Directions docs: https://developers.google.com/maps/documentation/distancematrix/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 34 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 35 | origins = flag.String("origins", "", "One or more addresses and/or textual latitude/longitude values, separated with the pipe (|) character, from which to calculate distance and time.") 36 | destinations = flag.String("destinations", "", "One or more addresses and/or textual latitude/longitude values, separated with the pipe (|) character, to which to calculate distance and time.") 37 | mode = flag.String("mode", "", "Specifies the mode of transport to use when calculating distance.") 38 | language = flag.String("language", "", "The language in which to return results.") 39 | avoid = flag.String("avoid", "", "Introduces restrictions to the route.") 40 | units = flag.String("units", "", "Specifies the unit system to use when expressing distance as text.") 41 | departureTime = flag.String("departure_time", "", "The desired time of departure.") 42 | arrivalTime = flag.String("arrival_time", "", "Specifies the desired time of arrival.") 43 | transitMode = flag.String("transit_mode", "", "Specifies one or more preferred modes of transit.") 44 | transitRoutingPreference = flag.String("transit_routing_preference", "", "Specifies preferences for transit requests.") 45 | trafficModel = flag.String("traffic_model", "", "Specifies the assumptions to use when calculating time in traffic.") 46 | ) 47 | 48 | func usageAndExit(msg string) { 49 | fmt.Fprintln(os.Stderr, msg) 50 | fmt.Println("Flags:") 51 | flag.PrintDefaults() 52 | os.Exit(2) 53 | } 54 | 55 | func check(err error) { 56 | if err != nil { 57 | log.Fatalf("fatal error: %s", err) 58 | } 59 | } 60 | 61 | func main() { 62 | flag.Parse() 63 | 64 | var client *maps.Client 65 | var err error 66 | if *apiKey != "" { 67 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 68 | } else if *clientID != "" || *signature != "" { 69 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 70 | } else { 71 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 72 | } 73 | check(err) 74 | 75 | r := &maps.DistanceMatrixRequest{ 76 | Language: *language, 77 | DepartureTime: *departureTime, 78 | ArrivalTime: *arrivalTime, 79 | } 80 | 81 | if *origins != "" { 82 | r.Origins = strings.Split(*origins, "|") 83 | } 84 | if *destinations != "" { 85 | r.Destinations = strings.Split(*destinations, "|") 86 | } 87 | 88 | lookupMode(*mode, r) 89 | lookupAvoid(*avoid, r) 90 | lookupUnits(*units, r) 91 | lookupTransitMode(*transitMode, r) 92 | lookupTransitRoutingPreference(*transitRoutingPreference, r) 93 | lookupTrafficModel(*trafficModel, r) 94 | 95 | resp, err := client.DistanceMatrix(context.Background(), r) 96 | check(err) 97 | 98 | pretty.Println(resp) 99 | } 100 | 101 | func lookupMode(mode string, r *maps.DistanceMatrixRequest) { 102 | switch mode { 103 | case "driving": 104 | r.Mode = maps.TravelModeDriving 105 | case "walking": 106 | r.Mode = maps.TravelModeWalking 107 | case "bicycling": 108 | r.Mode = maps.TravelModeBicycling 109 | case "transit": 110 | r.Mode = maps.TravelModeTransit 111 | case "": 112 | // ignore 113 | default: 114 | log.Fatalf("Unknown mode %s", mode) 115 | } 116 | } 117 | 118 | func lookupAvoid(avoid string, r *maps.DistanceMatrixRequest) { 119 | switch avoid { 120 | case "tolls": 121 | r.Avoid = maps.AvoidTolls 122 | case "highways": 123 | r.Avoid = maps.AvoidHighways 124 | case "ferries": 125 | r.Avoid = maps.AvoidFerries 126 | case "": 127 | // ignore 128 | default: 129 | log.Fatalf("Unknown avoid restriction %s", avoid) 130 | } 131 | } 132 | 133 | func lookupUnits(units string, r *maps.DistanceMatrixRequest) { 134 | switch units { 135 | case "metric": 136 | r.Units = maps.UnitsMetric 137 | case "imperial": 138 | r.Units = maps.UnitsImperial 139 | case "": 140 | // ignore 141 | default: 142 | log.Fatalf("Unknown units %s", units) 143 | } 144 | } 145 | 146 | func lookupTransitMode(transitMode string, r *maps.DistanceMatrixRequest) { 147 | if transitMode != "" { 148 | for _, m := range strings.Split(transitMode, "|") { 149 | switch m { 150 | case "bus": 151 | r.TransitMode = append(r.TransitMode, maps.TransitModeBus) 152 | case "subway": 153 | r.TransitMode = append(r.TransitMode, maps.TransitModeSubway) 154 | case "train": 155 | r.TransitMode = append(r.TransitMode, maps.TransitModeTrain) 156 | case "tram": 157 | r.TransitMode = append(r.TransitMode, maps.TransitModeTram) 158 | case "rail": 159 | r.TransitMode = append(r.TransitMode, maps.TransitModeRail) 160 | default: 161 | log.Fatalf("Unknown transit_mode %s", m) 162 | } 163 | } 164 | } 165 | } 166 | 167 | func lookupTransitRoutingPreference(transitRoutingPreference string, r *maps.DistanceMatrixRequest) { 168 | switch transitRoutingPreference { 169 | case "fewer_transfers": 170 | r.TransitRoutingPreference = maps.TransitRoutingPreferenceFewerTransfers 171 | case "less_walking": 172 | r.TransitRoutingPreference = maps.TransitRoutingPreferenceLessWalking 173 | case "": 174 | // ignore 175 | default: 176 | log.Fatalf("Unknown transit routing preference %s", transitRoutingPreference) 177 | } 178 | } 179 | 180 | func lookupTrafficModel(trafficModel string, r *maps.DistanceMatrixRequest) { 181 | switch trafficModel { 182 | case "best_guess": 183 | r.TrafficModel = maps.TrafficModelBestGuess 184 | case "pessimistic": 185 | r.TrafficModel = maps.TrafficModelPessimistic 186 | case "optimistic": 187 | r.TrafficModel = maps.TrafficModelOptimistic 188 | case "": 189 | // ignore 190 | default: 191 | log.Fatalf("Unknown traffic_model %s", trafficModel) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /examples/elevation/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Elevation API 16 | // Directions docs: https://developers.google.com/maps/documentation/distancematrix/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 34 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 35 | locations = flag.String("locations", "", "defines the location(s) on the earth from which to return elevation data. This parameter takes either a single location as a comma-separated pair or multiple latitude/longitude pairs passed as an array or as an encoded polyline.") 36 | path = flag.String("path", "", "defines a path on the earth for which to return elevation data.") 37 | samples = flag.Int("samples", 0, "specifies the number of sample points along a path for which to return elevation data.") 38 | ) 39 | 40 | func usageAndExit(msg string) { 41 | fmt.Fprintln(os.Stderr, msg) 42 | fmt.Println("Flags:") 43 | flag.PrintDefaults() 44 | os.Exit(2) 45 | } 46 | 47 | func check(err error) { 48 | if err != nil { 49 | log.Fatalf("fatal error: %s", err) 50 | } 51 | } 52 | 53 | func main() { 54 | flag.Parse() 55 | 56 | var client *maps.Client 57 | var err error 58 | if *apiKey != "" { 59 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 60 | } else if *clientID != "" || *signature != "" { 61 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 62 | } else { 63 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 64 | } 65 | check(err) 66 | 67 | r := &maps.ElevationRequest{} 68 | 69 | if *samples > 0 { 70 | r.Samples = *samples 71 | } 72 | 73 | if *locations != "" { 74 | l, err := decodeLocations(*locations) 75 | check(err) 76 | r.Locations = l 77 | } 78 | 79 | if *path != "" { 80 | p, err := decodePath(*path) 81 | check(err) 82 | r.Path = p 83 | } 84 | 85 | resp, err := client.Elevation(context.Background(), r) 86 | if err != nil { 87 | log.Fatalf("Could not request elevations: %v", err) 88 | } 89 | 90 | pretty.Println(resp) 91 | } 92 | 93 | // decodeLocations takes a location argument string and decodes it. 94 | // This argument has three different forms, as per documentation at 95 | // https://developers.google.com/maps/documentation/elevation/#Locations 96 | func decodeLocations(location string) ([]maps.LatLng, error) { 97 | if strings.HasPrefix(location, "enc:") { 98 | return maps.DecodePolyline(location[len("enc:"):]) 99 | } 100 | 101 | if strings.Contains(location, "|") { 102 | return maps.ParseLatLngList(location) 103 | } 104 | 105 | // single location 106 | ll, err := maps.ParseLatLng(location) 107 | check(err) 108 | return []maps.LatLng{ll}, nil 109 | } 110 | 111 | // decodePath takes a location argument string and decodes it. 112 | // This argument has two different forms, as per documentation at 113 | // https://developers.google.com/maps/documentation/elevation/#Paths 114 | func decodePath(path string) ([]maps.LatLng, error) { 115 | if strings.HasPrefix(path, "enc:") { 116 | return maps.DecodePolyline(path[len("enc:"):]) 117 | } 118 | result := []maps.LatLng{} 119 | if strings.Contains(path, "|") { 120 | // | delimited list of locations 121 | ls := strings.Split(path, "|") 122 | for _, l := range ls { 123 | ll, err := maps.ParseLatLng(l) 124 | check(err) 125 | result = append(result, ll) 126 | } 127 | return result, nil 128 | } 129 | return result, fmt.Errorf("Invalid Path argument: '%s'", path) 130 | } 131 | -------------------------------------------------------------------------------- /examples/geocoding/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Geocoding API 16 | // Documentation: https://developers.google.com/maps/documentation/geocoding/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strconv" 26 | "strings" 27 | 28 | "github.com/kr/pretty" 29 | "googlemaps.github.io/maps" 30 | ) 31 | 32 | var ( 33 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 34 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 35 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 36 | address = flag.String("address", "", "The street address that you want to geocode, in the format used by the national postal service of the country concerned.") 37 | components = flag.String("components", "", "A component filter for which you wish to obtain a geocode.") 38 | bounds = flag.String("bounds", "", "The bounding box of the viewport within which to bias geocode results more prominently.") 39 | language = flag.String("language", "", "The language in which to return results.") 40 | region = flag.String("region", "", "The region code, specified as a ccTLD two-character value.") 41 | latlng = flag.String("latlng", "", "The textual latitude/longitude value for which you wish to obtain the closest, human-readable address.") 42 | resultType = flag.String("result_type", "", "One or more address types, separated by a pipe (|).") 43 | locationType = flag.String("location_type", "", "One or more location types, separated by a pipe (|).") 44 | enableAddressDescriptor = flag.String("enable_address_descriptor", "", "True or False. Whether to return the Address Descriptors in the response.") 45 | ) 46 | 47 | func usageAndExit(msg string) { 48 | fmt.Fprintln(os.Stderr, msg) 49 | fmt.Println("Flags:") 50 | flag.PrintDefaults() 51 | os.Exit(2) 52 | } 53 | 54 | func check(err error) { 55 | if err != nil { 56 | log.Fatalf("fatal error: %s", err) 57 | } 58 | } 59 | 60 | func main() { 61 | flag.Parse() 62 | 63 | var client *maps.Client 64 | var err error 65 | if *apiKey != "" { 66 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 67 | } else if *clientID != "" || *signature != "" { 68 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 69 | } else { 70 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 71 | } 72 | check(err) 73 | 74 | r := &maps.GeocodingRequest{ 75 | Address: *address, 76 | Language: *language, 77 | Region: *region, 78 | } 79 | 80 | parseComponents(*components, r) 81 | parseBounds(*bounds, r) 82 | parseLatLng(*latlng, r) 83 | parseResultType(*resultType, r) 84 | parseLocationType(*locationType, r) 85 | parseEnableAddressDescriptor(*enableAddressDescriptor, r) 86 | 87 | resp, err := client.Geocode(context.Background(), r) 88 | check(err) 89 | 90 | pretty.Println(resp) 91 | } 92 | 93 | func parseComponents(components string, r *maps.GeocodingRequest) { 94 | if components == "" { 95 | return 96 | } 97 | if r.Components == nil { 98 | r.Components = make(map[maps.Component]string) 99 | } 100 | 101 | c := strings.Split(components, "|") 102 | for _, cf := range c { 103 | i := strings.Split(cf, ":") 104 | switch i[0] { 105 | case "route": 106 | r.Components[maps.ComponentRoute] = i[1] 107 | case "locality": 108 | r.Components[maps.ComponentLocality] = i[1] 109 | case "administrative_area": 110 | r.Components[maps.ComponentAdministrativeArea] = i[1] 111 | case "postal_code": 112 | r.Components[maps.ComponentPostalCode] = i[1] 113 | case "country": 114 | r.Components[maps.ComponentCountry] = i[1] 115 | default: 116 | log.Fatalf("parseComponents: component name %#v unknown", i[0]) 117 | } 118 | } 119 | } 120 | 121 | func parseBounds(bounds string, r *maps.GeocodingRequest) { 122 | if bounds != "" { 123 | b := strings.Split(bounds, "|") 124 | sw := strings.Split(b[0], ",") 125 | ne := strings.Split(b[1], ",") 126 | 127 | swLat, err := strconv.ParseFloat(sw[0], 64) 128 | if err != nil { 129 | log.Fatalf("Couldn't parse bounds: %#v", err) 130 | } 131 | swLng, err := strconv.ParseFloat(sw[1], 64) 132 | if err != nil { 133 | log.Fatalf("Couldn't parse bounds: %#v", err) 134 | } 135 | neLat, err := strconv.ParseFloat(ne[0], 64) 136 | if err != nil { 137 | log.Fatalf("Couldn't parse bounds: %#v", err) 138 | } 139 | neLng, err := strconv.ParseFloat(ne[1], 64) 140 | if err != nil { 141 | log.Fatalf("Couldn't parse bounds: %#v", err) 142 | } 143 | 144 | r.Bounds = &maps.LatLngBounds{ 145 | NorthEast: maps.LatLng{Lat: neLat, Lng: neLng}, 146 | SouthWest: maps.LatLng{Lat: swLat, Lng: swLng}, 147 | } 148 | } 149 | } 150 | 151 | func parseLatLng(latlng string, r *maps.GeocodingRequest) { 152 | if latlng != "" { 153 | l := strings.Split(latlng, ",") 154 | lat, err := strconv.ParseFloat(l[0], 64) 155 | if err != nil { 156 | log.Fatalf("Couldn't parse latlng: %#v", err) 157 | } 158 | lng, err := strconv.ParseFloat(l[1], 64) 159 | if err != nil { 160 | log.Fatalf("Couldn't parse latlng: %#v", err) 161 | } 162 | r.LatLng = &maps.LatLng{ 163 | Lat: lat, 164 | Lng: lng, 165 | } 166 | } 167 | } 168 | 169 | func parseResultType(resultType string, r *maps.GeocodingRequest) { 170 | if resultType != "" { 171 | r.ResultType = strings.Split(resultType, "|") 172 | } 173 | } 174 | 175 | func parseLocationType(locationType string, r *maps.GeocodingRequest) { 176 | if locationType != "" { 177 | for _, l := range strings.Split(locationType, "|") { 178 | switch l { 179 | case "ROOFTOP": 180 | r.LocationType = append(r.LocationType, maps.GeocodeAccuracyRooftop) 181 | case "RANGE_INTERPOLATED": 182 | r.LocationType = append(r.LocationType, maps.GeocodeAccuracyRangeInterpolated) 183 | case "GEOMETRIC_CENTER": 184 | r.LocationType = append(r.LocationType, maps.GeocodeAccuracyGeometricCenter) 185 | case "APPROXIMATE": 186 | r.LocationType = append(r.LocationType, maps.GeocodeAccuracyApproximate) 187 | } 188 | } 189 | 190 | } 191 | } 192 | 193 | func parseEnableAddressDescriptor(enableAddressDescriptor string, r *maps.GeocodingRequest) { 194 | if enableAddressDescriptor == "True" { 195 | r.EnableAddressDescriptor = true 196 | } else { 197 | r.EnableAddressDescriptor = false 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /examples/places/findplacefromtext/findplacefromtext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Find Place From Text API 16 | package main 17 | 18 | import ( 19 | "context" 20 | "flag" 21 | "fmt" 22 | "log" 23 | "os" 24 | "strings" 25 | 26 | "github.com/kr/pretty" 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | input = flag.String("input", "", "The text input specifying which place to search for (for example, a name, address, or phone number).") 33 | inputType = flag.String("inputtype", "", "The type of input. This can be one of either textquery or phonenumber.") 34 | fields = flag.String("fields", "", "Comma seperated list of Fields") 35 | locationbias = flag.String("locationbias", "", "Location bias for this request. Optional. One of ipbias, point, circle, or rectangle.") 36 | point = flag.String("point", "", "The latitude/longitude for location bias point. This must be specified as latitude,longitude.") 37 | center = flag.String("center", "", "The center latitude/longitude for location bias circle. This must be specified as latitude,longitude.") 38 | radius = flag.Int("radius", 0, "The radius for location bias circle.") 39 | southwest = flag.String("southwest", "", "The South West latitude/longitude for location bias rectangle. This must be specified as latitude,longitude.") 40 | northeast = flag.String("northeast", "", "The North East latitude/longitude for location bias rectangle. This must be specified as latitude,longitude.") 41 | ) 42 | 43 | func usageAndExit(msg string) { 44 | fmt.Fprintln(os.Stderr, msg) 45 | fmt.Println("Flags:") 46 | flag.PrintDefaults() 47 | os.Exit(2) 48 | } 49 | 50 | func check(err error) { 51 | if err != nil { 52 | log.Fatalf("fatal error: %s", err) 53 | } 54 | } 55 | 56 | func main() { 57 | flag.Parse() 58 | 59 | var client *maps.Client 60 | var err error 61 | if *apiKey == "" { 62 | usageAndExit("Please specify an API Key.") 63 | } 64 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 65 | check(err) 66 | 67 | r := &maps.FindPlaceFromTextRequest{ 68 | Input: *input, 69 | InputType: parseInputType(*inputType), 70 | } 71 | 72 | if *locationbias != "" { 73 | lb, err := maps.ParseFindPlaceFromTextLocationBiasType(*locationbias) 74 | check(err) 75 | r.LocationBias = lb 76 | switch lb { 77 | case maps.FindPlaceFromTextLocationBiasPoint: 78 | l, err := maps.ParseLatLng(*point) 79 | check(err) 80 | r.LocationBiasPoint = &l 81 | case maps.FindPlaceFromTextLocationBiasCircular: 82 | l, err := maps.ParseLatLng(*center) 83 | check(err) 84 | r.LocationBiasCenter = &l 85 | r.LocationBiasRadius = *radius 86 | case maps.FindPlaceFromTextLocationBiasRectangular: 87 | sw, err := maps.ParseLatLng(*southwest) 88 | check(err) 89 | r.LocationBiasSouthWest = &sw 90 | ne, err := maps.ParseLatLng(*northeast) 91 | check(err) 92 | r.LocationBiasNorthEast = &ne 93 | } 94 | } 95 | 96 | if *fields != "" { 97 | f, err := parseFields(*fields) 98 | check(err) 99 | r.Fields = f 100 | } 101 | 102 | resp, err := client.FindPlaceFromText(context.Background(), r) 103 | check(err) 104 | 105 | pretty.Println(resp) 106 | } 107 | 108 | func parseFields(fields string) ([]maps.PlaceSearchFieldMask, error) { 109 | var res []maps.PlaceSearchFieldMask 110 | for _, s := range strings.Split(fields, ",") { 111 | f, err := maps.ParsePlaceSearchFieldMask(s) 112 | if err != nil { 113 | return nil, err 114 | } 115 | res = append(res, f) 116 | } 117 | return res, nil 118 | } 119 | 120 | func parseInputType(inputType string) maps.FindPlaceFromTextInputType { 121 | var it maps.FindPlaceFromTextInputType 122 | switch inputType { 123 | case "textquery": 124 | it = maps.FindPlaceFromTextInputTypeTextQuery 125 | case "phonenumber": 126 | it = maps.FindPlaceFromTextInputTypePhoneNumber 127 | default: 128 | usageAndExit(fmt.Sprintf("Unknown input type: '%s'", inputType)) 129 | } 130 | return it 131 | } 132 | -------------------------------------------------------------------------------- /examples/places/nearbysearch/nearbysearch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places API Text Search 16 | // Documentation: https://developers.google.com/places/web-service/search#TextSearchRequests 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | "github.com/kr/pretty" 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 33 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 34 | location = flag.String("location", "", "The latitude/longitude around which to retrieve place information. This must be specified as latitude,longitude.") 35 | radius = flag.Uint("radius", 0, "Defines the distance (in meters) within which to bias place results. The maximum allowed radius is 50,000 meters.") 36 | keyword = flag.String("keyword", "", "A term to be matched against all content that Google has indexed for this place, including but not limited to name, type, and address, as well as customer reviews and other third-party content.") 37 | language = flag.String("language", "", "The language in which to return results.") 38 | minPrice = flag.String("minprice", "", "Restricts results to only those places within the specified price level.") 39 | maxPrice = flag.String("maxprice", "", "Restricts results to only those places within the specified price level.") 40 | name = flag.String("name", "", "One or more terms to be matched against the names of places, separated with a space character.") 41 | openNow = flag.Bool("open_now", false, "Restricts results to only those places that are open for business at the time the query is sent.") 42 | rankBy = flag.String("rankby", "", "Specifies the order in which results are listed. Valid values are prominence or distance.") 43 | placeType = flag.String("type", "", "Restricts the results to places matching the specified type.") 44 | pageToken = flag.String("pagetoken", "", "Set to retrieve the next page of results.") 45 | ) 46 | 47 | func usageAndExit(msg string) { 48 | fmt.Fprintln(os.Stderr, msg) 49 | fmt.Println("Flags:") 50 | flag.PrintDefaults() 51 | os.Exit(2) 52 | } 53 | 54 | func check(err error) { 55 | if err != nil { 56 | log.Fatalf("fatal error: %s", err) 57 | } 58 | } 59 | 60 | func main() { 61 | flag.Parse() 62 | 63 | var client *maps.Client 64 | var err error 65 | if *apiKey != "" { 66 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 67 | } else if *clientID != "" || *signature != "" { 68 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 69 | } else { 70 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 71 | } 72 | check(err) 73 | 74 | r := &maps.NearbySearchRequest{ 75 | Radius: *radius, 76 | Keyword: *keyword, 77 | Language: *language, 78 | Name: *name, 79 | OpenNow: *openNow, 80 | PageToken: *pageToken, 81 | } 82 | 83 | parseLocation(*location, r) 84 | parsePriceLevels(*minPrice, *maxPrice, r) 85 | parseRankBy(*rankBy, r) 86 | parsePlaceType(*placeType, r) 87 | 88 | resp, err := client.NearbySearch(context.Background(), r) 89 | check(err) 90 | 91 | pretty.Println(resp) 92 | } 93 | 94 | func parseLocation(location string, r *maps.NearbySearchRequest) { 95 | if location != "" { 96 | l, err := maps.ParseLatLng(location) 97 | check(err) 98 | r.Location = &l 99 | } 100 | } 101 | 102 | func parsePriceLevel(priceLevel string) maps.PriceLevel { 103 | switch priceLevel { 104 | case "0": 105 | return maps.PriceLevelFree 106 | case "1": 107 | return maps.PriceLevelInexpensive 108 | case "2": 109 | return maps.PriceLevelModerate 110 | case "3": 111 | return maps.PriceLevelExpensive 112 | case "4": 113 | return maps.PriceLevelVeryExpensive 114 | default: 115 | usageAndExit(fmt.Sprintf("Unknown price level: '%s'", priceLevel)) 116 | } 117 | return maps.PriceLevelFree 118 | } 119 | 120 | func parsePriceLevels(minPrice string, maxPrice string, r *maps.NearbySearchRequest) { 121 | if minPrice != "" { 122 | r.MinPrice = parsePriceLevel(minPrice) 123 | } 124 | 125 | if maxPrice != "" { 126 | r.MaxPrice = parsePriceLevel(minPrice) 127 | } 128 | } 129 | 130 | func parseRankBy(rankBy string, r *maps.NearbySearchRequest) { 131 | switch rankBy { 132 | case "prominence": 133 | r.RankBy = maps.RankByProminence 134 | return 135 | case "distance": 136 | r.RankBy = maps.RankByDistance 137 | return 138 | case "": 139 | return 140 | default: 141 | usageAndExit(fmt.Sprintf("Unknown rank by: \"%v\"", rankBy)) 142 | } 143 | } 144 | 145 | func parsePlaceType(placeType string, r *maps.NearbySearchRequest) { 146 | if placeType != "" { 147 | t, err := maps.ParsePlaceType(placeType) 148 | if err != nil { 149 | usageAndExit(fmt.Sprintf("Unknown place type \"%v\"", placeType)) 150 | } 151 | 152 | r.Type = t 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /examples/places/photo/photo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places Photos API 16 | // Documentation: https://developers.google.com/places/web-service/photos 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "image/jpeg" 24 | "log" 25 | "os" 26 | 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 33 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 34 | photoreference = flag.String("photoreference", "", "Textual identifier that uniquely identifies a place photo.") 35 | maxheight = flag.Int("maxheight", 0, "Specifies the maximum desired height, in pixels, of the image returned by the Place Photos service. One of maxheight and maxwidth is required.") 36 | maxwidth = flag.Int("maxwidth", 0, "Specifies the maximum desired width, in pixels, of the image returned by the Place Photos service. One of maxheight and maxwidth is required.") 37 | basename = flag.String("basename", "", "Base name of file to write image to. If not specified, no file will be written.") 38 | ) 39 | 40 | func usageAndExit(msg string) { 41 | fmt.Fprintln(os.Stderr, msg) 42 | fmt.Println("Flags:") 43 | flag.PrintDefaults() 44 | os.Exit(2) 45 | } 46 | 47 | func check(err error) { 48 | if err != nil { 49 | log.Fatalf("fatal error: %s", err) 50 | } 51 | } 52 | 53 | func main() { 54 | flag.Parse() 55 | 56 | var client *maps.Client 57 | var err error 58 | if *apiKey != "" { 59 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 60 | } else if *clientID != "" || *signature != "" { 61 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 62 | } else { 63 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 64 | } 65 | check(err) 66 | 67 | r := &maps.PlacePhotoRequest{ 68 | PhotoReference: *photoreference, 69 | MaxHeight: uint(*maxheight), 70 | MaxWidth: uint(*maxwidth), 71 | } 72 | 73 | resp, err := client.PlacePhoto(context.Background(), r) 74 | check(err) 75 | 76 | log.Printf("Content-Type: %v\n", resp.ContentType) 77 | img, err := resp.Image() 78 | check(err) 79 | log.Printf("Image bounds: %v", img.Bounds()) 80 | 81 | if *basename != "" { 82 | filename := fmt.Sprintf("%s.%s", *basename, "jpg") 83 | f, err := os.Create(filename) 84 | check(err) 85 | err = jpeg.Encode(f, img, &jpeg.Options{Quality: 85}) 86 | check(err) 87 | 88 | log.Printf("Wrote image to %s\n", filename) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/places/placeautocomplete/placeautocomplete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places API Query Autocomplete 16 | // Documentation: https://developers.google.com/places/web-service/query 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 34 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 35 | input = flag.String("input", "", "Text string on which to search.") 36 | language = flag.String("language", "", "The language in which to return results.") 37 | offset = flag.Uint("offset", 0, "The character position in the input term at which the service uses text for predictions.") 38 | location = flag.String("location", "", "The latitude/longitude around which to retrieve place information. This must be specified as latitude,longitude.") 39 | radius = flag.Uint("radius", 0, "Defines the distance (in meters) within which to bias place results. The maximum allowed radius is 50,000 meters.") 40 | placeType = flag.String("types", "", "Restricts the results to places matching the specified type.") 41 | components = flag.String("components", "", "A component filter for specifying which country to perform autocomplete inside of") 42 | strictbounds = flag.Bool("strictbounds", false, "Whether to strictly enforce bounds.") 43 | ) 44 | 45 | func usageAndExit(msg string) { 46 | fmt.Fprintln(os.Stderr, msg) 47 | fmt.Println("Flags:") 48 | flag.PrintDefaults() 49 | os.Exit(2) 50 | } 51 | 52 | func check(err error) { 53 | if err != nil { 54 | log.Fatalf("fatal error: %s", err) 55 | } 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | 61 | var client *maps.Client 62 | var err error 63 | if *apiKey != "" { 64 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 65 | } else if *clientID != "" || *signature != "" { 66 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 67 | } else { 68 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 69 | } 70 | check(err) 71 | 72 | // s is a session token. Thread this through a series of requests that comprise 73 | // a single autocomplete search session. 74 | s := maps.NewPlaceAutocompleteSessionToken() 75 | 76 | r := &maps.PlaceAutocompleteRequest{ 77 | Input: *input, 78 | Language: *language, 79 | Offset: *offset, 80 | Radius: *radius, 81 | StrictBounds: *strictbounds, 82 | SessionToken: s, 83 | } 84 | 85 | parseLocation(*location, r) 86 | parsePlaceType(*placeType, r) 87 | 88 | resp, err := client.PlaceAutocomplete(context.Background(), r) 89 | check(err) 90 | 91 | pretty.Println(resp) 92 | } 93 | 94 | func parseLocation(location string, r *maps.PlaceAutocompleteRequest) { 95 | if location != "" { 96 | l, err := maps.ParseLatLng(location) 97 | check(err) 98 | r.Location = &l 99 | } 100 | } 101 | 102 | func parsePlaceType(placeType string, r *maps.PlaceAutocompleteRequest) { 103 | if placeType != "" { 104 | t, err := maps.ParseAutocompletePlaceType(placeType) 105 | check(err) 106 | r.Types = t 107 | } 108 | } 109 | 110 | func parseComponents(components string, r *maps.PlaceAutocompleteRequest) { 111 | if components != "" { 112 | c := strings.Split(components, "|") 113 | for _, cf := range c { 114 | i := strings.Split(cf, ":") 115 | switch i[0] { 116 | case "country": 117 | r.Components[maps.ComponentCountry] = append(r.Components[maps.ComponentCountry], i[1]) 118 | default: 119 | log.Fatalf("Unsupported component \"%v\"", i[0]) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/places/placedetails/placedetails.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places API Text Search 16 | // Documentation: https://developers.google.com/places/web-service/search#TextSearchRequests 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 34 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 35 | placeID = flag.String("place_id", "", "Textual identifier that uniquely identifies a place.") 36 | fields = flag.String("fields", "", "Comma seperated list of Fields") 37 | ) 38 | 39 | func usageAndExit(msg string) { 40 | fmt.Fprintln(os.Stderr, msg) 41 | fmt.Println("Flags:") 42 | flag.PrintDefaults() 43 | os.Exit(2) 44 | } 45 | 46 | func check(err error) { 47 | if err != nil { 48 | log.Fatalf("fatal error: %s", err) 49 | } 50 | } 51 | 52 | func main() { 53 | flag.Parse() 54 | 55 | var client *maps.Client 56 | var err error 57 | if *apiKey != "" { 58 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 59 | } else if *clientID != "" || *signature != "" { 60 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 61 | } else { 62 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 63 | } 64 | check(err) 65 | 66 | r := &maps.PlaceDetailsRequest{ 67 | PlaceID: *placeID, 68 | } 69 | 70 | if *fields != "" { 71 | f, err := parseFields(*fields) 72 | check(err) 73 | r.Fields = f 74 | } 75 | 76 | resp, err := client.PlaceDetails(context.Background(), r) 77 | check(err) 78 | 79 | pretty.Println(resp) 80 | } 81 | 82 | func parseFields(fields string) ([]maps.PlaceDetailsFieldMask, error) { 83 | var res []maps.PlaceDetailsFieldMask 84 | for _, s := range strings.Split(fields, ",") { 85 | f, err := maps.ParsePlaceDetailsFieldMask(s) 86 | if err != nil { 87 | return nil, err 88 | } 89 | res = append(res, f) 90 | } 91 | return res, nil 92 | } 93 | -------------------------------------------------------------------------------- /examples/places/queryautocomplete/queryautocomplete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places API Query Autocomplete 16 | // Documentation: https://developers.google.com/places/web-service/query 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | "github.com/kr/pretty" 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 33 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 34 | input = flag.String("input", "", "Text string on which to search.") 35 | language = flag.String("language", "", "The language in which to return results.") 36 | offset = flag.Uint("offset", 0, "The character position in the input term at which the service uses text for predictions.") 37 | location = flag.String("location", "", "The latitude/longitude around which to retrieve place information. This must be specified as latitude,longitude.") 38 | radius = flag.Uint("radius", 0, "Defines the distance (in meters) within which to bias place results. The maximum allowed radius is 50,000 meters.") 39 | ) 40 | 41 | func usageAndExit(msg string) { 42 | fmt.Fprintln(os.Stderr, msg) 43 | fmt.Println("Flags:") 44 | flag.PrintDefaults() 45 | os.Exit(2) 46 | } 47 | 48 | func check(err error) { 49 | if err != nil { 50 | log.Fatalf("fatal error: %s", err) 51 | } 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | 57 | var client *maps.Client 58 | var err error 59 | if *apiKey != "" { 60 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 61 | } else if *clientID != "" || *signature != "" { 62 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 63 | } else { 64 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 65 | } 66 | check(err) 67 | 68 | r := &maps.QueryAutocompleteRequest{ 69 | Input: *input, 70 | Language: *language, 71 | Radius: *radius, 72 | Offset: *offset, 73 | } 74 | 75 | parseLocation(*location, r) 76 | 77 | resp, err := client.QueryAutocomplete(context.Background(), r) 78 | check(err) 79 | 80 | pretty.Println(resp) 81 | } 82 | 83 | func parseLocation(location string, r *maps.QueryAutocompleteRequest) { 84 | if location != "" { 85 | l, err := maps.ParseLatLng(location) 86 | check(err) 87 | r.Location = &l 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/places/textsearch/textsearch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Places API Text Search 16 | // Documentation: https://developers.google.com/places/web-service/search#TextSearchRequests 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | "github.com/kr/pretty" 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 33 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 34 | query = flag.String("query", "", "Text Search query to execute.") 35 | language = flag.String("language", "", "The language in which to return results.") 36 | location = flag.String("location", "", "The latitude/longitude around which to retrieve place information. This must be specified as latitude,longitude.") 37 | radius = flag.Uint("radius", 0, "Defines the distance (in meters) within which to bias place results. The maximum allowed radius is 50,000 meters.") 38 | minprice = flag.String("min_price", "", "Restricts results to only those places within the specified price level.") 39 | maxprice = flag.String("max_price", "", "Restricts results to only those places within the specified price level.") 40 | opennow = flag.Bool("open_now", false, "Restricts results to only those places that are open for business at the time the query is sent.") 41 | placeType = flag.String("type", "", "Restricts the results to places matching the specified type.") 42 | ) 43 | 44 | func usageAndExit(msg string) { 45 | fmt.Fprintln(os.Stderr, msg) 46 | fmt.Println("Flags:") 47 | flag.PrintDefaults() 48 | os.Exit(2) 49 | } 50 | 51 | func check(err error) { 52 | if err != nil { 53 | log.Fatalf("fatal error: %s", err) 54 | } 55 | } 56 | 57 | func main() { 58 | flag.Parse() 59 | 60 | var client *maps.Client 61 | var err error 62 | if *apiKey != "" { 63 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 64 | } else if *clientID != "" || *signature != "" { 65 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 66 | } else { 67 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 68 | } 69 | check(err) 70 | 71 | r := &maps.TextSearchRequest{ 72 | Query: *query, 73 | Language: *language, 74 | Radius: *radius, 75 | OpenNow: *opennow, 76 | } 77 | 78 | parseLocation(*location, r) 79 | parsePriceLevels(*minprice, *maxprice, r) 80 | parsePlaceType(*placeType, r) 81 | 82 | resp, err := client.TextSearch(context.Background(), r) 83 | check(err) 84 | 85 | pretty.Println(resp) 86 | } 87 | 88 | func parseLocation(location string, r *maps.TextSearchRequest) { 89 | if location != "" { 90 | l, err := maps.ParseLatLng(location) 91 | check(err) 92 | r.Location = &l 93 | } 94 | } 95 | 96 | func parsePriceLevel(priceLevel string) maps.PriceLevel { 97 | switch priceLevel { 98 | case "0": 99 | return maps.PriceLevelFree 100 | case "1": 101 | return maps.PriceLevelInexpensive 102 | case "2": 103 | return maps.PriceLevelModerate 104 | case "3": 105 | return maps.PriceLevelExpensive 106 | case "4": 107 | return maps.PriceLevelVeryExpensive 108 | default: 109 | usageAndExit(fmt.Sprintf("Unknown price level: '%s'", priceLevel)) 110 | } 111 | return maps.PriceLevelFree 112 | } 113 | 114 | func parsePriceLevels(minprice string, maxprice string, r *maps.TextSearchRequest) { 115 | if minprice != "" { 116 | r.MinPrice = parsePriceLevel(minprice) 117 | } 118 | 119 | if maxprice != "" { 120 | r.MaxPrice = parsePriceLevel(minprice) 121 | } 122 | } 123 | 124 | func parsePlaceType(placeType string, r *maps.TextSearchRequest) { 125 | if placeType != "" { 126 | t, err := maps.ParsePlaceType(placeType) 127 | if err != nil { 128 | usageAndExit(fmt.Sprintf("Unknown place type \"%v\"", placeType)) 129 | } 130 | 131 | r.Type = t 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /examples/roads/snaptoroad/snaptoroad.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Timezone API 16 | // Directions docs: https://developers.google.com/maps/documentation/timezone/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | path = flag.String("path", "", "The path to be snapped. The path parameter accepts a list of latitude/longitude pairs. Latitude and longitude values should be separated by commas. Coordinates should be separated by the pipe character.") 34 | interpolate = flag.Bool("interpolate", false, "Whether to interpolate a path to include all points forming the full road-geometry.") 35 | ) 36 | 37 | func usageAndExit(msg string) { 38 | fmt.Fprintln(os.Stderr, msg) 39 | fmt.Println("Flags:") 40 | flag.PrintDefaults() 41 | os.Exit(2) 42 | } 43 | 44 | func check(err error) { 45 | if err != nil { 46 | log.Fatalf("fatal error: %s", err) 47 | } 48 | } 49 | 50 | func main() { 51 | flag.Parse() 52 | if *apiKey == "" { 53 | usageAndExit("Please specify an API Key.") 54 | } 55 | client, err := maps.NewClient(maps.WithAPIKey(*apiKey)) 56 | check(err) 57 | r := &maps.SnapToRoadRequest{ 58 | Interpolate: *interpolate, 59 | } 60 | 61 | if *path == "" { 62 | usageAndExit("Please specify a path to be snapped.") 63 | } 64 | parsePath(*path, r) 65 | 66 | resp, err := client.SnapToRoad(context.Background(), r) 67 | check(err) 68 | 69 | pretty.Println(resp) 70 | } 71 | 72 | // parsePath takes a location argument string and decodes it. 73 | func parsePath(path string, r *maps.SnapToRoadRequest) { 74 | if path != "" { 75 | ls := strings.Split(path, "|") 76 | for _, l := range ls { 77 | ll, err := maps.ParseLatLng(l) 78 | check(err) 79 | r.Path = append(r.Path, ll) 80 | } 81 | } else { 82 | usageAndExit("Path required") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/roads/speedlimits/speedlimits.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Timezone API 16 | // Directions docs: https://developers.google.com/maps/documentation/timezone/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strings" 26 | 27 | "github.com/kr/pretty" 28 | "googlemaps.github.io/maps" 29 | ) 30 | 31 | var ( 32 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 33 | path = flag.String("path", "", "The path to be snapped. The path parameter accepts a list of latitude/longitude pairs. Latitude and longitude values should be separated by commas. Coordinates should be separated by the pipe character.") 34 | placeIDs = flag.String("place_ids", "", "The place ID of the road segment. Place IDs are returned by the snapToRoads method. You can pass up to 100 Place IDs with each request. Place IDs should be separated by a comma.") 35 | units = flag.String("units", "", "Whether to return speed limits in kilometers or miles per hour. This can be set to either KPH or MPH. Defaults to KPH.") 36 | ) 37 | 38 | func usageAndExit(msg string) { 39 | fmt.Fprintln(os.Stderr, msg) 40 | fmt.Println("Flags:") 41 | flag.PrintDefaults() 42 | os.Exit(2) 43 | } 44 | 45 | func check(err error) { 46 | if err != nil { 47 | log.Fatalf("fatal error: %s", err) 48 | } 49 | } 50 | 51 | func main() { 52 | flag.Parse() 53 | if *apiKey == "" { 54 | usageAndExit("Please specify an API Key.") 55 | } 56 | client, err := maps.NewClient(maps.WithAPIKey(*apiKey)) 57 | if err != nil { 58 | log.Fatalf("error %v", err) 59 | } 60 | r := &maps.SpeedLimitsRequest{} 61 | 62 | if *units == "KPH" { 63 | r.Units = maps.SpeedLimitKPH 64 | } 65 | if *units == "MPH" { 66 | r.Units = maps.SpeedLimitMPH 67 | } 68 | 69 | if *path == "" && *placeIDs == "" { 70 | usageAndExit("Please specify either a path to be snapped, or a list of Place IDs.") 71 | } 72 | parsePath(*path, r) 73 | parsePlaceIDs(*placeIDs, r) 74 | 75 | resp, err := client.SpeedLimits(context.Background(), r) 76 | if err != nil { 77 | log.Fatalf("error %v", err) 78 | } 79 | 80 | pretty.Println(resp) 81 | } 82 | 83 | // parsePath takes a location argument string and decodes it. 84 | func parsePath(path string, r *maps.SpeedLimitsRequest) { 85 | if path != "" { 86 | ls := strings.Split(path, "|") 87 | for _, l := range ls { 88 | ll, err := maps.ParseLatLng(l) 89 | check(err) 90 | r.Path = append(r.Path, ll) 91 | } 92 | } 93 | } 94 | 95 | // parsePlacesIds takes a placesIds argument string and decodes it. 96 | func parsePlaceIDs(placeIDs string, r *maps.SpeedLimitsRequest) { 97 | if placeIDs != "" { 98 | ids := strings.Split(placeIDs, ",") 99 | for _, id := range ids { 100 | r.PlaceID = append(r.PlaceID, id) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/staticmap/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Static Maps API 16 | // Documentation: https://developers.google.com/maps/documentation/static-maps/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | "github.com/kr/pretty" 27 | "googlemaps.github.io/maps" 28 | ) 29 | 30 | var ( 31 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 32 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 33 | signature = flag.String("signature", "", "Signature for Maps for Work API access or optional APIs.") 34 | center = flag.String("center", "", "Center the center of the map, equidistant from all edges of the map.") 35 | zoom = flag.Int("zoom", -1, "Zoom the zoom level of the map, which determines the magnification level of the map.") 36 | size = flag.String("size", "", "Size defines the rectangular dimensions of the map image.") 37 | scale = flag.Int("scale", -1, "Scale affects the number of pixels that are returned.") 38 | format = flag.String("format", "", "Format defines the format of the resulting image.") 39 | maptype = flag.String("maptype", "", "Maptype defines the type of map to construct.") 40 | mapid = flag.String("mapid", "", "MapId defines the mapid to use.") 41 | language = flag.String("language", "", "Language defines the language to use for display of labels on map tiles.") 42 | region = flag.String("region", "", "Region the appropriate borders to display, based on geo-political sensitivities.") 43 | ) 44 | 45 | func usageAndExit(msg string) { 46 | fmt.Fprintln(os.Stderr, msg) 47 | fmt.Println("Flags:") 48 | flag.PrintDefaults() 49 | os.Exit(2) 50 | } 51 | 52 | func check(err error) { 53 | if err != nil { 54 | log.Fatalf("fatal error: %s", err) 55 | } 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | 61 | var client *maps.Client 62 | var err error 63 | if *apiKey != "" { 64 | if *signature != "" { 65 | client, err = maps.NewClient(maps.WithAPIKeyAndSignature(*apiKey, *signature)) 66 | } else { 67 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 68 | } 69 | } else if *clientID != "" || *signature != "" { 70 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 71 | } else { 72 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 73 | } 74 | check(err) 75 | 76 | r := &maps.StaticMapRequest{ 77 | Center: *center, 78 | Zoom: *zoom, 79 | Size: *size, 80 | Scale: *scale, 81 | Format: maps.Format(*format), 82 | Language: *language, 83 | Region: *region, 84 | MapType: maps.MapType(*maptype), 85 | MapId: *mapid, 86 | } 87 | 88 | resp, err := client.StaticMap(context.Background(), r) 89 | check(err) 90 | 91 | pretty.Println(resp) 92 | } 93 | -------------------------------------------------------------------------------- /examples/timezone/cmdline/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // Package main contains a simple command line tool for Timezone API 16 | // Directions docs: https://developers.google.com/maps/documentation/timezone/ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strconv" 26 | "time" 27 | 28 | "github.com/kr/pretty" 29 | "googlemaps.github.io/maps" 30 | ) 31 | 32 | var ( 33 | apiKey = flag.String("key", "", "API Key for using Google Maps API.") 34 | clientID = flag.String("client_id", "", "ClientID for Maps for Work API access.") 35 | signature = flag.String("signature", "", "Signature for Maps for Work API access.") 36 | location = flag.String("location", "", "a comma-separated lat,lng tuple (eg. location=-33.86,151.20), representing the location to look up.") 37 | timestamp = flag.String("timestamp", "", "specifies the desired time as seconds since midnight, January 1, 1970 UTC.") 38 | language = flag.String("language", "", "The language in which to return results.") 39 | ) 40 | 41 | func usageAndExit(msg string) { 42 | fmt.Fprintln(os.Stderr, msg) 43 | fmt.Println("Flags:") 44 | flag.PrintDefaults() 45 | os.Exit(2) 46 | } 47 | 48 | func check(err error) { 49 | if err != nil { 50 | log.Fatalf("fatal error: %s", err) 51 | } 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | 57 | var client *maps.Client 58 | var err error 59 | if *apiKey != "" { 60 | client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) 61 | } else if *clientID != "" || *signature != "" { 62 | client, err = maps.NewClient(maps.WithClientIDAndSignature(*clientID, *signature)) 63 | } else { 64 | usageAndExit("Please specify an API Key, or Client ID and Signature.") 65 | } 66 | check(err) 67 | 68 | t, err := strconv.Atoi(*timestamp) 69 | check(err) 70 | 71 | r := &maps.TimezoneRequest{ 72 | Language: *language, 73 | Timestamp: time.Unix(int64(t), 0), 74 | } 75 | 76 | parseLocation(*location, r) 77 | 78 | resp, err := client.Timezone(context.Background(), r) 79 | check(err) 80 | 81 | pretty.Println(resp) 82 | } 83 | 84 | func parseLocation(location string, r *maps.TimezoneRequest) { 85 | if location != "" { 86 | l, err := maps.ParseLatLng(location) 87 | check(err) 88 | r.Location = &l 89 | } else { 90 | usageAndExit("location is required") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /geocoding.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Geocoding API is available on 16 | // https://developers.google.com/maps/documentation/geocoding 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "net/url" 24 | "strings" 25 | ) 26 | 27 | var geocodingAPI = &apiConfig{ 28 | host: "https://maps.googleapis.com", 29 | path: "/maps/api/geocode/json", 30 | acceptsClientID: true, 31 | acceptsSignature: false, 32 | } 33 | 34 | // Geocode makes a Geocoding API request 35 | func (c *Client) Geocode(ctx context.Context, r *GeocodingRequest) (GeocodingResponse, error) { 36 | if r.Address == "" && len(r.Components) == 0 && r.LatLng == nil { 37 | return GeocodingResponse{}, errors.New("maps: address, components and LatLng are all missing") 38 | } 39 | 40 | var response struct { 41 | Results []GeocodingResult `json:"results"` 42 | commonResponse 43 | } 44 | 45 | if err := c.getJSON(ctx, geocodingAPI, r, &response); err != nil { 46 | return GeocodingResponse{}, err 47 | } 48 | 49 | if err := response.StatusError(); err != nil { 50 | return GeocodingResponse{}, err 51 | } 52 | 53 | return GeocodingResponse{response.Results, AddressDescriptor{}}, nil 54 | } 55 | 56 | // ReverseGeocode makes a Reverse Geocoding API request 57 | func (c *Client) ReverseGeocode(ctx context.Context, r *GeocodingRequest) (GeocodingResponse, error) { 58 | // Since Geocode() does not allow a nil LatLng, whereas it is allowed here 59 | if r.LatLng == nil && r.PlaceID == "" { 60 | return GeocodingResponse{}, errors.New("maps: LatLng and PlaceID are both missing") 61 | } 62 | 63 | var response struct { 64 | Results []GeocodingResult `json:"results"` 65 | AddressDescriptor AddressDescriptor `json:"address_descriptor"` 66 | commonResponse 67 | } 68 | 69 | if err := c.getJSON(ctx, geocodingAPI, r, &response); err != nil { 70 | return GeocodingResponse{}, err 71 | } 72 | 73 | if err := response.StatusError(); err != nil { 74 | return GeocodingResponse{}, err 75 | } 76 | 77 | return GeocodingResponse{response.Results, response.AddressDescriptor}, nil 78 | } 79 | 80 | func (r *GeocodingRequest) params() url.Values { 81 | q := make(url.Values) 82 | 83 | for k, v := range r.Custom { 84 | q[k] = v 85 | } 86 | 87 | if r.Address != "" { 88 | q.Set("address", r.Address) 89 | } 90 | var cf []string 91 | for c, f := range r.Components { 92 | cf = append(cf, string(c)+":"+f) 93 | } 94 | if len(cf) > 0 { 95 | q.Set("components", strings.Join(cf, "|")) 96 | } 97 | if r.Bounds != nil { 98 | q.Set("bounds", r.Bounds.String()) 99 | } 100 | if r.Region != "" { 101 | q.Set("region", r.Region) 102 | } 103 | if r.LatLng != nil { 104 | q.Set("latlng", r.LatLng.String()) 105 | } 106 | if len(r.ResultType) > 0 { 107 | q.Set("result_type", strings.Join(r.ResultType, "|")) 108 | } 109 | if len(r.LocationType) > 0 { 110 | var lt []string 111 | for _, l := range r.LocationType { 112 | lt = append(lt, string(l)) 113 | } 114 | q.Set("location_type", strings.Join(lt, "|")) 115 | } 116 | if r.PlaceID != "" { 117 | q.Set("place_id", r.PlaceID) 118 | } 119 | if r.Language != "" { 120 | q.Set("language", r.Language) 121 | } 122 | if r.EnableAddressDescriptor == true { 123 | q.Set("enable_address_descriptor", "true") 124 | } 125 | 126 | return q 127 | } 128 | 129 | // GeocodeAccuracy is the type of a location result from the Geocoding API. 130 | type GeocodeAccuracy string 131 | 132 | const ( 133 | // GeocodeAccuracyRooftop restricts the results to addresses for which Google has 134 | // location information accurate down to street address precision. 135 | GeocodeAccuracyRooftop = GeocodeAccuracy("ROOFTOP") 136 | // GeocodeAccuracyRangeInterpolated restricts the results to those that reflect an 137 | // approximation interpolated between two precise points. 138 | GeocodeAccuracyRangeInterpolated = GeocodeAccuracy("RANGE_INTERPOLATED") 139 | // GeocodeAccuracyGeometricCenter restricts the results to geometric centers of a 140 | // location such as a polyline or polygon. 141 | GeocodeAccuracyGeometricCenter = GeocodeAccuracy("GEOMETRIC_CENTER") 142 | // GeocodeAccuracyApproximate restricts the results to those that are characterized 143 | // as approximate. 144 | GeocodeAccuracyApproximate = GeocodeAccuracy("APPROXIMATE") 145 | ) 146 | 147 | // GeocodingRequest is the request structure for Geocoding API 148 | type GeocodingRequest struct { 149 | // Geocoding fields 150 | 151 | // Address is the street address that you want to geocode, in the format used by 152 | // the national postal service of the country concerned. 153 | Address string 154 | // Components is a component filter for which you wish to obtain a geocode. Either 155 | // Address or Components is required in a geocoding request. For more detail on 156 | // Component Filtering please see 157 | // https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering 158 | Components map[Component]string 159 | // Bounds is the bounding box of the viewport within which to bias geocode results 160 | // more prominently. Optional. 161 | Bounds *LatLngBounds 162 | // Region is the region code, specified as a ccTLD two-character value. Optional. 163 | Region string 164 | 165 | // Reverse geocoding fields 166 | 167 | // LatLng is the textual latitude/longitude value for which you wish to obtain the 168 | // closest, human-readable address. Either LatLng or PlaceID is required for 169 | // Reverse Geocoding. 170 | LatLng *LatLng 171 | // ResultType is an array of one or more address types. Optional. 172 | ResultType []string 173 | // LocationType is an array of one or more geocoding accuracy types. Optional. 174 | LocationType []GeocodeAccuracy 175 | // PlaceID is a string which contains the place_id, which can be used for reverse 176 | // geocoding requests. Either LatLng or PlaceID is required for Reverse Geocoding. 177 | PlaceID string 178 | 179 | // Language is the language in which to return results. Optional. 180 | Language string 181 | 182 | // Language is the language in which to return results. Optional. 183 | EnableAddressDescriptor bool 184 | 185 | // Custom allows passing through custom parameters to the Geocoding back end. 186 | // Use with caution. For more detail on why this is required, please see 187 | // https://googlegeodevelopers.blogspot.com/2016/11/address-geocoding-in-google-maps-apis.html 188 | Custom url.Values 189 | } 190 | 191 | // GeocodingResponse is the response to a Geocoding API request. 192 | type GeocodingResponse struct { 193 | // Results is the Geocoding results 194 | Results []GeocodingResult 195 | // The Address Descriptor for the target in the reverse geocoding requeest 196 | AddressDescriptor AddressDescriptor 197 | } 198 | 199 | // GeocodingResult is a single geocoded address 200 | type GeocodingResult struct { 201 | AddressComponents []AddressComponent `json:"address_components"` 202 | FormattedAddress string `json:"formatted_address"` 203 | Geometry AddressGeometry `json:"geometry"` 204 | Types []string `json:"types"` 205 | PlaceID string `json:"place_id"` 206 | 207 | // PartialMatch indicates that the geocoder did not return an exact match for 208 | // the original request, though it was able to match part of the requested address. 209 | // You may wish to examine the original request for misspellings and/or an incomplete address. 210 | // Partial matches most often occur for street addresses that do not exist within the 211 | // locality you pass in the request. 212 | // Partial matches may also be returned when a request matches two or more locations in 213 | // the same locality. For example, "21 Henr St, Bristol, UK" will return a partial match 214 | // for both Henry Street and Henrietta Street. 215 | // Note that if a request includes a misspelled address component, the geocoding service may 216 | // suggest an alternative address. 217 | // Suggestions triggered in this way will also be marked as a partial match. 218 | PartialMatch bool `json:"partial_match"` 219 | 220 | // PlusCode (see https://en.wikipedia.org/wiki/Open_Location_Code and https://plus.codes/) 221 | // is an encoded location reference, derived from latitude and longitude coordinates, 222 | // that represents an area: 1/8000th of a degree by 1/8000th of a degree (about 14m x 14m at the equator) 223 | // or smaller. 224 | // 225 | // Plus codes can be used as a replacement for street addresses in places where they do not exist 226 | // (where buildings are not numbered or streets are not named). 227 | // The plus code is formatted as a global code and a compound code: 228 | // Typically, both the global code and compound code are returned. 229 | // However, if the result is in a remote location (for example, an ocean or desert) 230 | // only the global code may be returned. 231 | PlusCode AddressPlusCode `json:"plus_code"` 232 | } 233 | 234 | // AddressPlusCode (see https://en.wikipedia.org/wiki/Open_Location_Code and https://plus.codes/) 235 | // is an encoded location reference, derived from latitude and longitude coordinates, 236 | // that represents an area: 1/8000th of a degree by 1/8000th of a degree (about 14m x 14m at the equator) 237 | // or smaller. 238 | // 239 | // Plus codes can be used as a replacement for street addresses in places where they do not exist 240 | // (where buildings are not numbered or streets are not named). 241 | // The plus code is formatted as a global code and a compound code: 242 | // Typically, both the global code and compound code are returned. 243 | // However, if the result is in a remote location (for example, an ocean or desert) 244 | // only the global code may be returned. 245 | type AddressPlusCode struct { 246 | // GlobalCode is a 4 character area code and 6 character or longer local code (849VCWC8+R9). 247 | GlobalCode string `json:"global_code"` 248 | // CompoundCode is a 6 character or longer local code with an explicit location (CWC8+R9, Mountain View, CA, USA). 249 | CompoundCode string `json:"compound_code"` 250 | } 251 | 252 | // AddressComponent is a part of an address 253 | type AddressComponent struct { 254 | LongName string `json:"long_name"` 255 | ShortName string `json:"short_name"` 256 | Types []string `json:"types"` 257 | } 258 | 259 | // AddressGeometry is the location of a an address 260 | type AddressGeometry struct { 261 | Location LatLng `json:"location"` 262 | LocationType string `json:"location_type"` 263 | Bounds LatLngBounds `json:"bounds"` 264 | Viewport LatLngBounds `json:"viewport"` 265 | Types []string `json:"types"` 266 | } 267 | -------------------------------------------------------------------------------- /geolocation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Geolocation API is available on 16 | // https://developers.google.com/maps/documentation/geolocation 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | ) 24 | 25 | var geolocationAPI = &apiConfig{ 26 | host: "https://www.googleapis.com", 27 | path: "/geolocation/v1/geolocate", 28 | acceptsClientID: true, 29 | acceptsSignature: false, 30 | } 31 | 32 | // Geolocate makes a Geolocation API request 33 | func (c *Client) Geolocate(ctx context.Context, r *GeolocationRequest) (*GeolocationResult, error) { 34 | var response struct { 35 | GeolocationResult 36 | Error GeolocationError 37 | } 38 | if err := c.postJSON(ctx, geolocationAPI, r, &response); err != nil { 39 | return nil, err 40 | } 41 | // TODO: much more error detail available here, what do? 42 | if response.Error.Code != 0 || len(response.Error.Errors) > 0 { 43 | return nil, fmt.Errorf("%s", response.Error.Message) 44 | } 45 | return &response.GeolocationResult, nil 46 | } 47 | 48 | // RadioType defines mobile radio types 49 | type RadioType string 50 | 51 | // Allowed radio types 52 | const ( 53 | RadioTypeLTE RadioType = "lte" 54 | RadioTypeGSM RadioType = "gsm" 55 | RadioTypeCDMA RadioType = "cdma" 56 | RadioTypeWCDMA RadioType = "wcdma" 57 | ) 58 | 59 | // CellTower is a cell tower object for localisation requests 60 | type CellTower struct { 61 | // CellID Unique identifier of the cell 62 | CellID int `json:"cellId,omitempty"` 63 | // LocationAreaCode is the Location Area Code (LAC) for GSM and WCDMAnetworks. The 64 | // Network ID (NID) for CDMA networks. 65 | LocationAreaCode int `json:"locationAreaCode,omitempty"` 66 | // MobileCountryCode is the cell tower's Mobile Country Code (MCC). 67 | MobileCountryCode int `json:"mobileCountryCode,omitempty"` 68 | // MobileNetworkCode is the cell tower's Mobile Network Code. This is the MNC for 69 | // GSM and WCDMA; CDMA uses the System ID (SID). 70 | MobileNetworkCode int `json:"mobileNetworkCode,omitempty"` 71 | // Age is the number of milliseconds since this cell was primary. If age is 0, the 72 | // cellId represents a current measurement. 73 | Age int `json:"age,omitempty"` 74 | // SignalStrength is the radio signal strength measured in dBm. 75 | SignalStrength int `json:"signalStrength,omitempty"` 76 | // TimingAdvance is the timing advance value. Please see 77 | // https://en.wikipedia.org/wiki/Timing_advance for more detail. 78 | TimingAdvance int `json:"timingAdvance,omitempty"` 79 | } 80 | 81 | // WiFiAccessPoint is a WiFi access point object for localisation requests 82 | type WiFiAccessPoint struct { 83 | // MacAddress is the MAC address of the WiFi node. Separators must be : (colon). 84 | MACAddress string `json:"macAddress,omitempty"` 85 | // SignalStrength is the current signal strength measured in dBm. 86 | SignalStrength float64 `json:"signalStrength,omitempty"` 87 | // Age is the number of milliseconds since this access point was detected. 88 | Age uint64 `json:"age,omitempty"` 89 | // Channel is the channel over which the client is communicating with the access 90 | // point. 91 | Channel int `json:"channel,omitempty"` 92 | // SignalToNoiseRatio is the current signal to noise ratio measured in dB. 93 | SignalToNoiseRatio float64 `json:"signalToNoiseRatio,omitempty"` 94 | } 95 | 96 | // GeolocationRequest is the request structure for Geolocation API 97 | // All fields are optional 98 | type GeolocationRequest struct { 99 | // HomeMobileCountryCode is the mobile country code (MCC) for the device's home 100 | // network. 101 | HomeMobileCountryCode int `json:"homeMobileCountryCode,omitempty"` 102 | // HomeMobileNetworkCode is the mobile network code (MNC) for the device's home 103 | // network. 104 | HomeMobileNetworkCode int `json:"homeMobileNetworkCode,omitempty"` 105 | // RadioType is the mobile radio type, this is optional but should be included if 106 | // available 107 | RadioType RadioType `json:"radioType,omitempty"` 108 | // Carrier is the carrier name 109 | Carrier string `json:"carrier,omitempty"` 110 | // ConsiderIP Specifies whether to fall back to IP geolocation if wifi and cell 111 | // tower signals are not available. 112 | ConsiderIP bool `json:"considerIp"` 113 | // CellTowers is an array of CellTower objects. 114 | CellTowers []CellTower `json:"cellTowers,omitempty"` 115 | // WifiAccessPoints is an array of WifiAccessPoint objects. 116 | WiFiAccessPoints []WiFiAccessPoint `json:"wifiAccessPoints,omitempty"` 117 | } 118 | 119 | // GeolocationResult is an approximate location and accuracy 120 | type GeolocationResult struct { 121 | // Location is the predicted location 122 | Location LatLng 123 | // Accuracy is the accuracy of the provided location in meters 124 | Accuracy float64 125 | } 126 | 127 | // GeolocationError is an error object reporting a request error 128 | type GeolocationError struct { 129 | // Errors lists errors that occurred 130 | Errors []struct { 131 | Domain string 132 | // Reason is an identifier for the error 133 | Reason string 134 | // Message is a short description of the error 135 | Message string 136 | } 137 | // Code is the error code (same as HTTP response) 138 | Code int 139 | // Message is a short description of the error 140 | Message string 141 | } 142 | -------------------------------------------------------------------------------- /geolocation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Geolocation API is available on 16 | // https://developers.google.com/maps/documentation/geolocation 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "io/ioutil" 24 | "net/http" 25 | "net/http/httptest" 26 | "reflect" 27 | "testing" 28 | ) 29 | 30 | func TestGeolocation(t *testing.T) { 31 | 32 | // Denver, the mile high city 33 | response := `{ 34 | "location" : { 35 | "lat" : 39.73915360, 36 | "lng" : -104.98470340 37 | }, 38 | "accuracy" : 4.771975994110107 39 | }` 40 | 41 | server := mockServer(200, response) 42 | defer server.Close() 43 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 44 | r := &GeolocationRequest{} 45 | 46 | resp, err := c.Geolocate(context.Background(), r) 47 | if err != nil { 48 | t.Errorf("r.Get returned non nil error, was %+v", err) 49 | } 50 | 51 | correctResponse := GeolocationResult{ 52 | Location: LatLng{ 53 | Lat: 39.73915360, 54 | Lng: -104.98470340, 55 | }, 56 | Accuracy: 4.771975994110107, 57 | } 58 | 59 | if !reflect.DeepEqual(*resp, correctResponse) { 60 | t.Errorf("expected %+v, was %+v", correctResponse, resp) 61 | } 62 | } 63 | 64 | func TestGeolocationError(t *testing.T) { 65 | 66 | // An error response 67 | response := `{ 68 | "error": { 69 | "errors": [ 70 | { 71 | "domain": "global", 72 | "reason": "invalid", 73 | "message": "Invalid value for UnsignedInteger: ", 74 | "locationType": "other", 75 | "location": "homeMobileCountryCode" 76 | } 77 | ], 78 | "code": 400, 79 | "message": "Invalid value for UnsignedInteger: " 80 | } 81 | }` 82 | 83 | server := mockServer(200, response) 84 | defer server.Close() 85 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 86 | r := &GeolocationRequest{} 87 | 88 | _, err := c.Geolocate(context.Background(), r) 89 | if err == nil { 90 | t.Errorf("r.Get returned nil error") 91 | } 92 | 93 | correctResponse := "Invalid value for UnsignedInteger: " 94 | 95 | if !reflect.DeepEqual(err.Error(), correctResponse) { 96 | t.Errorf("expected %+v, was %+v", correctResponse, err) 97 | } 98 | } 99 | 100 | func TestCellTowerAndWiFiRequest(t *testing.T) { 101 | // Denver, the mile high city 102 | response := `{ 103 | "location" : { 104 | "lat" : 39.73915360, 105 | "lng" : -104.98470340 106 | }, 107 | "accuracy" : 4.771975994110107 108 | }` 109 | 110 | server := &countingServer{} 111 | 112 | failResponse := func(reason string, w http.ResponseWriter, r *http.Request) { 113 | server.failed = append(server.failed, r.URL.RawQuery) 114 | s := fmt.Sprintf(`{"status":"fail", "message": "%s"}`, reason) 115 | http.Error(w, s, 999) 116 | } 117 | 118 | server.s = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 119 | 120 | b, err := ioutil.ReadAll(r.Body) 121 | if err != nil { 122 | failResponse("failed to read body", w, r) 123 | return 124 | } 125 | body := string(b) 126 | expected := `{"homeMobileCountryCode":310,` + 127 | `"homeMobileNetworkCode":410,` + 128 | `"radioType":"gsm",` + 129 | `"carrier":"Vodafone",` + 130 | `"considerIp":true,` + 131 | `"cellTowers":[{"cellId":42,` + 132 | `"locationAreaCode":415,` + 133 | `"mobileCountryCode":310,` + 134 | `"mobileNetworkCode":410,` + 135 | `"signalStrength":-60,` + 136 | `"timingAdvance":15}],` + 137 | `"wifiAccessPoints":[{"macAddress":"00:25:9c:cf:1c:ac",` + 138 | `"signalStrength":-43,` + 139 | `"channel":11}]}` 140 | if body != expected { 141 | failResponse("failed to parse body", w, r) 142 | return 143 | } 144 | 145 | server.successful++ 146 | 147 | w.WriteHeader(200) 148 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 149 | fmt.Fprintln(w, response) 150 | })) 151 | 152 | defer server.s.Close() 153 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.s.URL)) 154 | r := &GeolocationRequest{ 155 | HomeMobileCountryCode: 310, 156 | HomeMobileNetworkCode: 410, 157 | RadioType: RadioTypeGSM, 158 | Carrier: "Vodafone", 159 | ConsiderIP: true, 160 | CellTowers: []CellTower{{ 161 | CellID: 42, 162 | LocationAreaCode: 415, 163 | MobileCountryCode: 310, 164 | MobileNetworkCode: 410, 165 | Age: 0, 166 | SignalStrength: -60, 167 | TimingAdvance: 15, 168 | }}, 169 | WiFiAccessPoints: []WiFiAccessPoint{{ 170 | MACAddress: "00:25:9c:cf:1c:ac", 171 | SignalStrength: -43, 172 | Age: 0, 173 | Channel: 11, 174 | SignalToNoiseRatio: 0, 175 | }}, 176 | } 177 | 178 | resp, err := c.Geolocate(context.Background(), r) 179 | if err != nil { 180 | t.Errorf("r.Get returned non nil error, was %+v", err) 181 | } 182 | 183 | correctResponse := GeolocationResult{ 184 | Location: LatLng{ 185 | Lat: 39.73915360, 186 | Lng: -104.98470340, 187 | }, 188 | Accuracy: 4.771975994110107, 189 | } 190 | 191 | if !reflect.DeepEqual(*resp, correctResponse) { 192 | t.Errorf("expected %+v, was %+v", correctResponse, resp) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module googlemaps.github.io/maps 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/google/uuid v1.1.1 7 | github.com/kr/pretty v0.2.0 8 | github.com/sergi/go-diff v1.1.0 9 | github.com/stretchr/testify v1.6.1 10 | go.opencensus.io v0.22.3 11 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 8 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= 9 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 14 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 15 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 16 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 17 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 18 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 19 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 20 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 21 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 22 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 26 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 27 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 28 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 29 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 30 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 31 | go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= 32 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 35 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 36 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 37 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 38 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 39 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 40 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 41 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 42 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 43 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 44 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 45 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 52 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= 53 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 56 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 57 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 58 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 59 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 60 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 61 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 62 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 63 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 64 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 66 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 67 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 68 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 69 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 70 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 72 | -------------------------------------------------------------------------------- /internal/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package internal 16 | 17 | import ( 18 | "crypto/hmac" 19 | "crypto/sha1" 20 | "encoding/base64" 21 | "fmt" 22 | "net/url" 23 | ) 24 | 25 | // generateSignature generates the base64 URL-encoded HMAC-SHA1 signature for the key and plaintext message. 26 | func generateSignature(key []byte, message string) (string, error) { 27 | mac := hmac.New(sha1.New, key) 28 | mac.Write([]byte(message)) 29 | return base64.URLEncoding.EncodeToString(mac.Sum(nil)), nil 30 | } 31 | 32 | // SignURL signs a url with a signature. 33 | // The signature is assumed to be in URL safe base64 encoding. 34 | // The returned signature string is URLEncoded. 35 | // See: https://developers.google.com/maps/documentation/business/webservices/auth#digital_signatures 36 | // See: https://developers.google.com/maps/faq#using-google-maps-apis 37 | func SignURL(path string, signature []byte, q url.Values) (string, error) { 38 | encodedQuery := q.Encode() 39 | message := fmt.Sprintf("%s?%s", path, encodedQuery) 40 | s, err := generateSignature(signature, message) 41 | if err != nil { 42 | return "", err 43 | } 44 | return fmt.Sprintf("%s&signature=%s", encodedQuery, s), nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/signer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package internal 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/hex" 20 | "testing" 21 | ) 22 | 23 | // From http://en.wikipedia.org/wiki/Hash-based_message_authentication_code 24 | // HMAC_SHA1("key", "The quick brown fox jumps over the lazy dog") 25 | // = 0xde7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 26 | var message = "The quick brown fox jumps over the lazy dog" 27 | var signingKey = []byte("key") 28 | var signature = "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" 29 | 30 | func TestSigner(t *testing.T) { 31 | s, err := hex.DecodeString(signature) 32 | if err != nil { 33 | t.Errorf("Couldn't decode expected signature: %+v", err) 34 | } 35 | expected := base64.URLEncoding.EncodeToString(s) 36 | generated, err := generateSignature(signingKey, message) 37 | if err != nil { 38 | t.Errorf("Couldn't generate actual signature: %+v", err) 39 | } 40 | if expected != generated { 41 | t.Errorf("expected equal signature, was %s, expected %s", generated, expected) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package internal 16 | 17 | import "time" 18 | 19 | // DateTime is the public API representation of a point in time. 20 | type DateTime struct { 21 | // Text is the time specified as a string. The time is displayed in 22 | // the corresponding TimeZone. 23 | Text string `json:"text"` 24 | 25 | // TimeZone is the name of the time zone in the IANA Time Zone Database. For 26 | // example, "America/New_York" or "Australia/Sydney". 27 | TimeZone string `json:"time_zone"` 28 | 29 | // Value is the number of seconds since midnight 01 January, 1970 UTC. 30 | Value int64 `json:"value"` 31 | } 32 | 33 | // Time returns the time.Time for this DateTime. 34 | func (dt *DateTime) Time() time.Time { 35 | if dt == nil { 36 | return time.Time{} 37 | } 38 | 39 | loc, err := time.LoadLocation(dt.TimeZone) 40 | t := time.Unix(dt.Value, 0) 41 | if err == nil && loc != nil { 42 | t = t.In(loc) 43 | } 44 | return t 45 | } 46 | 47 | // NewDateTime builds a DateTime from the given time.Time. This will be nil 48 | // if time.Time is the zero time. 49 | func NewDateTime(t time.Time) *DateTime { 50 | if t.IsZero() { 51 | return nil 52 | } 53 | 54 | loc := t.Location() 55 | return &DateTime{ 56 | Text: t.Format(time.RFC1123), // TODO(samthor): better format 57 | TimeZone: loc.String(), 58 | Value: t.UnixNano() / int64(time.Second), 59 | } 60 | } 61 | 62 | // Duration is the public API representation of a duration. 63 | type Duration struct { 64 | // Value indicates the duration, in seconds. 65 | Value int64 `json:"value"` 66 | 67 | // Text contains a human-readable representation of the duration. 68 | Text string `json:"text"` 69 | } 70 | 71 | // Duration returns the time.Duration for this internal Duration. 72 | func (d *Duration) Duration() time.Duration { 73 | if d == nil { 74 | return time.Duration(0) 75 | } 76 | return time.Duration(d.Value) * time.Second 77 | } 78 | 79 | // NewDuration builds an internal Duration from the passed time.Duration. 80 | func NewDuration(d time.Duration) *Duration { 81 | if d == 0 { 82 | return &Duration{} 83 | } 84 | return &Duration{ 85 | Value: int64(d / time.Second), 86 | Text: d.String(), 87 | } 88 | } 89 | 90 | // Location is the Roads API+ representation of a location on the Earth. It 91 | // differs only in the encoded names, which are the longer forms of 'latitude' 92 | // and 'longitude'. 93 | type Location struct { 94 | Latitude float64 `json:"latitude"` 95 | Longitude float64 `json:"longitude"` 96 | } 97 | -------------------------------------------------------------------------------- /internal/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package internal 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestDateTime(t *testing.T) { 23 | if blank := NewDateTime(time.Time{}); blank != nil { 24 | t.Errorf("expected nil DateTime from zero time, was %v", blank) 25 | } 26 | 27 | loc, _ := time.LoadLocation("Australia/Sydney") 28 | orig := time.Date(2015, time.February, 25, 9, 9, 41, 0, loc) 29 | dt := NewDateTime(orig) 30 | if expected := "Australia/Sydney"; dt.TimeZone != expected { 31 | t.Errorf("expected timezone %v, was %v", expected, dt.TimeZone) 32 | } 33 | 34 | if actual := dt.Time(); !actual.Equal(orig) { 35 | t.Errorf("expected known time %v, was %v", orig, actual) 36 | } 37 | 38 | var blank *DateTime 39 | if expected := (time.Time{}); blank.Time() != expected { 40 | t.Errorf("expected nil DateTime to be zero time, was %v", blank.Time()) 41 | } 42 | } 43 | 44 | func TestDuration(t *testing.T) { 45 | if empty := NewDuration(time.Duration(0)); empty.Text != "" || empty.Value != 0 { 46 | t.Errorf("expected empty duration, was %v", empty) 47 | } 48 | 49 | orig := time.Second * time.Duration(133) 50 | d := NewDuration(orig) 51 | if expected := "2m13s"; d.Text != expected { 52 | t.Errorf("expected text duration %v, was %v", expected, d.Text) 53 | } 54 | 55 | if actual := d.Duration(); actual != orig { 56 | t.Errorf("expected known duration %v, was %v", d, actual) 57 | } 58 | 59 | var blank *Duration 60 | if expected := time.Duration(0); blank.Duration() != expected { 61 | t.Errorf("expected nil Duration to be zero duration, was %v", blank.Duration()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /latlng.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "math" 19 | "strconv" 20 | "strings" 21 | ) 22 | 23 | // LatLng represents a location on the Earth. 24 | type LatLng struct { 25 | Lat float64 `json:"lat"` 26 | Lng float64 `json:"lng"` 27 | } 28 | 29 | // ParseLatLng will parse a string representation of a Lat,Lng pair. 30 | func ParseLatLng(location string) (LatLng, error) { 31 | l := strings.Split(location, ",") 32 | lat, err := strconv.ParseFloat(l[0], 64) 33 | if err != nil { 34 | return LatLng{}, err 35 | } 36 | lng, err := strconv.ParseFloat(l[1], 64) 37 | if err != nil { 38 | return LatLng{}, err 39 | } 40 | return LatLng{Lat: lat, Lng: lng}, nil 41 | } 42 | 43 | // ParseLatLngList will parse a string of | separated Lat,Lng pairs. 44 | func ParseLatLngList(locations string) ([]LatLng, error) { 45 | result := []LatLng{} 46 | 47 | ls := strings.Split(locations, "|") 48 | for _, l := range ls { 49 | ll, err := ParseLatLng(l) 50 | if err != nil { 51 | return []LatLng{}, err 52 | } 53 | result = append(result, ll) 54 | } 55 | return result, nil 56 | } 57 | 58 | func (l *LatLng) String() string { 59 | return strconv.FormatFloat(l.Lat, 'f', -1, 64) + 60 | "," + 61 | strconv.FormatFloat(l.Lng, 'f', -1, 64) 62 | } 63 | 64 | // AlmostEqual returns whether this LatLng is almost equal (below epsilon) to 65 | // the other LatLng. 66 | func (l *LatLng) AlmostEqual(other *LatLng, epsilon float64) bool { 67 | return math.Abs(l.Lat-other.Lat) < epsilon && math.Abs(l.Lng-other.Lng) < epsilon 68 | } 69 | 70 | // LatLngBounds represents a bounded square area on the Earth. 71 | type LatLngBounds struct { 72 | NorthEast LatLng `json:"northeast"` 73 | SouthWest LatLng `json:"southwest"` 74 | } 75 | 76 | func (b *LatLngBounds) String() string { 77 | return b.SouthWest.String() + "|" + b.NorthEast.String() 78 | } 79 | -------------------------------------------------------------------------------- /latlng_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import "testing" 18 | 19 | func TestParseLatLng(t *testing.T) { 20 | expected := &LatLng{Lat: 12.34, Lng: 56.78} 21 | actual, err := ParseLatLng("12.34,56.78") 22 | if err != nil { 23 | t.Errorf("Failed to parse simple LatLng %+v", err) 24 | } 25 | 26 | if !actual.AlmostEqual(expected, 0.0001) { 27 | t.Errorf("LatLng failed to parse expected value. Actual '%+v', expected '%+v'", actual, expected) 28 | } 29 | } 30 | 31 | func TestParseLatLngList(t *testing.T) { 32 | expected0 := &LatLng{Lat: 12.34, Lng: 56.78} 33 | expected1 := &LatLng{Lat: 14.89, Lng: 123.89} 34 | 35 | actual, err := ParseLatLngList("12.34,56.78|14.89,123.89") 36 | if err != nil { 37 | t.Errorf("Failed to parse LatLngList %+v", err) 38 | } 39 | 40 | if !actual[0].AlmostEqual(expected0, 0.0001) { 41 | t.Errorf("LatLng failed to parse expected value. Actual '%+v', expected '%+v'", actual[0], expected0) 42 | } 43 | 44 | if !actual[1].AlmostEqual(expected1, 0.0001) { 45 | t.Errorf("LatLng failed to parse expected value. Actual '%+v', expected '%+v'", actual[1], expected1) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /metrics/metric.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type Reporter interface { 9 | NewRequest(name string) Request 10 | } 11 | 12 | type Request interface { 13 | EndRequest(ctx context.Context, err error, httpResp *http.Response, metro string) 14 | } 15 | 16 | type NoOpReporter struct { 17 | } 18 | 19 | func (n NoOpReporter) NewRequest(name string) Request { 20 | return noOpRequest{} 21 | } 22 | 23 | type noOpRequest struct { 24 | } 25 | 26 | func (n noOpRequest) EndRequest(ctx context.Context, err error, httpResp *http.Response, metro string) { 27 | } 28 | -------------------------------------------------------------------------------- /metrics/metric_test.go: -------------------------------------------------------------------------------- 1 | package metrics_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "googlemaps.github.io/maps" 11 | "googlemaps.github.io/maps/metrics" 12 | ) 13 | 14 | type testReporter struct { 15 | start, end int 16 | } 17 | 18 | func (t *testReporter) NewRequest(name string) metrics.Request { 19 | t.start++ 20 | return &testMetric{reporter: t} 21 | } 22 | 23 | type testMetric struct { 24 | reporter *testReporter 25 | } 26 | 27 | func (t *testMetric) EndRequest(ctx context.Context, err error, httpResp *http.Response, metro string) { 28 | t.reporter.end++ 29 | } 30 | 31 | func mockServer(codes []int, body string) *httptest.Server { 32 | i := 0 33 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 | w.WriteHeader(codes[i]) 35 | i++ 36 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 37 | fmt.Fprintln(w, body) 38 | })) 39 | return server 40 | } 41 | 42 | func TestClientWithMetricReporter(t *testing.T) { 43 | server := mockServer([]int{200}, `{"results" : [], "status" : "OK"}`) 44 | defer server.Close() 45 | reporter := &testReporter{} 46 | c, err := maps.NewClient( 47 | maps.WithAPIKey("AIza-Maps-API-Key"), 48 | maps.WithBaseURL(server.URL), 49 | maps.WithMetricReporter(reporter)) 50 | if err != nil { 51 | t.Errorf("Unable to create client with MetricReporter") 52 | } 53 | r := &maps.ElevationRequest{ 54 | Locations: []maps.LatLng{ 55 | { 56 | Lat: 39.73915360, 57 | Lng: -104.9847034, 58 | }, 59 | }, 60 | } 61 | _, err = c.Elevation(context.Background(), r) 62 | if err != nil { 63 | t.Errorf("r.Get returned non nil error, was %+v", err) 64 | } 65 | if reporter.start != 1 { 66 | t.Errorf("expected one start call") 67 | } 68 | if reporter.end != 1 { 69 | t.Errorf("expected one end call") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /metrics/opencensus.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "go.opencensus.io/stats" 6 | "go.opencensus.io/stats/view" 7 | "go.opencensus.io/tag" 8 | "net/http" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | var ( 14 | latency_measure = stats.Int64("maps.googleapis.com/measure/client/latency", "Latency in msecs", stats.UnitMilliseconds) 15 | 16 | requestName = tag.MustNewKey("request_name") 17 | apiStatus = tag.MustNewKey("api_status") 18 | httpCode = tag.MustNewKey("http_code") 19 | metroArea = tag.MustNewKey("metro_area") 20 | 21 | Count = &view.View{ 22 | Name: "maps.googleapis.com/client/count", 23 | Description: "Request Counts", 24 | TagKeys: []tag.Key{requestName, apiStatus, httpCode, metroArea}, 25 | Measure: latency_measure, 26 | Aggregation: view.Count(), 27 | } 28 | 29 | Latency = &view.View{ 30 | Name: "maps.googleapis.com/client/request_latency", 31 | Description: "Total time between library method called and results returned", 32 | TagKeys: []tag.Key{requestName, apiStatus, httpCode, metroArea}, 33 | Measure: latency_measure, 34 | Aggregation: view.Distribution(20.0, 25.2, 31.7, 40.0, 50.4, 63.5, 80.0, 100.8, 127.0, 160.0, 201.6, 254.0, 320.0, 403.2, 508.0, 640.0, 806.3, 1015.9, 1280.0, 1612.7, 2031.9, 2560.0, 3225.4, 4063.7), 35 | } 36 | ) 37 | 38 | func RegisterViews() error { 39 | return view.Register(Latency, Count) 40 | } 41 | 42 | type OpenCensusReporter struct { 43 | } 44 | 45 | func (o OpenCensusReporter) NewRequest(name string) Request { 46 | return &openCensusRequest{ 47 | name: name, 48 | start: time.Now().UnixNano() / int64(time.Millisecond), 49 | } 50 | } 51 | 52 | type openCensusRequest struct { 53 | name string 54 | start int64 55 | } 56 | 57 | func (o *openCensusRequest) EndRequest(ctx context.Context, err error, httpResp *http.Response, metro string) { 58 | now := time.Now().UnixNano() / int64(time.Millisecond) 59 | duration := now - o.start 60 | errStr := "" 61 | if err != nil { 62 | errStr = err.Error() 63 | } 64 | httpCodeStr := "" 65 | if httpResp != nil { 66 | httpCodeStr = strconv.Itoa(httpResp.StatusCode) 67 | } 68 | stats.RecordWithTags(ctx, []tag.Mutator{ 69 | tag.Upsert(requestName, o.name), 70 | tag.Upsert(apiStatus, errStr), 71 | tag.Upsert(httpCode, httpCodeStr), 72 | tag.Upsert(metroArea, metro), 73 | }, latency_measure.M(duration)) 74 | } 75 | -------------------------------------------------------------------------------- /metrics/opencensus_test.go: -------------------------------------------------------------------------------- 1 | package metrics_test 2 | 3 | import ( 4 | "context" 5 | "go.opencensus.io/stats/view" 6 | "testing" 7 | 8 | "googlemaps.github.io/maps" 9 | "googlemaps.github.io/maps/metrics" 10 | ) 11 | 12 | func TestClientWithOpenCensus(t *testing.T) { 13 | metrics.RegisterViews() 14 | server := mockServer([]int{200, 400}, `{"results" : [], "status" : "OK"}`) 15 | defer server.Close() 16 | c, err := maps.NewClient( 17 | maps.WithAPIKey("AIza-Maps-API-Key"), 18 | maps.WithBaseURL(server.URL), 19 | maps.WithMetricReporter(metrics.OpenCensusReporter{})) 20 | if err != nil { 21 | t.Errorf("Unable to create client with OpenCensusReporter") 22 | } 23 | r := &maps.ElevationRequest{ 24 | Locations: []maps.LatLng{ 25 | { 26 | Lat: 39.73915360, 27 | Lng: -104.9847034, 28 | }, 29 | }, 30 | } 31 | _, err = c.Elevation(context.Background(), r) 32 | if err != nil { 33 | t.Errorf("r.Get returned non nil error, was %+v", err) 34 | } 35 | _, err = c.Elevation(context.Background(), r) 36 | if err != nil { 37 | t.Errorf("r.Get returned non nil error, was %+v", err) 38 | } 39 | count, _ := view.RetrieveData("maps.googleapis.com/client/count") 40 | if len(count) != 2 { 41 | t.Errorf("expected two metrics, got %v", len(count)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /polyline.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | ) 21 | 22 | // Polyline represents a list of lat,lng points encoded as a byte array. 23 | // See: https://developers.google.com/maps/documentation/utilities/polylinealgorithm 24 | type Polyline struct { 25 | Points string `json:"points"` 26 | } 27 | 28 | // DecodePolyline converts a polyline encoded string to an array of LatLng objects. 29 | func DecodePolyline(poly string) ([]LatLng, error) { 30 | p := &Polyline{ 31 | Points: poly, 32 | } 33 | return p.Decode() 34 | } 35 | 36 | // Decode converts this encoded Polyline to an array of LatLng objects. 37 | func (p *Polyline) Decode() ([]LatLng, error) { 38 | input := bytes.NewBufferString(p.Points) 39 | 40 | var lat, lng int64 41 | path := make([]LatLng, 0, len(p.Points)/2) 42 | for { 43 | dlat, _ := decodeInt(input) 44 | dlng, err := decodeInt(input) 45 | if err == io.EOF { 46 | return path, nil 47 | } 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | lat, lng = lat+dlat, lng+dlng 53 | path = append(path, LatLng{ 54 | Lat: float64(lat) * 1e-5, 55 | Lng: float64(lng) * 1e-5, 56 | }) 57 | } 58 | } 59 | 60 | // Encode returns a new encoded Polyline from the given path. 61 | func Encode(path []LatLng) string { 62 | var prevLat, prevLng int64 63 | 64 | out := new(bytes.Buffer) 65 | out.Grow(len(path) * 4) 66 | 67 | for _, point := range path { 68 | lat := int64(point.Lat * 1e5) 69 | lng := int64(point.Lng * 1e5) 70 | 71 | encodeInt(lat-prevLat, out) 72 | encodeInt(lng-prevLng, out) 73 | 74 | prevLat, prevLng = lat, lng 75 | } 76 | 77 | return out.String() 78 | } 79 | 80 | // decodeInt reads an encoded int64 from the passed io.ByteReader. 81 | func decodeInt(r io.ByteReader) (int64, error) { 82 | result := int64(0) 83 | var shift uint8 84 | 85 | for { 86 | raw, err := r.ReadByte() 87 | if err != nil { 88 | return 0, err 89 | } 90 | 91 | b := raw - 63 92 | result += int64(b&0x1f) << shift 93 | shift += 5 94 | 95 | if b < 0x20 { 96 | bit := result & 1 97 | result >>= 1 98 | if bit != 0 { 99 | result = ^result 100 | } 101 | return result, nil 102 | } 103 | } 104 | } 105 | 106 | // encodeInt writes an encoded int64 to the passed io.ByteWriter. 107 | func encodeInt(v int64, w io.ByteWriter) { 108 | if v < 0 { 109 | v = ^(v << 1) 110 | } else { 111 | v <<= 1 112 | } 113 | for v >= 0x20 { 114 | w.WriteByte((0x20 | (byte(v) & 0x1f)) + 63) 115 | v >>= 5 116 | } 117 | w.WriteByte(byte(v) + 63) 118 | } 119 | -------------------------------------------------------------------------------- /polyline_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | ) 21 | 22 | const ( 23 | epsilon = 0.0001 24 | routeSydMel = "rvumEis{y[`NsfA~tAbF`bEj^h{@{KlfA~eA~`AbmEghAt~D|e@jlRpO~yH_\\v}LjbBh~FdvCxu@`nCplDbcBf_B|w" + 25 | "BhIfhCnqEb~D~jCn_EngApdEtoBbfClf@t_CzcCpoEr_Gz_DxmAphDjjBxqCviEf}B|pEvsEzbE~qGfpExjBlqCx}" + 26 | "BvmLb`FbrQdpEvkAbjDllD|uDldDj`Ef|AzcEx_Gtm@vuI~xArwD`dArlFnhEzmHjtC~eDluAfkC|eAdhGpJh}N_m" + 27 | "ArrDlr@h|HzjDbsAvy@~~EdTxpJje@jlEltBboDjJdvKyZpzExrAxpHfg@pmJg[tgJuqBnlIarAh}DbN`hCeOf_Ib" + 28 | "xA~uFt|A|xEt_ArmBcN|sB|h@b_DjOzbJ{RlxCcfAp~AahAbqG~Gr}AerA`dCwlCbaFo]twKt{@bsG|}A~fDlvBvz" + 29 | "@tw@rpD_r@rqB{PvbHek@vsHlh@ptNtm@fkD[~xFeEbyKnjDdyDbbBtuA|~Br|Gx_AfxCt}CjnHv`Ew\\lnBdrBfq" + 30 | "BraD|{BldBxpG|]jqC`mArcBv]rdAxgBzdEb{InaBzyC}AzaEaIvrCzcAzsCtfD~qGoPfeEh]h`BxiB`e@`kBxfAv" + 31 | "^pyA`}BhkCdoCtrC~bCxhCbgEplKrk@tiAteBwAxbCwuAnnCc]b{FjrDdjGhhGzfCrlDruBzSrnGhvDhcFzw@n{@z" + 32 | "xAf}Fd{IzaDnbDjoAjqJjfDlbIlzAraBxrB}K~`GpuD~`BjmDhkBp{@r_AxCrnAjrCx`AzrBj{B|r@~qBbdAjtDnv" + 33 | "CtNzpHxeApyC|GlfM`fHtMvqLjuEtlDvoFbnCt|@xmAvqBkGreFm~@hlHw|AltC}NtkGvhBfaJ|~@riAxuC~gErwC" + 34 | "ttCzjAdmGuF`iFv`AxsJftD|nDr_QtbMz_DheAf~Buy@rlC`i@d_CljC`gBr|H|nAf_Fh{G|mE~kAhgKviEpaQnu@" + 35 | "zwAlrA`G~gFnvItz@j{Cng@j{D{]`tEftCdcIsPz{DddE~}PlnE|dJnzG`eG`mF|aJdqDvoAwWjzHv`H`wOtjGzeX" + 36 | "hhBlxErfCf{BtsCjpEjtD|}Aja@xnAbdDt|ErMrdFh{CzgAnlCnr@`wEM~mE`bA`uD|MlwKxmBvuFlhB|sN`_@fvB" + 37 | "p`CxhCt_@loDsS|eDlmChgFlqCbjCxk@vbGxmCjbMba@rpBaoClcCk_DhgEzYdzBl\\vsA_JfGztAbShkGtEhlDzh" + 38 | "C~w@hnB{e@yF}`D`_Ayx@~vGqn@l}CafC" 39 | routeWith0b = "ynkrFq|zfE?sCnBpA" 40 | ) 41 | 42 | var expectedWith0b = []LatLng{{Lat: 39.87709, Lng: 32.74713}, {Lat: 39.87709, Lng: 32.74787}, {Lat: 39.87653, Lng: 32.74746}} 43 | 44 | func TestPolylineDecode(t *testing.T) { 45 | decoded, err := DecodePolyline(routeSydMel) 46 | if err != nil { 47 | t.Error("Didn't decode correctly", err) 48 | } 49 | l := len(decoded) 50 | 51 | if expected, actual := (&LatLng{-33.86746, 151.207090}), decoded[0]; !expected.AlmostEqual(&actual, epsilon) { 52 | t.Errorf("first point was %v, expected %v", decoded[0], expected) 53 | } 54 | if expected, actual := (&LatLng{-37.814130, 144.963180}), decoded[l-1]; !expected.AlmostEqual(&actual, epsilon) { 55 | t.Errorf("last point was %v, expected %v", decoded[l-1], expected) 56 | } 57 | 58 | decoded, err = DecodePolyline(routeWith0b) 59 | if err != nil { 60 | t.Error("Didn't decode correctly", err) 61 | } 62 | if len(decoded) != len(expectedWith0b) { 63 | t.Errorf("expected equal encoding for 0 change in one direction length mismatch, was len %v, expected len %v", len(decoded), len(expectedWith0b)) 64 | } 65 | for i := range decoded { 66 | if expected, actual := (expectedWith0b[i]), (decoded[i]); !expected.AlmostEqual(&actual, epsilon) { 67 | t.Errorf("expected equal encoding for 0 change in one direction mismatch coordinate, was %+v, expected %+v", decoded, expectedWith0b) 68 | break 69 | } 70 | } 71 | } 72 | 73 | func TestPolylineEncode(t *testing.T) { 74 | decoded, err := DecodePolyline(routeSydMel) 75 | if err != nil { 76 | t.Error("Didn't decode correctly", err) 77 | } 78 | 79 | encoded := Encode(decoded) 80 | if !reflect.DeepEqual(encoded, routeSydMel) { 81 | t.Errorf("expected equal encoding, was len %v, expected len %v", len(routeSydMel), len(encoded)) 82 | } 83 | 84 | decoded2, err := DecodePolyline(routeWith0b) 85 | if err != nil { 86 | t.Error("Didn't decode correctly", err) 87 | } 88 | 89 | encoded2 := Encode(decoded2) 90 | if !reflect.DeepEqual(encoded2, routeWith0b) { 91 | t.Errorf("expected 2 equal encoding, was len %v, expected len %v", len(routeWith0b), len(encoded2)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /roads.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Distance Matrix API is available on 16 | // https://developers.google.com/maps/documentation/distancematrix/ 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "net/url" 24 | "strings" 25 | ) 26 | 27 | var snapToRoadsAPI = &apiConfig{ 28 | host: "https://roads.googleapis.com", 29 | path: "/v1/snapToRoads", 30 | acceptsClientID: false, 31 | acceptsSignature: false, 32 | } 33 | 34 | var nearestRoadsAPI = &apiConfig{ 35 | host: "https://roads.googleapis.com", 36 | path: "/v1/nearestRoads", 37 | acceptsClientID: false, 38 | acceptsSignature: false, 39 | } 40 | 41 | var speedLimitsAPI = &apiConfig{ 42 | host: "https://roads.googleapis.com", 43 | path: "/v1/speedLimits", 44 | acceptsClientID: false, 45 | acceptsSignature: false, 46 | } 47 | 48 | // SnapToRoad makes a Snap to Road API request 49 | func (c *Client) SnapToRoad(ctx context.Context, r *SnapToRoadRequest) (*SnapToRoadResponse, error) { 50 | 51 | if len(r.Path) == 0 { 52 | return nil, errors.New("maps: Path empty") 53 | } 54 | 55 | response := &SnapToRoadResponse{} 56 | 57 | if err := c.getJSON(ctx, snapToRoadsAPI, r, response); err != nil { 58 | return nil, err 59 | } 60 | 61 | return response, nil 62 | } 63 | 64 | func (r *SnapToRoadRequest) params() url.Values { 65 | q := make(url.Values) 66 | var p []string 67 | for _, e := range r.Path { 68 | p = append(p, e.String()) 69 | } 70 | 71 | q.Set("path", strings.Join(p, "|")) 72 | if r.Interpolate { 73 | q.Set("interpolate", "true") 74 | } 75 | 76 | return q 77 | } 78 | 79 | // SnapToRoadRequest is the request structure for the Roads Snap to Road API. 80 | type SnapToRoadRequest struct { 81 | // Path is the path to be snapped. 82 | Path []LatLng 83 | 84 | // Interpolate is whether to interpolate a path to include all points forming the 85 | // full road-geometry. 86 | Interpolate bool 87 | } 88 | 89 | // SnapToRoadResponse is an array of snapped points. 90 | type SnapToRoadResponse struct { 91 | SnappedPoints []SnappedPoint `json:"snappedPoints"` 92 | } 93 | 94 | // SnappedPoint is the original path point snapped to a road. 95 | type SnappedPoint struct { 96 | // Location of the snapped point. 97 | Location LatLng `json:"location"` 98 | 99 | // OriginalIndex is an integer that indicates the corresponding value in the 100 | // original request. Not present on interpolated points. 101 | OriginalIndex *int `json:"originalIndex"` 102 | 103 | // PlaceID is a unique identifier for a place. 104 | PlaceID string `json:"placeId"` 105 | } 106 | 107 | // NearestRoads makes a Nearest Roads API request 108 | func (c *Client) NearestRoads(ctx context.Context, r *NearestRoadsRequest) (*NearestRoadsResponse, error) { 109 | 110 | if len(r.Points) == 0 { 111 | return nil, errors.New("maps: Points empty") 112 | } 113 | 114 | response := &NearestRoadsResponse{} 115 | 116 | if err := c.getJSON(ctx, nearestRoadsAPI, r, response); err != nil { 117 | return nil, err 118 | } 119 | 120 | return response, nil 121 | } 122 | 123 | func (r *NearestRoadsRequest) params() url.Values { 124 | q := make(url.Values) 125 | var p []string 126 | for _, e := range r.Points { 127 | p = append(p, e.String()) 128 | } 129 | 130 | q.Set("points", strings.Join(p, "|")) 131 | 132 | return q 133 | } 134 | 135 | // NearestRoadsRequest is the request structure for the Nearest Roads API. 136 | type NearestRoadsRequest struct { 137 | // Points is the list of points to be snapped. 138 | Points []LatLng 139 | } 140 | 141 | // NearestRoadsResponse is an array of snapped points. 142 | type NearestRoadsResponse struct { 143 | SnappedPoints []SnappedPoint `json:"snappedPoints"` 144 | } 145 | 146 | // SpeedLimits makes a Speed Limits API request 147 | func (c *Client) SpeedLimits(ctx context.Context, r *SpeedLimitsRequest) (*SpeedLimitsResponse, error) { 148 | 149 | if len(r.Path) == 0 && len(r.PlaceID) == 0 { 150 | return nil, errors.New("maps: Path and PlaceID both empty") 151 | } 152 | 153 | response := &SpeedLimitsResponse{} 154 | 155 | if err := c.getJSON(ctx, speedLimitsAPI, r, response); err != nil { 156 | return nil, err 157 | } 158 | 159 | return response, nil 160 | } 161 | 162 | func (r *SpeedLimitsRequest) params() url.Values { 163 | q := make(url.Values) 164 | 165 | var p []string 166 | for _, e := range r.Path { 167 | p = append(p, e.String()) 168 | } 169 | 170 | if len(p) > 0 { 171 | q.Set("path", strings.Join(p, "|")) 172 | } 173 | for _, id := range r.PlaceID { 174 | q.Add("placeId", id) 175 | } 176 | if r.Units != "" { 177 | q.Set("units", string(r.Units)) 178 | } 179 | 180 | return q 181 | } 182 | 183 | type speedLimitUnit string 184 | 185 | const ( 186 | // SpeedLimitMPH is for requesting speed limits in Miles Per Hour. 187 | SpeedLimitMPH = "MPH" 188 | // SpeedLimitKPH is for requesting speed limits in Kilometers Per Hour. 189 | SpeedLimitKPH = "KPH" 190 | ) 191 | 192 | // SpeedLimitsRequest is the request structure for the Roads Speed Limits API. 193 | type SpeedLimitsRequest struct { 194 | // Path is the path to be snapped and speed limits requested. 195 | Path []LatLng 196 | 197 | // PlaceID is the PlaceIDs to request speed limits for. 198 | PlaceID []string 199 | 200 | // Units is whether to return speed limits in `SpeedLimitKPH` or `SpeedLimitMPH`. 201 | // Optional, default behavior is to return results in KPH. 202 | Units speedLimitUnit 203 | } 204 | 205 | // SpeedLimitsResponse is an array of snapped points and an array of speed limits. 206 | type SpeedLimitsResponse struct { 207 | SpeedLimits []SpeedLimit `json:"speedLimits"` 208 | SnappedPoints []SnappedPoint `json:"snappedPoints"` 209 | } 210 | 211 | // SpeedLimit is the speed limit for a PlaceID 212 | type SpeedLimit struct { 213 | // PlaceID is a unique identifier for a place. 214 | PlaceID string `json:"placeId"` 215 | // SpeedLimit is the speed limit for that road segment. 216 | SpeedLimit float64 `json:"speedLimit"` 217 | // Units is either KPH or MPH. 218 | Units speedLimitUnit `json:"units"` 219 | } 220 | -------------------------------------------------------------------------------- /staticmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "image" 22 | "io/ioutil" 23 | "net/http" 24 | "net/url" 25 | "strconv" 26 | "strings" 27 | 28 | _ "image/jpeg" // Loaded for image decoder 29 | _ "image/png" // Loaded for image decoder 30 | ) 31 | 32 | var staticMapAPI = &apiConfig{ 33 | host: "https://maps.googleapis.com", 34 | path: "/maps/api/staticmap", 35 | acceptsClientID: true, 36 | acceptsSignature: true, 37 | } 38 | 39 | // MapType (optional) defines the type of map to construct. There are several possible 40 | // maptype values, including roadmap, satellite, hybrid, and terrain 41 | type MapType string 42 | 43 | // Format defines the format of the resulting image 44 | type Format string 45 | 46 | // MarkerSize specifies the size of marker from the set {tiny, mid, small} 47 | type MarkerSize string 48 | 49 | // Anchor sets how the icon is placed in relation to the specified markers locations 50 | type Anchor string 51 | 52 | const ( 53 | // RoadMap (default) specifies a standard roadmap image, as is normally shown on the 54 | // Google Maps website. If no maptype value is specified, the Google Static Maps API 55 | // serves roadmap tiles by default. 56 | RoadMap MapType = "roadmap" 57 | //Satellite specifies a satellite image. 58 | Satellite MapType = "satellite" 59 | //Terrain specifies a physical relief map image, showing terrain and vegetation. 60 | Terrain MapType = "terrain" 61 | // Hybrid specifies a hybrid of the satellite and roadmap image, showing a 62 | // transparent layer of major streets and place names on the satellite image. 63 | Hybrid MapType = "hybrid" 64 | //PNG8 or png (default) specifies the 8-bit PNG format. 65 | PNG8 Format = "png8" 66 | // PNG32 specifies the 32-bit PNG format. 67 | PNG32 Format = "png32" 68 | // GIF specifies the GIF format. 69 | GIF Format = "gif" 70 | // JPG specifies the JPEG compression format. 71 | JPG Format = "jpg" 72 | // JPGBaseline specifies a non-progressive JPEG compression format. 73 | JPGBaseline Format = "jpg-baseline" 74 | 75 | // Tiny Marker size 76 | Tiny MarkerSize = "tiny" 77 | // Mid Marker size 78 | Mid MarkerSize = "mid" 79 | // Small Marker size 80 | Small MarkerSize = "small" 81 | 82 | // Top Marker anchor position 83 | Top Anchor = "top" 84 | // Bottom Marker anchor position 85 | Bottom Anchor = "Bottom" 86 | // Left Marker anchor position 87 | Left Anchor = "left" 88 | // Right Marker anchor position 89 | Right Anchor = "right" 90 | // Center Marker anchor position 91 | Center Anchor = "center" 92 | // Topleft Marker anchor position 93 | Topleft Anchor = "topleft" 94 | // Topright Marker anchor position 95 | Topright Anchor = "topright" 96 | // Bottomleft Marker anchor position 97 | Bottomleft Anchor = "bottomleft" 98 | // Bottomright Marker anchor position 99 | Bottomright Anchor = "bottomright" 100 | ) 101 | 102 | // CustomIcon replace the default Map Pin 103 | type CustomIcon struct { 104 | // IconURL is th icon URL 105 | IconURL string 106 | // Anchor sets how the icon is placed in relation to the specified markers locations 107 | Anchor Anchor 108 | // Scale is the custom icon scale 109 | Scale int 110 | } 111 | 112 | func (c CustomIcon) String() string { 113 | var r []string 114 | 115 | if c.IconURL != "" { 116 | r = append(r, fmt.Sprintf("icon:%s", c.IconURL)) 117 | } 118 | 119 | if c.Anchor != "" { 120 | r = append(r, fmt.Sprintf("anchor:%s", c.Anchor)) 121 | } 122 | 123 | if c.Scale != 0 { 124 | r = append(r, fmt.Sprintf("scale:%d", c.Scale)) 125 | } 126 | 127 | return strings.Join(r, "|") 128 | } 129 | 130 | // Marker is a Map pin 131 | type Marker struct { 132 | // Color specifies a 24-bit color (example: color=0xFFFFCC) or a predefined color 133 | // from the set {black, brown, green, purple, yellow, blue, gray, orange, red, 134 | // white}. 135 | Color string 136 | // Label specifies a single uppercase alphanumeric character from the set {A-Z, 0-9} 137 | Label string 138 | // MarkerSize specifies the size of marker from the set {tiny, mid, small} 139 | Size string 140 | // CustomIcon replace the default Map Pin 141 | CustomIcon CustomIcon 142 | // Location is the Marker position 143 | Location []LatLng 144 | // LocationAddress is the Marker position as a postal address or other geocodable location. 145 | LocationAddress string 146 | } 147 | 148 | func (m Marker) String() string { 149 | var r []string 150 | 151 | if m.CustomIcon != (CustomIcon{}) { 152 | r = append(r, m.CustomIcon.String()) 153 | } else { 154 | if m.Color != "" { 155 | r = append(r, fmt.Sprintf("color:%s", m.Color)) 156 | } 157 | 158 | if m.Label != "" { 159 | r = append(r, fmt.Sprintf("label:%s", m.Label)) 160 | } 161 | 162 | if m.Size != "" { 163 | r = append(r, fmt.Sprintf("size:%s", m.Size)) 164 | } 165 | } 166 | 167 | for _, l := range m.Location { 168 | r = append(r, l.String()) 169 | } 170 | if m.LocationAddress != "" { 171 | r = append(r, m.LocationAddress) 172 | } 173 | 174 | return strings.Join(r, "|") 175 | } 176 | 177 | // Path defines a single path of two or more connected points to overlay on the image 178 | // at specified locations 179 | type Path struct { 180 | // Weight (optional) specifies the thickness of the path in pixels. 181 | Weight int 182 | // Color (optional) specifies a color in HEX 183 | Color string 184 | // Fillcolor (optional) indicates both that the path marks off a polygonal area and 185 | // specifies the fill color to use as an overlay within that area. 186 | FillColor string 187 | // Geodesic (optional) indicates that the requested path should be interpreted as a 188 | // geodesic line that follows the curvature of the earth. 189 | Geodesic bool 190 | // Location two or more connected points to overlay on the image at specified 191 | // locations 192 | Location []LatLng 193 | } 194 | 195 | func (p Path) String() string { 196 | var r []string 197 | 198 | if p.Color != "" { 199 | r = append(r, fmt.Sprintf("color:%s", p.Color)) 200 | } 201 | 202 | if p.FillColor != "" { 203 | r = append(r, fmt.Sprintf("fillcolor:%s", p.FillColor)) 204 | } 205 | 206 | if p.Weight != 0 { 207 | r = append(r, fmt.Sprintf("weight:%d", p.Weight)) 208 | } 209 | 210 | if p.Geodesic { 211 | r = append(r, "geodesic:true") 212 | } 213 | 214 | if len(p.Location) == 0 { 215 | return strings.Join(r, "|") 216 | } 217 | 218 | encodedLocationString := fmt.Sprintf("enc:%s", Encode(p.Location)) 219 | latLngsToString := make([]string, len(p.Location)) 220 | for i, l := range p.Location { 221 | latLngsToString[i] = l.String() 222 | } 223 | locationString := strings.Join(latLngsToString, "|") 224 | if len(locationString) > len(encodedLocationString) { 225 | r = append(r, encodedLocationString) 226 | } else { 227 | r = append(r, latLngsToString...) 228 | } 229 | return strings.Join(r, "|") 230 | } 231 | 232 | // StaticMapRequest is the functional options struct for staticMap.Get 233 | type StaticMapRequest struct { 234 | // Center focus the map at the correct location 235 | Center string 236 | // Zoom (required if markers not present) defines the zoom level of the map 237 | Zoom int 238 | // Size (required) defines the rectangular dimensions of the map image. This 239 | // parameter takes a string of the form {horizontal_value}x{vertical_value} 240 | Size string 241 | // Scale (optional) affects the number of pixels that are returned. Accepted values 242 | // are 2 and 4 243 | Scale int 244 | // Format format (optional) defines the format of the resulting image. Default: PNG. 245 | // Accepted Values: There are several possible formats including GIF, JPEG and PNG 246 | // types. 247 | Format Format 248 | // Language (optional) defines the language to use for display of labels on map 249 | // tiles 250 | Language string 251 | // Region (optional) defines the appropriate borders to display, based on 252 | // geo-political sensitivities. 253 | Region string 254 | // MapType (optional) defines the type of map to construct. 255 | MapType MapType 256 | // MapId (optional) defines the identifier for a specific map. 257 | MapId string 258 | // Markers (optional) define one or more markers to attach to the image at specified 259 | // locations. 260 | Markers []Marker 261 | // Paths (optional) defines multiple paths of two or more connected points to 262 | // overlay on the image at specified locations 263 | Paths []Path 264 | // Visible specifies one or more locations that should remain visible on the map, 265 | // though no markers or other indicators will be displayed. 266 | Visible []LatLng 267 | // MapStyles (optional) contains map styles. 268 | MapStyles []string 269 | } 270 | 271 | func (r *StaticMapRequest) params() url.Values { 272 | q := make(url.Values) 273 | 274 | if r.Center != "" { 275 | q.Set("center", r.Center) 276 | } 277 | 278 | if r.Zoom > 0 { 279 | q.Set("zoom", strconv.Itoa(r.Zoom)) 280 | } 281 | if r.Size != "" { 282 | q.Set("size", r.Size) 283 | } 284 | if r.Scale > 0 { 285 | q.Set("scale", strconv.Itoa(r.Scale)) 286 | } 287 | if r.Format != "" { 288 | q.Set("format", string(r.Format)) 289 | } 290 | 291 | if r.Language != "" { 292 | q.Set("language", r.Language) 293 | } 294 | 295 | if r.Region != "" { 296 | q.Set("region", r.Region) 297 | } 298 | if r.MapType != "" { 299 | q.Set("maptype", string(r.MapType)) 300 | } 301 | if r.MapId != "" { 302 | q.Set("map_id", r.MapId) 303 | } 304 | 305 | for _, m := range r.Markers { 306 | q.Add("markers", m.String()) 307 | } 308 | 309 | for _, ps := range r.Paths { 310 | q.Add("path", ps.String()) 311 | } 312 | 313 | if len(r.Visible) > 0 { 314 | t := make([]string, len(r.Visible)) 315 | for i, l := range r.Visible { 316 | t[i] = l.String() 317 | } 318 | q.Set("visible", strings.Join(t, "|")) 319 | } 320 | 321 | for _, style := range r.MapStyles { 322 | q.Add("style", style) 323 | } 324 | 325 | return q 326 | } 327 | 328 | // StaticMap makes a StaticMap API request. 329 | func (c *Client) StaticMap(ctx context.Context, r *StaticMapRequest) (image.Image, error) { 330 | if len(r.Markers) == 0 && r.Center == "" && r.Zoom == 0 { 331 | return nil, errors.New("maps: Center & Zoom required if Markers empty") 332 | } 333 | if r.Size == "" { 334 | return nil, errors.New("maps: Size empty") 335 | } 336 | 337 | resp, err := c.getBinary(ctx, staticMapAPI, r) 338 | if err != nil { 339 | return nil, err 340 | } 341 | defer resp.data.Close() 342 | 343 | if resp.statusCode != http.StatusOK { 344 | b, err := ioutil.ReadAll(resp.data) 345 | if err != nil { 346 | return nil, err 347 | } 348 | return nil, fmt.Errorf("Maps Static API: %d - %s", resp.statusCode, b) 349 | } 350 | 351 | img, _, err := image.Decode(resp.data) 352 | return img, err 353 | } 354 | -------------------------------------------------------------------------------- /staticmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "image" 21 | "image/png" 22 | "net/http" 23 | "net/http/httptest" 24 | "testing" 25 | ) 26 | 27 | // mockServerForQueryWithImage returns a mock server that only responds to a particular query string, and responds with an encoded Image. 28 | func mockServerForQueryWithImage(query string, code int, img image.Image) *countingServer { 29 | server := &countingServer{} 30 | 31 | server.s = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 32 | if query != "" && r.URL.RawQuery != query { 33 | server.failed = append(server.failed, r.URL.RawQuery) 34 | http.Error(w, fmt.Sprintf("Expected '%s', got '%s'", query, r.URL.RawQuery), 999) 35 | return 36 | } 37 | server.successful++ 38 | 39 | w.WriteHeader(code) 40 | w.Header().Set("Content-Type", "image/png") 41 | png.Encode(w, img) 42 | })) 43 | 44 | return server 45 | } 46 | 47 | func TestStaticMode(t *testing.T) { 48 | 49 | response := image.NewRGBA(image.Rect(0, 0, 640, 400)) 50 | 51 | server := mockServerForQueryWithImage("center=Brooklyn+Bridge%2CNew+York%2CNY&format=PNG&key=AIzaNotReallyAnAPIKey&language=EN-us&maptype=roadmap®ion=US&scale=2&size=600x300&zoom=13", 200, response) 52 | defer server.s.Close() 53 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.s.URL)) 54 | r := &StaticMapRequest{ 55 | Center: "Brooklyn Bridge,New York,NY", 56 | Size: "600x300", 57 | Zoom: 13, 58 | Scale: 2, 59 | Language: "EN-us", 60 | Format: "PNG", 61 | Region: "US", 62 | MapType: MapType("roadmap"), 63 | } 64 | 65 | resp, err := c.StaticMap(context.Background(), r) 66 | if err != nil { 67 | t.Errorf("r.StaticMap returned non nil error: %+v", err) 68 | } 69 | 70 | if resp.Bounds().Min.X != 0 || resp.Bounds().Min.Y != 0 || resp.Bounds().Max.X != 640 || resp.Bounds().Max.Y != 400 { 71 | t.Errorf("Response image not of the correct dimensions") 72 | } 73 | } 74 | 75 | func TestMapStyles(t *testing.T) { 76 | r := StaticMapRequest{ 77 | MapStyles: []string{ 78 | "x", "y", 79 | }, 80 | } 81 | values := r.params() 82 | if q := values.Encode(); q != "style=x&style=y" { 83 | t.Errorf("Generated query string is wrong: %s", q) 84 | } 85 | } 86 | 87 | func TestCustomIconMarkers(t *testing.T) { 88 | marker := Marker{ 89 | CustomIcon: CustomIcon{ 90 | IconURL: "icon@2x.png", 91 | Anchor: "topleft", 92 | Scale: 2, 93 | }, 94 | Location: []LatLng{ 95 | { 96 | Lat: 3.1225951401, 97 | Lng: 101.6404967928, 98 | }, 99 | }, 100 | } 101 | 102 | r := StaticMapRequest{ 103 | Markers: []Marker{marker}, 104 | } 105 | 106 | markers := r.params().Get("markers") 107 | if m := markers; m != "icon:icon@2x.png|anchor:topleft|scale:2|3.1225951401,101.6404967928" { 108 | t.Errorf("Generated query string is wrong: %s", m) 109 | } 110 | } 111 | 112 | func TestMarkersWithLocationAndAddress(t *testing.T) { 113 | marker := Marker{ 114 | Location: []LatLng{ 115 | { 116 | Lat: 3.1225951401, 117 | Lng: 101.6404967928, 118 | }, 119 | }, 120 | LocationAddress: "my Marker address", 121 | } 122 | 123 | r := StaticMapRequest{ 124 | Markers: []Marker{marker}, 125 | } 126 | 127 | markers := r.params().Get("markers") 128 | if m := markers; m != "3.1225951401,101.6404967928|my Marker address" { 129 | t.Errorf("Generated query string is wrong: %s", m) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /timezone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | // More information about Google Distance Matrix API is available on 16 | // https://developers.google.com/maps/documentation/distancematrix/ 17 | 18 | package maps 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "net/url" 24 | "strconv" 25 | "time" 26 | ) 27 | 28 | var timezoneAPI = &apiConfig{ 29 | host: "https://maps.googleapis.com", 30 | path: "/maps/api/timezone/json", 31 | acceptsClientID: true, 32 | acceptsSignature: false, 33 | } 34 | 35 | // Timezone makes a Timezone API request 36 | func (c *Client) Timezone(ctx context.Context, r *TimezoneRequest) (*TimezoneResult, error) { 37 | if r.Location == nil { 38 | return nil, errors.New("maps: Location missing") 39 | } 40 | 41 | var response struct { 42 | TimezoneResult 43 | commonResponse 44 | } 45 | 46 | if err := c.getJSON(ctx, timezoneAPI, r, &response); err != nil { 47 | return nil, err 48 | } 49 | 50 | if err := response.StatusError(); err != nil { 51 | return nil, err 52 | } 53 | 54 | return &response.TimezoneResult, nil 55 | } 56 | 57 | func (r *TimezoneRequest) params() url.Values { 58 | q := make(url.Values) 59 | q.Set("location", r.Location.String()) 60 | q.Set("timestamp", strconv.FormatInt(r.Timestamp.Unix(), 10)) 61 | if r.Language != "" { 62 | q.Set("language", r.Language) 63 | } 64 | return q 65 | } 66 | 67 | // TimezoneRequest is the request structure for Timezone API. 68 | type TimezoneRequest struct { 69 | // Location represents the location to look up. 70 | Location *LatLng 71 | // Timestamp specifies the desired time. Time Zone API uses the timestamp to 72 | // determine whether or not Daylight Savings should be applied. 73 | Timestamp time.Time 74 | // Language in which to return results. 75 | Language string 76 | } 77 | 78 | // TimezoneResult is a single timezone result. 79 | type TimezoneResult struct { 80 | // DstOffset is the offset for daylight-savings time in seconds. 81 | DstOffset int `json:"dstOffset"` 82 | // RawOffset is the offset from UTC for the given location. 83 | RawOffset int `json:"rawOffset"` 84 | // TimeZoneID is a string containing the "tz" ID of the time zone. 85 | TimeZoneID string `json:"timeZoneId"` 86 | // TimeZoneName is a string containing the long form name of the time zone. 87 | TimeZoneName string `json:"timeZoneName"` 88 | } 89 | -------------------------------------------------------------------------------- /timezone_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "context" 19 | "reflect" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestTimezoneNevada(t *testing.T) { 25 | 26 | response := `{ 27 | "dstOffset" : 0, 28 | "rawOffset" : -28800, 29 | "status" : "OK", 30 | "timeZoneId" : "America/Los_Angeles", 31 | "timeZoneName" : "Pacific Standard Time" 32 | }` 33 | 34 | server := mockServer(200, response) 35 | defer server.Close() 36 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 37 | r := &TimezoneRequest{ 38 | Location: &LatLng{ 39 | Lat: 39.6034810, 40 | Lng: -119.6822510, 41 | }, 42 | Timestamp: time.Unix(1331161200, 0), 43 | } 44 | 45 | resp, err := c.Timezone(context.Background(), r) 46 | 47 | if err != nil { 48 | t.Errorf("r.Get returned non nil error: %v", err) 49 | } 50 | 51 | correctResponse := &TimezoneResult{ 52 | DstOffset: 0, 53 | RawOffset: -28800, 54 | TimeZoneID: "America/Los_Angeles", 55 | TimeZoneName: "Pacific Standard Time", 56 | } 57 | 58 | if !reflect.DeepEqual(resp, correctResponse) { 59 | t.Errorf("expected %+v, was %+v", correctResponse, resp) 60 | println(resp) 61 | println(correctResponse) 62 | } 63 | } 64 | 65 | func TestTimezoneLocationMissing(t *testing.T) { 66 | c, _ := NewClient(WithAPIKey(apiKey)) 67 | r := &TimezoneRequest{ 68 | Timestamp: time.Unix(1331161200, 0), 69 | } 70 | 71 | if _, err := c.Timezone(context.Background(), r); err == nil { 72 | t.Errorf("Missing Location should return error") 73 | } 74 | } 75 | 76 | func TestTimezoneWithCancelledContext(t *testing.T) { 77 | c, _ := NewClient(WithAPIKey(apiKey)) 78 | r := &TimezoneRequest{ 79 | Location: &LatLng{ 80 | Lat: 39.6034810, 81 | Lng: -119.6822510, 82 | }, 83 | Timestamp: time.Unix(1331161200, 0), 84 | } 85 | ctx, cancel := context.WithCancel(context.Background()) 86 | cancel() 87 | if _, err := c.Timezone(ctx, r); err == nil { 88 | t.Errorf("Cancelled context should return error") 89 | } 90 | } 91 | 92 | func TestTimezoneFailingServer(t *testing.T) { 93 | server := mockServer(500, `{"status" : "ERROR"}`) 94 | defer server.Close() 95 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 96 | r := &TimezoneRequest{ 97 | Location: &LatLng{Lat: 36.578581, Lng: -118.291994}, 98 | } 99 | 100 | if _, err := c.Timezone(context.Background(), r); err == nil { 101 | t.Errorf("Failing server should return error") 102 | } 103 | } 104 | 105 | func TestTimezoneRequestURL(t *testing.T) { 106 | expectedQuery := "key=AIzaNotReallyAnAPIKey&language=es&location=1%2C2×tamp=-62135596800" 107 | 108 | server := mockServerForQuery(expectedQuery, 200, `{"status":"OK"}"`) 109 | defer server.s.Close() 110 | 111 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.s.URL)) 112 | 113 | r := &TimezoneRequest{ 114 | Location: &LatLng{1, 2}, 115 | Timestamp: time.Time{}, 116 | Language: "es", 117 | } 118 | 119 | _, err := c.Timezone(context.Background(), r) 120 | if err != nil { 121 | t.Errorf("Unexpected error in constructing request URL: %+v", err) 122 | } 123 | if server.successful != 1 { 124 | t.Errorf("Got URL(s) %v, want %s", server.failed, expectedQuery) 125 | } 126 | } 127 | 128 | func TestTimezoneZeroResults(t *testing.T) { 129 | server := mockServer(200, `{"status" : "ZERO_RESULTS"}`) 130 | defer server.Close() 131 | c, _ := NewClient(WithAPIKey(apiKey), WithBaseURL(server.URL)) 132 | 133 | r := &TimezoneRequest{ 134 | Location: &LatLng{28.0, 140.0}, 135 | Timestamp: time.Time{}, 136 | } 137 | 138 | result, err := c.Timezone(context.Background(), r) 139 | 140 | if err != nil { 141 | t.Errorf("Unexpected error for ZERO_RESULTS status") 142 | } 143 | 144 | var empty TimezoneResult 145 | if *result != empty { 146 | t.Errorf("Unexpected result for ZERO_RESULTS status") 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | ) 21 | 22 | const userAgent = "GoogleGeoApiClientGo/0.1" 23 | 24 | // transport is an http.RoundTripper that replaces or appends userAgent the request's 25 | // User-Agent header. 26 | type transport struct { 27 | Base http.RoundTripper 28 | } 29 | 30 | // RoundTrip appends userAgent existing User-Agent header and performs the request 31 | // via t.Base. 32 | func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { 33 | req = cloneRequest(req) 34 | ua := req.Header.Get("User-Agent") 35 | if ua == "" { 36 | ua = userAgent 37 | } else { 38 | ua = fmt.Sprintf("%s;%s", ua, userAgent) 39 | } 40 | req.Header.Set("User-Agent", ua) 41 | return t.Base.RoundTrip(req) 42 | } 43 | 44 | // cloneRequest returns a clone of the provided *http.Request. 45 | // The clone is a shallow copy of the struct and its Header map. 46 | func cloneRequest(r *http.Request) *http.Request { 47 | // shallow copy of the struct 48 | r2 := new(http.Request) 49 | *r2 = *r 50 | // deep copy of the Header 51 | r2.Header = make(http.Header) 52 | for k, s := range r.Header { 53 | r2.Header[k] = s 54 | } 55 | return r2 56 | } 57 | -------------------------------------------------------------------------------- /transport_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 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 | package maps 16 | 17 | import ( 18 | "net/http" 19 | "testing" 20 | ) 21 | 22 | func TestClientTransportMutate(t *testing.T) { 23 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key"), WithHTTPClient(&http.Client{})) 24 | tr, ok := c.httpClient.Transport.(*transport) 25 | if !ok { 26 | t.Errorf("Transport is expected to be a maps.transport, found to be a %T", c.httpClient.Transport) 27 | } 28 | if _, ok := tr.Base.(*transport); ok { 29 | t.Errorf("Transport's Base shouldn't have been a maps.transport, found to be a %T", tr.Base) 30 | } 31 | } 32 | 33 | func TestDefaultClientTransportMutate(t *testing.T) { 34 | c, _ := NewClient(WithAPIKey("AIza-Maps-API-Key")) 35 | 36 | tr, ok := c.httpClient.Transport.(*transport) 37 | if !ok { 38 | t.Errorf("Transport is expected to be a maps.transport, found to be a %T", c.httpClient.Transport) 39 | } 40 | if _, ok := tr.Base.(*transport); ok { 41 | t.Errorf("Transport's Base shouldn't have been a maps.transport, found to be a %T", tr.Base) 42 | } 43 | } 44 | --------------------------------------------------------------------------------