rgeo 0.3.13 → 0.3.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/History.rdoc +8 -0
  2. data/README.rdoc +4 -4
  3. data/Version +1 -1
  4. data/ext/geos_c_impl/extconf.rb +1 -0
  5. data/ext/geos_c_impl/factory.c +118 -5
  6. data/ext/geos_c_impl/factory.h +24 -1
  7. data/ext/geos_c_impl/geometry.c +42 -53
  8. data/ext/geos_c_impl/geometry_collection.c +137 -54
  9. data/ext/geos_c_impl/geometry_collection.h +9 -0
  10. data/ext/geos_c_impl/line_string.c +88 -45
  11. data/ext/geos_c_impl/point.c +31 -17
  12. data/ext/geos_c_impl/polygon.c +50 -22
  13. data/ext/geos_c_impl/polygon.h +9 -0
  14. data/ext/geos_c_impl/preface.h +10 -0
  15. data/lib/rgeo/cartesian/factory.rb +9 -1
  16. data/lib/rgeo/coord_sys/cs/entities.rb +10 -1
  17. data/lib/rgeo/coord_sys/proj4.rb +1 -1
  18. data/lib/rgeo/feature/types.rb +29 -5
  19. data/lib/rgeo/geographic/factory.rb +5 -0
  20. data/lib/rgeo/geographic/projected_feature_classes.rb +3 -47
  21. data/lib/rgeo/geographic/projected_feature_methods.rb +69 -0
  22. data/lib/rgeo/geographic/spherical_feature_classes.rb +1 -74
  23. data/lib/rgeo/geographic/spherical_feature_methods.rb +84 -0
  24. data/lib/rgeo/geographic/spherical_math.rb +3 -3
  25. data/lib/rgeo/geos.rb +17 -9
  26. data/lib/rgeo/geos/{factory.rb → capi_factory.rb} +36 -15
  27. data/lib/rgeo/geos/{impl_additions.rb → capi_feature_classes.rb} +127 -16
  28. data/lib/rgeo/geos/ffi_factory.rb +55 -41
  29. data/lib/rgeo/geos/ffi_feature_classes.rb +168 -0
  30. data/lib/rgeo/geos/{ffi_classes.rb → ffi_feature_methods.rb} +56 -57
  31. data/lib/rgeo/geos/interface.rb +5 -5
  32. data/lib/rgeo/geos/utils.rb +7 -0
  33. data/lib/rgeo/geos/zm_factory.rb +40 -12
  34. data/lib/rgeo/geos/zm_feature_classes.rb +165 -0
  35. data/lib/rgeo/geos/{zm_impl.rb → zm_feature_methods.rb} +33 -52
  36. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +8 -0
  37. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +8 -0
  38. data/lib/rgeo/impl_helper/basic_point_methods.rb +5 -0
  39. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +8 -0
  40. data/test/common/factory_tests.rb +8 -2
  41. data/test/common/geometry_collection_tests.rb +23 -0
  42. data/test/common/line_string_tests.rb +25 -0
  43. data/test/common/multi_line_string_tests.rb +7 -0
  44. data/test/common/multi_point_tests.rb +7 -0
  45. data/test/common/multi_polygon_tests.rb +7 -0
  46. data/test/common/point_tests.rb +21 -0
  47. data/test/common/polygon_tests.rb +15 -0
  48. data/test/coord_sys/tc_proj4.rb +8 -1
  49. data/test/geos_capi/tc_misc.rb +1 -1
  50. data/test/tc_mixins.rb +1 -1
  51. metadata +9 -7
@@ -127,6 +127,11 @@ module RGeo
127
127
  alias_method :==, :eql?
128
128
 
129
129
 
130
+ def hash
131
+ @hash ||= [@impl_prefix, @support_z, @support_m, @proj4].hash
132
+ end
133
+
134
+
130
135
  # Marshal support
131
136
 
132
137
  def marshal_dump # :nodoc:
@@ -46,37 +46,7 @@ module RGeo
46
46
  include ImplHelper::BasicGeometryMethods
47
47
  include ImplHelper::BasicPointMethods
48
48
  include ProjectedGeometryMethods
