gcloud 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,108 @@
1
+ # Copyright 2016 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
+ module Gcloud
17
+ module Vision
18
+ class Annotation
19
+ ##
20
+ # # Vertex
21
+ #
22
+ # A vertex in a set of bounding polygon vertices.
23
+ #
24
+ # See {Face::Bounds} and {Text}.
25
+ #
26
+ # @attr_reader [Integer] x The X coordinate.
27
+ # @attr_reader [Integer] y The Y coordinate.
28
+ #
29
+ # @example
30
+ # require "gcloud"
31
+ #
32
+ # gcloud = Gcloud.new
33
+ # vision = gcloud.vision
34
+ #
35
+ # image = vision.image "path/to/text.png"
36
+ # text = image.text
37
+ #
38
+ # text.bounds.count #=> 4
39
+ # vertex = text.bounds.first
40
+ # vertex.x #=> 13
41
+ # vertex.y #=> 8
42
+ #
43
+ class Vertex
44
+ attr_reader :x, :y
45
+
46
+ ##
47
+ # @private Creates a new Vertex instance.
48
+ def initialize x, y
49
+ @x = x
50
+ @y = y
51
+ end
52
+
53
+ ##
54
+ # Returns the object's property values as an array.
55
+ #
56
+ # @return [Array]
57
+ #
58
+ def to_a
59
+ to_ary
60
+ end
61
+
62
+ ##
63
+ # Returns the object's property values as an array.
64
+ #
65
+ # @return [Array]
66
+ #
67
+ def to_ary
68
+ [x, y]
69
+ end
70
+
71
+ ##
72
+ # Converts object to a hash. All keys will be symbolized.
73
+ #
74
+ # @return [Hash]
75
+ #
76
+ def to_h
77
+ to_hash
78
+ end
79
+
80
+ ##
81
+ # Converts object to a hash. All keys will be symbolized.
82
+ #
83
+ # @return [Hash]
84
+ #
85
+ def to_hash
86
+ { x: x, y: y }
87
+ end
88
+
89
+ # @private
90
+ def to_s
91
+ "(x: #{x.inspect}, y: #{y.inspect})"
92
+ end
93
+
94
+ # @private
95
+ def inspect
96
+ "#<Vertex #{self}>"
97
+ end
98
+
99
+ ##
100
+ # @private New Annotation::Entity::Bounds::Vertex from a Google API
101
+ # Client object.
102
+ def self.from_gapi gapi
103
+ new gapi["x"], gapi["y"]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright 2016 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
+ require "gcloud/version"
17
+ require "google/api_client"
18
+
19
+ module Gcloud
20
+ module Vision
21
+ ##
22
+ # @private Represents the connection to Vision,
23
+ # as well as expose the API calls.
24
+ class Connection
25
+ attr_accessor :project
26
+ attr_accessor :credentials
27
+
28
+ ##
29
+ # Creates a new Connection instance.
30
+ def initialize project, credentials
31
+ @project = project
32
+ @credentials = credentials
33
+ @client = Google::APIClient.new application_name: "gcloud-ruby",
34
+ application_version: Gcloud::VERSION
35
+ @client.authorization = @credentials.client
36
+ custom_discovery_url = Addressable::URI.parse(
37
+ "https://vision.googleapis.com/$discovery/rest?version=v1")
38
+ @client.register_discovery_uri "vision", "v1", custom_discovery_url
39
+ @vision = @client.discovered_api "vision", "v1"
40
+ end
41
+
42
+ def annotate requests
43
+ @client.execute(
44
+ api_method: @vision.images.annotate,
45
+ body_object: { requests: requests }
46
+ )
47
+ end
48
+
49
+ def inspect
50
+ "#{self.class}(#{@project})"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright 2016 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
+ require "gcloud/credentials"
17
+
18
+ module Gcloud
19
+ module Vision
20
+ ##
21
+ # @private Represents the OAuth 2.0 signing logic for Vision.
22
+ class Credentials < Gcloud::Credentials
23
+ SCOPE = ["https://www.googleapis.com/auth/cloud-platform"]
24
+ PATH_ENV_VARS = %w(VISION_KEYFILE GCLOUD_KEYFILE GOOGLE_CLOUD_KEYFILE)
25
+ JSON_ENV_VARS = %w(VISION_KEYFILE_JSON GCLOUD_KEYFILE_JSON
26
+ GOOGLE_CLOUD_KEYFILE_JSON)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ # Copyright 2016 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
+ require "gcloud/errors"
17
+
18
+ module Gcloud
19
+ module Vision
20
+ ##
21
+ # # Vision Error
22
+ #
23
+ # Base Vision exception class.
24
+ class Error < Gcloud::Error
25
+ ##
26
+ # The response object of the failed HTTP request.
27
+ attr_reader :response
28
+
29
+ # @private
30
+ def self.from_response resp
31
+ new.tap do |e|
32
+ e.instance_variable_set "@response", resp
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # # ApiError
39
+ #
40
+ # Raised when an API call is not successful.
41
+ class ApiError < Error
42
+ ##
43
+ # The HTTP code of the error.
44
+ attr_reader :code
45
+
46
+ ##
47
+ # The Google API error status.
48
+ attr_reader :status
49
+
50
+ ##
51
+ # The errors encountered.
52
+ attr_reader :errors
53
+
54
+ # @private
55
+ def self.from_response resp
56
+ if resp.data? && resp.data["error"]
57
+ new(resp.data["error"]["message"]).tap do |e|
58
+ e.instance_variable_set "@code", resp.data["error"]["code"]
59
+ e.instance_variable_set "@status", resp.data["error"]["status"]
60
+ e.instance_variable_set "@errors", resp.data["error"]["errors"]
61
+ e.instance_variable_set "@response", resp
62
+ end
63
+ else
64
+ Error.from_response_status resp
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,593 @@
1
+ # Copyright 2016 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
+ require "gcloud/vision/location"
17
+ require "stringio"
18
+ require "base64"
19
+
20
+ module Gcloud
21
+ module Vision
22
+ ##
23
+ # # Image
24
+ #
25
+ # Represents an image for the Vision service.
26
+ #
27
+ # See {Project#image}.
28
+ #
29
+ # The Cloud Vision API supports a variety of image file formats, including
30
+ # JPEG, PNG8, PNG24, Animated GIF (first frame only), and RAW. See [Best
31
+ # Practices - Image Types](https://cloud.google.com/vision/docs/image-best-practices#image_types)
32
+ # for the list of formats. Be aware that Cloud Vision sets upper limits on
33
+ # file size as well as the total combined size of all images in a request.
34
+ # Reducing your file size can significantly improve throughput; however, be
35
+ # careful not to reduce image quality in the process. See [Best Practices -
36
+ # Image Sizing](https://cloud.google.com/vision/docs/image-best-practices#image_sizing)
37
+ # for current file size limits.
38
+ #
39
+ # @see https://cloud.google.com/vision/docs/image-best-practices Best
40
+ # Practices
41
+ #
42
+ # @example
43
+ # require "gcloud"
44
+ #
45
+ # gcloud = Gcloud.new
46
+ # vision = gcloud.vision
47
+ #
48
+ # image = vision.image "path/to/text.png"
49
+ #
50
+ # image.context.languages = ["en"]
51
+ #
52
+ # text = image.text
53
+ # text.words.count #=> 28
54
+ #
55
+ class Image
56
+ # Returns the image context for the image, which accepts metadata values
57
+ # such as location and language hints.
58
+ # @return [Context] The context instance for the image.
59
+ attr_reader :context
60
+
61
+ ##
62
+ # @private Creates a new Image instance.
63
+ def initialize
64
+ @io = nil
65
+ @url = nil
66
+ @vision = nil
67
+ @context = Context.new
68
+ end
69
+
70
+ ##
71
+ # @private Whether the Image has content.
72
+ #
73
+ # @see {#url?}
74
+ #
75
+ def content?
76
+ !@io.nil?
77
+ end
78
+
79
+ ##
80
+ # @private Whether the Image is a URL.
81
+ #
82
+ # @see {#content?}
83
+ #
84
+ def url?
85
+ !@url.nil?
86
+ end
87
+
88
+ ##
89
+ # @private The contents of the image, encoded via Base64.
90
+ #
91
+ # @return [String]
92
+ #
93
+ def content
94
+ @content ||= Base64.encode64 @io.read
95
+ end
96
+
97
+ ##
98
+ # @private The URL of the image.
99
+ #
100
+ # @return [String]
101
+ #
102
+ def url
103
+ @url
104
+ end
105
+
106
+ ##
107
+ # Performs the `FACE_DETECTION` feature on the image.
108
+ #
109
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
110
+ #
111
+ # @param [Integer] max_results The maximum number of results. The default
112
+ # is {Gcloud::Vision.default_max_faces}. Optional.
113
+ #
114
+ # @return [Array<Annotation::Face>] The results of face detection.
115
+ #
116
+ # @example
117
+ # require "gcloud"
118
+ #
119
+ # gcloud = Gcloud.new
120
+ # vision = gcloud.vision
121
+ # image = vision.image "path/to/face.jpg"
122
+ #
123
+ # faces = image.faces
124
+ #
125
+ # face = faces.first
126
+ # face.bounds.face.count #=> 4
127
+ # face.bounds.face.first #=> #<Vertex (x: 153, y: 34)>
128
+ #
129
+ def faces max_results = Gcloud::Vision.default_max_faces
130
+ ensure_vision!
131
+ annotation = @vision.mark self, faces: max_results
132
+ annotation.faces
133
+ end
134
+
135
+ ##
136
+ # Performs the `FACE_DETECTION` feature on the image and returns only the
137
+ # first result.
138
+ #
139
+ # @return [Annotation::Face] The first result of face detection.
140
+ #
141
+ def face
142
+ faces(1).first
143
+ end
144
+
145
+ ##
146
+ # Performs the `LANDMARK_DETECTION` feature on the image.
147
+ #
148
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
149
+ #
150
+ # @param [Integer] max_results The maximum number of results. The default
151
+ # is {Gcloud::Vision.default_max_landmarks}. Optional.
152
+ #
153
+ # @return [Array<Annotation::Entity>] The results of landmark detection.
154
+ #
155
+ # @example
156
+ # require "gcloud"
157
+ #
158
+ # gcloud = Gcloud.new
159
+ # vision = gcloud.vision
160
+ # image = vision.image "path/to/landmark.jpg"
161
+ #
162
+ # landmarks = image.landmarks
163
+ #
164
+ # landmark = landmarks.first
165
+ # landmark.score #=> 0.91912264
166
+ # landmark.description #=> "Mount Rushmore"
167
+ # landmark.mid #=> "/m/019dvv"
168
+ #
169
+ def landmarks max_results = Gcloud::Vision.default_max_landmarks
170
+ ensure_vision!
171
+ annotation = @vision.mark self, landmarks: max_results
172
+ annotation.landmarks
173
+ end
174
+
175
+ ##
176
+ # Performs the `LANDMARK_DETECTION` feature on the image and returns only
177
+ # the first result.
178
+ #
179
+ # @return [Annotation::Entity] The first result of landmark detection.
180
+ #
181
+ def landmark
182
+ landmarks(1).first
183
+ end
184
+
185
+ ##
186
+ # Performs the `LOGO_DETECTION` feature on the image.
187
+ #
188
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
189
+ #
190
+ # @param [Integer] max_results The maximum number of results. The default
191
+ # is {Gcloud::Vision.default_max_logos}. Optional.
192
+ #
193
+ # @return [Array<Annotation::Entity>] The results of logo detection.
194
+ #
195
+ # @example
196
+ # require "gcloud"
197
+ #
198
+ # gcloud = Gcloud.new
199
+ # vision = gcloud.vision
200
+ # image = vision.image "path/to/logo.jpg"
201
+ #
202
+ # logos = image.logos
203
+ #
204
+ # logo = logos.first
205
+ # logo.score #=> 0.70057315
206
+ # logo.description #=> "Google"
207
+ # logo.mid #=> "/m/0b34hf"
208
+ #
209
+ def logos max_results = Gcloud::Vision.default_max_logos
210
+ ensure_vision!
211
+ annotation = @vision.mark self, logos: max_results
212
+ annotation.logos
213
+ end
214
+
215
+ ##
216
+ # Performs the `LOGO_DETECTION` feature on the image and returns only the
217
+ # first result.
218
+ #
219
+ # @return [Annotation::Entity] The first result of logo detection.
220
+ #
221
+ def logo
222
+ logos(1).first
223
+ end
224
+
225
+ ##
226
+ # Performs the `LABEL_DETECTION` feature on the image.
227
+ #
228
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
229
+ #
230
+ # @param [Integer] max_results The maximum number of results. The default
231
+ # is {Gcloud::Vision.default_max_labels}. Optional.
232
+ #
233
+ # @return [Array<Annotation::Entity>] The results of label detection.
234
+ #
235
+ # @example
236
+ # require "gcloud"
237
+ #
238
+ # gcloud = Gcloud.new
239
+ # vision = gcloud.vision
240
+ # image = vision.image "path/to/face.jpg"
241
+ #
242
+ # labels = image.labels
243
+ #
244
+ # labels.count #=> 4
245
+ # label = labels.first
246
+ # label.score #=> 0.9481349
247
+ # label.description #=> "person"
248
+ # label.mid #=> "/m/01g317"
249
+ #
250
+ def labels max_results = Gcloud::Vision.default_max_labels
251
+ ensure_vision!
252
+ annotation = @vision.mark self, labels: max_results
253
+ annotation.labels
254
+ end
255
+
256
+ ##
257
+ # Performs the `LABEL_DETECTION` feature on the image and returns only the
258
+ # first result.
259
+ #
260
+ # @return [Annotation::Entity] The first result of label detection.
261
+ #
262
+ def label
263
+ labels(1).first
264
+ end
265
+
266
+ ##
267
+ # Performs the `TEXT_DETECTION` (OCR) feature on the image.
268
+ #
269
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
270
+ #
271
+ # @return [Annotation::Text] The results of text (OCR) detection.
272
+ #
273
+ # @example
274
+ # require "gcloud"
275
+ #
276
+ # gcloud = Gcloud.new
277
+ # vision = gcloud.vision
278
+ # image = vision.image "path/to/text.png"
279
+ #
280
+ # text = image.text
281
+ #
282
+ # text = image.text
283
+ # text.locale #=> "en"
284
+ # text.words.count #=> 28
285
+ # text.text
286
+ # #=> "Google Cloud Client Library for Ruby an idiomatic, intuitive... "
287
+ #
288
+ def text
289
+ ensure_vision!
290
+ annotation = @vision.mark self, text: true
291
+ annotation.text
292
+ end
293
+
294
+ ##
295
+ # Performs the `SAFE_SEARCH_DETECTION` feature on the image.
296
+ #
297
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
298
+ #
299
+ # @return [Annotation::SafeSearch] The results of safe search detection.
300
+ #
301
+ # @example
302
+ # require "gcloud"
303
+ #
304
+ # gcloud = Gcloud.new
305
+ # vision = gcloud.vision
306
+ # image = vision.image "path/to/face.jpg"
307
+ #
308
+ # safe_search = image.safe_search
309
+ #
310
+ # safe_search.spoof? #=> false
311
+ # safe_search.spoof #=> "VERY_UNLIKELY"
312
+ #
313
+ def safe_search
314
+ ensure_vision!
315
+ annotation = @vision.mark self, safe_search: true
316
+ annotation.safe_search
317
+ end
318
+
319
+ ##
320
+ # Performs the `IMAGE_PROPERTIES` feature on the image.
321
+ #
322
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
323
+ #
324
+ # @return [Annotation::Properties] The results of image properties
325
+ # detection.
326
+ #
327
+ # @example
328
+ # require "gcloud"
329
+ #
330
+ # gcloud = Gcloud.new
331
+ # vision = gcloud.vision
332
+ # image = vision.image "path/to/logo.jpg"
333
+ #
334
+ # properties = image.properties
335
+ #
336
+ # properties.colors.count #=> 10
337
+ # color = properties.colors.first
338
+ # color.red #=> 247.0
339
+ # color.green #=> 236.0
340
+ # color.blue #=> 20.0
341
+ #
342
+ def properties
343
+ ensure_vision!
344
+ annotation = @vision.mark self, properties: true
345
+ annotation.properties
346
+ end
347
+
348
+ # @private
349
+ def to_s
350
+ return "(io)" if content?
351
+ "(url: #{url})"
352
+ end
353
+
354
+ # @private
355
+ def inspect
356
+ "#<#{self.class.name} #{self}>"
357
+ end
358
+
359
+ ##
360
+ # @private The Google API Client object for the Image.
361
+ def to_gapi
362
+ if content?
363
+ { content: content }
364
+ elsif url?
365
+ { source: { gcsImageUri: @url } }
366
+ else
367
+ fail ArgumentError, "Unable to use Image with Vision service."
368
+ end
369
+ end
370
+
371
+ ##
372
+ # @private New Image from a source object.
373
+ def self.from_source source, vision = nil
374
+ if source.is_a?(IO) || source.is_a?(StringIO)
375
+ return from_io(source, vision)
376
+ end
377
+ # Convert Storage::File objects to the URL
378
+ source = source.to_gs_url if source.respond_to? :to_gs_url
379
+ # Everything should be a string from now on
380
+ source = String source
381
+ # Create an Image from the Google Storage URL
382
+ return from_url(source, vision) if source.start_with? "gs://"
383
+ # Create an image from a file on the filesystem
384
+ if File.file? source
385
+ unless File.readable? source
386
+ fail ArgumentError, "Cannot read #{source}"
387
+ end
388
+ return from_io(File.open(source, "rb"), vision)
389
+ end
390
+ fail ArgumentError, "Unable to convert #{source} to an Image"
391
+ end
392
+
393
+ ##
394
+ # @private New Image from an IO object.
395
+ def self.from_io io, vision
396
+ if !io.is_a?(IO) && !io.is_a?(StringIO)
397
+ puts io.inspect
398
+ fail ArgumentError, "Cannot create an Image without an IO object"
399
+ end
400
+ new.tap do |i|
401
+ i.instance_variable_set :@io, io
402
+ i.instance_variable_set :@vision, vision
403
+ end
404
+ end
405
+
406
+ ##
407
+ # @private New Image from an IO object.
408
+ def self.from_url url, vision
409
+ url = String url
410
+ unless url.start_with? "gs://"
411
+ fail ArgumentError, "Cannot create an Image without a Storage URL"
412
+ end
413
+ new.tap do |i|
414
+ i.instance_variable_set :@url, url
415
+ i.instance_variable_set :@vision, vision
416
+ end
417
+ end
418
+
419
+ protected
420
+
421
+ ##
422
+ # Raise an error unless an active vision project object is available.
423
+ def ensure_vision!
424
+ fail "Must have active connection" unless @vision
425
+ end
426
+ end
427
+
428
+ class Image
429
+ ##
430
+ # # Image::Context
431
+ #
432
+ # Represents an image context.
433
+ #
434
+ # @attr [Array<String>] languages A list of [ISO 639-1 language
435
+ # codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
436
+ # to use for text (OCR) detection. In most cases, an empty value
437
+ # will yield the best results as it will allow text detection to
438
+ # automatically detect the text language. For languages based on the
439
+ # latin alphabet a hint is not needed. In rare cases, when the
440
+ # language of the text in the image is known in advance, setting
441
+ # this hint will help get better results (although it will hurt a
442
+ # great deal if the hint is wrong).
443
+ #
444
+ # @example
445
+ # require "gcloud"
446
+ #
447
+ # gcloud = Gcloud.new
448
+ # vision = gcloud.vision
449
+ #
450
+ # image = vision.image "path/to/landmark.jpg"
451
+ # image.context.area.min = { longitude: -122.0862462,
452
+ # latitude: 37.4220041 }
453
+ # image.context.area.max = { longitude: -122.0762462,
454
+ # latitude: 37.4320041 }
455
+ #
456
+ class Context
457
+ # Returns a lat/long rectangle that specifies the location of the image.
458
+ # @return [Area] The lat/long pairs for `latLongRect`.
459
+ attr_reader :area
460
+
461
+ attr_accessor :languages
462
+
463
+ ##
464
+ # @private Creates a new Context instance.
465
+ def initialize
466
+ @area = Area.new
467
+ @languages = []
468
+ end
469
+
470
+ ##
471
+ # Returns `true` if either `min` or `max` are not populated.
472
+ #
473
+ # @return [Boolean]
474
+ #
475
+ def empty?
476
+ area.empty? && languages.empty?
477
+ end
478
+
479
+ ##
480
+ # @private
481
+ def to_gapi
482
+ return nil if empty?
483
+ gapi = {}
484
+ gapi[:latLongRect] = area.to_hash unless area.empty?
485
+ gapi[:languageHints] = languages unless languages.empty?
486
+ gapi
487
+ end
488
+
489
+ ##
490
+ # # Image::Context::Area
491
+ #
492
+ # A Lat/long rectangle that specifies the location of the image.
493
+ #
494
+ # @example
495
+ # require "gcloud"
496
+ #
497
+ # gcloud = Gcloud.new
498
+ # vision = gcloud.vision
499
+ #
500
+ # image = vision.image "path/to/landmark.jpg"
501
+ #
502
+ # image.context.area.min = { longitude: -122.0862462,
503
+ # latitude: 37.4220041 }
504
+ # image.context.area.max = { longitude: -122.0762462,
505
+ # latitude: 37.4320041 }
506
+ #
507
+ # entity = image.landmark
508
+ #
509
+ class Area
510
+ # Returns the min lat/long pair.
511
+ # @return [Location]
512
+ attr_reader :min
513
+
514
+ # Returns the max lat/long pair.
515
+ # @return [Location]
516
+ attr_reader :max
517
+
518
+ ##
519
+ # @private Creates a new Area instance.
520
+ def initialize
521
+ @min = Location.new nil, nil
522
+ @max = Location.new nil, nil
523
+ end
524
+
525
+ ##
526
+ # Sets the min lat/long pair for the area.
527
+ #
528
+ # @param [Hash(Symbol => Float)] location A Hash containing the keys
529
+ # `:latitude` and `:longitude` with corresponding values conforming
530
+ # to the [WGS84
531
+ # standard](http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf).
532
+ def min= location
533
+ if location.respond_to?(:to_hash) &&
534
+ location.to_hash.keys.sort == [:latitude, :longitude]
535
+ return @min = Location.new(location.to_hash[:latitude],
536
+ location.to_hash[:longitude])
537
+ end
538
+ fail ArgumentError, "Must pass a proper location value."
539
+ end
540
+
541
+ ##
542
+ # Sets the max lat/long pair for the area.
543
+ #
544
+ # @param [Hash(Symbol => Float)] location A Hash containing the keys
545
+ # `:latitude` and `:longitude` with corresponding values conforming
546
+ # to the [WGS84
547
+ # standard](http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf).
548
+ def max= location
549
+ if location.respond_to?(:to_hash) &&
550
+ location.to_hash.keys.sort == [:latitude, :longitude]
551
+ return @max = Location.new(location.to_hash[:latitude],
552
+ location.to_hash[:longitude])
553
+ end
554
+ fail ArgumentError, "Must pass a proper location value."
555
+ end
556
+
557
+ ##
558
+ # Returns `true` if either `min` or `max` are not populated.
559
+ #
560
+ # @return [Boolean]
561
+ #
562
+ def empty?
563
+ min.to_hash.values.reject(&:nil?).empty? ||
564
+ max.to_hash.values.reject(&:nil?).empty?
565
+ end
566
+
567
+ ##
568
+ # Deeply converts object to a hash. All keys will be symbolized.
569
+ #
570
+ # @return [Hash]
571
+ #
572
+ def to_h
573
+ to_hash
574
+ end
575
+
576
+ ##
577
+ # Deeply converts object to a hash. All keys will be symbolized.
578
+ #
579
+ # @return [Hash]
580
+ #
581
+ def to_hash
582
+ { minLatLng: min.to_hash, maxLatLng: max.to_hash }
583
+ end
584
+
585
+ def to_gapi
586
+ return nil if empty?
587
+ to_hash
588
+ end
589
+ end
590
+ end
591
+ end
592
+ end
593
+ end