rgeo 0.1.16 → 0.1.17

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.
@@ -56,14 +56,17 @@ module RGeo
56
56
  # constructor, or set on the object afterwards.
57
57
  #
58
58
  # <tt>:default_factory</tt>::
59
- # The default factory for generated geometries, used when no SRID
60
- # is explicitly specified in the input. If none is provided, the
61
- # default cartesian factory will be used.
62
- # <tt>:factory_from_srid</tt>::
63
- # A Proc that takes an SRID as the sole argument, and returns a
64
- # factory for generated geometries when that SRID is specified in
65
- # the input. If no such Proc is provided, the default_factory is
66
- # used, regardless of the input SRID.
59
+ # The default factory for parsed geometries, used when no factory
60
+ # generator is provided. If no default is provided either, the
61
+ # default cartesian factory will be used as the default.
62
+ # <tt>:factory_generator</tt>::
63
+ # A factory generator that should return a factory based on the
64
+ # srid and dimension settings in the input. The factory generator
65
+ # should understand the configuration options <tt>:srid</tt>,
66
+ # <tt>:support_z_coordinate</tt>, and <tt>:support_m_coordinate</tt>.
67
+ # See RGeo::Features::FactoryGenerator for more information.
68
+ # If no generator is provided, the <tt>:default_factory</tt> is
69
+ # used.
67
70
  # <tt>:support_ewkt</tt>::
68
71
  # Activate support for PostGIS EWKT type tags, which appends an "M"
69
72
  # to tags to indicate the presence of M but not Z, and also
@@ -89,7 +92,7 @@ module RGeo
89
92
 
90
93
  def initialize(opts_={})
91
94
  @default_factory = opts_[:default_factory] || Cartesian.preferred_factory
92
- @factory_from_srid = opts_[:factory_from_srid]
95
+ @factory_generator = opts_[:factory_generator]
93
96
  @support_ewkt = opts_[:support_ewkt] ? true : false
94
97
  @support_wkt12 = opts_[:support_wkt12] ? true : false
95
98
  @strict_wkt11 = @support_ewkt || @support_wkt12 ? false : opts_[:strict_wkt11] ? true : false
@@ -107,21 +110,21 @@ module RGeo
107
110
  @default_factory = value_ || Cartesian.preferred_factory
108
111
  end
109
112
 
110
- # Returns true if this parser has a factory_from_srid procedure.
113
+ # Returns the factory generator, or nil if there is none.
111
114
  # See WKTParser for details.
112
- def has_factory_from_srid?
113
- @factory_from_srid ? true : false
115
+ def factory_generator
116
+ @factory_generator
114
117
  end
115
118
 
116
- # Sets the factory_from_srid. See WKTParser for details.
117
- def factory_from_srid=(value_)
118
- @factory_from_srid = value_
119
+ # Sets the factory_generator. See WKTParser for details.
120
+ def factory_generator=(value_)
121
+ @factory_generator = value_
119
122
  end
120
123
 
121
- # Sets the factory_from_srid to the given block.
124
+ # Sets the factory_generator to the given block.
122
125
  # See WKTParser for details.
123
- def set_factory_from_srid(&block_)
124
- @factory_from_srid = block_
126
+ def to_generate_factory(&block_)
127
+ @factory_generator = block_
125
128
  end
126
129
 
127
130
  # Returns true if this parser supports EWKT.
@@ -172,21 +175,22 @@ module RGeo
172
175
  # Parse the given string, and return a geometry object.
173
176
 
174
177
  def parse(str_)
175
- @cur_factory = @default_factory
176
178
  str_ = str_.downcase
177
- if @support_ewkt && str_ =~ /^srid=(\d+);/i
178
- str_ = $'
179
- if @factory_from_srid
180
- @cur_factory = @factory_from_srid.call($1.to_i)
181
- end
179
+ @cur_factory = @factory_generator ? nil : @default_factory
180
+ if @cur_factory
181
+ @cur_factory_support_z = @cur_factory.has_capability?(:z_coordinate) ? true : false
182
+ @cur_factory_support_m = @cur_factory.has_capability?(:m_coordinate) ? true : false
182
183
  end
