rs_spatial_adapter 1.2.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,14 @@
1
+ include SpatialAdapter
2
+
3
+ ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
4
+ SpatialAdapter.geometry_data_types.keys.each do |column_type|
5
+ class_eval <<-EOV
6
+ def #{column_type}(*args)
7
+ options = args.extract_options!
8
+ column_names = args
9
+
10
+ column_names.each { |name| column(name, '#{column_type}', options) }
11
+ end
12
+ EOV
13
+ end
14
+ end
@@ -0,0 +1,98 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/mysql_adapter'
3
+
4
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
5
+ include SpatialAdapter
6
+
7
+ def supports_geographic?
8
+ false
9
+ end
10
+
11
+ alias :original_native_database_types :native_database_types
12
+ def native_database_types
13
+ original_native_database_types.merge!(geometry_data_types)
14
+ end
15
+
16
+ alias :original_quote :quote
17
+ #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
18
+ def quote(value, column = nil)
19
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
20
+ "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
21
+ else
22
+ original_quote(value,column)
23
+ end
24
+ end
25
+
26
+ #Redefinition of columns to add the information that a column is geometric
27
+ def columns(table_name, name = nil)#:nodoc:
28
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
29
+ columns = []
30
+ result = execute(sql, name)
31
+ result.each do |field|
32
+ klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysqlColumn : ActiveRecord::ConnectionAdapters::MysqlColumn
33
+ columns << klass.new(field[0], field[4], field[1], field[2] == "YES")
34
+ end
35
+ result.free
36
+ columns
37
+ end
38
+
39
+
40
+ #operations relative to migrations
41
+
42
+ #Redefines add_index to support the case where the index is spatial
43
+ #If the :spatial key in the options table is true, then the sql string for a spatial index is created
44
+ def add_index(table_name,column_name,options = {})
45
+ index_name = options[:name] || index_name(table_name,:column => Array(column_name))
46
+
47
+ if options[:spatial]
48
+ execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ #Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb)
55
+ def indexes(table_name, name = nil)#:nodoc:
56
+ indexes = []
57
+ current_index = nil
58
+ execute("SHOW KEYS FROM #{table_name}", name).each do |row|
59
+ if current_index != row[2]
60
+ next if row[2] == "PRIMARY" # skip the primary key
61
+ current_index = row[2]
62
+ indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
63
+ end
64
+ indexes.last.columns << row[4]
65
+ end
66
+ indexes
67
+ end
68
+
69
+ #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
70
+ def options_for(table)
71
+ result = execute("show table status like '#{table}'")
72
+ engine = result.fetch_row[1]
73
+ if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
74
+ "ENGINE=#{engine}"
75
+ else
76
+ nil
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ module ActiveRecord
83
+ module ConnectionAdapters
84
+ class SpatialMysqlColumn < MysqlColumn
85
+ include SpatialAdapter::SpatialColumn
86
+
87
+ #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
88
+ def self.string_to_geometry(string)
89
+ return string unless string.is_a?(String)
90
+ begin
91
+ GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
92
+ rescue Exception => exception
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,97 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/mysql2_adapter'
3
+
4
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
5
+ include SpatialAdapter
6
+
7
+ def supports_geographic?
8
+ false
9
+ end
10
+
11
+ alias :original_native_database_types :native_database_types
12
+ def native_database_types
13
+ original_native_database_types.merge!(geometry_data_types)
14
+ end
15
+
16
+ alias :original_quote :quote
17
+ #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
18
+ def quote(value, column = nil)
19
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
20
+ "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
21
+ else
22
+ original_quote(value,column)
23
+ end
24
+ end
25
+
26
+ #Redefinition of columns to add the information that a column is geometric
27
+ def columns(table_name, name = nil)#:nodoc:
28
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
29
+ columns = []
30
+ result = execute(sql, name)
31
+ result.each do |field|
32
+ klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysql2Column : ActiveRecord::ConnectionAdapters::Mysql2Column
33
+ columns << klass.new(field[0], field[4], field[1], field[2] == "YES")
34
+ end
35
+ columns
36
+ end
37
+
38
+
39
+ #operations relative to migrations
40
+
41
+ #Redefines add_index to support the case where the index is spatial
42
+ #If the :spatial key in the options table is true, then the sql string for a spatial index is created
43
+ def add_index(table_name,column_name,options = {})
44
+ index_name = options[:name] || index_name(table_name,:column => Array(column_name))
45
+
46
+ if options[:spatial]
47
+ execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ #Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb)
54
+ def indexes(table_name, name = nil)#:nodoc:
55
+ indexes = []
56
+ current_index = nil
57
+ (execute("SHOW KEYS FROM #{table_name}", name) || []).each do |row|
58
+ if current_index != row[2]
59
+ next if row[2] == "PRIMARY" # skip the primary key
60
+ current_index = row[2]
61
+ indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
62
+ end
63
+ indexes.last.columns << row[4]
64
+ end
65
+ indexes
66
+ end
67
+
68
+ #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
69
+ def options_for(table)
70
+ result = execute("show table status like '#{table}'")
71
+ engine = result.first[1]
72
+ if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
73
+ "ENGINE=#{engine}"
74
+ else
75
+ nil
76
+ end
77
+ end
78
+ end
79
+
80
+
81
+ module ActiveRecord
82
+ module ConnectionAdapters
83
+ class SpatialMysql2Column < Mysql2Column
84
+ include SpatialAdapter::SpatialColumn
85
+
86
+ #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
87
+ def self.string_to_geometry(string)
88
+ return string unless string.is_a?(String)
89
+ begin
90
+ GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
91
+ rescue Exception => exception
92
+ nil
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,388 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
5
+ include SpatialAdapter
6
+
7
+ def postgis_version
8
+ begin
9
+ select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0]
10
+ rescue ActiveRecord::StatementInvalid
11
+ nil
12
+ end
13
+ end
14
+
15
+ def postgis_major_version
16
+ version = postgis_version
17
+ version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil
18
+ end
19
+
20
+ def postgis_minor_version
21
+ version = postgis_version
22
+ version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil
23
+ end
24
+
25
+ def spatial?
26
+ !postgis_version.nil?
27
+ end
28
+
29
+ def supports_geographic?
30
+ postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5)
31
+ end
32
+
33
+ alias :original_native_database_types :native_database_types
34
+ def native_database_types
35
+ original_native_database_types.merge!(geometry_data_types)
36
+ end
37
+
38
+ alias :original_quote :quote
39
+ #Redefines the quote method to add behaviour for when a Geometry is encountered
40
+ def quote(value, column = nil)
41
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
42
+ "'#{value.as_hex_ewkb}'"
43
+ else
44
+ original_quote(value,column)
45
+ end
46
+ end
47
+
48
+ def columns(table_name, name = nil) #:nodoc:
49
+ raw_geom_infos = column_spatial_info(table_name)
50
+
51
+ column_definitions(table_name).collect do |name, type, default, notnull|
52
+ case type
53
+ when /geography/i
54
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_from_geography(name, default, type, notnull == 'f')
55
+ when /geometry/i
56
+ raw_geom_info = raw_geom_infos[name]
57
+ if raw_geom_info.nil?
58
+ # This column isn't in the geometry_columns table, so we don't know anything else about it
59
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f")
60
+ else
61
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.new(name, default, raw_geom_info.type, notnull == "f", raw_geom_info.srid, raw_geom_info.with_z, raw_geom_info.with_m)
62
+ end
63
+ else
64
+ ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f")
65
+ end
66
+ end
67
+ end
68
+
69
+ def create_table(table_name, options = {})
70
+ # Using the subclassed table definition
71
+ table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
72
+ table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
73
+
74
+ yield table_definition if block_given?
75
+
76
+ if options[:force] && table_exists?(table_name)
77
+ drop_table(table_name, options)
78
+ end
79
+
80
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
81
+ create_sql << "#{quote_table_name(table_name)} ("
82
+ create_sql << table_definition.to_sql
83
+ create_sql << ") #{options[:options]}"
84
+
85
+ # This is the additional portion for PostGIS
86
+ unless table_definition.geom_columns.nil?
87
+ table_definition.geom_columns.each do |geom_column|
88
+ geom_column.table_name = table_name
89
+ create_sql << "; " + geom_column.to_sql
90
+ end
91
+ end
92
+
93
+ execute create_sql
94
+ end
95
+
96
+ alias :original_remove_column :remove_column
97
+ def remove_column(table_name, *column_names)
98
+ column_names = column_names.flatten
99
+ columns(table_name).each do |col|
100
+ if column_names.include?(col.name.to_sym)
101
+ # Geometry columns have to be removed using DropGeometryColumn
102
+ if col.is_a?(SpatialColumn) && col.spatial? && !col.geographic?
103
+ execute "SELECT DropGeometryColumn('#{table_name}','#{col.name}')"
104
+ else
105
+ original_remove_column(table_name, col.name)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ alias :original_add_column :add_column
112
+ def add_column(table_name, column_name, type, options = {})
113
+ unless geometry_data_types[type].nil?
114
+ geom_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumnDefinition.new(self, column_name, type, nil, nil, options[:null], options[:srid] || -1 , options[:with_z] || false , options[:with_m] || false, options[:geographic] || false)
115
+ if geom_column.geographic
116
+ default = options[:default]
117
+ notnull = options[:null] == false
118
+
119
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{geom_column.to_sql}")
120
+
121
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
122
+ change_column_null(table_name, column_name, false, default) if notnull
123
+ else
124
+ geom_column.table_name = table_name
125
+ execute geom_column.to_sql
126
+ end
127
+ else
128
+ original_add_column(table_name, column_name, type, options)
129
+ end
130
+ end
131
+
132
+ # Adds an index to a column.
133
+ def add_index(table_name, column_name, options = {})
134
+ column_names = Array(column_name)
135
+ index_name = index_name(table_name, :column => column_names)
136
+
137
+ if Hash === options # legacy support, since this param was a string
138
+ index_type = options[:unique] ? "UNIQUE" : ""
139
+ index_name = options[:name] || index_name
140
+ index_method = options[:spatial] ? 'USING GIST' : ""
141
+ else
142
+ index_type = options
143
+ end
144
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
145
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})"
146
+ end
147
+
148
+ # Returns the list of all indexes for a table.
149
+ #
150
+ # This is a full replacement for the ActiveRecord method and as a result
151
+ # has a higher probability of breaking in future releases.
152
+ def indexes(table_name, name = nil)
153
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
154
+
155
+ # Changed from upstread: link to pg_am to grab the index type (e.g. "gist")
156
+ result = query(<<-SQL, name)
157
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname
158
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
159
+ WHERE i.relkind = 'i'
160
+ AND d.indexrelid = i.oid
161
+ AND d.indisprimary = 'f'
162
+ AND t.oid = d.indrelid
163
+ AND t.relname = '#{table_name}'
164
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
165
+ AND i.relam = am.oid
166
+ AND a.attrelid = t.oid
167
+ ORDER BY i.relname
168
+ SQL
169
+
170
+
171
+ indexes = []
172
+
173
+ indexes = result.map do |row|
174
+ index_name = row[0]
175
+ unique = row[1] == 't'
176
+ indkey = row[2].split(" ")
177
+ oid = row[3]
178
+ indtype = row[4]
179
+
180
+ # Changed from upstream: need to get the column types to test for spatial indexes
181
+ columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = [r[0], r[2]]; attlist}
182
+ SELECT a.attname, a.attnum, t.typname
183
+ FROM pg_attribute a, pg_type t
184
+ WHERE a.attrelid = #{oid}
185
+ AND a.attnum IN (#{indkey.join(",")})
186
+ AND a.atttypid = t.oid
187
+ SQL
188
+
189
+ # Only GiST indexes on spatial columns denote a spatial index
190
+ spatial = indtype == 'gist' && columns.size == 1 && (columns.values.first[1] == 'geometry' || columns.values.first[1] == 'geography')
191
+
192
+ column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil }
193
+ ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial)
194
+ end
195
+
196
+ indexes
197
+ end
198
+
199
+ def disable_referential_integrity(&block) #:nodoc:
200
+ if supports_disable_referential_integrity?() then
201
+ execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
202
+ end
203
+ yield
204
+ ensure
205
+ if supports_disable_referential_integrity?() then
206
+ execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def tables_without_postgis
213
+ tables - %w{ geometry_columns spatial_ref_sys }
214
+ end
215
+
216
+ def column_spatial_info(table_name)
217
+ constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'")
218
+
219
+ raw_geom_infos = {}
220
+ constr.each do |constr_def_a|
221
+ raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new
222
+ raw_geom_infos[constr_def_a[3]].type = constr_def_a[6]
223
+ raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i
224
+ raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i
225
+
226
+ if raw_geom_infos[constr_def_a[3]].type[-1] == ?M
227
+ raw_geom_infos[constr_def_a[3]].with_m = true
228
+ raw_geom_infos[constr_def_a[3]].type.chop!
229
+ else
230
+ raw_geom_infos[constr_def_a[3]].with_m = false
231
+ end
232
+ end
233
+
234
+ raw_geom_infos.each_value do |raw_geom_info|
235
+ #check the presence of z and m
236
+ raw_geom_info.convert!
237
+ end
238
+
239
+ raw_geom_infos
240
+
241
+ end
242
+ end
243
+
244
+ module ActiveRecord
245
+ module ConnectionAdapters
246
+ class PostgreSQLTableDefinition < TableDefinition
247
+ attr_reader :geom_columns
248
+
249
+ def column(name, type, options = {})
250
+ unless (@base.geometry_data_types[type.to_sym].nil? or
251
+ (options[:create_using_addgeometrycolumn] == false))
252
+
253
+ column = self[name] || PostgreSQLColumnDefinition.new(@base, name, type)
254
+ column.null = options[:null]
255
+ column.srid = options[:srid] || -1
256
+ column.with_z = options[:with_z] || false
257
+ column.with_m = options[:with_m] || false
258
+ column.geographic = options[:geographic] || false
259
+
260
+ if column.geographic
261
+ @columns << column unless @columns.include? column
262
+ else
263
+ # Hold this column for later
264
+ @geom_columns ||= []
265
+ @geom_columns << column
266
+ end
267
+ self
268
+ else
269
+ super(name, type, options)
270
+ end
271
+ end
272
+ end
273
+
274
+ class PostgreSQLColumnDefinition < ColumnDefinition
275
+ attr_accessor :table_name
276
+ attr_accessor :srid, :with_z, :with_m, :geographic
277
+ attr_reader :spatial
278
+
279
+ def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false, geographic=false)
280
+ super(base, name, type, limit, default, null)
281
+ @table_name = nil
282
+ @spatial = true
283
+ @srid = srid
284
+ @with_z = with_z
285
+ @with_m = with_m
286
+ @geographic = geographic
287
+ end
288
+
289
+ def sql_type
290
+ if geographic
291
+ type_sql = base.geometry_data_types[type.to_sym][:name]
292
+ type_sql += "Z" if with_z
293
+ type_sql += "M" if with_m
294
+ # SRID is not yet supported (defaults to 4326)
295
+ #type_sql += ", #{srid}" if (srid && srid != -1)
296
+ type_sql = "geography(#{type_sql})"
297
+ type_sql
298
+ else
299
+ super
300
+ end
301
+ end
302
+
303
+ def to_sql
304
+ if spatial && !geographic
305
+ type_sql = base.geometry_data_types[type.to_sym][:name]
306
+ type_sql += "M" if with_m and !with_z
307
+ if with_m and with_z
308
+ dimension = 4
309
+ elsif with_m or with_z
310
+ dimension = 3
311
+ else
312
+ dimension = 2
313
+ end
314
+
315
+ column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
316
+ column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
317
+ column_sql
318
+ else
319
+ super
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ module ActiveRecord
327
+ module ConnectionAdapters
328
+ class SpatialPostgreSQLColumn < PostgreSQLColumn
329
+ include SpatialAdapter::SpatialColumn
330
+
331
+ def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false, geographic = false)
332
+ super(name, default, sql_type, null, srid, with_z, with_m)
333
+ @geographic = geographic
334
+ end
335
+
336
+ def geographic?
337
+ @geographic
338
+ end
339
+
340
+ #Transforms a string to a geometry. PostGIS returns a HewEWKB string.
341
+ def self.string_to_geometry(string)
342
+ return string unless string.is_a?(String)
343
+ GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
344
+ end
345
+
346
+ def self.create_simplified(name, default, null = true)
347
+ new(name, default, "geometry", null)
348
+ end
349
+
350
+ def self.create_from_geography(name, default, sql_type, null = true)
351
+ params = extract_geography_params(sql_type)
352
+ new(name, default, sql_type, null, params[:srid], params[:with_z], params[:with_m], true)
353
+ end
354
+
355
+ private
356
+
357
+ # Add detection of PostGIS-specific geography columns
358
+ def geometry_simplified_type(sql_type)
359
+ case sql_type
360
+ when /geography\(point/i then :point
361
+ when /geography\(linestring/i then :line_string
362
+ when /geography\(polygon/i then :polygon
363
+ when /geography\(multipoint/i then :multi_point
364
+ when /geography\(multilinestring/i then :multi_line_string
365
+ when /geography\(multipolygon/i then :multi_polygon
366
+ when /geography\(geometrycollection/i then :geometry_collection
367
+ when /geography/i then :geometry
368
+ else
369
+ super
370
+ end
371
+ end
372
+
373
+ def self.extract_geography_params(sql_type)
374
+ params = {
375
+ :srid => 0,
376
+ :with_z => false,
377
+ :with_m => false
378
+ }
379
+ if sql_type =~ /geography(?:\((?:\w+?)(Z)?(M)?(?:,(\d+))?\))?/i
380
+ params[:with_z] = $1 == 'Z'
381
+ params[:with_m] = $2 == 'M'
382
+ params[:srid] = $3.to_i
383
+ end
384
+ params
385
+ end
386
+ end
387
+ end
388
+ end