rgeo 0.1.20 → 0.1.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/History.rdoc +10 -0
  2. data/README.rdoc +38 -35
  3. data/Version +1 -1
  4. data/lib/active_record/connection_adapters/mysql2spatial_adapter.rb +1 -3
  5. data/lib/active_record/connection_adapters/mysqlspatial_adapter.rb +4 -4
  6. data/lib/active_record/connection_adapters/postgis_adapter.rb +426 -0
  7. data/lib/active_record/connection_adapters/spatialite_adapter.rb +488 -0
  8. data/lib/rgeo.rb +10 -29
  9. data/lib/rgeo/active_record/arel_modifications.rb +1 -0
  10. data/lib/rgeo/active_record/base_modifications.rb +27 -10
  11. data/lib/rgeo/active_record/common.rb +128 -0
  12. data/lib/rgeo/active_record/mysql_common.rb +14 -51
  13. data/lib/rgeo/cartesian/factory.rb +2 -2
  14. data/lib/rgeo/coord_sys.rb +1 -1
  15. data/lib/rgeo/coord_sys/proj4.rb +3 -2
  16. data/lib/rgeo/error.rb +0 -3
  17. data/lib/rgeo/feature.rb +1 -3
  18. data/lib/rgeo/feature/factory_generator.rb +8 -0
  19. data/lib/rgeo/geography/factory.rb +2 -2
  20. data/lib/rgeo/geography/interface.rb +3 -3
  21. data/lib/rgeo/geos/zm_factory.rb +2 -2
  22. data/lib/rgeo/wkrep/wkb_parser.rb +35 -36
  23. data/lib/rgeo/wkrep/wkt_parser.rb +36 -38
  24. data/test/active_record/common_setup_methods.rb +129 -0
  25. data/test/active_record/readme.txt +10 -0
  26. data/test/active_record/tc_mysqlspatial.rb +22 -71
  27. data/test/active_record/tc_postgis.rb +282 -0
  28. data/test/active_record/tc_spatialite.rb +198 -0
  29. data/test/coord_sys/tc_proj4.rb +12 -5
  30. data/test/projected_geography/tc_geometry_collection.rb +1 -1
  31. data/test/projected_geography/tc_line_string.rb +1 -1
  32. data/test/projected_geography/tc_multi_line_string.rb +1 -1
  33. data/test/projected_geography/tc_multi_point.rb +1 -1
  34. data/test/projected_geography/tc_multi_polygon.rb +2 -2
  35. data/test/projected_geography/tc_point.rb +4 -4
  36. data/test/projected_geography/tc_polygon.rb +1 -1
  37. data/test/simple_mercator/tc_geometry_collection.rb +1 -1
  38. data/test/simple_mercator/tc_line_string.rb +1 -1
  39. data/test/simple_mercator/tc_multi_line_string.rb +1 -1
  40. data/test/simple_mercator/tc_multi_point.rb +1 -1
  41. data/test/simple_mercator/tc_multi_polygon.rb +2 -2
  42. data/test/simple_mercator/tc_point.rb +4 -4
  43. data/test/simple_mercator/tc_polygon.rb +1 -1
  44. data/test/simple_mercator/tc_window.rb +1 -1
  45. data/test/spherical_geography/tc_geometry_collection.rb +1 -1
  46. data/test/spherical_geography/tc_line_string.rb +1 -1
  47. data/test/spherical_geography/tc_multi_line_string.rb +1 -1
  48. data/test/spherical_geography/tc_multi_point.rb +1 -1
  49. data/test/spherical_geography/tc_multi_polygon.rb +2 -2
  50. data/test/spherical_geography/tc_point.rb +4 -4
  51. data/test/spherical_geography/tc_polygon.rb +1 -1
  52. data/test/tc_oneoff.rb +3 -3
  53. data/test/wkrep/tc_wkb_parser.rb +14 -14
  54. data/test/wkrep/tc_wkt_parser.rb +37 -45
  55. metadata +10 -3