49
-
50
-
51
- def _validate_geometry
52
- @y = 85.0511287 if @y > 85.0511287
53
- @y = -85.0511287 if @y < -85.0511287
54
- super
55
- end
56
-
57
-
58
- def canonical_x
59
- x_ = @x % 360.0
60
- x_ -= 360.0 if x_ >= 180.0
61
- x_
62
- end
63
- alias_method :canonical_longitude, :canonical_x
64
- alias_method :canonical_lon, :canonical_x
65
-
66
-
67
- def canonical_point
68
- if @x >= -180.0 && @x < 180.0
69
- self
70
- else
71
- PointImpl.new(@factory, canonical_x, @y)
72
- end
73
- end
74
-
75
-
76
- alias_method :longitude, :x
77
- alias_method :lon, :x
78
- alias_method :latitude, :y
79
- alias_method :lat, :y
49
+ include ProjectedPointMethods
80
50
 
81
51
 
82
52
  Feature::MixinCollection::GLOBAL.for_type(Feature::Point).include_in_class(self, true)
@@ -146,14 +116,7 @@ module RGeo
146
116
  include ImplHelper::BasicPolygonMethods
147
117
  include ProjectedGeometryMethods
148
118
  include ProjectedNSurfaceMethods
149
-
150
-
151
- def _validate_geometry
152
- super
153
- unless projection
154
- raise Error::InvalidGeometry, 'Polygon failed assertions'
155
- end
156
- end
119
+ include ProjectedPolygonMethods
157
120
 
158
121
 
159
122
  Feature::MixinCollection::GLOBAL.for_type(Feature::Polygon).include_in_class(self, true)
@@ -219,14 +182,7 @@ module RGeo
219
182
  include ImplHelper::BasicMultiPolygonMethods
220
183
  include ProjectedGeometryMethods
221
184
  include ProjectedNSurfaceMethods
222
-
223
-
224
- def _validate_geometry
225
- super
226
- unless projection
227
- raise Error::InvalidGeometry, 'MultiPolygon failed assertions'
228
- end
229
- end
185
+ include ProjectedMultiPolygonMethods
230
186
 
231
187
 
232
188
  Feature::MixinCollection::GLOBAL.for_type(Feature::MultiPolygon).include_in_class(self, true)
@@ -159,6 +159,47 @@ module RGeo
159
159
  end
160
160
 
161
161
 
162
+ module ProjectedPointMethods # :nodoc:
163
+
164
+
165
+ def _validate_geometry
166
+ @y = 85.0511287 if @y > 85.0511287
167
+ @y = -85.0511287 if @y < -85.0511287
168
+ super
169
+ end
170
+
171
+
172
+ def canonical_x
173
+ x_ = @x % 360.0
174
+ x_ -= 360.0 if x_ >= 180.0
175
+ x_
176
+ end
177
+ alias_method :canonical_longitude, :canonical_x
178
+ alias_method :canonical_lon, :canonical_x
179
+
180
+
181
+ def canonical_point
182
+ if @x >= -180.0 && @x < 180.0
183
+ self
184
+ else
185
+ PointImpl.new(@factory, canonical_x, @y)
186
+ end
187
+ end
188
+
189
+
190
+ def self.included(klass_)
191
+ klass_.module_eval do
192
+ alias_method :longitude, :x
193
+ alias_method :lon, :x
194
+ alias_method :latitude, :y
195
+ alias_method :lat, :y
196
+ end
197
+ end
198
+
199
+
200
+ end
201
+
202
+
162
203
  module ProjectedNCurveMethods # :nodoc:
163
204
 
164
205
 
@@ -224,6 +265,34 @@ module RGeo
224
265
  end
225
266
 
226
267
 
268
+ module ProjectedPolygonMethods # :nodoc:
269
+
270
+
271
+ def _validate_geometry
272
+ super
273
+ unless projection
274
+ raise Error::InvalidGeometry, 'Polygon failed assertions'
275
+ end
276
+ end
277
+
278
+
279
+ end
280
+
281
+
282
+ module ProjectedMultiPolygonMethods # :nodoc:
283
+
284
+
285
+ def _validate_geometry
286
+ super
287
+ unless projection
288
+ raise Error::InvalidGeometry, 'MultiPolygon failed assertions'
289
+ end
290
+ end
291
+
292
+
293
+ end
294
+
295
+
227
296
  end
