gcloud 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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