|
| 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 |
0 commit comments