IMPatternMatcher.java

/*
 * Copyright (c) 2023 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.operation.relateng;

import org.locationtech.jts.geom.Dimension;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.IntersectionMatrix;
import org.locationtech.jts.geom.Location;

/**
 * A predicate that matches a DE-9IM pattern.
 * 
 * <h3>FUTURE WORK</h3>
 * Extend the expressiveness of the DE-9IM pattern language to allow:
 * <ul>
 * <li>Combining patterns via disjunction using "|".
 * <li>Limiting patterns via geometry dimension.  
 * A dimension limit specifies the allowable dimensions 
 * for both or individual geometries as [d] or [ab] or [ab;cd]
 * </ul>
 * 
 * @author Martin Davis
 *
 */
class IMPatternMatcher extends IMPredicate
{
  private String imPattern = null;
  private IntersectionMatrix patternMatrix;
  
  public IMPatternMatcher(String imPattern) {
    this.imPattern = imPattern;
    this.patternMatrix = new IntersectionMatrix(imPattern);
  }
  
  public String name() { return "IMPattern"; }
  
  //TODO: implement requiresExteriorCheck by inspecting matrix entries for E
  
  public void init(Envelope envA, Envelope envB) {
    super.init(dimA, dimB);
    //-- if pattern specifies any non-E/non-E interaction, envelopes must not be disjoint
    boolean requiresInteraction = requireInteraction(patternMatrix);
    boolean isDisjoint = envA.disjoint(envB);
    setValueIf(false, requiresInteraction && isDisjoint);
  }

  @Override
  public boolean requireInteraction() {
    return requireInteraction(patternMatrix);
  }
  
  private static boolean requireInteraction(IntersectionMatrix im) {
    boolean requiresInteraction = 
        isInteraction(im.get(Location.INTERIOR, Location.INTERIOR))
        || isInteraction(im.get(Location.INTERIOR, Location.BOUNDARY))
        || isInteraction(im.get(Location.BOUNDARY, Location.INTERIOR))
        || isInteraction(im.get(Location.BOUNDARY, Location.BOUNDARY));
    return requiresInteraction;
  }
  
  private static boolean isInteraction(int imDim) {
    return imDim == Dimension.TRUE || imDim >= Dimension.P;
  }

  @Override
  public boolean isDetermined() {
    /**
     * Matrix entries only increase in dimension as topology is computed.
     * The predicate can be short-circuited (as false) if
     * any computed entry is greater than the mask value. 
     */
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        int patternEntry = patternMatrix.get(i, j);
        
        if (patternEntry == Dimension.DONTCARE)
          continue;
        
        int matrixVal = getDimension(i, j);
        
        //-- mask entry TRUE requires a known matrix entry
        if (patternEntry == Dimension.TRUE) {
          if (matrixVal < 0)
            return false;
        }
        //-- result is known (false) if matrix entry has exceeded mask
        else if (matrixVal > patternEntry)
          return true;
      }
    }
    return false;
  }
  
  @Override
  public boolean valueIM() {
    boolean val = intMatrix.matches(imPattern);
    return val;
  }
  
  public String toString() {
    return name() + "(" + imPattern + ")";
  }
}