183
- @cur_factory_support_z = @cur_factory.has_capability?(:z_coordinate)
184
- @cur_factory_support_m = @cur_factory.has_capability?(:m_coordinate)
185
184
  @cur_expect_z = nil
186
185
  @cur_expect_m = nil
186
+ @cur_srid = nil
187
+ if @support_ewkt && str_ =~ /^srid=(\d+);/i
188
+ str_ = $'
189
+ @cur_srid = $1.to_i
190
+ end
187
191
  begin
188
192
  _start_scanner(str_)
189
- obj_ = _parse_type_tag
193
+ obj_ = _parse_type_tag(false)
190
194
  if @cur_token && !@ignore_extra_tokens
191
195
  raise Errors::ParseError, "Extra tokens beginning with #{@cur_token.inspect}."
192
196
  end
@@ -197,7 +201,31 @@ module RGeo
197
201
  end
198
202
 
199
203
 
200
- def _parse_type_tag # :nodoc:
204
+ def _check_factory_support # :nodoc:
205
+ if @cur_expect_z && !@cur_factory_support_z
206
+ raise Errors::ParseError, "Geometry calls for Z coordinate but factory doesn't support it."
207
+ end
208
+ if @cur_expect_m && !@cur_factory_support_m
209
+ raise Errors::ParseError, "Geometry calls for M coordinate but factory doesn't support it."
210
+ end
211
+ end
212
+
213
+
214
+ def _ensure_factory # :nodoc:
215
+ unless @cur_factory
216
+ if @factory_generator
217
+ @cur_factory = @factory_generator.call(:srid => @cur_srid, :support_z_coordinate => @cur_expect_z, :support_m_coordinate => @cur_expect_m)
218
+ end
219
+ @cur_factory ||= @default_factory
220
+ @cur_factory_support_z = @cur_factory.has_capability?(:z_coordinate) ? true : false
221
+ @cur_factory_support_m = @cur_factory.has_capability?(:m_coordinate) ? true : false
222
+ _check_factory_support unless @cur_expect_z.nil?
223
+ end
224
+ @cur_factory
225
+ end
226
+
227
+
228
+ def _parse_type_tag(contained_) # :nodoc:
201
229
  _expect_token_type(::String)
202
230
  if @support_ewkt && @cur_token =~ /^(.+)(m)$/
203
231
  type_ = $1
@@ -212,22 +240,26 @@ module RGeo
212
240
  _next_token
213
241
  end
214
242
  if zm_.length > 0 || @strict_wkt11
215
- expect_z_ = zm_[0,1] == 'z'
216
- if !@cur_expect_z.nil? && expect_z_ != @cur_expect_z
243
+ creating_expectation_ = @cur_expect_z.nil?
244
+ expect_z_ = zm_[0,1] == 'z' ? true : false
245
+ if @cur_expect_z.nil?
246
+ @cur_expect_z = expect_z_
247
+ elsif expect_z_ != @cur_expect_z
217
248
  raise Errors::ParseError, "Surrounding collection has Z but contained geometry doesn't."
218
249
  end
219
- @cur_expect_z = expect_z_
220
- expect_m_ = zm_[-1,1] == 'm'
221
- if !@cur_expect_m.nil? && expect_m_ != @cur_expect_m
250
+ expect_m_ = zm_[-1,1] == 'm' ? true : false
251
+ if @cur_expect_m.nil?
252
+ @cur_expect_m = expect_m_
253
+ else expect_m_ != @cur_expect_m
222
254
  raise Errors::ParseError, "Surrounding collection has M but contained geometry doesn't."
223
255
  end
224
- @cur_expect_m = expect_m_
225
- end
226
- if @cur_expect_z && !@cur_factory_support_z
227
- raise Errors::ParseError, "Type tag declares #{zm_.inspect} but factory doesn't support Z."
228
- end
229
- if @cur_expect_m && !@cur_factory_support_m
230
- raise Errors::ParseError, "Type tag declares #{zm_.inspect} but factory doesn't support M."
256
+ if creating_expectation_
257
+ if @cur_factory
258
+ _check_factory_support
259
+ else
260
+ _ensure_factory
261
+ end
262
+ end
231
263
  end
232
264
  case type_
233
265
  when 'point'
