rgeo 0.1.20 → 0.1.21
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.
- data/History.rdoc +10 -0
- data/README.rdoc +38 -35
- data/Version +1 -1
- data/lib/active_record/connection_adapters/mysql2spatial_adapter.rb +1 -3
- data/lib/active_record/connection_adapters/mysqlspatial_adapter.rb +4 -4
- data/lib/active_record/connection_adapters/postgis_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/spatialite_adapter.rb +488 -0
- data/lib/rgeo.rb +10 -29
- data/lib/rgeo/active_record/arel_modifications.rb +1 -0
- data/lib/rgeo/active_record/base_modifications.rb +27 -10
- data/lib/rgeo/active_record/common.rb +128 -0
- data/lib/rgeo/active_record/mysql_common.rb +14 -51
- data/lib/rgeo/cartesian/factory.rb +2 -2
- data/lib/rgeo/coord_sys.rb +1 -1
- data/lib/rgeo/coord_sys/proj4.rb +3 -2
- data/lib/rgeo/error.rb +0 -3
- data/lib/rgeo/feature.rb +1 -3
- data/lib/rgeo/feature/factory_generator.rb +8 -0
- data/lib/rgeo/geography/factory.rb +2 -2
- data/lib/rgeo/geography/interface.rb +3 -3
- data/lib/rgeo/geos/zm_factory.rb +2 -2
- data/lib/rgeo/wkrep/wkb_parser.rb +35 -36
- data/lib/rgeo/wkrep/wkt_parser.rb +36 -38
- data/test/active_record/common_setup_methods.rb +129 -0
- data/test/active_record/readme.txt +10 -0
- data/test/active_record/tc_mysqlspatial.rb +22 -71
- data/test/active_record/tc_postgis.rb +282 -0
- data/test/active_record/tc_spatialite.rb +198 -0
- data/test/coord_sys/tc_proj4.rb +12 -5
- data/test/projected_geography/tc_geometry_collection.rb +1 -1
- data/test/projected_geography/tc_line_string.rb +1 -1
- data/test/projected_geography/tc_multi_line_string.rb +1 -1
- data/test/projected_geography/tc_multi_point.rb +1 -1
- data/test/projected_geography/tc_multi_polygon.rb +2 -2
- data/test/projected_geography/tc_point.rb +4 -4
- data/test/projected_geography/tc_polygon.rb +1 -1
- data/test/simple_mercator/tc_geometry_collection.rb +1 -1
- data/test/simple_mercator/tc_line_string.rb +1 -1
- data/test/simple_mercator/tc_multi_line_string.rb +1 -1
- data/test/simple_mercator/tc_multi_point.rb +1 -1
- data/test/simple_mercator/tc_multi_polygon.rb +2 -2
- data/test/simple_mercator/tc_point.rb +4 -4
- data/test/simple_mercator/tc_polygon.rb +1 -1
- data/test/simple_mercator/tc_window.rb +1 -1
- data/test/spherical_geography/tc_geometry_collection.rb +1 -1
- data/test/spherical_geography/tc_line_string.rb +1 -1
- data/test/spherical_geography/tc_multi_line_string.rb +1 -1
- data/test/spherical_geography/tc_multi_point.rb +1 -1
- data/test/spherical_geography/tc_multi_polygon.rb +2 -2
- data/test/spherical_geography/tc_point.rb +4 -4
- data/test/spherical_geography/tc_polygon.rb +1 -1
- data/test/tc_oneoff.rb +3 -3
- data/test/wkrep/tc_wkb_parser.rb +14 -14
- data/test/wkrep/tc_wkt_parser.rb +37 -45
- 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
|
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
|
20
|
-
Ruby programming language.
|
21
|
-
|
22
|
-
and
|
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
|
31
|
-
|
32
|
-
* Store and retrieve
|
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
|
-
*
|
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
|
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
|
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,
|
61
|
-
|
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
|
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
|
100
|
+
This is our planned roadmap, in rough priority order.
|
99
101
|
|
100
|
-
*
|
101
|
-
|
102
|
-
*
|
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;
|
130
|
-
|
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).
|
134
|
-
|
135
|
-
us a head start
|
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.
|
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
|
-
|
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
|
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
|
-
|
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
|