├── .circleci
└── config.yml
├── .editorconfig
├── .gitignore
├── .rspec
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib
├── what3words.rb
└── what3words
│ ├── api.rb
│ └── version.rb
├── sample
└── sample.rb
├── spec
├── config.sample.yaml
├── lib
│ └── what3words
│ │ └── what3words_api_spec.rb
└── spec_helper.rb
└── what3words.gemspec
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | ruby-executor:
5 | docker:
6 | - image: cimg/ruby:2.7.2
7 |
8 | jobs:
9 | ruby-test:
10 | executor: ruby-executor
11 | steps:
12 | - checkout
13 | - run:
14 | name: Install dependencies
15 | command: bundle install
16 | - run:
17 | name: Run tests
18 | command: bundle exec rspec
19 |
20 | workflows:
21 | test:
22 | jobs:
23 | - ruby-test
24 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | # Set default charset
13 | charset = utf-8
14 |
15 | # Indentation
16 | indent_style = space
17 | indent_size = 2
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/ruby,macos
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=ruby,macos
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### macOS Patch ###
34 | # iCloud generated files
35 | *.icloud
36 |
37 | ### Ruby ###
38 | *.gem
39 | *.rbc
40 | /.config
41 | /coverage/
42 | /InstalledFiles
43 | /pkg/
44 | /spec/reports/
45 | /spec/examples.txt
46 | /test/tmp/
47 | /test/version_tmp/
48 | /tmp/
49 |
50 | # Used by dotenv library to load environment variables.
51 | # .env
52 |
53 | # Ignore Byebug command history file.
54 | .byebug_history
55 |
56 | ## Specific to RubyMotion:
57 | .dat*
58 | .repl_history
59 | build/
60 | *.bridgesupport
61 | build-iPhoneOS/
62 | build-iPhoneSimulator/
63 |
64 | ## Specific to RubyMotion (use of CocoaPods):
65 | #
66 | # We recommend against adding the Pods directory to your .gitignore. However
67 | # you should judge for yourself, the pros and cons are mentioned at:
68 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
69 | # vendor/Pods/
70 |
71 | ## Documentation cache and generated files:
72 | /.yardoc/
73 | /_yardoc/
74 | /doc/
75 | /rdoc/
76 |
77 | ## Environment normalization:
78 | /.bundle/
79 | /vendor/bundle
80 | /lib/bundler/man/
81 |
82 | # for a library or gem, you might want to ignore these files since the code is
83 | # intended to run in multiple environments; otherwise, check them in:
84 | # Gemfile.lock
85 | # .ruby-version
86 | # .ruby-gemset
87 |
88 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
89 | .rvmrc
90 |
91 | # Used by RuboCop. Remote config files pulled in from inherit_from directive.
92 | # .rubocop-https?--*
93 |
94 | # End of https://www.toptal.com/developers/gitignore/api/ruby,macos
95 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.4
4 | script: rake rubocop spec
5 | notifications:
6 | slack:
7 | secure: DvejcrOH6RdAdrGEPLUrnia9cES8iCHVlVSFAMwx2vaR/I3T9bfUiHnb3VHfiqx4cvDL+9wT3opG0sZkUCFJGqzA3E//VObKNXjwUFCUptFG68qgR7LZ3vDatWlUqU+32gWeYaHM3FpNP2E/IARjphaiF3jRUZbronqVzJZN2MbMsUYBeGq7v0AaLkgbxVgOWtBnMVcBPOnarEJqHyJlqQ2unYnIy+GNoOFtPMPNZobYTf0zxrycldYpP937yT4CTsY3I7RxuEtnY2sPYW+eFBESGLg6EfnjeOruROY+b9cSHIh1Qzr7Oup6a+oQ3+vCvLbYsGpEO04RTeTTtOGc7aRlUylmvuFPi7qQwOTZ+4KD5aIBh4p7bQiPfvAeqDU4pVJxPHHZirgjFpUpAvofj48SvDgwX9cFyAhAlDZ9qBOctmhphW9KNkcAW4MY421AdVAjFWwc5CU8G5oPdKcyWam3ZB3nHwKgZm6LLAcZgbG0rjZT+iPeQBvTKH3kPx7E1CkxZNkEralHJ3l+bDwY3GdzsgcMpT3bWNl3pLszFzgXljDScMU8my1+xjDQINEnI3TCJA4MhKkzYkRgfrEBkgPb2SiF7qnhbyMzSFbCU4jsM1MLQZolNG9EsjsxJLuL4YTC2wKOHajViUUeaCNIcNU5UXBQuit93RyMu7RrLRM=
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | gemspec
5 | gem 'rspec'
6 | gem 'bundler', '~> 2.1'
7 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | what3words (3.4.0)
5 | rest-client (>= 1.8, < 3.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | addressable (2.8.7)
11 | public_suffix (>= 2.0.2, < 7.0)
12 | ast (2.4.2)
13 | bigdecimal (3.1.8)
14 | crack (1.0.0)
15 | bigdecimal
16 | rexml
17 | diff-lcs (1.5.1)
18 | domain_name (0.5.20190701)
19 | unf (>= 0.0.5, < 1.0.0)
20 | hashdiff (1.1.0)
21 | http-accept (1.7.0)
22 | http-cookie (1.0.6)
23 | domain_name (~> 0.5)
24 | mime-types (3.5.2)
25 | mime-types-data (~> 3.2015)
26 | mime-types-data (3.2024.0702)
27 | netrc (0.11.0)
28 | parallel (1.24.0)
29 | parser (2.7.2.0)
30 | ast (~> 2.4.1)
31 | powerpack (0.1.3)
32 | public_suffix (5.1.1)
33 | rainbow (2.2.2)
34 | rake
35 | rake (12.3.3)
36 | rest-client (2.1.0)
37 | http-accept (>= 1.7.0, < 2.0)
38 | http-cookie (>= 1.0.2, < 2.0)
39 | mime-types (>= 1.16, < 4.0)
40 | netrc (~> 0.8)
41 | rexml (3.3.6)
42 | strscan
43 | rspec (3.13.0)
44 | rspec-core (~> 3.13.0)
45 | rspec-expectations (~> 3.13.0)
46 | rspec-mocks (~> 3.13.0)
47 | rspec-core (3.13.0)
48 | rspec-support (~> 3.13.0)
49 | rspec-expectations (3.13.1)
50 | diff-lcs (>= 1.2.0, < 2.0)
51 | rspec-support (~> 3.13.0)
52 | rspec-mocks (3.13.1)
53 | diff-lcs (>= 1.2.0, < 2.0)
54 | rspec-support (~> 3.13.0)
55 | rspec-support (3.13.1)
56 | rubocop (0.49.0)
57 | parallel (~> 1.10)
58 | parser (>= 2.3.3.1, < 3.0)
59 | powerpack (~> 0.1)
60 | rainbow (>= 1.99.1, < 3.0)
61 | ruby-progressbar (~> 1.7)
62 | unicode-display_width (~> 1.0, >= 1.0.1)
63 | ruby-progressbar (1.13.0)
64 | strscan (3.1.0)
65 | unf (0.1.4)
66 | unf_ext
67 | unf_ext (0.0.9.1)
68 | unicode-display_width (1.8.0)
69 | webmock (3.23.1)
70 | addressable (>= 2.8.0)
71 | crack (>= 0.3.2)
72 | hashdiff (>= 0.4.0, < 2.0.0)
73 |
74 | PLATFORMS
75 | universal-darwin-23
76 | x86_64-linux
77 |
78 | DEPENDENCIES
79 | bundler (~> 2.1)
80 | rake (~> 12.3)
81 | rspec
82 | rubocop (~> 0.49.0)
83 | webmock (~> 3.0)
84 | what3words!
85 |
86 | BUNDLED WITH
87 | 2.3.16
88 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, 2017 What3Words Limited
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
what3words Ruby wrapper
2 |
3 | [](https://travis-ci.org/what3words/w3w-ruby-wrapper)
4 |
5 | The Ruby wrapper is useful for Ruby developers who wish to seamlessly integrate the [what3words Public API](https://developer.what3words.com/public-api) into their Ruby applications, without the hassle of having to manage the low level API calls themselves.
6 |
7 | The what3words API is a fast, simple interface which allows you to convert what3words addresses such as `///index.home.raft` to latitude and longitude coordinates such as `-0.203586, 51.521251` and vice versa. It features a powerful autosuggest function, which can validate and autocorrect user input and limit it to certain geographic areas (this powers the search box on our map site). It allows you to request a section of the what3words grid (which can be requested as GeoJSON for easy display on online maps), and to request the list of all languages supported by what3words. For advanced users, autosuggest can be used to post-process voice output.
8 |
9 | All coordinates are latitude,longitude pairs in standard `WGS-84` (as commonly used worldwide in GPS systems). All latitudes must be in the range of `-90 to 90 (inclusive)`.
10 |
11 | ## Installation
12 |
13 | The library is available through [RubyGems](https://rubygems.org/gems/what3words).
14 |
15 | You can simply add this line to your application's Gemfile:
16 |
17 | ```
18 | gem 'what3words', '~> 3.4'
19 | ```
20 |
21 | And then execute:
22 |
23 | ```shell
24 | $ bundle
25 | ```
26 |
27 | Or install it yourself as:
28 |
29 | ```shell
30 | $ gem install what3words
31 | ```
32 |
33 | ## Usage
34 |
35 | Sign up for an API key at [https://developer.what3words.com](https://developer.what3words.com)
36 |
37 | See [https://developer.what3words.com/public-api/docs](https://developer.what3words.com/public-api/docs) for all parameters that can be passed to the API calls.
38 |
39 | If not using Bundler, require it:
40 |
41 | ```ruby
42 | require 'what3words'
43 | ```
44 |
45 | Then:
46 |
47 | ```ruby
48 | what3words = What3Words::API.new(:key => "YOURAPIKEY")
49 | ```
50 |
51 | Convert to Coordinates: convert a what3words address into GPS coordinates (WGS84)
52 |
53 | ```ruby
54 | what3words.convert_to_coordinates 'prom.cape.pump'
55 | ```
56 |
57 | **Expected Output**
58 | ```
59 | # => {:country=>"GB", :square=>{:southwest=>{:lng=>-0.195426, :lat=>51.484449}, :northeast=>{:lng=>-0.195383, :lat=>51.484476}}, :nearestPlace=>"Kensington, London", :coordinates=>{:lng=>-0.195405, :lat=>51.484463}, :words=>"prom.cape.pump", :language=>"en", :map=>"https://w3w.co/prom.cape.pump"}
60 | ```
61 |
62 | ## API
63 | ### Convert to Coordinates
64 | Convert a what3words address into GPS coordinates and return what3words for the same position.
65 |
66 | ```ruby
67 | what3words.convert_to_coordinates "prom.cape.pump"
68 | ```
69 |
70 | **Expected Output**
71 | ```
72 | # => {:country=>"GB", :square=>{:southwest=>{:lng=>-0.195426, :lat=>51.484449}, :northeast=>{:lng=>-0.195383, :lat=>51.484476}}, :nearestPlace=>"Kensington, London", :coordinates=>{:lng=>-0.195405, :lat=>51.484463}, :words=>"prom.cape.pump", :language=>"en", :map=>"https://w3w.co/prom.cape.pump"}
73 | ```
74 | Supported keyword params for `convert_to_coordinates` call:
75 |
76 | * `words` A what3words address as a string
77 | * `format` Return data format type. It can be one of json (the default) or geojson
78 |
79 |
80 | ### Convert to 3WA
81 | Convert position information, latitude and longitude coordinates, into a what3words address.
82 |
83 | ```ruby
84 | what3words.convert_to_3wa [29.567041, 106.587875]
85 | ```
86 |
87 | **Expected Output**
88 | ```
89 | # => {:country=>"CN", :square=>{:southwest=>{:lng=>106.58786, :lat=>29.567028}, :northeast=>{:lng=>106.587891, :lat=>29.567055}}, :nearestPlace=>"Chongqing", :coordinates=>{:lng=>106.587875, :lat=>29.567041}, :words=>"disclose.strain.redefined", :language=>"en", :map=>"https://w3w.co/disclose.strain.redefined"}
90 | ```
91 |
92 | Convert position information to a what3words address in a specific language
93 |
94 | ```ruby
95 | what3words.convert_to_3wa [29.567041, 106.587875], language: 'fr'
96 | ```
97 |
98 | **Expected Output**
99 | ```
100 | # => :country=>"CN", :square=>{:southwest=>{:lng=>106.58786, :lat=>29.567028}, :northeast=>{:lng=>106.587891, :lat=>29.567055}}, :nearestPlace=>"Chongqing", :coordinates=>{:lng=>106.587875, :lat=>29.567041}, :words=>"courgette.rabotons.infrason", :language=>"fr", :map=>"https://w3w.co/courgette.rabotons.infrason"}
101 | ```
102 |
103 | Supported keyword params for `convert_to_3wa` call:
104 |
105 | * `coordinates` The coordinates of the location to convert to what3words address
106 | * `language` (defaults to en) - A supported what3words address language as an ISO 639-1 2 letter code
107 | * `format` Return data format type. It can be one of json (the default) or geojson
108 |
109 | ### Autosuggest
110 | Returns a list of what3words addresses based on user input and other parameters.
111 |
112 | This resource provides corrections for the following types of input error:
113 | - typing errors
114 | - spelling errors
115 | - misremembered words (e.g. singular vs. plural)
116 | - words in the wrong order
117 |
118 | The autosuggest resource determines possible corrections to the supplied what3words address string based on the probability of the input errors listed above and returns a ranked list of suggestions. This resource can also take into consideration the geographic proximity of possible corrections to a given location to further improve the suggestions returned.
119 |
120 | See [https://developer.what3words.com/public-api/docs#autosuggest](https://developer.what3words.com/public-api/docs#autosuggest) for detailed information
121 |
122 | Gets suggestions in french for this address:
123 |
124 | ```ruby
125 | what3words.autosuggest 'trop.caler.perdre', language: 'fr'
126 | ```
127 |
128 | **Expected Output**
129 | ```
130 | # => {:suggestions=>[{:country=>"FR", :nearestPlace=>"Saint-Lary-Soulan, Hautes-Pyrénées", :words=>"trier.caler.perdre", :rank=>1, :language=>"fr"}, {:country=>"ET", :nearestPlace=>"Asbe Teferi, Oromiya", :words=>"trôler.caler.perdre", :rank=>2, :language=>"fr"}, {:country=>"CN", :nearestPlace=>"Ulanhot, Inner Mongolia", :words=>"froc.caler.perdre", :rank=>3, :language=>"fr"}]}
131 | ```
132 |
133 | Gets suggestions for a different number of suggestions, i.e. 10 for this address:
134 |
135 | ```ruby
136 | what3words.autosuggest 'disclose.strain.redefin', language: 'en', 'n-results': 10
137 | ```
138 |
139 | **Expected Output**
140 | ```
141 | # => {:suggestions=>[{:country=>"SO", :nearestPlace=>"Jamaame, Lower Juba", :words=>"disclose.strain.redefine", :rank=>1, :language=>"en"}, {:country=>"ZW", :nearestPlace=>"Mutoko, Mashonaland East", :words=>"discloses.strain.redefine", :rank=>2, :language=>"en"}, {:country=>"MM", :nearestPlace=>"Mogok, Mandalay", :words=>"disclose.strains.redefine", :rank=>3, :language=>"en"}, {:country=>"CN", :nearestPlace=>"Chongqing", :words=>"disclose.strain.redefined", :rank=>4, :language=>"en"}, {:country=>"ZM", :nearestPlace=>"Binga, Matabeleland North", :words=>"disclosing.strain.redefine", :rank=>5, :language=>"en"}, {:country=>"XH", :nearestPlace=>"Leh, Ladakh", :words=>"disclose.straining.redefine", :rank=>6, :language=>"en"}, {:country=>"US", :nearestPlace=>"Kamas, Utah", :words=>"disclose.strain.redefining", :rank=>7, :language=>"en"}, {:country=>"GN", :nearestPlace=>"Boké", :words=>"disclose.strained.redefine", :rank=>8, :language=>"en"}, {:country=>"BO", :nearestPlace=>"Pailón, Santa Cruz", :words=>"discloses.strains.redefine", :rank=>9, :language=>"en"}, {:country=>"US", :nearestPlace=>"McGrath, Alaska", :words=>"discloses.strain.redefined", :rank=>10, :language=>"en"}]}
142 | ```
143 |
144 | Gets suggestions when the coordinates for focus has been provided for this address:
145 |
146 | ```ruby
147 | what3words.autosuggest 'filled.count.soap', focus: [51.4243877,-0.34745]
148 | ```
149 |
150 | **Expected Output**
151 | ```
152 | # => {:suggestions=>[{:country=>"US", :nearestPlace=>"Homer, Alaska", :words=>"fund.with.code", :rank=>1, :language=>"en"}, {:country=>"AU", :nearestPlace=>"Kumpupintil, Western Australia", :words=>"funk.with.code", :rank=>2, :language=>"en"}, {:country=>"US", :nearestPlace=>"Charleston, West Virginia", :words=>"fund.with.cove", :rank=>3, :language=>"en"}]}
153 | ```
154 |
155 | Gets suggestions for a different number of focus results for this address:
156 |
157 | ```ruby
158 | what3words.autosuggest 'disclose.strain.redefin', language: 'en', 'n-focus-results': 3
159 | ```
160 |
161 | **Expected Output**
162 | ```
163 | # => {:suggestions=>[{:country=>"SO", :nearestPlace=>"Jamaame, Lower Juba", :words=>"disclose.strain.redefine", :rank=>1, :language=>"en"}, {:country=>"ZW", :nearestPlace=>"Mutoko, Mashonaland East", :words=>"discloses.strain.redefine", :rank=>2, :language=>"en"}, {:country=>"MM", :nearestPlace=>"Mogok, Mandalay", :words=>"disclose.strains.redefine", :rank=>3, :language=>"en"}]}
164 | ```
165 |
166 | Gets suggestions for a voice input type mode, i.e. generic-voice, for this address:
167 |
168 | ```ruby
169 | what3words.autosuggest 'fun with code', 'input-type': 'generic-voice', language: 'en'
170 | ```
171 |
172 | **Expected Output**
173 | ```
174 | # => {:suggestions=>[{:country=>"US", :nearestPlace=>"Homer, Alaska", :words=>"fund.with.code", :rank=>1, :language=>"en"}, {:country=>"AU", :nearestPlace=>"Kumpupintil, Western Australia", :words=>"funk.with.code", :rank=>2, :language=>"en"}, {:country=>"US", :nearestPlace=>"Charleston, West Virginia", :words=>"fund.with.cove", :rank=>3, :language=>"en"}]}
175 | ```
176 |
177 | Gets suggestions for a restricted area by clipping to country for this address:
178 |
179 | ```ruby
180 | what3words.autosuggest 'disclose.strain.redefin', 'clip-to-country': 'GB,BE'
181 | ```
182 |
183 | **Expected Output**
184 | ```
185 | # => {:suggestions=>[{:country=>"GB", :nearestPlace=>"Nether Stowey, Somerset", :words=>"disclose.retrain.redefined", :rank=>1, :language=>"en"}, {:country=>"BE", :nearestPlace=>"Zemst, Flanders", :words=>"disclose.strain.reckon", :rank=>2, :language=>"en"}, {:country=>"GB", :nearestPlace=>"Waddington, Lincolnshire", :words=>"discloses.trains.redefined", :rank=>3, :language=>"en"}]}
186 | ```
187 |
188 | Gets suggestions for a restricted area by clipping to a bounding-box for this address:
189 |
190 | ```ruby
191 | what3words.autosuggest 'disclose.strain.redefin', 'clip-to-bounding-box': [51.521, -0.343, 52.6, 2.3324]
192 | ```
193 |
194 | **Expected Output**
195 | ```
196 | # => {:suggestions=>[{:country=>"GB", :nearestPlace=>"Saxmundham, Suffolk", :words=>"discloses.strain.reddish", :rank=>1, :language=>"en"}]}
197 | ```
198 |
199 |
200 | Gets suggestions for a restricted area by clipping to a circle in km for this address:
201 |
202 | ```ruby
203 | what3words.autosuggest 'disclose.strain.redefin', 'clip-to-circle': [51.521, -0.343, 142]
204 | ```
205 |
206 | **Expected Output**
207 | ```
208 | # => {:suggestions=>[{:country=>"GB", :nearestPlace=>"Market Harborough, Leicestershire", :words=>"discloses.strain.reduce", :rank=>1, :language=>"en"}]}
209 | ```
210 |
211 | Gets suggestions for a restricted area by clipping to a polygon for this address:
212 |
213 | ```ruby
214 | what3words.autosuggest 'disclose.strain.redefin', 'clip-to-polygon': [51.521, -0.343, 52.6, 2.3324, 54.234, 8.343, 51.521, -0.343]
215 | ```
216 |
217 | **Expected Output**
218 | ```
219 | # => {:suggestions=>[{:country=>"GB", :nearestPlace=>"Saxmundham, Suffolk", :words=>"discloses.strain.reddish", :rank=>1, :language=>"en"}]}
220 | ```
221 |
222 | Gets suggestions for a restricted area by clipping to a polygon for this address:
223 |
224 | ```ruby
225 | what3words.w3w.autosuggest 'disclose.strain.redefin', 'prefer-land': false, 'n-results': 10
226 | ```
227 |
228 | **Expected Output**
229 | ```
230 | # => {:suggestions=>[{:country=>"SO", :nearestPlace=>"Jamaame, Lower Juba", :words=>"disclose.strain.redefine", :rank=>1, :language=>"en"}, {:country=>"ZW", :nearestPlace=>"Mutoko, Mashonaland East", :words=>"discloses.strain.redefine", :rank=>2, :language=>"en"}, {:country=>"MM", :nearestPlace=>"Mogok, Mandalay", :words=>"disclose.strains.redefine", :rank=>3, :language=>"en"}, {:country=>"CN", :nearestPlace=>"Chongqing", :words=>"disclose.strain.redefined", :rank=>4, :language=>"en"}, {:country=>"ZM", :nearestPlace=>"Binga, Matabeleland North", :words=>"disclosing.strain.redefine", :rank=>5, :language=>"en"}, {:country=>"XH", :nearestPlace=>"Leh, Ladakh", :words=>"disclose.straining.redefine", :rank=>6, :language=>"en"}, {:country=>"US", :nearestPlace=>"Kamas, Utah", :words=>"disclose.strain.redefining", :rank=>7, :language=>"en"}, {:country=>"GN", :nearestPlace=>"Boké", :words=>"disclose.strained.redefine", :rank=>8, :language=>"en"}, {:country=>"BO", :nearestPlace=>"Pailón, Santa Cruz", :words=>"discloses.strains.redefine", :rank=>9, :language=>"en"}, {:country=>"US", :nearestPlace=>"McGrath, Alaska", :words=>"discloses.strain.redefined", :rank=>10, :language=>"en"}]}
231 | ```
232 |
233 | Supported keyword params for `autosuggest` call:
234 | * `input` The full or partial what3words address to obtain suggestions for. At minimum this must be the first two complete words plus at least one character from the third word.
235 | * `language` A supported what3words address language as an ISO 639-1 2 letter code. This setting is on by default. Use false to disable this setting and receive more suggestions in the sea.
236 | * `n_results` The number of AutoSuggest results to return. A maximum of 100 results can be specified, if a number greater than this is requested, this will be truncated to the maximum. The default is 3.
237 | * `n_focus_results` Specifies the number of results (must be <= n_results) within the results set which will have a focus. Defaults to n_results. This allows you to run autosuggest with a mix of focussed and unfocussed results, to give you a "blend" of the two.
238 | * `clip-to-country` Restricts autosuggest to only return results inside the countries specified by comma-separated list of uppercase ISO 3166-1 alpha-2 country codes (for example, to restrict to Belgium and the UK, use clip_to_country="GB,BE").
239 | * `clip-to-bounding-box` Restrict autosuggest results to a bounding box, specified by coordinates.
240 | * `clip-to-circle` Restrict autosuggest results to a circle, specified by the center of the circle, latitude and longitude, and a distance in kilometres which represents the radius. For convenience, longitude is allowed to wrap around 180 degrees. For example 181 is equivalent to -179.
241 | * `clip-to-polygon` Restrict autosuggest results to a polygon, specified by a list of coordinates. The polygon should be closed, i.e. the first element should be repeated as the last element; also the list should contain at least 4 entries. The API is currently limited to accepting up to 25 pairs.
242 | * `input-type` For power users, used to specify voice input mode. Can be text (default), vocon-hybrid, nmdp-asr or generic-voice.
243 | * `prefer-land` Makes autosuggest prefer results on land to those in the sea.
244 |
245 | ### Grid
246 | Returns a section of the 3m x 3m what3words grid for a given area.
247 |
248 | See [https://developer.what3words.com/public-api/docs#grid-section](https://developer.what3words.com/public-api/docs#grid-section) for detailed information.
249 |
250 | Gets grid for these bounding box northeast 52.208867,0.117540,52.207988,0.116126.
251 |
252 | ```ruby
253 | what3words.grid_section '52.208867,0.117540,52.207988,0.116126'
254 | ```
255 |
256 | **Expected Output**
257 | ```
258 | # => {:lines=>[{:start=>{:lng=>0.116126, :lat=>52.20801}, :end=>{:lng=>0.11754, :lat=>52.20801}}, {:start=>{:lng=>0.116126, :lat=>52.208037}, :end=>{:lng=>0.11754, :lat=>52.208037}}, {:start=>{:lng=>0.116126, :lat=>52.208064}, :end=>{:lng=>0.11754, :lat=>52.208064}}, ___...___ ]}
259 | ```
260 |
261 | Supported keyword params for `grid_section` call:
262 | * `bounding-box` The bounding box is specified by the northeast and southwest corner coordinates, for which the grid should be returned
263 | * `format` Return data format type. It can be one of json (the default) or geojson
264 |
265 | ### Get Languages
266 | Retrieve a list of available what3words languages.
267 |
268 | ```ruby
269 | what3words.available_languages
270 | ```
271 |
272 | **Expected Output**
273 | ```
274 | # => {:languages=>[{:nativeName=>"Deutsch", :code=>"de", :name=>"German"}, {:nativeName=>"हिन्दी", :code=>"hi", :name=>"Hindi"}, {:nativeName=>"Português", :code=>"pt", :name=>"Portuguese"}, {:nativeName=>"Magyar", :code=>"hu", :name=>"Hungarian"}, {:nativeName=>"Українська", :code=>"uk", :name=>"Ukrainian"}, {:nativeName=>"Bahasa Indonesia", :code=>"id", :name=>"Bahasa Indonesia"}, {:nativeName=>"اردو", :code=>"ur", :name=>"Urdu"}, ___...___]}
275 | ```
276 |
277 | See [https://developer.what3words.com/public-api/docs#available-languages](https://developer.what3words.com/public-api/docs#available-languages) for the original API call documentation.
278 |
279 | ### RegEx functions
280 |
281 | This section introduces RegEx functions that can assist with checking and finding possible what3words addresses in strings. The three main functions covered are:
282 |
283 | `isPossible3wa` – Match what3words address format;
284 | `findPossible3wa` – Find what3words address in Text;
285 | `isValid3wa` – Verify a what3words address with the API;
286 |
287 | #### isPossible3wa
288 |
289 | Our API wrapper RegEx function `isPossible3wa` can be used used to detect if a text string (like `filled.count.soap`) in the format of a what3words address without having to ask the API. This functionality checks if a given string could be a what3words address. It returns true if it could be, otherwise false.
290 |
291 | **Note**: This function checks the text format but not the validity of a what3words address. Use `isValid3wa` to verify validity.
292 |
293 | ```ruby
294 | require 'what3words'
295 |
296 | def main
297 | # Initialize the What3Words API with your API key
298 | api_key = 'YOUR_API_KEY'
299 | w3w = What3Words::API.new(:key => api_key)
300 |
301 | # Example what3words addresses
302 | addresses = ["filled.count.soap", "not a 3wa", "not.3wa address"]
303 |
304 | # Check if the addresses are possible what3words addresses
305 | addresses.each do |address|
306 | is_possible = w3w.isPossible3wa(address)
307 | puts "Is '#{address}' a possible what3words address? #{is_possible}"
308 | end
309 | end
310 |
311 | if __FILE__ == $0
312 | main
313 | end
314 | ```
315 |
316 | **Expected Output**
317 |
318 | isPossible3wa(“filled.count.soap”) returns true
319 | isPossible3wa(“not a 3wa”) returns false
320 | isPossible3wa(“not.3wa address”)returns false
321 |
322 | #### findPossible3wa
323 |
324 | Our API wrapper RegEx function `findPossible3wa` can be used to detect a what3words address within a block of text, useful for finding a what3words address in fields like Delivery Notes. For example, it can locate a what3words address in a note like “Leave at my front door ///filled.count.soap”. The function will match if there is a what3words address within the text. If no possible addresses are found, it returns an empty list.
325 |
326 | **Note**:
327 |
328 | - This function checks the text format but not the validity of a what3words address. Use `isValid3wa` to verify validity.
329 | - This function is designed to work across languages but do not work for `Vietnamese (VI)` due to spaces within words.
330 |
331 | ```ruby
332 | require 'what3words'
333 |
334 | def main
335 | # Initialize the what3words API with your API key
336 | api_key = 'YOUR_API_KEY'
337 | w3w = What3Words::API.new(:key => api_key)
338 |
339 | # Example texts
340 | texts = [
341 | "Please leave by my porch at filled.count.soap",
342 | "Please leave by my porch at filled.count.soap or deed.tulip.judge",
343 | "Please leave by my porch at"
344 | ]
345 |
346 | # Check if the texts contain possible what3words addresses
347 | texts.each do |text|
348 | possible_addresses = w3w.findPossible3wa(text)
349 | puts "Possible what3words addresses in '#{text}': #{possible_addresses}"
350 | end
351 | end
352 |
353 | if __FILE__ == $0
354 | main
355 | end
356 | ```
357 |
358 | **Expected Output**
359 |
360 | findPossible3wa(“Please leave by my porch at filled.count.soap”) returns ['filled.count.soap']
361 | findPossible3wa(“Please leave by my porch at filled.count.soap or deed.tulip.judge”) returns ['filled.count.soap', 'deed.tulip.judge']
362 | findPossible3wa(“Please leave by my porch at”) returns []
363 |
364 | #### isValid3wa
365 |
366 | Our API wrapper RegEx function `isValid3wa` can be used to determine if a string is a valid what3words address by checking it against the what3words RegEx filter and verifying it with the what3words API.
367 |
368 | ```ruby
369 | require 'what3words'
370 |
371 | def main
372 | # Initialize the what3words API with your API key
373 | api_key = 'YOUR_API_KEY'
374 | w3w = What3Words::API.new(:key => api_key)
375 |
376 | # Example addresses
377 | addresses = [
378 | "filled.count.soap",
379 | "filled.count.",
380 | "coding.is.cool"
381 | ]
382 |
383 | # Check if the addresses are valid what3words addresses
384 | addresses.each do |address|
385 | is_valid = w3w.isValid3wa(address)
386 | puts "Is '#{address}' a valid what3words address? #{is_valid}"
387 | end
388 | end
389 |
390 | if __FILE__ == $0
391 | main
392 | end
393 | ```
394 | **Expected Outputs**
395 |
396 | isValid3wa(“filled.count.soap”) returns True
397 | isValid3wa(“filled.count.”) returns False
398 | isValid3wa(“coding.is.cool”) returns False
399 |
400 | Also make sure to replace `` with your actual API key. These functionalities provide different levels of validation for what3words addresses, from simply identifying potential addresses to verifying their existence on Earth.
401 |
402 |
403 | See [https://developer.what3words.com/tutorial/ruby#regex-functions](https://developer.what3words.com/tutorial/ruby#regex-functions) for further documentation.
404 |
405 |
406 | ## Testing
407 |
408 | * Prerequisite : we are using [bundler](https://rubygems.org/gems/bundler) `$ gem install bundler`
409 |
410 | * W3W-API-KEY: For safe storage of your API key on your computer, you can define that API key using your system’s environment variables.
411 | ```bash
412 | $ export W3W_API_KEY=
413 | ```
414 |
415 | * on your cloned folder
416 | 1. `$ cd w3w-ruby-wrapper`
417 | 1. `$ bundle update`
418 | 1. `$ rake rubocop spec`
419 |
420 | To run the tests, type on your terminal:
421 | ```bash
422 | $ bundle exec rspec
423 | ```
424 |
425 | ## Issues
426 |
427 | Find a bug or want to request a new feature? Please let us know by submitting an issue.
428 |
429 | ## Contributing
430 | Anyone and everyone is welcome to contribute.
431 |
432 | 1. Fork it (http://github.com/what3words/w3w-ruby-wrapper and click "Fork")
433 | 1. Create your feature branch (`git checkout -b my-new-feature`)
434 | 1. Commit your changes (`git commit -am 'Add some feature'`)
435 | 1. Don't forget to update README and bump [version](./lib/what3words/version.rb) using [semver](https://semver.org/)
436 | 1. Push to the branch (`git push origin my-new-feature`)
437 | 1. Create new Pull Request
438 |
439 | # Revision History
440 |
441 | * `v3.4.0` 15/01/25 - Update dependencies and upgrade ruby gemspec
442 | * `v3.3.0` 12/08/24 - Update error message to handle c2c calls and dependencies
443 | * `v3.2.0` 17/07/24 - Update regex patterns
444 | * `v3.1.0` 16/07/24 - Update tests and code to host the regex functions
445 | * `v3.0.0` 12/05/22 - Update endpoints and tests to API v3, added HTTP headers
446 | * `v2.2.0` 03/01/18 - Enforce Ruby 2.4 Support - Thanks to PR from Dimitrios Zorbas [@Zorbash](https://github.com/zorbash)
447 | * `v2.1.1` 22/05/17 - Update gemspec to use rubocop 0.48.1, and fixes spec accordingly
448 | * `v2.1.0` 28/03/17 - Added multilingual version of `autosuggest` and `standardblend`
449 | * `v2.0.4` 27/03/17 - Updated README with `languages` method result updated from live result
450 | * `v2.0.3` 24/10/16 - Fixed `display` in `assemble_common_request_params`
451 | * `v2.0.2` 10/06/16 - Added travis-ci builds
452 | * `v2.0.0` 10/06/16 - Updated wrapper to use what3words API v2
453 |
454 | ## Licensing
455 |
456 | The MIT License (MIT)
457 |
458 | A copy of the license is available in the repository's [license](LICENSE.txt) file.
459 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 |
5 | begin
6 | require 'rubocop/rake_task'
7 | RuboCop::RakeTask.new(:rubocop) do |t|
8 | t.options = ['--display-cop-names']
9 | end
10 | require 'rspec/core/rake_task'
11 | RSpec::Core::RakeTask.new(:spec)
12 | rescue LoadError => e
13 | print e
14 | end
15 |
--------------------------------------------------------------------------------
/lib/what3words.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module What3Words # :nodoc: don't document this
4 | end
5 |
6 | require 'what3words/api'
7 |
--------------------------------------------------------------------------------
/lib/what3words/api.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rest-client'
4 | require 'json'
5 | require File.expand_path('../version', __FILE__)
6 | require 'what3words/version'
7 |
8 | module What3Words
9 | # What3Words v3 API wrapper
10 | class API
11 | class Error < RuntimeError; end
12 | # class ResponseError < Error; end
13 | class ResponseError < StandardError; end
14 | class WordError < Error; end
15 |
16 | REGEX_3_WORD_ADDRESS = /^\/*(?:[^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}[.。。・・︒។։။۔።।][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}[.。。・・︒។։။۔።।][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}|[<.,>?\/\";:£§º©®\s]+[.。。・・︒។։။۔።।][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+|[^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+([\u0020\u00A0][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+){1,3}[.。。・・︒។։။۔።।][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+([\u0020\u00A0][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+){1,3}[.。。・・︒។։။۔።।][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+([\u0020\u00A0][^0-9`~!@#$%^&*()+\-_=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]+){1,3})$/u.freeze
17 | BASE_URL = 'https://api.what3words.com/v3/'
18 |
19 | ENDPOINTS = {
20 | convert_to_coordinates: 'convert-to-coordinates',
21 | convert_to_3wa: 'convert-to-3wa',
22 | available_languages: 'available-languages',
23 | autosuggest: 'autosuggest',
24 | grid_section: 'grid-section'
25 | }.freeze
26 |
27 | WRAPPER_VERSION = What3Words::VERSION
28 |
29 | def initialize(params)
30 | @key = params.fetch(:key)
31 | end
32 |
33 | attr_reader :key
34 |
35 | def convert_to_coordinates(words, params = {})
36 | """
37 | Take a 3 word address and turn it into a pair of coordinates.
38 |
39 | Params
40 | ------
41 | :param string words: A 3 word address as a string
42 | :param string format: Return data format type; can be one of json (the default), geojson
43 | :rtype: Hash
44 | """
45 | words_string = get_words_string(words)
46 | request_params = assemble_convert_to_coordinates_request_params(words_string, params)
47 | request!(:convert_to_coordinates, request_params)
48 | end
49 |
50 | def convert_to_3wa(position, params = {})
51 | """
52 | Take latitude and longitude coordinates and turn them into a 3 word address.
53 |
54 | Params
55 | ------
56 | :param array position: The coordinates of the location to convert to 3 word address
57 | :param string format: Return data format type; can be one of json (the default), geojson
58 | :param string language: A supported 3 word address language as an ISO 639-1 2 letter code.
59 | :rtype: Hash
60 | """
61 | request_params = assemble_convert_to_3wa_request_params(position, params)
62 | request!(:convert_to_3wa, request_params)
63 | end
64 |
65 | def grid_section(bbox, params = {})
66 | """
67 | Returns a section of the 3m x 3m what3words grid for a given area.
68 |
69 | Params
70 | ------
71 | :param string bbox: Bounding box, specified by the northeast and southwest corner coordinates,
72 | :param string format: Return data format type; can be one of json (the default), geojson
73 | :rtype: Hash
74 | """
75 | request_params = assemble_grid_request_params(bbox, params)
76 | request!(:grid_section, request_params)
77 | end
78 |
79 | def available_languages
80 | """
81 | Retrieve a list of available 3 word languages.
82 |
83 | :rtype: Hash
84 | """
85 | request_params = assemble_common_request_params({})
86 | request!(:available_languages, request_params)
87 | end
88 |
89 | def autosuggest(input, params = {})
90 | """
91 | Returns a list of 3 word addresses based on user input and other parameters.
92 |
93 | Params
94 | ------
95 | :param string input: The full or partial 3 word address to obtain suggestions for.
96 | :param int n_results: The number of AutoSuggest results to return.
97 | :param array focus: A location, specified as a latitude,longitude used to refine the results.
98 | :param int n_focus_results: Specifies the number of results (must be <= n_results) within the results set which will have a focus.
99 | :param string clip_to_country: Restricts autosuggest to only return results inside the countries specified by comma-separated list of uppercase ISO 3166-1 alpha-2 country codes.
100 | :param array clip_to_bounding_box: Restrict autosuggest results to a bounding box, specified by coordinates.
101 | :param array clip_to_circle: Restrict autosuggest results to a circle, specified by the center of the circle, latitude and longitude, and a distance in kilometres which represents the radius.
102 | :param array clip_to_polygon: Restrict autosuggest results to a polygon, specified by a list of coordinates.
103 | :param string input_type: For power users, used to specify voice input mode. Can be text (default), vocon-hybrid, nmdp-asr or generic-voice.
104 | :param string prefer_land: Makes autosuggest prefer results on land to those in the sea.
105 | :param string language: A supported 3 word address language as an ISO 639-1 2 letter code.
106 | :rtype: Hash
107 | """
108 | request_params = assemble_autosuggest_request_params(input, params)
109 | request!(:autosuggest, request_params)
110 | end
111 |
112 | def isPossible3wa(text)
113 | """
114 | Determines if the string passed in is the form of a three word address.
115 | This does not validate whether it is a real address as it returns true for x.x.x
116 |
117 | Params
118 | ------
119 | :param string text: text to check
120 | :rtype: Boolean
121 | """
122 | regex_match = REGEX_3_WORD_ADDRESS
123 | !(text.match(regex_match).nil?)
124 | end
125 |
126 | def findPossible3wa(text)
127 | """
128 | Searches the string passed in for all substrings in the form of a three word address.
129 | This does not validate whether it is a real address as it will return x.x.x as a result
130 |
131 | Params
132 | ------
133 | :param string text: text to check
134 | :rtype: Array
135 | """
136 | regex_search = /[^\d`~!@#$%^&*()+\-=\[\]{}\\|'<>.,?\/\";:£§º©®\s]{1,}[.。。・・︒។։။۔።।][^\d`~!@#$%^&*()+\-=\[\]{}\\|'<>.,?\/\";:£§º©®\s]{1,}[.。。・・︒។։။۔።।][^\d`~!@#$%^&*()+\-=\[\]{}\\|'<>.,?\/\";:£§º©®\s]{1,}/u
137 | text.scan(regex_search)
138 | end
139 |
140 | def didYouMean(text)
141 | """
142 | Determines if the string passed in is almost in the form of a three word address.
143 | This will return True for values such as 'filled-count-soap' and 'filled count soap'
144 |
145 | Params
146 | ------
147 | :param string text: text to check
148 | :rtype: Boolean
149 | """
150 | regex_didyoumean = /^\/?[^0-9`~!@#$%^&*()+\-=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}[.\uFF61\u3002\uFF65\u30FB\uFE12\u17D4\u0964\u1362\u3002:။^_۔։ ,\\\/+'&\\:;|\u3000-]{1,2}[^0-9`~!@#$%^&*()+\-=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}[.\uFF61\u3002\uFF65\u30FB\uFE12\u17D4\u0964\u1362\u3002:။^_۔։ ,\\\/+'&\\:;|\u3000-]{1,2}[^0-9`~!@#$%^&*()+\-=\[\{\]}\\|'<>.,?\/\";:£§º©®\s]{1,}$/u
151 | !(text.match(regex_didyoumean).nil?)
152 | end
153 |
154 | def isValid3wa(text)
155 | """
156 | Determines if the string passed in is a real three word address. It calls the API
157 | to verify it refers to an actual place on earth.
158 |
159 | Params
160 | ------
161 | :param String text: text to check
162 |
163 | :rtype: Boolean
164 | """
165 | if isPossible3wa(text)
166 | result = autosuggest(text, 'n-results': 1)
167 | if result[:suggestions] && result[:suggestions].length > 0
168 | return result[:suggestions][0][:words] == text
169 | end
170 | end
171 | false
172 | end
173 |
174 | private
175 |
176 | def request!(endpoint_name, params)
177 | headers = { "X-W3W-Wrapper": "what3words-Ruby/#{WRAPPER_VERSION}" }
178 | response = RestClient.get(endpoint(endpoint_name), params: params, headers: headers)
179 | parsed_response = JSON.parse(response.body)
180 |
181 | if parsed_response['error']
182 | error_code = parsed_response['error']['code']
183 | error_message = parsed_response['error']['message']
184 | raise ResponseError, "#{error_code}: #{error_message}"
185 | end
186 |
187 | deep_symbolize_keys(parsed_response)
188 | rescue RestClient::ExceptionWithResponse => e
189 | handle_rest_client_error(e)
190 | rescue RestClient::Exception => e
191 | raise ResponseError, "RestClient error: #{e.message}"
192 | end
193 |
194 | def handle_rest_client_error(error)
195 | response = error.response
196 | begin
197 | parsed_response = JSON.parse(response.body)
198 | rescue JSON::ParserError
199 | raise ResponseError, "Invalid JSON response: #{response}"
200 | end
201 |
202 | if parsed_response['error']
203 | error_code = parsed_response['error']['code']
204 | error_message = parsed_response['error']['message']
205 | raise ResponseError, "#{error_code}: #{error_message}"
206 | else
207 | raise ResponseError, "Unknown error: #{response}"
208 | end
209 | end
210 |
211 | def get_words_string(words)
212 | words_string = words.is_a?(Array) ? words.join('.') : words.to_s
213 | check_words(words_string)
214 | words_string
215 | end
216 |
217 | def check_words(words)
218 | raise WordError, "#{words} is not a valid 3 word address" unless REGEX_3_WORD_ADDRESS.match?(words)
219 | end
220 |
221 | def assemble_common_request_params(params)
222 | { key: key }.merge(params.slice(:language, :format))
223 | end
224 |
225 | def assemble_convert_to_coordinates_request_params(words_string, params)
226 | { words: words_string }.merge(assemble_common_request_params(params))
227 | end
228 |
229 | def assemble_convert_to_3wa_request_params(position, params)
230 | { coordinates: position.join(',') }.merge(assemble_common_request_params(params))
231 | end
232 |
233 | def assemble_grid_request_params(bbox, params)
234 | { 'bounding-box': bbox }.merge(assemble_common_request_params(params))
235 | end
236 |
237 | def assemble_autosuggest_request_params(input, params)
238 | result = { input: input }
239 | result[:'n-results'] = params[:'n-results'].to_i if params[:'n-results']
240 | result[:focus] = params[:focus].join(',') if params[:focus].respond_to?(:join)
241 | result[:'n-focus-results'] = params[:'n-focus-results'].to_i if params[:'n-focus-results']
242 | result[:'clip-to-country'] = params[:'clip-to-country'] if params[:'clip-to-country'].respond_to?(:to_str)
243 | result[:'clip-to-bounding-box'] = params[:'clip-to-bounding-box'].join(',') if params[:'clip-to-bounding-box'].respond_to?(:join)
244 | result[:'clip-to-circle'] = params[:'clip-to-circle'].join(',') if params[:'clip-to-circle'].respond_to?(:join)
245 | result[:'clip-to-polygon'] = params[:'clip-to-polygon'].join(',') if params[:'clip-to-polygon'].respond_to?(:join)
246 | result[:'input-type'] = params[:'input-type'] if params[:'input-type'].respond_to?(:to_str)
247 | result[:'prefer-land'] = params[:'prefer-land'] if params[:'prefer-land']
248 | result.merge(assemble_common_request_params(params))
249 | end
250 |
251 | def deep_symbolize_keys(value)
252 | case value
253 | when Hash
254 | value.transform_keys(&:to_sym).transform_values { |v| deep_symbolize_keys(v) }
255 | when Array
256 | value.map { |v| deep_symbolize_keys(v) }
257 | else
258 | value
259 | end
260 | end
261 |
262 | def endpoint(name)
263 | BASE_URL + ENDPOINTS.fetch(name)
264 | end
265 | end
266 | end
267 |
--------------------------------------------------------------------------------
/lib/what3words/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # What3Words v3 API wrapper
4 | module What3Words
5 | VERSION = '3.4.0' unless defined?(::What3Words::VERSION)
6 | end
7 |
--------------------------------------------------------------------------------
/sample/sample.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | #!/usr/bin/env
3 |
4 | require 'what3words'
5 | require 'json'
6 |
7 | api_key = ENV['W3W_API_KEY']
8 |
9 | what3words = What3Words::API.new(:key => api_key, :format => 'json')
10 |
11 | # ## convert_to_coordinates #########
12 | res = what3words.convert_to_coordinates 'prom.cape.pump'
13 | puts '######### convert_to_coordinate #########'
14 | puts res
15 |
16 | # ## convert_to_3wa #########
17 | res = what3words.convert_to_3wa [29.567041, 106.587875]
18 | puts '######### convert_to_3wa #########'
19 | puts res
20 |
21 | # ## grid_section #########
22 | res = what3words.grid_section '52.208867,0.117540,52.207988,0.116126'
23 | puts '######### grid_section #########'
24 | puts res
25 |
26 | # ## available_languages #########
27 | res = what3words.available_languages
28 | puts '######### available_languages #########'
29 | puts res
30 |
31 |
32 | # ## Vanilla autosuggest, limiting the number of results to three #########
33 | res = what3words.autosuggest 'disclose.strain.redefin', language: 'en', 'n-results': 10
34 | puts '######### autosuggest n-results #########'
35 | puts res
36 |
37 | # ## autosuggest demonstrating clipping to polygon, circle, bounding box, and country #########
38 | res_polygon = what3words.autosuggest 'disclose.strain.redefin', 'clip-to-polygon': [51.521, -0.343, 52.6, 2.3324, 54.234, 8.343, 51.521, -0.343]
39 | res_circle = what3words.autosuggest 'disclose.strain.redefin', 'clip-to-circle': [51.521, -0.343, 142]
40 | res_bbox = what3words.autosuggest 'disclose.strain.redefin', 'clip-to-bounding-box': [51.521, -0.343, 52.6, 2.3324]
41 | res_country = what3words.autosuggest 'disclose.strain.redefin', 'clip-to-country': 'GB,BE'
42 | puts '######### autosuggest clipping options #########'
43 | puts res_polygon
44 | puts res_circle
45 | puts res_bbox
46 | puts res_country
47 |
48 | # ## autosuggest with a focus, with that focus only applied to the first result #########
49 | res = what3words.autosuggest 'filled.count.soap', focus: [51.4243877, -0.34745], 'n-focus-results': 3, 'n-results': 10
50 | puts '######### autosuggest with a focus ######### '
51 | puts res
52 |
53 | # ## autosuggest with an input type of Generic Voice #########
54 | res = what3words.autosuggest 'fun with code', 'input-type': 'generic-voice', language: 'en'
55 | puts '######### autosuggest with Generic Voice as input type ######### '
56 | puts res
57 |
58 | # ## isPossible3wa #########
59 | addresses = ["filled.count.soap", "not a 3wa", "not.3wa address"]
60 | addresses.each do |address|
61 | is_possible = what3words.isPossible3wa(address)
62 | puts "Is '#{address}' a possible what3words address? #{is_possible}"
63 | end
64 |
65 | # ## findPossible3wa #########
66 | texts = [
67 | "Please leave by my porch at filled.count.soap",
68 | "Please leave by my porch at filled.count.soap or deed.tulip.judge",
69 | "Please leave by my porch at"
70 | ]
71 | texts.each do |text|
72 | possible_addresses = what3words.findPossible3wa(text)
73 | puts "Possible what3words addresses in '#{text}': #{possible_addresses}"
74 | end
75 |
76 | # ## didYouMean #########
77 | addresses = ["filled-count-soap", "filled count soap", "invalid#address!example", "this is not a w3w address"]
78 | addresses.each do |address|
79 | suggestion = what3words.didYouMean(address)
80 | puts "Did you mean '#{address}'? #{suggestion}"
81 | end
82 |
83 | # ## isValid3wa #########
84 | addresses = ["filled.count.soap", "filled.count.", "coding.is.cool"]
85 | addresses.each do |address|
86 | is_valid = what3words.isValid3wa(address)
87 | puts "Is '#{address}' a valid what3words address? #{is_valid}"
88 | end
89 |
90 |
--------------------------------------------------------------------------------
/spec/config.sample.yaml:
--------------------------------------------------------------------------------
1 | # config.sample.yaml
2 |
--------------------------------------------------------------------------------
/spec/lib/what3words/what3words_api_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 | require 'webmock/rspec'
5 | require_relative '../../../lib/what3words/api'
6 |
7 | # to run the test type on terminal --> bundle exec rspec
8 |
9 | describe What3Words::API, 'integration', integration: true do
10 | before(:all) do
11 | WebMock.allow_net_connect!
12 | end
13 |
14 | let(:api_key) { ENV['W3W_API_KEY'] }
15 | let(:w3w) { described_class.new(key: api_key) }
16 |
17 | it 'returns errors from API with an invalid key' do
18 | badw3w = described_class.new(key: 'BADKEY')
19 | expect { badw3w.convert_to_coordinates('prom.cape.pump') }
20 | .to raise_error(described_class::ResponseError)
21 | end
22 |
23 | describe 'convert_to_coordinates' do
24 | it 'works with a valid 3 word address' do
25 | result = nil
26 | begin
27 | result = w3w.convert_to_coordinates('prom.cape.pump')
28 | rescue What3Words::API::ResponseError => e
29 | puts e.message
30 | end
31 | expect(result).to include(
32 | words: 'prom.cape.pump',
33 | language: 'en'
34 | ) if result
35 | expect(result[:coordinates]).to include(
36 | lat: 51.484463,
37 | lng: -0.195405
38 | ) if result
39 | end
40 |
41 | it 'raises a ResponseError with the correct message when quota is exceeded' do
42 | error_response = {
43 | "error": {
44 | "code": "QuotaExceeded",
45 | "message": "Quota Exceeded. Please upgrade your usage plan, or contact support@what3words.com"
46 | }
47 | }.to_json
48 |
49 | stub_request(:get, /api.what3words.com/).to_return(status: 402, body: error_response, headers: { content_type: 'application/json' })
50 |
51 | expect {
52 | w3w.convert_to_coordinates('filled.count.soap')
53 | }.to raise_error(What3Words::API::ResponseError, 'QuotaExceeded: Quota Exceeded. Please upgrade your usage plan, or contact support@what3words.com')
54 | end
55 |
56 | it 'sends language parameter for 3 words' do
57 | result = nil
58 | begin
59 | result = w3w.convert_to_coordinates('prom.cape.pump')
60 | rescue What3Words::API::ResponseError => e
61 | puts e.message
62 | end
63 | expect(result).to include(
64 | words: 'prom.cape.pump',
65 | language: 'en'
66 | ) if result
67 | end
68 |
69 | it 'raises an error for an invalid 3 word address format' do
70 | expect { w3w.convert_to_coordinates('1.cape.pump') }
71 | .to raise_error(described_class::WordError)
72 | end
73 |
74 | it 'sends json format parameter for 3 words' do
75 | result = nil
76 | begin
77 | result = w3w.convert_to_coordinates('prom.cape.pump', format: 'json')
78 | rescue What3Words::API::ResponseError => e
79 | puts e.message
80 | end
81 | expect(result).to include(
82 | words: 'prom.cape.pump',
83 | language: 'en',
84 | country: 'GB',
85 | square: {
86 | southwest: {
87 | lng: -0.195426,
88 | lat: 51.484449
89 | },
90 | northeast: {
91 | lng: -0.195383,
92 | lat: 51.484476
93 | }
94 | },
95 | nearestPlace: 'Kensington, London',
96 | coordinates: {
97 | lng: -0.195405,
98 | lat: 51.484463
99 | },
100 | map: 'https://w3w.co/prom.cape.pump'
101 | ) if result
102 | end
103 | end
104 |
105 | describe 'convert_to_3wa' do
106 | it 'converts coordinates to a 3 word address in English' do
107 | begin
108 | result = w3w.convert_to_3wa([29.567041, 106.587875], format: 'json')
109 | expect(result).to include(
110 | words: 'disclose.strain.redefined',
111 | language: 'en'
112 | )
113 | expect(result[:coordinates]).to include(
114 | lat: 29.567041,
115 | lng: 106.587875
116 | )
117 | rescue What3Words::API::ResponseError => e
118 | expect(e.message).to include('QuotaExceeded')
119 | end
120 | end
121 |
122 | it 'converts coordinates to a 3 word address in French' do
123 | begin
124 | result = w3w.convert_to_3wa([29.567041, 106.587875], language: 'fr', format: 'json')
125 | expect(result).to include(
126 | words: 'courgette.rabotons.infrason',
127 | language: 'fr'
128 | )
129 | rescue What3Words::API::ResponseError => e
130 | expect(e.message).to include('QuotaExceeded')
131 | end
132 | end
133 | end
134 |
135 | describe 'autosuggest' do
136 | it 'returns suggestions for a valid input' do
137 | result = w3w.autosuggest('filled.count.soap')
138 | expect(result[:suggestions]).not_to be_empty
139 | end
140 |
141 | it 'returns the default number of suggestions for a simple input' do
142 | result = w3w.autosuggest('disclose.strain.redefin', language: 'en')
143 | expect(result[:suggestions].count).to eq(3)
144 | end
145 |
146 | it 'returns suggestions in the specified language' do
147 | result = w3w.autosuggest('trop.caler.perdre', language: 'fr')
148 | result[:suggestions].each do |suggestion|
149 | expect(suggestion[:language]).to eq('fr')
150 | end
151 | end
152 |
153 | it 'returns suggestions for an input in Arabic' do
154 | result = w3w.autosuggest('مربية.الصباح.المده', language: 'ar')
155 | expect(result[:suggestions]).not_to be_empty
156 | end
157 |
158 | it 'returns a specified number of results' do
159 | result = w3w.autosuggest('disclose.strain.redefin', language: 'en', 'n-results': 10)
160 | expect(result[:suggestions].count).to be >= 10
161 | end
162 |
163 | it 'returns suggestions with focus parameter' do
164 | result = w3w.autosuggest('filled.count.soap', focus: [51.4243877, -0.34745])
165 | expect(result[:suggestions]).not_to be_empty
166 | end
167 |
168 | it 'returns focused suggestions' do
169 | result = w3w.autosuggest('disclose.strain.redefin', language: 'en', 'n-focus-results': 3)
170 | expect(result[:suggestions].count).to be >= 3
171 | end
172 |
173 | it 'returns suggestions for generic-voice input type' do
174 | # @:param string input-type: For power users, used to specify voice input mode. Can be
175 | # text (default), vocon-hybrid, nmdp-asr or generic-voice.
176 | result = w3w.autosuggest 'fun with code', 'input-type': 'generic-voice', language: 'en'
177 | suggestions = result[:suggestions]
178 | output = ['fund.with.code', 'funds.with.code', 'fund.whiff.code']
179 | suggestions.each_with_index do |item, index|
180 | # puts item[:words]
181 | expect(item[:words]).to eq(output[index])
182 | end
183 |
184 | expect(result).not_to be_empty
185 | end
186 |
187 | it 'returns different suggestions with prefer-land parameter' do
188 | result_sea = w3w.autosuggest('///yourselves.frolicking.supernova', 'prefer-land': true, 'n-results': 1)
189 | result_land = w3w.autosuggest('///yourselves.frolicking.supernov', 'prefer-land': false,'n-results': 1)
190 |
191 | # puts "Sea suggestions: #{result_sea[:suggestions]}"
192 | # puts "Land suggestions: #{result_land[:suggestions]}"
193 |
194 | # Check if the suggestions arrays have different lengths or elements
195 | suggestions_different = (result_sea[:suggestions].length != result_land[:suggestions].length) ||
196 | (result_sea[:suggestions] != result_land[:suggestions])
197 |
198 | expect(suggestions_different).to be true
199 | end
200 |
201 | it 'returns suggestions within specified countries' do
202 | result = w3w.autosuggest('disclose.strain.redefin', 'clip-to-country': 'GB,BE')
203 | result[:suggestions].each do |suggestion|
204 | expect(['GB', 'BE']).to include(suggestion[:country])
205 | end
206 | end
207 |
208 | it 'returns suggestions within a specified bounding box' do
209 | result = w3w.autosuggest('disclose.strain.redefin', 'clip-to-bounding-box': [51.521, -0.343, 52.6, 2.3324])
210 | expect(result[:suggestions]).to include(
211 | country: 'GB',
212 | nearestPlace: 'Saxmundham, Suffolk',
213 | words: 'discloses.strain.reddish',
214 | rank: 1,
215 | language: 'en'
216 | )
217 | end
218 |
219 | it 'raises an error with an invalid bounding box' do
220 | expect { w3w.autosuggest('disclose.strain.redefin', 'clip-to-bounding-box': [51.521, -0.343, 52.6]) }
221 | .to raise_error(described_class::ResponseError)
222 | end
223 |
224 | it 'raises an error with a second invalid bounding box' do
225 | expect { w3w.autosuggest('disclose.strain.redefin', 'clip-to-bounding-box': [51.521, -0.343, 55.521, -5.343]) }
226 | .to raise_error(described_class::ResponseError)
227 | end
228 |
229 | it 'returns suggestions within a specified circle' do
230 | result = w3w.autosuggest('disclose.strain.redefin', 'clip-to-circle': [51.521, -0.343, 142])
231 | expect(result[:suggestions]).to include(
232 | country: 'GB',
233 | nearestPlace: 'Market Harborough, Leicestershire',
234 | words: 'discloses.strain.reduce',
235 | rank: 1,
236 | language: 'en'
237 | )
238 | end
239 |
240 | it 'returns suggestions within a specified polygon' do
241 | result = w3w.autosuggest('disclose.strain.redefin', 'clip-to-polygon': [51.521, -0.343, 52.6, 2.3324, 54.234, 8.343, 51.521, -0.343])
242 | expect(result[:suggestions]).to include(
243 | country: 'GB',
244 | nearestPlace: 'Saxmundham, Suffolk',
245 | words: 'discloses.strain.reddish',
246 | rank: 1,
247 | language: 'en'
248 | )
249 | end
250 | end
251 |
252 | describe 'grid_section' do
253 | it 'returns a grid section for a valid bounding box' do
254 | begin
255 | result = w3w.grid_section('52.208867,0.117540,52.207988,0.116126')
256 | expect(result).not_to be_empty
257 | rescue What3Words::API::ResponseError => e
258 | expect(e.message).to include('QuotaExceeded')
259 | end
260 | end
261 |
262 | it 'raises an error for an invalid bounding box' do
263 | begin
264 | expect { w3w.grid_section('50.0,178,50.01,180.0005') }
265 | .to raise_error(What3Words::API::ResponseError)
266 | rescue What3Words::API::ResponseError => e
267 | expect(e.message).to include('QuotaExceeded')
268 | end
269 | end
270 | end
271 |
272 | describe 'available_languages' do
273 | it 'retrieves all available languages' do
274 | result = w3w.available_languages
275 | expect(result[:languages].count).to be >= 51
276 | end
277 |
278 | it 'does not return an empty list of languages' do
279 | result = w3w.available_languages
280 | expect(result[:languages]).not_to be_empty
281 | end
282 | end
283 |
284 | describe 'isPossible3wa' do
285 | it 'returns true for a valid 3 word address' do
286 | expect(w3w.isPossible3wa('filled.count.soap')).to be true
287 | end
288 |
289 | it 'returns false for an invalid address with spaces' do
290 | expect(w3w.isPossible3wa('not a 3wa')).to be false
291 | end
292 |
293 | it 'returns false for an invalid address with mixed formats' do
294 | expect(w3w.isPossible3wa('not.3wa address')).to be false
295 | end
296 | end
297 |
298 | describe 'findPossible3wa' do
299 | it 'finds a single 3 word address in text' do
300 | text = "Please leave by my porch at filled.count.soap"
301 | expect(w3w.findPossible3wa(text)).to eq(['filled.count.soap'])
302 | end
303 |
304 | it 'finds multiple 3 word addresses in text' do
305 | text = "Please leave by my porch at filled.count.soap or deed.tulip.judge"
306 | expect(w3w.findPossible3wa(text)).to eq(['filled.count.soap', 'deed.tulip.judge'])
307 | end
308 |
309 | it 'returns an empty array when no 3 word address is found' do
310 | text = "Please leave by my porch at"
311 | expect(w3w.findPossible3wa(text)).to eq([])
312 | end
313 | end
314 |
315 | describe 'didYouMean' do
316 | it 'returns true for valid three word address with hyphens' do
317 | expect(w3w.didYouMean('filled-count-soap')).to be true
318 | end
319 |
320 | it 'returns true for valid three word address with spaces' do
321 | expect(w3w.didYouMean('filled count soap')).to be true
322 | end
323 |
324 | it 'returns false for invalid address with special characters' do
325 | expect(w3w.didYouMean('invalid#address!example')).to be false
326 | end
327 |
328 | it 'returns false for random text not in w3w format' do
329 | expect(w3w.didYouMean('this is not a w3w address')).to be false
330 | end
331 | end
332 |
333 | describe 'isValid3wa' do
334 | it 'returns true for a valid 3 word address' do
335 | expect(w3w.isValid3wa('filled.count.soap')).to be true
336 | end
337 |
338 | it 'returns false for an invalid 3 word address' do
339 | expect(w3w.isValid3wa('invalid.address.here')).to be false
340 | end
341 |
342 | it 'returns false for a random string' do
343 | expect(w3w.isValid3wa('this is not a w3w address')).to be false
344 | end
345 | end
346 |
347 | describe 'technical' do
348 | it 'deep_symbolize_keys helper works correctly' do
349 | expect(w3w.send(:deep_symbolize_keys, 'foo' => { 'bar' => true }))
350 | .to eq(foo: { bar: true })
351 | end
352 | end
353 | end
354 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/setup'
4 |
5 | Bundler.setup
6 |
7 | require 'webmock/rspec'
8 |
9 | require 'what3words'
10 |
--------------------------------------------------------------------------------
/what3words.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path('lib', __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require 'what3words/version'
6 |
7 | Gem::Specification.new do |spec|
8 | spec.name = 'what3words'
9 | spec.version = What3Words::VERSION
10 | spec.authors = ['what3words']
11 | spec.email = ['development@what3words.com']
12 | spec.description = 'A Ruby wrapper for the what3words API'
13 | spec.summary = 'Ruby wrapper for the what3words API'
14 | spec.homepage = 'https://github.com/what3words/w3w-ruby-wrapper'
15 | spec.license = 'MIT'
16 |
17 | spec.files = Dir["lib/**/*.rb", "README.md"]
18 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19 | spec.test_files = spec.files.grep(%r{^spec/})
20 | spec.require_paths = ['lib']
21 | spec.platform = Gem::Platform::RUBY
22 | spec.required_ruby_version = '>= 2.6', '< 4.0'
23 |
24 |
25 | spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/what3words"
26 | spec.metadata["source_code_uri"] = "https://github.com/what3words/w3w-ruby-wrapper"
27 |
28 | spec.add_dependency('rest-client', '>= 1.8', '< 3.0')
29 |
30 | spec.add_development_dependency 'bundler', '~> 2.1'
31 | spec.add_development_dependency 'rake', '~> 12.3'
32 | spec.add_development_dependency 'rspec', '~> 3.4'
33 | spec.add_development_dependency 'rubocop', '~> 0.49.0'
34 | spec.add_development_dependency 'webmock', '~> 3.0'
35 | end
36 |
--------------------------------------------------------------------------------