data/History.rdoc CHANGED
@@ -1,3 +1,13 @@
1
+ === 0.1.21 / 2010-12-03
2
+
3
+ * API CHANGE: Added "_factory" to the end of the Geography toplevel interface methods, for consistency with the rest of the API.
4
+ * API CHANGE: Simplified initializer API for WKTParser and WKBParser.
5
+ * API CHANGE: Removed ActiveRecord::Base.rgeo_default_factory, and provided a reasonable default rgeo_factory_generator.
6
+ * Removed deprecated pluralized names RGeo::Features and RGeo::Errors.
7
+ * First pass implementation of the ActiveRecord adapters for SpatiaLite and PostGIS.
8
+ * Fixed problems with Proj4 equivalence testing.
9
+ * Several more minor fixes and documentation updates.
10
+
1
11
  === 0.1.20 / 2010-11-30
2
12
 
3
13
  * API CHANGE: Methods that raised MethodUnimplemented now raise UnsupportedCapability instead. Removed MethodUnimplemented.
data/README.rdoc CHANGED
@@ -1,25 +1,17 @@
1
1
  == RGeo
2
2
 
3
- RGeo is a spatial data library for Ruby.
4
-
5
- It provides an implementation of the Open Geospatial Consortium's Simple
6
- Features Specification, used by most standard spatial/geographic data
7
- storage systems such as PostGIS. It also provides a suite of useful tools
8
- for writing location-aware applications using Ruby-based frameworks such
9
- as Ruby On Rails.
10
-
11
- IMPORTANT: RGeo is currently under development, and we consider it to be
12
- in a "pre-alpha" state. Several features are not yet complete, and there
13
- are known bugs and holes in the test suite. We also expect the APIs to be
14
- in flux for a short while longer. Therefore, we do not yet recommend the
15
- use of RGeo in production.
3
+ RGeo is a geospatial data library for Ruby.
16
4
 
17
5
  === Summary
18
6
 
19
- RGeo is a core component for writing location-aware applications in the
20
- Ruby programming language. It provides the basic tools for modeling
21
- location data and communicating with location-aware storage systems
22
- and services.
7
+ RGeo is a key component for writing location-aware applications in the
8
+ Ruby programming language. At its core is an implementation of the
9
+ industry standard OGC Simple Features Specification, which provides data
10
+ representations of geometric objects such as points, lines, and polygons,
11
+ along with a set of geometric analysis operations. It also includes a
12
+ suite of components for modeling location data, communicating with
13
+ location-aware services and database systems, and writing location-aware
14
+ applications with Ruby On Rails.
23
15
 
24
16
  Use RGeo to:
25
17
 
@@ -27,22 +19,31 @@ Use RGeo to:
27
19
  and polygons in your Ruby application.
28
20
  * Perform standard spatial analysis operations such as finding
29
21
  intersections, creating buffers, and computing lengths and areas.
30
- * Correctly handle spherical geometry, and compute projections for
31
- geographic data analysis.
32
- * Store and retrieve spatial data in industry standard spatial database
22
+ * Correctly handle spherical geometry, and compute geographic projections
23
+ for map display and data analysis.
24
+ * Store and retrieve data in industry standard spatial database
33
25
  systems such as MySQL Spatial, SpatiaLite, and PostGIS.
34
26
  * Generate and interpret GeoJSON data for communication with common
35
27
  location-based web services such as SimpleGeo.
36
28
  * Read legacy GIS data from ESRI shapefiles.
37
29
  * Extend Ruby On Rails to handle location data in a web application.
38
- * Follow the latest standards from the Open Geospatial Consortium.
30
+ * Write to the latest standards from the Open Geospatial Consortium.
31
+
32
+ IMPORTANT: RGeo is currently under development, and we consider it to be
33
+ in a "pre-alpha" state. Several features are not yet complete, and there
34
+ are known bugs and holes in the test suite. We also expect the APIs to be
35
+ in flux for a short while longer. Therefore, we do not yet recommend the
36
+ use of RGeo in production.
39
37
 
40
38
  === Requirements
41
39
 
42
- RGeo has the following prerequisites:
40
+ RGeo has the following hard requirements:
43
41
 
44
42
  * Ruby 1.8.7 or later. Ruby 1.9.2 or later preferred.
45
43
  Rubinius and JRuby are not yet supported.
44
+
45
+ Some features also require the following:
46
+
46
47
  * GEOS 3.2 or later is highly recommended. Some functions will not be