@@ -264,31 +296,32 @@ module RGeo
264
296
  _next_token
265
297
  end
266
298
  num_extras_ = extra_.size
267
- @cur_expect_z = num_extras_ > 0 && @cur_factory_support_z ? true : false
299
+ @cur_expect_z = num_extras_ > 0 && (!@cur_factory || @cur_factory_support_z) ? true : false
268
300
  num_extras_ -= 1 if @cur_expect_z
269
- @cur_expect_m = num_extras_ > 0 && @cur_factory_support_m ? true : false
301
+ @cur_expect_m = num_extras_ > 0 && (!@cur_factory || @cur_factory_support_m) ? true : false
270
302
  num_extras_ -= 1 if @cur_expect_m
271
303
  if num_extras_ > 0
272
304
  raise Errors::ParseError, "Found #{extra_.size+2} coordinates, which is too many for this factory."
273
305
  end
306
+ _ensure_factory
274
307
  else
308
+ val_ = 0
309
+ if @cur_expect_z
310
+ _expect_token_type(::Numeric)
311
+ val_ = @cur_token
312
+ _next_token
313
+ end
275
314
  if @cur_factory_support_z
276
- if @cur_expect_z
277
- _expect_token_type(::Numeric)
278
- extra_ << @cur_token
279
- _next_token
280
- else
281
- extra_ << 0
282
- end
315
+ extra_ << val_
316
+ end
317
+ val_ = 0
318
+ if @cur_expect_m
319
+ _expect_token_type(::Numeric)
320
+ val_ = @cur_token
321
+ _next_token
283
322
  end
284
323
  if @cur_factory_support_m
285
- if @cur_expect_m
286
- _expect_token_type(::Numeric)
287
- extra_ << @cur_token
288
- _next_token
289
- else
290
- extra_ << 0
291
- end
324
+ extra_ << val_
292
325
  end
293
326
  end
294
327
  @cur_factory.point(x_, y_, *extra_)
@@ -297,7 +330,7 @@ module RGeo
297
330
 
298
331
  def _parse_point(convert_empty_=false) # :nodoc:
299
332
  if convert_empty_ && @cur_token == 'empty'
300
- point_ = @cur_factory.multi_point([])
333
+ point_ = _ensure_factory.multi_point([])
301
334
  else
302
335
  _expect_token_type(:begin)
303
336
  _next_token
@@ -322,7 +355,7 @@ module RGeo
322
355
  end
323
356
  end
324
357
  _next_token
325
- @cur_factory.line_string(points_)
358
+ _ensure_factory.line_string(points_)
326
359
  end
327
360
 
328
361
 
@@ -342,7 +375,7 @@ module RGeo
342
375
  end
343
376
  end
344
377
  _next_token
345
- @cur_factory.polygon(outer_ring_, inner_rings_)
378
+ _ensure_factory.polygon(outer_ring_, inner_rings_)
346
379
  end
347
380
 
348
381
 
@@ -352,14 +385,14 @@ module RGeo
352
385
  _expect_token_type(:begin)
353
386
  _next_token
354
387
  loop do
355
- geometries_ << _parse_type_tag
388
+ geometries_ << _parse_type_tag(true)
356
389
  break if @cur_token == :end
357
390
  _expect_token_type(:comma)
358
391
  _next_token
359
392
  end
360
393
  end
361
394
  _next_token
362
- @cur_factory.collection(geometries_)
395
+ _ensure_factory.collection(geometries_)
363
396
  end
364
397
 
365
398
 
@@ -376,7 +409,7 @@ module RGeo
376
409
  end
377
410
  end
378
411
  _next_token
379
- @cur_factory.multi_point(points_)
412
+ _ensure_factory.multi_point(points_)
380
413
  end
381
414
 
382
415
 
@@ -393,7 +426,7 @@ module RGeo
393
426
  end
394
427
  end
395
428
  _next_token
396
- @cur_factory.multi_line_string(line_strings_)
429
+ _ensure_factory.multi_line_string(line_strings_)
397
430
  end
398
431
 
399
432
 
@@ -410,7 +443,7 @@ module RGeo
410
443
  end
411
444
  end
412
445
  _next_token
