activerecord-postgis 0.2.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/README.md +128 -9
- data/lib/active_record/connection_adapters/postgis/test_helpers.rb +202 -0
- data/lib/active_record/connection_adapters/postgis/version.rb +1 -1
- data/lib/active_record/connection_adapters/postgis.rb +11 -0
- data/lib/activerecord-postgis/test_helper.rb +51 -0
- data/lib/arel/visitors/postgis.rb +125 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 348c302fed46b25f5815a94a08f24882b3c9ce9a4f29907ba5e3fcd6570dbf27
|
4
|
+
data.tar.gz: dd1a7f7de5e9a71178a2e98981221861824336c20861fce51a2da6d6b6eeceac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08ce6f86d903016208598a2c70e8da83d59fbf9b11afd8f7db0b4b1a8cb953599c43ce102ac3b84938a6b934e82af0791d2641ba453c2a17a6b75c15ad85ca13'
|
7
|
+
data.tar.gz: 6d529987e15c03978f48e346cb174f900d95493e6b1c1f2a1e417c71005ff2e7d75e1e3ee76099fcabd03b7705f28c60b04d983e609e6969929329f5d7d7a494
|
data/README.md
CHANGED
@@ -93,22 +93,42 @@ parks_in_city = Park.where(
|
|
93
93
|
)
|
94
94
|
```
|
95
95
|
|
96
|
-
**With Arel Spatial Methods
|
96
|
+
**With Arel Spatial Methods** (Now with expanded arsenal!):
|
97
97
|
|
98
98
|
```ruby
|
99
|
-
#
|
99
|
+
# Basic spatial queries
|
100
100
|
Location.where(
|
101
101
|
Location.arel_table[:coordinates].st_distance(point).lt(1000)
|
102
102
|
)
|
103
103
|
|
104
|
-
#
|
105
|
-
|
106
|
-
|
104
|
+
# NEW: K-Nearest Neighbor - Lightning fast "find nearest" queries
|
105
|
+
# Uses spatial index for incredible performance!
|
106
|
+
nearest_locations = Location
|
107
|
+
.order(Location.arel_table[:coordinates].distance_operator(my_position))
|
108
|
+
.limit(10)
|
109
|
+
|
110
|
+
# Advanced spatial predicates
|
111
|
+
# Find intersecting routes
|
112
|
+
Route.where(Route.arel_table[:path].st_intersects(restricted_zone))
|
113
|
+
|
114
|
+
# Find points within efficient distance (uses spatial index!)
|
115
|
+
Location.where(
|
116
|
+
Location.arel_table[:coordinates].st_dwithin(headquarters, 5000)
|
117
|
+
)
|
118
|
+
|
119
|
+
# Create buffer zones
|
120
|
+
safe_zones = DangerZone.select(
|
121
|
+
DangerZone.arel_table[:area].st_buffer(100).as('safety_perimeter')
|
122
|
+
)
|
123
|
+
|
124
|
+
# Transform between coordinate systems
|
125
|
+
global_coords = Location.select(
|
126
|
+
Location.arel_table[:local_position].st_transform(4326).as('wgs84_position')
|
107
127
|
)
|
108
128
|
|
109
|
-
# Calculate
|
110
|
-
|
111
|
-
|
129
|
+
# Calculate areas
|
130
|
+
territories = Region.select(
|
131
|
+
Region.arel_table[:boundary].st_area.as('territory_size')
|
112
132
|
)
|
113
133
|
```
|
114
134
|
|
@@ -139,6 +159,97 @@ puts location.coordinates.x # -5.923647
|
|
139
159
|
puts location.coordinates.y # 35.790897
|
140
160
|
```
|
141
161
|
|
162
|
+
## Testing
|
163
|
+
|
164
|
+
When testing spatial functionality in your Rails application, this gem provides helpful test utilities:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# In your test_helper.rb or rails_helper.rb
|
168
|
+
require 'activerecord-postgis/test_helper'
|
169
|
+
|
170
|
+
class ActiveSupport::TestCase
|
171
|
+
include ActiveRecordPostgis::TestHelper
|
172
|
+
end
|
173
|
+
|
174
|
+
# Or for RSpec
|
175
|
+
RSpec.configure do |config|
|
176
|
+
config.include ActiveRecordPostgis::TestHelper
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
### Test Helper Methods
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class LocationTest < ActiveSupport::TestCase
|
184
|
+
def test_spatial_operations
|
185
|
+
# Create test geometries
|
186
|
+
point1 = create_point(-5.9, 35.8)
|
187
|
+
point2 = create_point(-5.91, 35.81)
|
188
|
+
polygon = create_test_polygon
|
189
|
+
|
190
|
+
location = Location.create!(coordinates: point1, boundary: polygon)
|
191
|
+
|
192
|
+
# Traditional assertions
|
193
|
+
assert_spatial_equal point1, location.coordinates
|
194
|
+
assert_within_distance point1, point2, 200 # meters
|
195
|
+
assert_contains polygon, point1
|
196
|
+
|
197
|
+
# New chainable syntax (recommended)
|
198
|
+
assert_spatial_column(location.coordinates)
|
199
|
+
.has_srid(4326)
|
200
|
+
.is_type(:point)
|
201
|
+
.is_geographic
|
202
|
+
|
203
|
+
assert_spatial_column(location.boundary)
|
204
|
+
.is_type(:polygon)
|
205
|
+
.has_srid(4326)
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_3d_geometry
|
209
|
+
point_3d = create_point(1.0, 2.0, srid: 4326, z: 10.0)
|
210
|
+
|
211
|
+
assert_spatial_column(point_3d)
|
212
|
+
.has_z
|
213
|
+
.has_srid(4326)
|
214
|
+
.is_type(:point)
|
215
|
+
.is_cartesian
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
**Available Test Helpers:**
|
221
|
+
|
222
|
+
**Traditional Assertions:**
|
223
|
+
- `assert_spatial_equal(expected, actual)` - Assert spatial objects are equal
|
224
|
+
- `assert_within_distance(point1, point2, distance)` - Assert points within distance
|
225
|
+
- `assert_contains(container, contained)` - Assert geometry contains another
|
226
|
+
- `assert_within(inner, outer)` - Assert geometry is within another
|
227
|
+
- `assert_intersects(geom1, geom2)` - Assert geometries intersect
|
228
|
+
- `assert_disjoint(geom1, geom2)` - Assert geometries don't intersect
|
229
|
+
|
230
|
+
**Chainable Spatial Column Assertions:**
|
231
|
+
- `assert_spatial_column(geometry).has_z` - Assert has Z dimension
|
232
|
+
- `assert_spatial_column(geometry).has_m` - Assert has M dimension
|
233
|
+
- `assert_spatial_column(geometry).has_srid(srid)` - Assert SRID value
|
234
|
+
- `assert_spatial_column(geometry).is_type(type)` - Assert geometry type
|
235
|
+
- `assert_spatial_column(geometry).is_geographic` - Assert geographic factory
|
236
|
+
- `assert_spatial_column(geometry).is_cartesian` - Assert cartesian factory
|
237
|
+
|
238
|
+
**Geometry Factories:**
|
239
|
+
- `create_point(x, y, srid: 4326)` - Create test points
|
240
|
+
- `create_test_polygon(srid: 4326)` - Create test polygons
|
241
|
+
- `create_test_linestring(srid: 4326)` - Create test linestrings
|
242
|
+
- `factory(srid: 4326, geographic: false)` - Get geometry factory
|
243
|
+
- `geographic_factory(srid: 4326)` - Get geographic factory
|
244
|
+
- `cartesian_factory(srid: 0)` - Get cartesian factory
|
245
|
+
|
246
|
+
## Documentation
|
247
|
+
|
248
|
+
📚 **Learn Like You're Defending the Galaxy**
|
249
|
+
|
250
|
+
- [🚀 Spatial Warfare Manual](docs/SPATIAL_WARFARE.md) - Advanced PostGIS arsenal explained through space combat
|
251
|
+
- [🍳 The PostGIS Cookbook](docs/COOKBOOK.md) - Real-world recipes from delivery fleets to geofencing
|
252
|
+
|
142
253
|
## Features
|
143
254
|
|
144
255
|
🌍 **Complete PostGIS Type Support**
|
@@ -148,7 +259,14 @@ puts location.coordinates.y # 35.790897
|
|
148
259
|
- Support for SRID, Z/M dimensions
|
149
260
|
|
150
261
|
🔍 **Spatial Query Methods**
|
151
|
-
- `st_distance`, `st_contains`, `st_within`, `st_length`
|
262
|
+
- Core methods: `st_distance`, `st_contains`, `st_within`, `st_length`
|
263
|
+
- **NEW:** Advanced spatial operations:
|
264
|
+
- `<->` (distance_operator) - K-Nearest Neighbor search (blazing fast!)
|
265
|
+
- `st_intersects` - Detect geometry intersections
|
266
|
+
- `st_dwithin` - Efficient proximity queries (index-optimized!)
|
267
|
+
- `st_buffer` - Create buffer zones around geometries
|
268
|
+
- `st_transform` - Convert between coordinate systems
|
269
|
+
- `st_area` - Calculate polygon areas
|
152
270
|
- Custom Arel visitor for PostGIS SQL generation
|
153
271
|
- Seamless integration with ActiveRecord queries
|
154
272
|
|
@@ -163,6 +281,7 @@ puts location.coordinates.y # 35.790897
|
|
163
281
|
- Works with existing PostgreSQL tools
|
164
282
|
- Clear error messages and debugging
|
165
283
|
- Full RGeo integration
|
284
|
+
- Comprehensive test helpers for spatial assertions
|
166
285
|
|
167
286
|
## Acknowledgments
|
168
287
|
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostGIS
|
6
|
+
# Test helpers for spatial data assertions
|
7
|
+
module TestHelpers
|
8
|
+
# Assert that two spatial objects are equal
|
9
|
+
def assert_spatial_equal(expected, actual, msg = nil)
|
10
|
+
msg ||= "Expected spatial object #{expected.as_text} but got #{actual.as_text}"
|
11
|
+
|
12
|
+
if expected.respond_to?(:equals?) && actual.respond_to?(:equals?)
|
13
|
+
assert expected.equals?(actual), msg
|
14
|
+
else
|
15
|
+
assert_equal expected.to_s, actual.to_s, msg
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Assert that a point is within a specified distance of another point
|
20
|
+
def assert_within_distance(point1, point2, distance, msg = nil)
|
21
|
+
actual_distance = point1.distance(point2)
|
22
|
+
msg ||= "Distance #{actual_distance} exceeds maximum allowed distance of #{distance}"
|
23
|
+
assert actual_distance <= distance, msg
|
24
|
+
end
|
25
|
+
|
26
|
+
# Assert that a geometry contains another geometry
|
27
|
+
def assert_contains(container, contained, msg = nil)
|
28
|
+
msg ||= "Expected #{container.as_text} to contain #{contained.as_text}"
|
29
|
+
assert container.contains?(contained), msg
|
30
|
+
end
|
31
|
+
|
32
|
+
# Assert that a geometry is within another geometry
|
33
|
+
def assert_within(inner, outer, msg = nil)
|
34
|
+
msg ||= "Expected #{inner.as_text} to be within #{outer.as_text}"
|
35
|
+
assert inner.within?(outer), msg
|
36
|
+
end
|
37
|
+
|
38
|
+
# Assert that two geometries intersect
|
39
|
+
def assert_intersects(geom1, geom2, msg = nil)
|
40
|
+
msg ||= "Expected #{geom1.as_text} to intersect #{geom2.as_text}"
|
41
|
+
assert geom1.intersects?(geom2), msg
|
42
|
+
end
|
43
|
+
|
44
|
+
# Assert that two geometries do not intersect
|
45
|
+
def assert_disjoint(geom1, geom2, msg = nil)
|
46
|
+
msg ||= "Expected #{geom1.as_text} to be disjoint from #{geom2.as_text}"
|
47
|
+
assert geom1.disjoint?(geom2), msg
|
48
|
+
end
|
49
|
+
|
50
|
+
# Assert that a geometry has the expected SRID
|
51
|
+
def assert_srid(geometry, expected_srid, msg = nil)
|
52
|
+
actual_srid = geometry.srid
|
53
|
+
msg ||= "Expected SRID #{expected_srid} but got #{actual_srid}"
|
54
|
+
assert_equal expected_srid, actual_srid, msg
|
55
|
+
end
|
56
|
+
|
57
|
+
# Chainable spatial column assertion builder
|
58
|
+
def assert_spatial_column(geometry, msg_prefix = nil)
|
59
|
+
SpatialColumnAssertion.new(geometry, self, msg_prefix)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Assert that a geometry is of the expected type
|
63
|
+
def assert_geometry_type(geometry, expected_type, msg = nil)
|
64
|
+
actual_type = geometry.geometry_type.type_name.downcase
|
65
|
+
expected_type = expected_type.to_s.downcase.gsub("_", "")
|
66
|
+
actual_type = actual_type.gsub("_", "")
|
67
|
+
msg ||= "Expected geometry type #{expected_type} but got #{actual_type}"
|
68
|
+
assert_equal expected_type, actual_type, msg
|
69
|
+
end
|
70
|
+
|
71
|
+
# Legacy methods for backward compatibility
|
72
|
+
def assert_has_z(geometry, msg = nil)
|
73
|
+
assert_spatial_column(geometry, msg).has_z
|
74
|
+
end
|
75
|
+
|
76
|
+
def assert_has_m(geometry, msg = nil)
|
77
|
+
assert_spatial_column(geometry, msg).has_m
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create a point for testing
|
81
|
+
def create_point(x, y, srid: 4326, z: nil, m: nil)
|
82
|
+
if z || m
|
83
|
+
# Use cartesian factory for 3D/4D points
|
84
|
+
factory = RGeo::Cartesian.preferred_factory(srid: srid, has_z_coordinate: !!z, has_m_coordinate: !!m)
|
85
|
+
if z && m
|
86
|
+
factory.point(x, y, z, m)
|
87
|
+
elsif z
|
88
|
+
factory.point(x, y, z)
|
89
|
+
elsif m
|
90
|
+
factory.point(x, y, 0, m) # Default Z to 0 for M-only
|
91
|
+
else
|
92
|
+
factory.point(x, y)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
|
96
|
+
factory.point(x, y)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create a test polygon for testing
|
101
|
+
def create_test_polygon(srid: 4326)
|
102
|
+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
|
103
|
+
factory.polygon(
|
104
|
+
factory.linear_ring([
|
105
|
+
factory.point(0, 0),
|
106
|
+
factory.point(0, 1),
|
107
|
+
factory.point(1, 1),
|
108
|
+
factory.point(1, 0),
|
109
|
+
factory.point(0, 0)
|
110
|
+
])
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create a test linestring for testing
|
115
|
+
def create_test_linestring(srid: 4326)
|
116
|
+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
|
117
|
+
factory.line_string([
|
118
|
+
factory.point(0, 0),
|
119
|
+
factory.point(1, 1),
|
120
|
+
factory.point(2, 0)
|
121
|
+
])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Chainable spatial column assertion class
|
126
|
+
class SpatialColumnAssertion
|
127
|
+
def initialize(geometry, test_case, msg_prefix = nil)
|
128
|
+
@geometry = geometry
|
129
|
+
@test_case = test_case
|
130
|
+
@msg_prefix = msg_prefix
|
131
|
+
end
|
132
|
+
|
133
|
+
def has_z
|
134
|
+
msg = build_message("to have Z dimension")
|
135
|
+
has_z = detect_has_z(@geometry)
|
136
|
+
@test_case.assert has_z, msg
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def has_m
|
141
|
+
msg = build_message("to have M dimension")
|
142
|
+
has_m = detect_has_m(@geometry)
|
143
|
+
@test_case.assert has_m, msg
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
def has_srid(expected_srid)
|
148
|
+
msg = build_message("to have SRID #{expected_srid}")
|
149
|
+
actual_srid = @geometry.srid
|
150
|
+
@test_case.assert_equal expected_srid, actual_srid, msg
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
def is_type(expected_type)
|
155
|
+
msg = build_message("to be of type #{expected_type}")
|
156
|
+
actual_type = @geometry.geometry_type.type_name.downcase
|
157
|
+
expected_type = expected_type.to_s.downcase.gsub("_", "")
|
158
|
+
actual_type = actual_type.gsub("_", "")
|
159
|
+
@test_case.assert_equal expected_type, actual_type, msg
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
def is_geographic
|
164
|
+
msg = build_message("to be geographic")
|
165
|
+
# Check if factory is geographic
|
166
|
+
is_geo = @geometry.factory.respond_to?(:spherical?) && @geometry.factory.spherical?
|
167
|
+
@test_case.assert is_geo, msg
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
def is_cartesian
|
172
|
+
msg = build_message("to be cartesian")
|
173
|
+
# Check if factory is cartesian
|
174
|
+
is_cart = !(@geometry.factory.respond_to?(:spherical?) && @geometry.factory.spherical?)
|
175
|
+
@test_case.assert is_cart, msg
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def build_message(expectation)
|
182
|
+
prefix = @msg_prefix ? "#{@msg_prefix}: " : ""
|
183
|
+
"#{prefix}Expected geometry #{expectation}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def detect_has_z(geometry)
|
187
|
+
return geometry.has_z_coordinate? if geometry.respond_to?(:has_z_coordinate?)
|
188
|
+
return geometry.has_z? if geometry.respond_to?(:has_z?)
|
189
|
+
return !geometry.z.nil? if geometry.respond_to?(:z)
|
190
|
+
false
|
191
|
+
end
|
192
|
+
|
193
|
+
def detect_has_m(geometry)
|
194
|
+
return geometry.has_m_coordinate? if geometry.respond_to?(:has_m_coordinate?)
|
195
|
+
return geometry.has_m? if geometry.respond_to?(:has_m?)
|
196
|
+
return !geometry.m.nil? if geometry.respond_to?(:m)
|
197
|
+
false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -142,6 +142,16 @@ module ActiveRecord
|
|
142
142
|
private
|
143
143
|
|
144
144
|
def create_spatial_type_from_sql(sql_type)
|
145
|
+
# Handle empty sql_type (common in joins) - this is an upstream Rails issue
|
146
|
+
# where sql_type comes back as empty string, making it impossible to determine
|
147
|
+
# the correct spatial type properties
|
148
|
+
if sql_type.nil? || sql_type.empty?
|
149
|
+
# Log warning about potential type mismatch due to upstream issue
|
150
|
+
# Users experiencing this should use explicit attribute registration as workaround
|
151
|
+
# See: https://github.com/rgeo/activerecord-postgis-adapter/pull/334
|
152
|
+
return Type::Geometry.new(srid: 0, has_z: false, has_m: false, geographic: false)
|
153
|
+
end
|
154
|
+
|
145
155
|
# Extract SRID and dimensions from SQL type
|
146
156
|
srid = extract_srid_from_sql(sql_type)
|
147
157
|
# Check for dimension suffixes (e.g., PointZ, PointM, PointZM)
|
@@ -182,6 +192,7 @@ module ActiveRecord
|
|
182
192
|
when /geometry/i
|
183
193
|
Type::Geometry.new(srid: srid, has_z: has_z, has_m: has_m)
|
184
194
|
else
|
195
|
+
# Fallback for unrecognized types
|
185
196
|
Type::Geometry.new(srid: srid, has_z: has_z, has_m: has_m)
|
186
197
|
end
|
187
198
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../active_record/connection_adapters/postgis/test_helpers"
|
4
|
+
|
5
|
+
module ActiveRecordPostgis
|
6
|
+
module TestHelper
|
7
|
+
include ActiveRecord::ConnectionAdapters::PostGIS::TestHelpers
|
8
|
+
|
9
|
+
# Additional convenience methods for PostGIS testing
|
10
|
+
def factory(srid: 4326, geographic: false)
|
11
|
+
if geographic
|
12
|
+
RGeo::Geographic.spherical_factory(srid: srid)
|
13
|
+
else
|
14
|
+
RGeo::Cartesian.preferred_factory(srid: srid)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def geographic_factory(srid: 4326)
|
19
|
+
RGeo::Geographic.spherical_factory(srid: srid)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cartesian_factory(srid: 0)
|
23
|
+
RGeo::Cartesian.preferred_factory(srid: srid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def spatial_factory_store
|
27
|
+
RGeo::ActiveRecord::SpatialFactoryStore.instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_spatial_store
|
31
|
+
spatial_factory_store.clear
|
32
|
+
spatial_factory_store.default = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a test table with spatial columns
|
36
|
+
def create_spatial_table(table_name, connection = ActiveRecord::Base.connection)
|
37
|
+
connection.create_table table_name, force: true do |t|
|
38
|
+
t.st_point :coordinates, srid: 4326
|
39
|
+
t.st_point :location, srid: 4326, geographic: true
|
40
|
+
t.st_polygon :boundary, srid: 4326
|
41
|
+
t.st_line_string :path, srid: 4326
|
42
|
+
t.timestamps
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Clean up spatial tables after tests
|
47
|
+
def drop_spatial_table(table_name, connection = ActiveRecord::Base.connection)
|
48
|
+
connection.drop_table table_name if connection.table_exists?(table_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -16,6 +16,33 @@ module Arel
|
|
16
16
|
class SpatialLength < Unary; end
|
17
17
|
class SpatialContains < SpatialNode; end
|
18
18
|
class SpatialWithin < SpatialNode; end
|
19
|
+
class SpatialIntersects < SpatialNode; end
|
20
|
+
class SpatialDWithin < SpatialNode
|
21
|
+
attr_reader :distance
|
22
|
+
|
23
|
+
def initialize(left, right, distance)
|
24
|
+
super(left, right)
|
25
|
+
@distance = distance
|
26
|
+
end
|
27
|
+
end
|
28
|
+
class SpatialBuffer < SpatialNode
|
29
|
+
def st_area
|
30
|
+
SpatialArea.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class SpatialTransform < SpatialNode
|
34
|
+
def st_area
|
35
|
+
SpatialArea.new(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
class SpatialArea < Unary; end
|
39
|
+
|
40
|
+
# K-Nearest Neighbor distance operator
|
41
|
+
class SpatialDistanceOperator < Binary
|
42
|
+
def initialize(left, right)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
19
46
|
|
20
47
|
# Wrapper for spatial values that need special handling
|
21
48
|
class SpatialValue < Node
|
@@ -36,6 +63,32 @@ module Arel
|
|
36
63
|
def st_within(other)
|
37
64
|
SpatialWithin.new(self, other)
|
38
65
|
end
|
66
|
+
|
67
|
+
def st_intersects(other)
|
68
|
+
SpatialIntersects.new(self, other)
|
69
|
+
end
|
70
|
+
|
71
|
+
def st_dwithin(other, distance)
|
72
|
+
SpatialDWithin.new(self, other, distance)
|
73
|
+
end
|
74
|
+
|
75
|
+
def st_buffer(distance)
|
76
|
+
SpatialBuffer.new(self, distance)
|
77
|
+
end
|
78
|
+
|
79
|
+
def st_transform(srid)
|
80
|
+
SpatialTransform.new(self, srid)
|
81
|
+
end
|
82
|
+
|
83
|
+
def st_area
|
84
|
+
SpatialArea.new(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def distance_operator(other)
|
88
|
+
SpatialDistanceOperator.new(self, other)
|
89
|
+
end
|
90
|
+
|
91
|
+
alias :'<->' :distance_operator
|
39
92
|
end
|
40
93
|
end
|
41
94
|
|
@@ -56,6 +109,32 @@ module Arel
|
|
56
109
|
def st_within(other)
|
57
110
|
Arel::Nodes::SpatialWithin.new(self, other)
|
58
111
|
end
|
112
|
+
|
113
|
+
def st_intersects(other)
|
114
|
+
Arel::Nodes::SpatialIntersects.new(self, other)
|
115
|
+
end
|
116
|
+
|
117
|
+
def st_dwithin(other, distance)
|
118
|
+
Arel::Nodes::SpatialDWithin.new(self, other, distance)
|
119
|
+
end
|
120
|
+
|
121
|
+
def st_buffer(distance)
|
122
|
+
Arel::Nodes::SpatialBuffer.new(self, distance)
|
123
|
+
end
|
124
|
+
|
125
|
+
def st_transform(srid)
|
126
|
+
Arel::Nodes::SpatialTransform.new(self, srid)
|
127
|
+
end
|
128
|
+
|
129
|
+
def st_area
|
130
|
+
Arel::Nodes::SpatialArea.new(self)
|
131
|
+
end
|
132
|
+
|
133
|
+
def distance_operator(other)
|
134
|
+
Arel::Nodes::SpatialDistanceOperator.new(self, other)
|
135
|
+
end
|
136
|
+
|
137
|
+
alias :'<->' :distance_operator
|
59
138
|
end
|
60
139
|
end
|
61
140
|
|
@@ -112,6 +191,52 @@ module Arel
|
|
112
191
|
visit_spatial_operand(node.value, collector)
|
113
192
|
end
|
114
193
|
|
194
|
+
def visit_Arel_Nodes_SpatialIntersects(node, collector)
|
195
|
+
collector << "ST_Intersects("
|
196
|
+
visit(node.left, collector)
|
197
|
+
collector << ", "
|
198
|
+
visit_spatial_operand(node.right, collector)
|
199
|
+
collector << ")"
|
200
|
+
end
|
201
|
+
|
202
|
+
def visit_Arel_Nodes_SpatialDWithin(node, collector)
|
203
|
+
collector << "ST_DWithin("
|
204
|
+
visit(node.left, collector)
|
205
|
+
collector << ", "
|
206
|
+
visit_spatial_operand(node.right, collector)
|
207
|
+
collector << ", "
|
208
|
+
collector << node.distance.to_s
|
209
|
+
collector << ")"
|
210
|
+
end
|
211
|
+
|
212
|
+
def visit_Arel_Nodes_SpatialBuffer(node, collector)
|
213
|
+
collector << "ST_Buffer("
|
214
|
+
visit(node.left, collector)
|
215
|
+
collector << ", "
|
216
|
+
collector << node.right.to_s
|
217
|
+
collector << ")"
|
218
|
+
end
|
219
|
+
|
220
|
+
def visit_Arel_Nodes_SpatialTransform(node, collector)
|
221
|
+
collector << "ST_Transform("
|
222
|
+
visit(node.left, collector)
|
223
|
+
collector << ", "
|
224
|
+
collector << node.right.to_s
|
225
|
+
collector << ")"
|
226
|
+
end
|
227
|
+
|
228
|
+
def visit_Arel_Nodes_SpatialArea(node, collector)
|
229
|
+
collector << "ST_Area("
|
230
|
+
visit(node.expr, collector)
|
231
|
+
collector << ")"
|
232
|
+
end
|
233
|
+
|
234
|
+
def visit_Arel_Nodes_SpatialDistanceOperator(node, collector)
|
235
|
+
visit(node.left, collector)
|
236
|
+
collector << " <-> "
|
237
|
+
visit_spatial_operand(node.right, collector)
|
238
|
+
end
|
239
|
+
|
115
240
|
private
|
116
241
|
|
117
242
|
def visit_spatial_operand(operand, collector)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-postgis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- lib/active_record/connection_adapters/postgis/spatial_column_methods.rb
|
81
81
|
- lib/active_record/connection_adapters/postgis/spatial_column_type.rb
|
82
82
|
- lib/active_record/connection_adapters/postgis/table_definition.rb
|
83
|
+
- lib/active_record/connection_adapters/postgis/test_helpers.rb
|
83
84
|
- lib/active_record/connection_adapters/postgis/type/geography.rb
|
84
85
|
- lib/active_record/connection_adapters/postgis/type/geometry.rb
|
85
86
|
- lib/active_record/connection_adapters/postgis/type/geometry_collection.rb
|
@@ -92,6 +93,7 @@ files:
|
|
92
93
|
- lib/active_record/connection_adapters/postgis/type/spatial.rb
|
93
94
|
- lib/active_record/connection_adapters/postgis/version.rb
|
94
95
|
- lib/activerecord-postgis.rb
|
96
|
+
- lib/activerecord-postgis/test_helper.rb
|
95
97
|
- lib/arel/visitors/postgis.rb
|
96
98
|
homepage: https://github.com/seuros/activerecord-postgis
|
97
99
|
licenses:
|
@@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
116
|
- !ruby/object:Gem::Version
|
115
117
|
version: '0'
|
116
118
|
requirements: []
|
117
|
-
rubygems_version: 3.6.
|
119
|
+
rubygems_version: 3.6.9
|
118
120
|
specification_version: 4
|
119
121
|
summary: PostGIS Type support for ActiveRecord
|
120
122
|
test_files: []
|