47
48
  available without it. This C/C++ library may be available via your
48
49
  operating system's package manager, or you can download it from
@@ -55,10 +56,11 @@ RGeo has the following prerequisites:
55
56
  install the "json" gem. Ruby 1.9 has JSON support in its standard
56
57
  library and does not require the gem.
57
58
  * If you are using the shapefile reader, you should install the "dbf"
58
- gem, version 1.5.2 or later, which is required to read the attributes.
59
+ gem, version 1.5.2 or later, which is needed to read the attributes.
59
60
  * The ActiveRecord adapters for MySQL Spatial, PostGIS, and SpatiaLite
60
- require ActiveRecord 3.0.3 or later, Arel 2.0 or later, and the
61
- corresponding database driver gems (e.g. "mysql" or "mysql2").
61
+ require ActiveRecord 3.0.3 or later, and the corresponding database
62
+ driver gems (e.g. "mysql", "mysql2", "sqlite3-ruby", "pg", etc.).
63
+ Note that the adapters are NOT compatible with ActiveRecord 2.3.
62
64
 
63
65
  === Installation
64
66
 
@@ -81,7 +83,7 @@ the switches interpreted by the gem command. For example:
81
83
 
82
84
  gem install rgeo -- --with-geos-dir=/path/to/my/geos/installation
83
85
 
84
- Similarly, the gem installation looks for the Proj.4 library in the
86
+ Similarly, the gem installation looks for the Proj4 library in the
85
87
  following locations by default:
86
88
 
87
89
  * /usr/local
@@ -95,13 +97,14 @@ installation prefix directory using the "--with-proj-dir" option.
95
97
 
96
98
  === To-do items
97
99
 
98
- This is our planned feature roadmap, in rough priority order.
100
+ This is our planned roadmap, in rough priority order.
99
101
 
100
- * SpatiaLite and PostGIS adapters for ActiveRecord. (We already have an
101
- adapter for MySQL Spatial.)
102
- * Integration with certain third-party services such as SimpleGeo.
102
+ * Continued work on the ActiveRecord adapters.
103
+ * Possible Arel extensions for constructing spatial queries.
104
+ * OGC coordinate system representation.
103
105
  * Support for bbox and crs elements of GeoJSON.
104
106
  * Ellipsoidal geography implementation, probably utilizing geographiclib.
107
+ * Integration with certain third-party services such as SimpleGeo.
105
108
  * Rubinius support for C library integration.
106
109
  * JRuby support via JTS integration.
107
110
  * Support for writing shapefiles. (Reading is already implemented.)