413
- @cur_factory.multi_polygon(polygons_)
446
+ _ensure_factory.multi_polygon(polygons_)
414
447
  end
415
448
 
416
449
 
@@ -0,0 +1,33 @@
1
+ The ActiveRecord adapter unit tests require that you connect to actual
2
+ databases. In order for these tests to run, you must therefore provide
3
+ the database connection parameters, in the form of a "database.yml" file
4
+ located in this directory. If this file is not present, the ActiveRecord
5
+ adapter tests will be skipped.
6
+
7
+ The format of this file is the same as the Rails "config/database.yml"
8
+ file, except that the main keys, instead of environment names, should be
9
+ adapter names.
10
+
11
+ For example:
12
+
13
+ ####
14
+ mysqlspatial:
15
+ adapter: mysqlspatial
16
+ encoding: utf8
17
+ reconnect: false
18
+ database: <mysql_test_database>
19
+ username: <mysql_user>
20
+ password: <mysql_password>
21
+ host: localhost
22
+ mysql2spatial:
23
+ adapter: mysql2spatial
24
+ encoding: utf8
25
+ reconnect: false
26
+ database: <mysql_test_database>
27
+ username: <mysql_user>
28
+ password: <mysql_password>
29
+ host: localhost
30
+ ####
31
+
32
+ Note that the tests assume they "own" these databases, and they may
33
+ modify and/or delete any and all tables at any time.
@@ -0,0 +1,219 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Tests for the MysqlSpatial ActiveRecord adapter
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ if ::File.exists?(::File.dirname(__FILE__)+'/database.yml')
38
+
39
+ require 'test/unit'
40
+ require 'rgeo'
41
+ require 'yaml'
42
+ require 'active_record'
43
+
44
+
45
+ module RGeo
46
+ module Tests # :nodoc:
47
+ module ActiveRecord # :nodoc:
48
+
49
+ ALL_DATABASES_CONFIG = ::YAML.load_file(::File.dirname(__FILE__)+'/database.yml')
50
+
51
+
52
+ module Classes # :nodoc:
53
+
54
+ @class_num = 0
55
+
56
+ def self.new_class(config_)
57
+ klass_ = ::Class.new(::ActiveRecord::Base)
58
+ @class_num += 1
59
+ self.const_set("Klass#{@class_num}".to_sym, klass_)
60
+ klass_.class_eval do
61
+ establish_connection(config_)
62
+ set_table_name(:spatial_test)
63
+ end
64
+ klass_
65
+ end
66
+
67
+ end
68
+
69
+
70
+ module CommonTestMethods # :nodoc:
71
+
72
+ def self.included(klass_)
73
+ database_config_ = ALL_DATABASES_CONFIG[klass_.const_get(:ADAPTER_NAME)]
74
+ klass_.const_set(:DATABASE_CONFIG, database_config_)
75
+ klass_.const_set(:DEFAULT_AR_CLASS, Classes.new_class(database_config_))
76
+ end
77
+
78
+
79
+ def setup
80
+ @factory = ::RGeo::Cartesian.preferred_factory(:srid => 4326)
81
+ cleanup_tables
82
+ end
83
+
84
+
85
+ def teardown
86
+ cleanup_tables
87
+ end
88
+
89
+
90
+ def cleanup_tables
91
+ klass_ = self.class.const_get(:DEFAULT_AR_CLASS)
92
+ tables_ = klass_.connection.select_values('SHOW TABLES')
93
+ tables_.each{ |table_| klass_.connection.drop_table(table_) }
94
+ end
95
+
96
+
97
+ def create_ar_class(opts_={})
98
+ klass_ = Classes.new_class(self.class.const_get(:DATABASE_CONFIG))
99
+ case opts_[:content]
100
+ when :latlon_point
101
+ klass_.connection.create_table(:spatial_test) do |t_|
102
+ t_.column 'latlon', :point
103
+ end
104
+ end
105
+ @ar_class = klass_
106
+ end
107
+
108
+
109
+ def test_create_simple_geometry
110
+ klass_ = create_ar_class
111
+ klass_.connection.create_table(:spatial_test) do |t_|
112
+ t_.column 'latlon', :geometry
113
+ end
114
+ assert_equal(::RGeo::Features::Geometry, klass_.columns.last.geometric_type)
115
+ assert(klass_.cached_attributes.include?('latlon'))
116
+ end
117
+
118
+
119
+ def test_create_point_geometry
120
+ klass_ = create_ar_class
121
+ klass_.connection.create_table(:spatial_test) do |t_|
122
+ t_.column 'latlon', :point
123
+ end
124
+ assert_equal(::RGeo::Features::Point, klass_.columns.last.geometric_type)
125
+ assert(klass_.cached_attributes.include?('latlon'))
126
+ end
127
+
128
+
129
+ def test_create_geometry_with_index
130
+ klass_ = create_ar_class
131
+ klass_.connection.create_table(:spatial_test, :options => 'ENGINE=MyISAM') do |t_|
132
+ t_.column 'latlon', :geometry, :null => false
133
+ end
134
+ klass_.connection.change_table(:spatial_test) do |t_|
135
+ t_.index([:latlon], :spatial => true)
136
+ end
137
+ assert(klass_.connection.indexes(:spatial_test).last.spatial)
138
+ end
139
+
140
+
141
+ def test_set_and_get_point
142
+ klass_ = create_ar_class(:content => :latlon_point)
143
+ obj_ = klass_.new
144
+ assert_nil(obj_.latlon)
145
+ obj_.latlon = @factory.point(1, 2)
146
+ assert_equal(@factory.point(1, 2), obj_.latlon)
147
+ assert_equal(4326, obj_.latlon.srid)
148
+ end
149
+
150
+
151
+ def test_set_and_get_point_from_wkt
152
+ klass_ = create_ar_class(:content => :latlon_point)
153
+ obj_ = klass_.new
154
+ assert_nil(obj_.latlon)
155
+ obj_.latlon = 'SRID=1000;POINT(1 2)'
156
+ assert_equal(@factory.point(1, 2), obj_.latlon)
157
+ assert_equal(1000, obj_.latlon.srid)
158
+ end
159
+
160
+
161
+ def test_save_and_load_point
162
+ klass_ = create_ar_class(:content => :latlon_point)
163
+ obj_ = klass_.new
164
+ obj_.latlon = @factory.point(1, 2)
165
+ obj_.save!
166
+ id_ = obj_.id
167
+ obj2_ = klass_.find(id_)
168
+ assert_equal(@factory.point(1, 2), obj2_.latlon)
169
+ assert_equal(4326, obj2_.latlon.srid)
170
+ end
171
+
172
+
173
+ def test_save_and_load_point_from_wkt
174
+ klass_ = create_ar_class(:content => :latlon_point)
175
+ obj_ = klass_.new
176
+ obj_.latlon = 'SRID=1000;POINT(1 2)'
177
+ obj_.save!
178
+ id_ = obj_.id
179
+ obj2_ = klass_.find(id_)
180
+ assert_equal(@factory.point(1, 2), obj2_.latlon)
181
+ assert_equal(1000, obj2_.latlon.srid)
182
+ end
183
+
184
+
185
+ end
186
+
187
+
188
+ if ALL_DATABASES_CONFIG.include?('mysqlspatial')
189
+ class TestMysqlSpatial < ::Test::Unit::TestCase # :nodoc:
190
+ ADAPTER_NAME = 'mysqlspatial'
191
+ include CommonTestMethods
192
+ end
193
+ else
194
+ puts "WARNING: Couldn't find mysqlspatial in database.yml. Skipping those tests."
195
+ puts " See tests/active_record/readme.txt for more info."
196
+ end
197
+
198
+
199
+ if ALL_DATABASES_CONFIG.include?('mysql2spatial')
200
+ class TestMysql2Spatial < ::Test::Unit::TestCase # :nodoc:
201
+ ADAPTER_NAME = 'mysql2spatial'
202
+ include CommonTestMethods
203
+ end
204
+ else
205
+ puts "WARNING: Couldn't find mysql2spatial in database.yml. Skipping those tests."
206
+ puts " See tests/active_record/readme.txt for more info."
207
+ end
208
+
209
+
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+
216
+ else
217
+ puts "WARNING: database.yml not found. Skipping ActiveRecord tests."
218
+ puts " See tests/active_record/readme.txt for more info."
219
+ end