228
297
 
229
298
  end
@@ -46,80 +46,7 @@ module RGeo
46
46
  include ImplHelper::BasicGeometryMethods
47
47
  include ImplHelper::BasicPointMethods
48
48
  include SphericalGeometryMethods
49
-
50
-
51
- def _validate_geometry
52
- if @x < -180.0 || @x >= 180.0
53
- @x = @x % 360.0
54
- @x -= 360.0 if @x >= 180.0
55
- end
56
- @y = 90.0 if @y > 90.0
57
- @y = -90.0 if @y < -90.0
58
- super
59
- end
60
-
61
-
62
- def _xyz
63
- @xyz ||= SphericalMath::PointXYZ.from_latlon(@y, @x)
64
- end
65
-
66
-
67
- def distance(rhs_)
68
- rhs_ = Feature.cast(rhs_, @factory)
69
- case rhs_
70
- when SphericalPointImpl
71
- _xyz.dist_to_point(rhs_._xyz) * SphericalMath::RADIUS
72
- else
73
- super
74
- end
75
- end
76
-
77
-
78
- def equals?(rhs_)
79
- return false unless rhs_.is_a?(self.class) && rhs_.factory == self.factory
80
- case rhs_
81
- when Feature::Point
82
- if @y == 90
83
- rhs_.y == 90
84
- elsif @y == -90
85
- rhs_.y == -90
86
- else
87
- rhs_.x == @x && rhs_.y == @y
88
- end
89
- when Feature::LineString
90
- rhs_.num_points > 0 && rhs_.points.all?{ |elem_| equals?(elem_) }
91
- when Feature::GeometryCollection
92
- rhs_.num_geometries > 0 && rhs_.all?{ |elem_| equals?(elem_) }
93
- else
94
- false
95
- end
96
- end
97
-
98
-
99
- def buffer(distance_)
100
- radius_ = distance_ / SphericalMath::RADIUS
101
- radius_ = 1.5 if radius_ > 1.5
102
- cos_ = ::Math.cos(radius_)
103
- sin_ = ::Math.sin(radius_)
104
- point_count_ = factory.property(:buffer_resolution) * 4
105
- p0_ = _xyz
106
- p1_ = p0_.create_perpendicular
107
- p2_ = p1_ % p0_
108
- angle_ = ::Math::PI * 2.0 / point_count_
109
- points_ = (0...point_count_).map do |i_|
110
- r_ = angle_ * i_
111
- pi_ = SphericalMath::PointXYZ.weighted_combination(p1_, ::Math.cos(r_), p2_, ::Math.sin(r_))
112
- p_ = SphericalMath::PointXYZ.weighted_combination(p0_, cos_, pi_, sin_)
113
- factory.point(*p_.lonlat)
114
- end
115
- factory.polygon(factory.linear_ring(points_))
116
- end
117
-
118
-
119
- alias_method :longitude, :x
120
- alias_method :lon, :x
121
- alias_method :latitude, :y
122
- alias_method :lat, :y
49
+ include SphericalPointMethods
123
50
 
124
51
 
125
52
  Feature::MixinCollection::GLOBAL.for_type(Feature::Point).include_in_class(self, true)
@@ -50,6 +50,90 @@ module RGeo
50
50
  end
51
51
 
52
52
 