@@ -126,13 +129,13 @@ Development of RGeo is sponsored by GeoPage, Inc. (http://www.geopage.com/).
126
129
  RGeo links with the GEOS library to handle most Cartesian geometric
127
130
  calculations, and with the Proj4 library to handle projections and
128
131
  coordinate transforms. These libraries are maintained by the Open Source
129
- Geospatial Foundation; these and other OSGeo projects can be found on
130
- OSGeo's web site (http://www.osgeo.org/).
132
+ Geospatial Foundation; more information is available on OSGeo's web site
133
+ (http://www.osgeo.org/).
131
134
 
132
135
  The ActiveRecord adapters owe some debt to the spatial_adapter plugin
133
- (http://github.com/fragility/spatial_adapter). We made a few different
134
- design decisions for RGeo, but studying the spatial_adapter source gave
135
- us a head start on our implementation.
136
+ (http://github.com/fragility/spatial_adapter). RGeo's implementation is
137
+ a little different because we made some different design decisions, but
138
+ studying the spatial_adapter source gave us a head start.
136
139
 
137
140
  Although we don't use shapelib (http://shapelib.maptools.org/) to read
138
141
  ESRI shapefiles, we did borrow a bunch of their test cases.
data/Version CHANGED
@@ -1 +1 @@
1
- 0.1.20
1
+ 0.1.21
@@ -107,9 +107,7 @@ module ActiveRecord
107
107
  if current_index_ != row_[:Key_name]
108
108
  next if row_[:Key_name] == 'PRIMARY' # skip the primary key
109
109
  current_index_ = row_[:Key_name]
110
- new_index_ = ::RGeo::ActiveRecord::MysqlCommon::IndexDefinition.new(row_[:Table], row_[:Key_name], row_[:Non_unique] == 0, [], [])
111
- new_index_.spatial = row_[:Index_type] == 'SPATIAL'
112
- indexes_ << new_index_
110
+ indexes_ << ::RGeo::ActiveRecord::Common::IndexDefinition.new(row_[:Table], row_[:Key_name], row_[:Non_unique] == 0, [], [], row_[:Index_type] == 'SPATIAL')
113
111
  end
114
112
  indexes_.last.columns << row_[:Column_name]
115
113
  indexes_.last.lengths << row_[:Sub_part]
@@ -102,7 +102,9 @@ module ActiveRecord
102
102
  def columns(table_name_, name_=nil)
103
103
  result_ = execute("SHOW FIELDS FROM #{quote_table_name(table_name_)}", :skip_logging)
104
104
  columns_ = []
105
- result_.each{ |field_| columns_ << SpatialColumn.new(field_[0], field_[4], field_[1], field_[2] == "YES") }
105
+ result_.each do |field_|
106
+ columns_ << SpatialColumn.new(field_[0], field_[4], field_[1], field_[2] == "YES")
107
+ end
106
108
  result_.free
107
109
  columns_
108
110
  end
@@ -116,9 +118,7 @@ module ActiveRecord
116
118
  if current_index_ != row_[2]
117
119
  next if row_[2] == "PRIMARY" # skip the primary key
118
120
  current_index_ = row_[2]
119
- new_index_ = ::RGeo::ActiveRecord::MysqlCommon::IndexDefinition.new(row_[0], row_[2], row_[1] == "0", [], [])
120
- new_index_.spatial = row_[10] == 'SPATIAL'
121
- indexes_ << new_index_
121
+ indexes_ << ::RGeo::ActiveRecord::Common::IndexDefinition.new(row_[0], row_[2], row_[1] == "0", [], [], row_[10] == 'SPATIAL')
122
122
  end
123
123
  indexes_.last.columns << row_[4]
124
124
  indexes_.last.lengths << row_[7]
@@ -0,0 +1,426 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # PostGIS adapter for ActiveRecord
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
+ require 'rgeo/active_record/common'
38
+ require 'active_record/connection_adapters/postgresql_adapter'
39
+
40
+
41
+ module ActiveRecord
42
+
43
+ class Base
44
+
45
+
46
+ # Create a postgis connection adapter.
47
+
48
+
49
+ def self.postgis_connection(config_)
50
+ require 'pg'
51
+
52
+ config_ = config_.symbolize_keys
53
+ host_ = config_[:host]
54
+ port_ = config_[:port] || 5432
55
+ username_ = config_[:username].to_s if config_[:username]
56
+ password_ = config_[:password].to_s if config_[:password]
57
+ if config_.has_key?(:database)
58
+ database_ = config_[:database]
59
+ else
60
+ raise ::ArgumentError, "No database specified. Missing argument: database."
61
+ end
62
+
63
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
64
+ # so just pass a nil connection object for the time being.
65
+ ConnectionAdapters::PostGisAdapter.new(nil, logger, [host_, port_, nil, nil, database_, username_, password_], config_)
66
+ end
67
+
68
+
69
+ end
70
+
71
+
72
+ module ConnectionAdapters # :nodoc:
73
+
74
+
75
+ class PostGisAdapter < PostgreSQLAdapter # :nodoc:
76
+
77
+
78
+ ADAPTER_NAME = 'PostGIS'.freeze
79
+
80
+ @@native_database_types = nil
81
+
82
+
83
+ def native_database_types
84
+ @@native_database_types ||= super.merge(:geometry => {:name => "geometry"}, :point => {:name => "point"}, :line_string => {:name => "linestring"}, :polygon => {:name => "polygon"}, :geometry_collection => {:name => "geometrycollection"}, :multi_point => {:name => "multipoint"}, :multi_line_string => {:name => "multilinestring"}, :multi_polygon => {:name => "multipolygon"})
85
+ end
86
+
87
+
88
+ def adapter_name
89
+ ADAPTER_NAME
90
+ end
91
+
92
+
93
+ def postgis_lib_version
94
+ unless defined?(@postgis_lib_version)
95
+ @postgis_lib_version = select_value("SELECT PostGIS_Lib_Version()") rescue nil
96
+ end
97
+ @postgis_lib_version
98
+ end
99
+
100
+
101
+ def quote(value_, column_=nil)
102
+ if ::RGeo::Feature::Geometry.check_type(value_)
103
+ "'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)}'"
104
+ else
105
+ super
106
+ end
107
+ end
108
+
109
+
110
+ def columns(table_name_, name_=nil) #:nodoc:
111
+ table_name_ = table_name_.to_s
112
+ spatial_info_ = spatial_column_info(table_name_)
113
+ column_definitions(table_name_).collect do |name_, type_, default_, notnull_|
114
+ SpatialColumn.new(name_, default_, type_, notnull_ == 'f', type_ =~ /geometry/i ? spatial_info_[name_] : nil)
115
+ end
116
+ end
117
+
118
+
119
+ def create_table(table_name_, options_={})
120
+ table_name_ = table_name_.to_s
121
+ table_definition_ = SpatialTableDefinition.new(self)
122
+ table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
123
+ yield table_definition_ if block_given?
124
+ if options_[:force] && table_exists?(table_name_)
125
+ drop_table(table_name_, options_)
126
+ end
127
+
128
+ create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
129
+ create_sql_ << "#{quote_table_name(table_name_)} ("
130
+ create_sql_ << table_definition_.to_sql
131
+ create_sql_ << ") #{options_[:options]}"
132
+ execute create_sql_
133
+
134
+ table_definition_.non_geographic_spatial_columns.each do |col_|
135
+ type_ = col_.type.to_s.gsub('_', '').upcase
136
+ has_z_ = col_.has_z?
137
+ has_m_ = col_.has_m?
138
+ type_ = "#{type_}M" if has_m_ && !has_z_
139
+ dimensions_ = 2
140
+ dimensions_ += 1 if has_z_
141
+ dimensions_ += 1 if has_m_
142
+ execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name)}', #{col_.srid}, '#{quote_string(type_)}', #{dimensions_})")
143
+ end
144
+ end
145
+
146
+
147
+ def drop_table(table_name_, options_={})
148
+ execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
149
+ super
150
+ end
151
+
152
+
153
+ def add_column(table_name_, column_name_, type_, options_={})
154
+ table_name_ = table_name_.to_s
155
+ if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(type_.to_sym)
156
+ type_ = type_.to_s.gsub('_', '').upcase
157
+ has_z_ = options_[:has_z]
158
+ has_m_ = options_[:has_m]
159
+ srid_ = (options_[:srid] || 4326).to_i
160
+ if options_[:geographic]
161
+ type_ << 'Z' if has_z_
162
+ type_ << 'M' if has_m_
163
+ execute("ALTER TABLE #{quote_table_name(table_name_)} ADD COLUMN #{quote_column_name(column_name_)} GEOGRAPHY(#{type_},#{srid_})")
164
+ change_column_default(table_name_, column_name_, options_[:default]) if options_include_default?(options_)
165
+ change_column_null(table_name_, column_name_, false, options_[:default]) if options_[:null] == false
166
+ else
167
+ type_ = "#{type_}M" if has_m_ && !has_z_
168
+ dimensions_ = 2
169
+ dimensions_ += 1 if has_z_
170
+ dimensions_ += 1 if has_m_
171
+ execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(column_name_.to_s)}', #{srid_}, '#{quote_string(type_)}', #{dimensions_})")
172
+ end
173
+ else
174
+ super
175
+ end
176
+ end
177
+
178
+
179
+ def remove_column(table_name_, *column_names_)
180
+ column_names_ = column_names_.flatten.map{ |n_| n_.to_s }
181
+ spatial_info_ = spatial_column_info(table_name_)
182
+ remaining_column_names_ = []
183
+ column_names_.each do |name_|
184
+ if spatial_info_.include?(name_)
185
+ execute("SELECT DropGeometryColumn('#{quote_string(table_name_.to_s)}','#{quote_string(name_)}')")
186
+ else
187
+ remaining_column_names_ << name_.to_sym
188
+ end
189
+ end
190
+ if remaining_column_names_.size > 0
191
+ super(table_name_, *remaining_column_names_)
192
+ end
193
+ end
194
+
195
+
196
+ def add_index(table_name_, column_name_, options_={})
197
+ table_name_ = table_name_.to_s
198
+ column_names_ = ::Array.wrap(column_name_)
199
+ index_name_ = index_name(table_name_, :column => column_names_)
200
+ gist_clause_ = ''
201
+ index_type_ = ''
202
+ if ::Hash === options_ # legacy support, since this param was a string
203
+ index_type_ = 'UNIQUE' if options_[:unique]
204
+ index_name_ = options_[:name].to_s if options_.key?(:name)
205
+ gist_clause_ = 'USING GIST' if options_[:spatial]
206
+ else
207
+ index_type_ = options_
208
+ end
209
+ if index_name_.length > index_name_length
210
+ raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' is too long; the limit is #{index_name_length} characters"
211
+ end
212
+ if index_name_exists?(table_name_, index_name_, false)
213
+ raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' already exists"
214
+ end
215
+ quoted_column_names_ = quoted_columns_for_index(column_names_, options_).join(", ")
216
+ execute "CREATE #{index_type_} INDEX #{quote_column_name(index_name_)} ON #{quote_table_name(table_name_)} #{gist_clause_} (#{quoted_column_names_})"
217
+ end
218
+
219
+
220
+ def spatial_column_info(table_name_)
221
+ info_ = query("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
222
+ result_ = {}
223
+ info_.each do |row_|
224
+ name_ = row_[3]
225
+ type_ = row_[6]
226
+ dimension_ = row_[4].to_i
227
+ has_m_ = type_ =~ /m$/i ? true : false
228
+ type_.sub!(/m$/, '')
229
+ has_z_ = dimension_ > 3 || dimension_ == 3 && !has_m_
230
+ result_[name_] = {
231
+ :name => name_,
232
+ :type => type_,
233
+ :dimension => dimension_,
234
+ :srid => row_[5].to_i,
235
+ :has_z => has_z_,
236
+ :has_m => has_m_,
237
+ }
238
+ end
239
+ result_
240
+ end
241
+
242
+
243
+ class SpatialTableDefinition < ConnectionAdapters::TableDefinition # :nodoc:
244
+
245
+ attr_reader :spatial_columns
246
+
247
+ def initialize(base_)
248
+ super
249
+ end
250
+
251
+ def column(name_, type_, options_={})
252
+ super
253
+ col_ = self[name_]
254
+ if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(col_.type.to_sym)
255
+ col_.extend(GeometricColumnDefinitionMethods) unless col_.respond_to?(:geographic?)
256
+ col_.set_geographic(options_[:geographic])
257
+ col_.set_srid((options_[:srid] || 4326).to_i)
258
+ col_.set_has_z(options_[:has_z])
259
+ col_.set_has_m(options_[:has_m])
260
+ end
261
+ self
262
+ end
263
+
264
+ def to_sql
265
+ @columns.find_all{ |c_| !c_.respond_to?(:geographic?) || c_.geographic? }.map{ |c_| c_.to_sql } * ', '
266
+ end
267
+
268
+ def non_geographic_spatial_columns
269
+ @columns.find_all{ |c_| c_.respond_to?(:geographic?) && !c_.geographic? }
270
+ end
271
+
272
+ end
273
+
274
+
275
+ module GeometricColumnDefinitionMethods # :nodoc:
276
+
277
+ def geographic?
278
+ defined?(@geographic) && @geographic
279
+ end
280
+
281
+ def srid
282
+ defined?(@srid) ? @srid : 4326
283
+ end
284
+
285
+ def has_z?
286
+ defined?(@has_z) && @has_z
287
+ end
288
+
289
+ def has_m?
290
+ defined?(@has_m) && @has_m
291
+ end
292
+
293
+ def set_geographic(value_)
294
+ @geographic = value_ ? true : false
295
+ end
296
+
297
+ def set_srid(value_)
298
+ @srid = value_
299
+ end
300
+
301
+ def set_has_z(value_)
302
+ @has_z = value_ ? true : false
303
+ end
304
+
305
+ def set_has_m(value_)
306
+ @has_m = value_ ? true : false
307
+ end
308
+
309
+ def sql_type
310
+ type_ = type.to_s.upcase.gsub('_', '')
311
+ type_ << 'Z' if has_z?
312
+ type_ << 'M' if has_m?
313
+ "GEOGRAPHY(#{type_},#{srid})"
314
+ end
315
+
316
+ end
317
+
318
+
319
+ class SpatialColumn < ConnectionAdapters::PostgreSQLColumn # :nodoc:
320
+
321
+
322
+ def initialize(name_, default_, sql_type_=nil, null_=true, opts_=nil)
323
+ super(name_, default_, sql_type_, null_)
324
+ @geographic = sql_type_ =~ /^geography/ ? true : false
325
+ if opts_
326
+ @geometric_type = ::RGeo::ActiveRecord::Common.geometric_type_from_name(opts_[:type])
327
+ @srid = opts_[:srid].to_i
328
+ @has_z = opts_[:has_z]
329
+ @has_m = opts_[:has_m]
330
+ elsif @geographic
331
+ if sql_type_ =~ /geography\((\w+[^,zm])(z?)(m?),(\d+)\)/i
332
+ @has_z = $2.length > 0
333
+ @has_m = $3.length > 0
334
+ @srid = $4.to_i
335
+ @geometric_type = ::RGeo::ActiveRecord::Common.geometric_type_from_name($1)
336
+ else
337
+ @geometric_type = ::RGeo::Feature::Geometry
338
+ @srid = 4326
339
+ @has_z = @has_m = false
340
+ end
341
+ else
342
+ @geometric_type = @has_z = @has_m = nil
343
+ @srid = 0
344
+ end
345
+ @ar_class = ::ActiveRecord::Base
346
+ end
347
+
348
+
349
+ def set_ar_class(val_)
350
+ @ar_class = val_
351
+ end
352
+
353
+
354
+ attr_reader :srid
355
+ attr_reader :geometric_type
356
+ attr_reader :has_z
357
+ attr_reader :has_m
358
+
359
+
360
+ def spatial?
361
+ type == :geometry
362
+ end
363
+
364
+
365
+ def geographic?
366
+ @geographic
367
+ end
368
+
369
+
370
+ def klass
371
+ type == :geometry ? ::RGeo::Feature::Geometry : super
372
+ end
373
+
374
+
375
+ def type_cast(value_)
376
+ type == :geometry ? SpatialColumn.string_to_geometry(value_, @ar_class, @geographic, @srid, @has_z, @has_m) : super
377
+ end
378
+
379
+
380
+ def type_cast_code(var_name_)
381
+ type == :geometry ? "::ActiveRecord::ConnectionAdapters::PostGisAdapter::SpatialColumn.string_to_geometry(#{var_name_}, self.class, #{@geographic ? 'true' : 'false'}, #{@srid.inspect}, #{@has_z ? 'true' : 'false'}, #{@has_m ? 'true' : 'false'})" : super
382
+ end
383
+
384
+
385
+ private
386
+
387
+
388
+ def simplified_type(sql_type_)
389
+ sql_type_ =~ /geography|geometry|point|linestring|polygon/i ? :geometry : super
390
+ end
391
+
392
+
393
+ def self.string_to_geometry(str_, ar_class_, geographic_, srid_, has_z_, has_m_)
394
+ case str_
395
+ when ::RGeo::Feature::Geometry
396
+ str_
397
+ when ::String
398
+ if str_.length == 0
399
+ nil
400
+ else
401
+ factory_ = ar_class_.rgeo_factory_generator.call(:srid => srid_, :support_z_coordinate => has_z_, :support_m_coordinate => has_m_, :geographic => geographic_)
402
+ marker_ = str_[0,1]
403
+ if marker_ == "\x00" || marker_ == "\x01"
404
+ ::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse(str_) rescue nil
405
+ elsif str_[0,4] =~ /[0-9a-fA-F]{4}/
406
+ ::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse_hex(str_) rescue nil
407
+ else
408
+ ::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(str_) rescue nil
409
+ end
410
+ end
411
+ else
412
+ nil
413
+ end
414
+ end
415
+
416
+
417
+ end
418
+
419
+
420
+ end
421
+
422
+
423
+ end
424
+
425
+
426
+ end