What are expressions and conditions?

Expression

An expression is an opaque object that returns a value upon evaluation.

Condition

A condition is a case of an expression that always returns a Boolean value.

Conditions are used to identify particular sub-sets of data such that specific actions can be applied on the sub-set only. Conditions are not modeled in the API as a separate class.

Expressions use the method isCondition to indicate that their result is a Boolean value.

Creating expressions and conditions

Expressions and conditions are created using the factory class ExpressionFactory.

For example:

Program (C++): Creating a condition
Expression literal1 = ExpressionFactory::literal(std::string("motorway"));
Expression literal2 = ExpressionFactory::literal(std::string("primary"));
Expression valueReference = ExpressionFactory::valueReference("roadType");
Expression equal1 = ExpressionFactory::equal(valueReference, literal1);
Expression equal2 = ExpressionFactory::equal(valueReference, literal2);

std::vector<Expression> qeVector = {equal1, equal2};
Expression condition = ExpressionFactory::orOp(qeVector);

The result of the example is:

Program (C++): Result of the example condition
std::string expected = "((prop(roadType) = 'motorway') OR (prop(roadType) = 'primary'))";

This is an example of bounds expression:

Program (C++): Creating a bounds expression
auto referenceExpected = CoordinateReferenceProvider::create("EPSG:4326");
auto reference = referenceExpected.value();
auto bounds1 = std::make_shared<Bounds>(reference, Coordinate{15, 15}, 20, 20, 1000);
auto bounds2 = std::make_shared<Bounds>(reference, Coordinate{10, 10}, 10, 10, 1000);
auto expression = ExpressionFactory::bbox(ExpressionFactory::literal({bounds1}), ExpressionFactory::literal({bounds2}));
auto expected = "Bounds [x:15, y:15, w:20, h:20] Intersects Bounds [x:10, y:10, w:10, h:10]";

Traversing an expression hierarchy

The expressions returned from the expression factory are opaque objects. To traverse the expression hierarchy, you can use a visitor IExpressionVisitor.

This is an example of traversing an expression tree with a visitor:

Program (C++): Traversing an expression tree with a visitor
class ToStringExpressionVisitor : public IExpressionVisitor {
  std::string result;
public:
  std::string getResult() {
    return result;
  }
  void visitLiteral(const ExpressionValue& value) override {
    if (value.getDataType() == DataType::getStringType()) {
      result += "'" + value.stringValue() + "'";
    } else if (value.getDataType() == DataType::getDoubleType()) {
      result += std::to_string(value.doubleValue());
    } else if (value.getDataType() == DataType::getIntType()) {
      result += std::to_string(value.intValue());
    } else if (value.getDataType() == DataType::getBooleanType()) {
      result += std::to_string(value.boolValue());
    } else if (value.getDataType() == DataType::getGeometryType()) {
      auto bounds = dynamic_cast<Bounds*>(value.geometryValue().get());
      if (bounds) {
        result += String::format("Bounds [x:{0}, y:{1}, w:{2}, h:{3}]", bounds->getLocation().x, bounds->getLocation().y, bounds->getWidth(),
                                 bounds->getHeight());
      }
    }
  }
  void visitValueReference(const std::string& propertyPath) override {
    result += "prop(" + propertyPath + ")";
  }
  void visitAnd(const std::vector<Expression>& conditions) override {
    result += "(";
    bool first = true;
    for (const Expression& condition : conditions) {
      if (!first) {
        result += " AND ";
      }
      condition.accept(*this);
      first = false;
    }
    result += ")";
  }
  void visitOr(const std::vector<Expression>& conditions) override {
    result += "(";
    bool first = true;
    for (const Expression& condition : conditions) {
      if (!first) {
        result += " OR ";
      }
      condition.accept(*this);
      first = false;
    }
    result += ")";
  }
  void visitNot(const Expression& condition) override {
    result += "!";
    condition.accept(*this);
  }
  void visitEqual(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " = ";
    right.accept(*this);
    result += ")";
  }
  void visitNotEqual(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " != ";
    right.accept(*this);
    result += ")";
  }
  void visitGt(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " > ";
    right.accept(*this);
    result += ")";
  }
  void visitGte(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " >= ";
    right.accept(*this);
    result += ")";
  }
  void visitLt(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " < ";
    right.accept(*this);
    result += ")";
  }
  void visitLte(const Expression& left, const Expression& right) override {
    result += "(";
    left.accept(*this);
    result += " <= ";
    right.accept(*this);
    result += ")";
  }
  void visitBBox(const Expression& left, const Expression& right) override {
    left.accept(*this);
    result += " Intersects ";
    right.accept(*this);
  }
  void visitBBox(const Expression& expression) override {
    result += "Intersects ";
    expression.accept(*this);
  }
};