53
+ module SphericalPointMethods # :nodoc:
54
+
55
+
56
+ def _validate_geometry
57
+ if @x < -180.0 || @x >= 180.0
58
+ @x = @x % 360.0
59
+ @x -= 360.0 if @x >= 180.0
60
+ end
61
+ @y = 90.0 if @y > 90.0
62
+ @y = -90.0 if @y < -90.0
63
+ super
64
+ end
65
+
66
+
67
+ def _xyz
68
+ @xyz ||= SphericalMath::PointXYZ.from_latlon(@y, @x)
69
+ end
70
+
71
+
72
+ def distance(rhs_)
73
+ rhs_ = Feature.cast(rhs_, @factory)
74
+ case rhs_
75
+ when SphericalPointImpl
76
+ _xyz.dist_to_point(rhs_._xyz) * SphericalMath::RADIUS
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+
83
+ def equals?(rhs_)
84
+ return false unless rhs_.is_a?(self.class) && rhs_.factory == self.factory
85
+ case rhs_
86
+ when Feature::Point
87
+ if @y == 90
88
+ rhs_.y == 90
89
+ elsif @y == -90
90
+ rhs_.y == -90
91
+ else
92
+ rhs_.x == @x && rhs_.y == @y
93
+ end
94
+ when Feature::LineString
95
+ rhs_.num_points > 0 && rhs_.points.all?{ |elem_| equals?(elem_) }
96
+ when Feature::GeometryCollection
97
+ rhs_.num_geometries > 0 && rhs_.all?{ |elem_| equals?(elem_) }
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+
104
+ def buffer(distance_)
105
+ radius_ = distance_ / SphericalMath::RADIUS
106
+ radius_ = 1.5 if radius_ > 1.5
107
+ cos_ = ::Math.cos(radius_)
108
+ sin_ = ::Math.sin(radius_)
109
+ point_count_ = factory.property(:buffer_resolution) * 4
110
+ p0_ = _xyz
111
+ p1_ = p0_.create_perpendicular
112
+ p2_ = p1_ % p0_
113
+ angle_ = ::Math::PI * 2.0 / point_count_
114
+ points_ = (0...point_count_).map do |i_|
115
+ r_ = angle_ * i_
116
+ pi_ = SphericalMath::PointXYZ.weighted_combination(p1_, ::Math.cos(r_), p2_, ::Math.sin(r_))
117
+ p_ = SphericalMath::PointXYZ.weighted_combination(p0_, cos_, pi_, sin_)
118
+ factory.point(*p_.lonlat)
119
+ end
120
+ factory.polygon(factory.linear_ring(points_))
121
+ end
122
+
123
+
124
+ def self.included(klass_)
125
+ klass_.module_eval do
126
+ alias_method :longitude, :x
127
+ alias_method :lon, :x
128
+ alias_method :latitude, :y
129
+ alias_method :lat, :y
130
+ end
131
+ end
132
+
133
+
134
+ end
135
+
136
+
53
137
  module SphericalLineStringMethods # :nodoc:
54
138
 
55
139
 
@@ -59,9 +59,9 @@ module RGeo
59
59
 
60
60
  def initialize(x_, y_, z_)
61
61
  r_ = ::Math.sqrt(x_ * x_ + y_ * y_ + z_ * z_)
62
- @x = x_ / r_
63
- @y = y_ / r_
64
- @z = z_ / r_
62
+ @x = (x_ / r_).to_f
63
+ @y = (y_ / r_).to_f
64
+ @z = (z_ / r_).to_f
65
65
  raise "Not a number" if @x.nan? || @y.nan? || @z.nan?
66
66
  end
67
67
 
data/lib/rgeo/geos.rb CHANGED
@@ -65,30 +65,38 @@ end
65
65
 
66
66
  # Implementation files
67
67
  require 'rgeo/geos/utils'
68
- require 'rgeo/geos/factory'
69
68
  require 'rgeo/geos/interface'
70
69
  begin
71
70
  require 'rgeo/geos/geos_c_impl'
72
71
  rescue ::LoadError; end
73
- require 'rgeo/geos/impl_additions'
72
+ ::RGeo::Geos::CAPI_SUPPORTED = ::RGeo::Geos.const_defined?(:CAPIGeometryMethods)
73
+ if ::RGeo::Geos::CAPI_SUPPORTED
74
+ require 'rgeo/geos/capi_feature_classes'
75
+ require 'rgeo/geos/capi_factory'
76
+ end
77
+ require 'rgeo/geos/ffi_feature_methods'
78
+ require 'rgeo/geos/ffi_feature_classes'
74
79
  require 'rgeo/geos/ffi_factory'
75
- require 'rgeo/geos/ffi_classes'
80
+ require 'rgeo/geos/zm_feature_methods'
81
+ require 'rgeo/geos/zm_feature_classes'
76
82
  require 'rgeo/geos/zm_factory'
77
- require 'rgeo/geos/zm_impl'
78
83
 
79
84
  # Determine ffi support.
80
85
  begin
81
86
  require 'ffi-geos'
