1
1
use crate :: { coordinate_position:: CoordPos , dimensions:: Dimensions } ;
2
2
3
+ use crate :: geometry_cow:: GeometryCow :: Point ;
4
+ use std:: str:: FromStr ;
5
+
3
6
/// Models a *Dimensionally Extended Nine-Intersection Model (DE-9IM)* matrix.
4
7
///
5
8
/// DE-9IM matrix values (such as "212FF1FF2") specify the topological relationship between
@@ -261,6 +264,52 @@ impl IntersectionMatrix {
261
264
pub fn get ( & self , lhs : CoordPos , rhs : CoordPos ) -> Dimensions {
262
265
self . 0 [ lhs] [ rhs]
263
266
}
267
+
268
+ /// Does the intersection matrix match the provided de-9im specification string?
269
+ ///
270
+ /// A de-9im spec string must be 9 characters long, and each character
271
+ /// must be one of the following:
272
+ ///
273
+ /// - 0: matches a 0-dimensional (point) intersection
274
+ /// - 1: matches a 1-dimensional (line) intersection
275
+ /// - 2: matches a 2-dimensional (area) intersection
276
+ /// - f or F: matches only empty dimensions
277
+ /// - t or T: matches anything non-empty
278
+ /// - *: matches anything
279
+ ///
280
+ /// ```
281
+ /// use geo::algorithm::Relate;
282
+ /// use wkt::TryFromWkt;
283
+ /// use geo_types::Polygon;
284
+ ///
285
+ /// let a = Polygon::<f64>::try_from_wkt_str("POLYGON((0 0,4 0,4 4,0 4,0 0))").expect("valid WKT");
286
+ /// let b = Polygon::<f64>::try_from_wkt_str("POLYGON((1 1,4 0,4 4,0 4,1 1))").expect("valid WKT");
287
+ /// let im = a.relate(&b);
288
+ /// assert!(im.matches("212F11FF2").expect("valid de-9im spec"));
289
+ /// assert!(im.matches("TTT***FF2").expect("valid de-9im spec"));
290
+ /// assert!(!im.matches("TTT***FFF").expect("valid de-9im spec"));
291
+ /// ```
292
+ pub fn matches ( & self , spec : & str ) -> Result < bool , InvalidInputError > {
293
+ if spec. len ( ) != 9 {
294
+ return Err ( InvalidInputError :: new ( format ! (
295
+ "de-9im specification must be exactly 9 characters. Got {len}" ,
296
+ len = spec. len( )
297
+ ) ) ) ;
298
+ }
299
+
300
+ let mut chars = spec. chars ( ) ;
301
+ for a in & [ CoordPos :: Inside , CoordPos :: OnBoundary , CoordPos :: Outside ] {
302
+ for b in & [ CoordPos :: Inside , CoordPos :: OnBoundary , CoordPos :: Outside ] {
303
+ let dim_spec = dimension_matcher:: DimensionMatcher :: try_from (
304
+ chars. next ( ) . expect ( "already validated length is 9" ) ,
305
+ ) ?;
306
+ if !dim_spec. matches ( self . 0 [ * a] [ * b] ) {
307
+ return Ok ( false ) ;
308
+ }
309
+ }
310
+ }
311
+ Ok ( true )
312
+ }
264
313
}
265
314
266
315
/// Build an IntersectionMatrix based on a string specification.
@@ -272,11 +321,83 @@ impl IntersectionMatrix {
272
321
/// assert!(intersection_matrix.is_intersects());
273
322
/// assert!(!intersection_matrix.is_contains());
274
323
/// ```
275
- impl std :: str :: FromStr for IntersectionMatrix {
324
+ impl FromStr for IntersectionMatrix {
276
325
type Err = InvalidInputError ;
277
326
fn from_str ( str : & str ) -> Result < Self , Self :: Err > {
278
327
let mut im = IntersectionMatrix :: empty ( ) ;
279
328
im. set_at_least_from_string ( str) ?;
280
329
Ok ( im)
281
330
}
282
331
}
332
+
333
+ pub ( crate ) mod dimension_matcher {
334
+ use super :: Dimensions ;
335
+ use super :: InvalidInputError ;
336
+
337
+ /// A single letter from a de-9im matching specification like "1*T**FFF*"
338
+ pub ( crate ) enum DimensionMatcher {
339
+ Anything ,
340
+ NonEmpty ,
341
+ Exact ( Dimensions ) ,
342
+ }
343
+
344
+ impl DimensionMatcher {
345
+ pub fn matches ( & self , dim : Dimensions ) -> bool {
346
+ match ( self , dim) {
347
+ ( Self :: Anything , _) => true ,
348
+ ( DimensionMatcher :: NonEmpty , d) => d != Dimensions :: Empty ,
349
+ ( DimensionMatcher :: Exact ( a) , b) => a == & b,
350
+ }
351
+ }
352
+ }
353
+
354
+ impl TryFrom < char > for DimensionMatcher {
355
+ type Error = InvalidInputError ;
356
+
357
+ fn try_from ( value : char ) -> Result < Self , Self :: Error > {
358
+ Ok ( match value {
359
+ '*' => Self :: Anything ,
360
+ 't' | 'T' => Self :: NonEmpty ,
361
+ 'f' | 'F' => Self :: Exact ( Dimensions :: Empty ) ,
362
+ '0' => Self :: Exact ( Dimensions :: ZeroDimensional ) ,
363
+ '1' => Self :: Exact ( Dimensions :: OneDimensional ) ,
364
+ '2' => Self :: Exact ( Dimensions :: TwoDimensional ) ,
365
+ _ => {
366
+ return Err ( InvalidInputError :: new ( format ! (
367
+ "invalid de-9im specification character: {value}"
368
+ ) ) )
369
+ }
370
+ } )
371
+ }
372
+ }
373
+ }
374
+
375
+ #[ cfg( test) ]
376
+ mod tests {
377
+ use super :: * ;
378
+
379
+ fn subject ( ) -> IntersectionMatrix {
380
+ // Topologically, this is a nonsense IM
381
+ IntersectionMatrix :: from_str ( "F00111222" ) . unwrap ( )
382
+ }
383
+
384
+ #[ test]
385
+ fn matches_exactly ( ) {
386
+ assert ! ( subject( ) . matches( "F00111222" ) . unwrap( ) ) ;
387
+ }
388
+
389
+ #[ test]
390
+ fn doesnt_match ( ) {
391
+ assert ! ( !subject( ) . matches( "222222222" ) . unwrap( ) ) ;
392
+ }
393
+
394
+ #[ test]
395
+ fn matches_truthy ( ) {
396
+ assert ! ( subject( ) . matches( "FTTTTTTTT" ) . unwrap( ) ) ;
397
+ }
398
+
399
+ #[ test]
400
+ fn matches_wildcard ( ) {
401
+ assert ! ( subject( ) . matches( "F0011122*" ) . unwrap( ) ) ;
402
+ }
403
+ }
0 commit comments