1""" 2Testing functionality for geopandas objects. 3""" 4import warnings 5 6import pandas as pd 7 8from geopandas import GeoDataFrame, GeoSeries 9from geopandas.array import GeometryDtype 10from geopandas import _vectorized 11 12 13def _isna(this): 14 """isna version that works for both scalars and (Geo)Series""" 15 with warnings.catch_warnings(): 16 # GeoSeries.isna will raise a warning about no longer returning True 17 # for empty geometries. This helper is used below always in combination 18 # with an is_empty check to preserve behaviour, and thus we ignore the 19 # warning here to avoid it bubbling up to the user 20 warnings.filterwarnings( 21 "ignore", r"GeoSeries.isna\(\) previously returned", UserWarning 22 ) 23 if hasattr(this, "isna"): 24 return this.isna() 25 elif hasattr(this, "isnull"): 26 return this.isnull() 27 else: 28 return pd.isnull(this) 29 30 31def _geom_equals_mask(this, that): 32 """ 33 Test for geometric equality. Empty or missing geometries are considered 34 equal. 35 36 Parameters 37 ---------- 38 this, that : arrays of Geo objects (or anything that has an `is_empty` 39 attribute) 40 41 Returns 42 ------- 43 Series 44 boolean Series, True if geometries in left equal geometries in right 45 """ 46 47 return ( 48 this.geom_equals(that) 49 | (this.is_empty & that.is_empty) 50 | (_isna(this) & _isna(that)) 51 ) 52 53 54def geom_equals(this, that): 55 """ 56 Test for geometric equality. Empty or missing geometries are considered 57 equal. 58 59 Parameters 60 ---------- 61 this, that : arrays of Geo objects (or anything that has an `is_empty` 62 attribute) 63 64 Returns 65 ------- 66 bool 67 True if all geometries in left equal geometries in right 68 """ 69 70 return _geom_equals_mask(this, that).all() 71 72 73def _geom_almost_equals_mask(this, that): 74 """ 75 Test for 'almost' geometric equality. Empty or missing geometries 76 considered equal. 77 78 This method allows small difference in the coordinates, but this 79 requires coordinates be in the same order for all components of a geometry. 80 81 Parameters 82 ---------- 83 this, that : arrays of Geo objects (or anything that has an `is_empty` 84 property) 85 86 Returns 87 ------- 88 Series 89 boolean Series, True if geometries in left almost equal geometries in right 90 """ 91 92 return ( 93 this.geom_almost_equals(that) 94 | (this.is_empty & that.is_empty) 95 | (_isna(this) & _isna(that)) 96 ) 97 98 99def geom_almost_equals(this, that): 100 """ 101 Test for 'almost' geometric equality. Empty or missing geometries 102 considered equal. 103 104 This method allows small difference in the coordinates, but this 105 requires coordinates be in the same order for all components of a geometry. 106 107 Parameters 108 ---------- 109 this, that : arrays of Geo objects (or anything that has an `is_empty` 110 property) 111 112 Returns 113 ------- 114 bool 115 True if all geometries in left almost equal geometries in right 116 """ 117 118 return _geom_almost_equals_mask(this, that).all() 119 120 121def assert_geoseries_equal( 122 left, 123 right, 124 check_dtype=True, 125 check_index_type=False, 126 check_series_type=True, 127 check_less_precise=False, 128 check_geom_type=False, 129 check_crs=True, 130 normalize=False, 131): 132 """ 133 Test util for checking that two GeoSeries are equal. 134 135 Parameters 136 ---------- 137 left, right : two GeoSeries 138 check_dtype : bool, default False 139 If True, check geo dtype [only included so it's a drop-in replacement 140 for assert_series_equal]. 141 check_index_type : bool, default False 142 Check that index types are equal. 143 check_series_type : bool, default True 144 Check that both are same type (*and* are GeoSeries). If False, 145 will attempt to convert both into GeoSeries. 146 check_less_precise : bool, default False 147 If True, use geom_almost_equals. if False, use geom_equals. 148 check_geom_type : bool, default False 149 If True, check that all the geom types are equal. 150 check_crs: bool, default True 151 If `check_series_type` is True, then also check that the 152 crs matches. 153 normalize: bool, default False 154 If True, normalize the geometries before comparing equality. 155 Typically useful with ``check_less_precise=True``, which uses 156 ``geom_almost_equals`` and requires exact coordinate order. 157 """ 158 assert len(left) == len(right), "%d != %d" % (len(left), len(right)) 159 160 if check_dtype: 161 msg = "dtype should be a GeometryDtype, got {0}" 162 assert isinstance(left.dtype, GeometryDtype), msg.format(left.dtype) 163 assert isinstance(right.dtype, GeometryDtype), msg.format(left.dtype) 164 165 if check_index_type: 166 assert isinstance(left.index, type(right.index)) 167 168 if check_series_type: 169 assert isinstance(left, GeoSeries) 170 assert isinstance(left, type(right)) 171 172 if check_crs: 173 assert left.crs == right.crs 174 else: 175 if not isinstance(left, GeoSeries): 176 left = GeoSeries(left) 177 if not isinstance(right, GeoSeries): 178 right = GeoSeries(right, index=left.index) 179 180 assert left.index.equals(right.index), "index: %s != %s" % (left.index, right.index) 181 182 if check_geom_type: 183 assert (left.type == right.type).all(), "type: %s != %s" % ( 184 left.type, 185 right.type, 186 ) 187 188 if normalize: 189 left = GeoSeries(_vectorized.normalize(left.array.data)) 190 right = GeoSeries(_vectorized.normalize(right.array.data)) 191 192 if not check_crs: 193 with warnings.catch_warnings(): 194 warnings.filterwarnings("ignore", "CRS mismatch", UserWarning) 195 _check_equality(left, right, check_less_precise) 196 else: 197 _check_equality(left, right, check_less_precise) 198 199 200def _truncated_string(geom): 201 """Truncated WKT repr of geom""" 202 s = str(geom) 203 if len(s) > 100: 204 return s[:100] + "..." 205 else: 206 return s 207 208 209def _check_equality(left, right, check_less_precise): 210 assert_error_message = ( 211 "{0} out of {1} geometries are not {3}equal.\n" 212 "Indices where geometries are not {3}equal: {2} \n" 213 "The first not {3}equal geometry:\n" 214 "Left: {4}\n" 215 "Right: {5}\n" 216 ) 217 if check_less_precise: 218 precise = "almost " 219 equal = _geom_almost_equals_mask(left, right) 220 else: 221 precise = "" 222 equal = _geom_equals_mask(left, right) 223 224 if not equal.all(): 225 unequal_left_geoms = left[~equal] 226 unequal_right_geoms = right[~equal] 227 raise AssertionError( 228 assert_error_message.format( 229 len(unequal_left_geoms), 230 len(left), 231 unequal_left_geoms.index.to_list(), 232 precise, 233 _truncated_string(unequal_left_geoms.iloc[0]), 234 _truncated_string(unequal_right_geoms.iloc[0]), 235 ) 236 ) 237 238 239def assert_geodataframe_equal( 240 left, 241 right, 242 check_dtype=True, 243 check_index_type="equiv", 244 check_column_type="equiv", 245 check_frame_type=True, 246 check_like=False, 247 check_less_precise=False, 248 check_geom_type=False, 249 check_crs=True, 250 normalize=False, 251): 252 """ 253 Check that two GeoDataFrames are equal/ 254 255 Parameters 256 ---------- 257 left, right : two GeoDataFrames 258 check_dtype : bool, default True 259 Whether to check the DataFrame dtype is identical. 260 check_index_type, check_column_type : bool, default 'equiv' 261 Check that index types are equal. 262 check_frame_type : bool, default True 263 Check that both are same type (*and* are GeoDataFrames). If False, 264 will attempt to convert both into GeoDataFrame. 265 check_like : bool, default False 266 If true, ignore the order of rows & columns 267 check_less_precise : bool, default False 268 If True, use geom_almost_equals. if False, use geom_equals. 269 check_geom_type : bool, default False 270 If True, check that all the geom types are equal. 271 check_crs: bool, default True 272 If `check_frame_type` is True, then also check that the 273 crs matches. 274 normalize: bool, default False 275 If True, normalize the geometries before comparing equality. 276 Typically useful with ``check_less_precise=True``, which uses 277 ``geom_almost_equals`` and requires exact coordinate order. 278 """ 279 try: 280 # added from pandas 0.20 281 from pandas.testing import assert_frame_equal, assert_index_equal 282 except ImportError: 283 from pandas.util.testing import assert_frame_equal, assert_index_equal 284 285 # instance validation 286 if check_frame_type: 287 assert isinstance(left, GeoDataFrame) 288 assert isinstance(left, type(right)) 289 290 if check_crs: 291 # no crs can be either None or {} 292 if not left.crs and not right.crs: 293 pass 294 else: 295 assert left.crs == right.crs 296 else: 297 if not isinstance(left, GeoDataFrame): 298 left = GeoDataFrame(left) 299 if not isinstance(right, GeoDataFrame): 300 right = GeoDataFrame(right) 301 302 # shape comparison 303 assert left.shape == right.shape, ( 304 "GeoDataFrame shape mismatch, left: {lshape!r}, right: {rshape!r}.\n" 305 "Left columns: {lcols!r}, right columns: {rcols!r}" 306 ).format( 307 lshape=left.shape, rshape=right.shape, lcols=left.columns, rcols=right.columns 308 ) 309 310 if check_like: 311 left, right = left.reindex_like(right), right 312 313 # column comparison 314 assert_index_equal( 315 left.columns, right.columns, exact=check_column_type, obj="GeoDataFrame.columns" 316 ) 317 318 # geometry comparison 319 for col, dtype in left.dtypes.iteritems(): 320 if isinstance(dtype, GeometryDtype): 321 assert_geoseries_equal( 322 left[col], 323 right[col], 324 normalize=normalize, 325 check_dtype=check_dtype, 326 check_less_precise=check_less_precise, 327 check_geom_type=check_geom_type, 328 check_crs=check_crs, 329 ) 330 331 # drop geometries and check remaining columns 332 left2 = left.drop([left._geometry_column_name], axis=1) 333 right2 = right.drop([right._geometry_column_name], axis=1) 334 assert_frame_equal( 335 left2, 336 right2, 337 check_dtype=check_dtype, 338 check_index_type=check_index_type, 339 check_column_type=check_column_type, 340 obj="GeoDataFrame", 341 ) 342