87
+ # An additional check to make sure FFI loaded okay. This can fail on
88
+ # some versions of ffi-geos and some versions of Rubinius.
89
+ raise 'Problem loading FFI' unless ::FFI::AutoPointer
82
90
  ::RGeo::Geos::FFI_SUPPORTED = true
83
- rescue ::LoadError
91
+ ::RGeo::Geos::FFI_SUPPORT_EXCEPTION = nil
92
+ rescue ::LoadError => ex_
84
93
  ::RGeo::Geos::FFI_SUPPORTED = false
85
- rescue
94
+ ::RGeo::Geos::FFI_SUPPORT_EXCEPTION = ex_
95
+ rescue => ex_
86
96
  ::RGeo::Geos::FFI_SUPPORTED = false
97
+ ::RGeo::Geos::FFI_SUPPORT_EXCEPTION = ex_
87
98
  end
88
99
 
89
- # Determine capi support.
90
- ::RGeo::Geos::CAPI_SUPPORTED = ::RGeo::Geos::Factory.respond_to?(:_create) ? true : false
91
-
92
100
  # Determine preferred native interface
93
101
  if ::RGeo::Geos::CAPI_SUPPORTED
94
102
  ::RGeo::Geos.preferred_native_interface = :capi
@@ -41,7 +41,7 @@ module RGeo
41
41
 
42
42
  # This the GEOS CAPI implementation of ::RGeo::Feature::Factory.
43
43
 
44
- class Factory
44
+ class CAPIFactory
45
45
 
46
46
 
47
47
  include Feature::Factory::Instance
@@ -158,13 +158,18 @@ module RGeo
158
158
  # Factory equivalence test.
159
159
 
160
160
  def eql?(rhs_)
161
- rhs_.is_a?(Factory) && rhs_.srid == _srid &&
161
+ rhs_.is_a?(CAPIFactory) && rhs_.srid == _srid &&
162
162
  rhs_._buffer_resolution == _buffer_resolution && rhs_._flags == _flags &&
163
163
  rhs_.proj4 == _proj4
164
164
  end
165
165
  alias_method :==, :eql?
166
166
 
167
167
 
168
+ def hash
169
+ @hash ||= [_srid, _buffer_resolution, _flags, _proj4].hash
170
+ end
171
+
172
+
168
173
  # Marshal support
169
174
 
170
175
  def marshal_dump # :nodoc:
@@ -201,7 +206,7 @@ module RGeo
201
206
  else
202
207
  coord_sys_ = nil
203
208
  end
