rgeo 0.1.16 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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