204
- initialize_copy(Factory.create(
209
+ initialize_copy(CAPIFactory.create(
205
210
  :has_z_coordinate => data_['hasz'],
206
211
  :has_m_coordinate => data_['hasm'],
207
212
  :srid => data_['srid'],
@@ -255,7 +260,7 @@ module RGeo
255
260
  else
256
261
  coord_sys_ = nil
257
262
  end
258
- initialize_copy(Factory.create(
263
+ initialize_copy(CAPIFactory.create(
259
264
  :has_z_coordinate => coder_['has_z_coordinate'],
260
265
  :has_m_coordinate => coder_['has_m_coordinate'],
261
266
  :srid => coder_['srid'],
@@ -344,7 +349,7 @@ module RGeo
344
349
  if extra_.length > (_flags & 6 == 0 ? 0 : 1)
345
350
  nil
346
351
  else
347
- PointImpl.create(self, x_, y_, extra_[0].to_f) rescue nil
352
+ CAPIPointImpl.create(self, x_, y_, extra_[0].to_f) rescue nil
348
353
  end
349
354
  end
350
355
 
@@ -353,14 +358,14 @@ module RGeo
353
358
 
354
359
  def line_string(points_)
355
360
  points_ = points_.to_a unless points_.kind_of?(::Array)
356
- LineStringImpl.create(self, points_) rescue nil
361
+ CAPILineStringImpl.create(self, points_) rescue nil
357
362
  end
358
363
 
359
364
 
360
365
  # See ::RGeo::Feature::Factory#line
361
366
 
362
367
  def line(start_, end_)
363
- LineImpl.create(self, start_, end_) rescue nil
368
+ CAPILineImpl.create(self, start_, end_) rescue nil
364
369
  end
365
370
 
366
371
 
@@ -368,7 +373,7 @@ module RGeo
368
373
 
369
374
  def linear_ring(points_)
370
375
  points_ = points_.to_a unless points_.kind_of?(::Array)
371
- LinearRingImpl.create(self, points_) rescue nil
376
+ CAPILinearRingImpl.create(self, points_) rescue nil
372
377
  end
373
378
 
374
379
 
@@ -376,7 +381,7 @@ module RGeo
376
381
 
377
382
  def polygon(outer_ring_, inner_rings_=nil)
378
383
  inner_rings_ = inner_rings_.to_a unless inner_rings_.kind_of?(::Array)
379
- PolygonImpl.create(self, outer_ring_, inner_rings_) rescue nil
384
+ CAPIPolygonImpl.create(self, outer_ring_, inner_rings_) rescue nil
380
385
  end
381
386
 
382
387
 
@@ -384,7 +389,7 @@ module RGeo
384
389
 
385
390
  def collection(elems_)
386
391
  elems_ = elems_.to_a unless elems_.kind_of?(::Array)
387
- GeometryCollectionImpl.create(self, elems_) rescue nil
392
+ CAPIGeometryCollectionImpl.create(self, elems_) rescue nil
388
393
  end
389
394
 
390
395
 
@@ -392,7 +397,7 @@ module RGeo
392
397
 
393
398
  def multi_point(elems_)
394
399
  elems_ = elems_.to_a unless elems_.kind_of?(::Array)
395
- MultiPointImpl.create(self, elems_) rescue nil
400
+ CAPIMultiPointImpl.create(self, elems_) rescue nil
396
401
  end
397
402
 
398
403
 
@@ -400,7 +405,7 @@ module RGeo
400
405
 
401
406
  def multi_line_string(elems_)
402
407
  elems_ = elems_.to_a unless elems_.kind_of?(::Array)
403
- MultiLineStringImpl.create(self, elems_) rescue nil
408
+ CAPIMultiLineStringImpl.create(self, elems_) rescue nil
404
409
  end
405
410
 
406
411
 
@@ -408,7 +413,7 @@ module RGeo
408
413
 
409
414
  def multi_polygon(elems_)
410
415
  elems_ = elems_.to_a unless elems_.kind_of?(::Array)
411
- MultiPolygonImpl.create(self, elems_) rescue nil
416
+ CAPIMultiPolygonImpl.create(self, elems_) rescue nil
412
417
  end
413
418
 
414
419
 
@@ -436,7 +441,7 @@ module RGeo
436
441
  type_ = original_.geometry_type
437
442
  ntype_ = type_ if keep_subtype_ && type_.include?(ntype_)
438
443
  case original_
439
- when GeometryImpl
444
+ when CAPIGeometryMethods
440
445
  # Optimization if we're just changing factories, but the
441
446
  # factories are zm-compatible and proj4-compatible.
442
447
  if original_.factory != self && ntype_ == type_ &&
@@ -455,7 +460,7 @@ module RGeo
455
460
  then
456
461
  return IMPL_CLASSES[ntype_]._copy_from(self, original_)
457
462
  end
458
- when ZMGeometryImpl
463
+ when ZMGeometryMethods
459
464
  # Optimization for just removing a coordinate from an otherwise
460
465
  # compatible factory
461
466
  if _flags & 0x6 == 0x2 && self == original_.factory.z_factory
@@ -468,6 +473,22 @@ module RGeo
468
473
  end
469
474
 
470
475
 
476
+ # :stopdoc:
477
+
478
+ IMPL_CLASSES = {
479
+ Feature::Point => CAPIPointImpl,
480
+ Feature::LineString => CAPILineStringImpl,
481
+ Feature::LinearRing => CAPILinearRingImpl,
482
+ Feature::Line => CAPILineImpl,
483
+ Feature::GeometryCollection => CAPIGeometryCollectionImpl,
484
+ Feature::MultiPoint => CAPIMultiPointImpl,
485
+ Feature::MultiLineString => CAPIMultiLineStringImpl,
486
+ Feature::MultiPolygon => CAPIMultiPolygonImpl,
487
+ }.freeze
488
+
489
+ # :startdoc:
490
+
491
+
471
492
  end
472
493
 
473
494