[libshumate] vector: Add filter expressions



commit 45e461b81f3730bfe7e64f41cb57e5160f14f684
Author: James Westman <james jwestman net>
Date:   Sat Aug 28 23:16:37 2021 -0500

    vector: Add filter expressions
    
    These expressions are used primarily to show only certain features in a
    layer (for example, a "landuse" layer might only cover certain
    landuses).

 demos/map-style.json                               |  12 +
 shumate/meson.build                                |   2 +
 shumate/shumate-vector-style.c                     |   5 +-
 .../shumate-vector-expression-filter-private.h     |  31 ++
 shumate/vector/shumate-vector-expression-filter.c  | 393 +++++++++++++++++++++
 shumate/vector/shumate-vector-expression.c         |  11 +-
 shumate/vector/shumate-vector-layer.c              |  15 +-
 .../vector/shumate-vector-render-scope-private.h   |   2 +
 shumate/vector/shumate-vector-render-scope.c       |  50 +++
 shumate/vector/shumate-vector-value-private.h      |   3 +
 shumate/vector/shumate-vector-value.c              |  30 ++
 tests/data/0.pbf                                   | Bin 0 -> 435 bytes
 tests/data/tests.gresource.xml                     |   1 +
 tests/vector-expression.c                          | 119 +++++++
 14 files changed, 665 insertions(+), 9 deletions(-)
---
diff --git a/demos/map-style.json b/demos/map-style.json
index 79d3c2c..52dc5c0 100644
--- a/demos/map-style.json
+++ b/demos/map-style.json
@@ -24,6 +24,18 @@
       "id": "country_boundary",
       "type": "line",
       "source-layer": "admin_boundary",
+      "filter": ["==", "admin_level", 2],
+      "paint": {
+        "line-color": "#9a9996",
+        "line-opacity": 0.7,
+        "line-width": 1.2
+      }
+    },
+    {
+      "id": "state_boundary",
+      "type": "line",
+      "source-layer": "admin_boundary",
+      "filter": ["==", "admin_level", 4],
       "paint": {
         "line-color": "#9a9996",
         "line-opacity": 0.5,
diff --git a/shumate/meson.build b/shumate/meson.build
index e49847f..08acdc7 100644
--- a/shumate/meson.build
+++ b/shumate/meson.build
@@ -28,6 +28,7 @@ libshumate_private_h = [
 
   'vector/shumate-vector-background-layer-private.h',
   'vector/shumate-vector-expression-private.h',
+  'vector/shumate-vector-expression-filter-private.h',
   'vector/shumate-vector-expression-interpolate-private.h',
   'vector/shumate-vector-expression-literal-private.h',
   'vector/shumate-vector-fill-layer-private.h',
@@ -65,6 +66,7 @@ libshumate_sources = [
   'vector/shumate-vector-background-layer.c',
   'vector/shumate-vector-expression.c',
   'vector/shumate-vector-expression-interpolate.c',
+  'vector/shumate-vector-expression-filter.c',
   'vector/shumate-vector-expression-literal.c',
   'vector/shumate-vector-fill-layer.c',
   'vector/shumate-vector-layer.c',
diff --git a/shumate/shumate-vector-style.c b/shumate/shumate-vector-style.c
index c692e4a..6726a5c 100644
--- a/shumate/shumate-vector-style.c
+++ b/shumate/shumate-vector-style.c
@@ -173,7 +173,10 @@ shumate_vector_style_initable_init (GInitable     *initable,
             return FALSE;
 
           if (!(layer = shumate_vector_layer_create_from_json (layer_obj, error)))
-            return FALSE;
+            {
+              g_prefix_error (error, "layer '%s': ", json_object_get_string_member (layer_obj, "id"));
+              return FALSE;
+            }
 
           g_ptr_array_add (self->layers, layer);
         }
diff --git a/shumate/vector/shumate-vector-expression-filter-private.h 
b/shumate/vector/shumate-vector-expression-filter-private.h
new file mode 100644
index 0000000..78a0072
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-filter-private.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 James Westman <james jwestman net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "shumate-vector-expression-private.h"
+
+G_BEGIN_DECLS
+
+#define SHUMATE_TYPE_VECTOR_EXPRESSION_FILTER (shumate_vector_expression_filter_get_type())
+G_DECLARE_FINAL_TYPE (ShumateVectorExpressionFilter, shumate_vector_expression_filter, SHUMATE, 
VECTOR_EXPRESSION_FILTER, ShumateVectorExpression)
+
+ShumateVectorExpression *shumate_vector_expression_filter_from_json_array (JsonArray  *array,
+                                                                           GError    **error);
+
+G_END_DECLS
diff --git a/shumate/vector/shumate-vector-expression-filter.c 
b/shumate/vector/shumate-vector-expression-filter.c
new file mode 100644
index 0000000..ecca473
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-filter.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2021 James Westman <james jwestman net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "shumate-vector-style.h"
+#include "shumate-vector-expression-filter-private.h"
+#include "shumate-vector-expression-literal-private.h"
+
+typedef enum {
+  EXPR_NOT,
+  EXPR_NONE,
+  EXPR_ANY,
+  EXPR_ALL,
+  EXPR_HAS,
+  EXPR_NOT_HAS,
+  EXPR_GET,
+  EXPR_IN,
+  EXPR_NOT_IN,
+  EXPR_EQ,
+  EXPR_NE,
+  EXPR_GT,
+  EXPR_LT,
+  EXPR_GE,
+  EXPR_LE,
+} ExpressionType;
+
+struct _ShumateVectorExpressionFilter
+{
+  ShumateVectorExpression parent_instance;
+
+  ExpressionType type;
+  GPtrArray *expressions;
+};
+
+G_DEFINE_TYPE (ShumateVectorExpressionFilter, shumate_vector_expression_filter, 
SHUMATE_TYPE_VECTOR_EXPRESSION)
+
+
+ShumateVectorExpression *
+shumate_vector_expression_filter_from_json_array (JsonArray *array, GError **error)
+{
+  g_autoptr(ShumateVectorExpressionFilter) self = g_object_new (SHUMATE_TYPE_VECTOR_EXPRESSION_FILTER, NULL);
+  JsonNode *op_node;
+  const char *op;
+  int expect_exprs = -1;
+  int expect_ge_exprs = -1;
+
+  if (json_array_get_length (array) == 0)
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Expected first element of filter array to be a string");
+      return NULL;
+    }
+
+  op_node = json_array_get_element (array, 0);
+  if (!JSON_NODE_HOLDS_VALUE (op_node) || json_node_get_value_type (op_node) != G_TYPE_STRING)
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Expected first element of filter array to be a string");
+      return NULL;
+    }
+
+  op = json_node_get_string (op_node);
+
+  if (g_strcmp0 ("!", op) == 0)
+    {
+      self->type = EXPR_NOT;
+      expect_exprs = 1;
+    }
+  else if (g_strcmp0 ("none", op) == 0)
+    self->type = EXPR_NONE;
+  else if (g_strcmp0 ("any", op) == 0)
+    self->type = EXPR_ANY;
+  else if (g_strcmp0 ("all", op) == 0)
+    self->type = EXPR_ALL;
+  else if (g_strcmp0 ("has", op) == 0)
+    {
+      self->type = EXPR_HAS;
+      expect_exprs = 1;
+    }
+  else if (g_strcmp0 ("!has", op) == 0)
+    {
+      self->type = EXPR_NOT_HAS;
+      expect_exprs = 1;
+    }
+  else if (g_strcmp0 ("in", op) == 0)
+    {
+      self->type = EXPR_IN;
+      expect_ge_exprs = 1;
+    }
+  else if (g_strcmp0 ("!in", op) == 0)
+    {
+      self->type = EXPR_NOT_IN;
+      expect_ge_exprs = 1;
+    }
+  else if (g_strcmp0 ("==", op) == 0)
+    {
+      self->type = EXPR_EQ;
+      expect_exprs = 2;
+    }
+  else if (g_strcmp0 ("!=", op) == 0)
+    {
+      self->type = EXPR_NE;
+      expect_exprs = 2;
+    }
+  else if (g_strcmp0 (">", op) == 0)
+    {
+      self->type = EXPR_GT;
+      expect_exprs = 2;
+    }
+  else if (g_strcmp0 ("<", op) == 0)
+    {
+      self->type = EXPR_LT;
+      expect_exprs = 2;
+    }
+  else if (g_strcmp0 (">=", op) == 0)
+    {
+      self->type = EXPR_GE;
+      expect_exprs = 2;
+    }
+  else if (g_strcmp0 ("<=", op) == 0)
+    {
+      self->type = EXPR_LE;
+      expect_exprs = 2;
+    }
+  else
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Unrecognized operator %s", op);
+      return FALSE;
+    }
+
+  if (expect_exprs > 0 && json_array_get_length (array) - 1 != expect_exprs)
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Operator `%s` expected exactly %d arguments, got %d",
+                   op, expect_exprs, json_array_get_length (array) - 1);
+      return FALSE;
+    }
+
+  if (expect_ge_exprs > 0 && json_array_get_length (array) - 1 < expect_ge_exprs)
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Operator `%s` expected at least %d arguments, got %d",
+                   op, expect_ge_exprs, json_array_get_length (array) - 1);
+      return FALSE;
+    }
+
+  for (int i = 1; i < json_array_get_length (array); i ++)
+    {
+      JsonNode *arg = json_array_get_element (array, i);
+      g_autoptr(ShumateVectorExpression) expr = NULL;
+
+      if (i == 1
+          && expect_exprs == 2
+          && JSON_NODE_HOLDS_VALUE (arg)
+          && json_node_get_value_type (arg) == G_TYPE_STRING)
+        {
+          /* If the first argument of 2-argument function is a string,
+           * convert it to a GET expression so we can do things like
+           * ["==", "admin_level", 2] */
+          g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+          expr = g_object_new (SHUMATE_TYPE_VECTOR_EXPRESSION_FILTER, NULL);
+          ((ShumateVectorExpressionFilter *)expr)->type = EXPR_GET;
+
+          shumate_vector_value_set_string (&value, json_node_get_string (arg));
+          g_ptr_array_add (((ShumateVectorExpressionFilter *)expr)->expressions, 
shumate_vector_expression_literal_new (&value));
+        }
+      else if (!(expr = shumate_vector_expression_from_json (arg, error)))
+        return NULL;
+
+      g_ptr_array_add (self->expressions, g_steal_pointer (&expr));
+    }
+
+  return (ShumateVectorExpression *)g_steal_pointer (&self);
+}
+
+
+static void
+shumate_vector_expression_filter_finalize (GObject *object)
+{
+  ShumateVectorExpressionFilter *self = (ShumateVectorExpressionFilter *)object;
+
+  g_clear_pointer (&self->expressions, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (shumate_vector_expression_filter_parent_class)->finalize (object);
+}
+
+
+static gboolean
+shumate_vector_expression_filter_eval (ShumateVectorExpression  *expr,
+                                       ShumateVectorRenderScope *scope,
+                                       ShumateVectorValue       *out)
+{
+  ShumateVectorExpressionFilter *self = (ShumateVectorExpressionFilter *)expr;
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  g_auto(ShumateVectorValue) value2 = SHUMATE_VECTOR_VALUE_INIT;
+  gboolean inverted = FALSE;
+  gboolean boolean;
+  double number, number2;
+  g_autofree char *string = NULL;
+  ShumateVectorExpression **expressions = (ShumateVectorExpression **)self->expressions->pdata;
+  guint n_expressions = self->expressions->len;
+
+  switch (self->type)
+    {
+    case EXPR_NOT:
+      g_assert (n_expressions == 1);
+
+      if (!shumate_vector_expression_eval (expressions[0], scope, &value))
+        return FALSE;
+      if (!shumate_vector_value_get_boolean (&value, &boolean))
+        return FALSE;
+
+      shumate_vector_value_set_boolean (out, !boolean);
+      return TRUE;
+
+    case EXPR_NONE:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_ANY:
+      for (int i = 0; i < n_expressions; i ++)
+        {
+          if (!shumate_vector_expression_eval (expressions[i], scope, &value))
+            return FALSE;
+          if (!shumate_vector_value_get_boolean (&value, &boolean))
+            return FALSE;
+
+          if (boolean)
+            {
+              shumate_vector_value_set_boolean (out, TRUE ^ inverted);
+              return TRUE;
+            }
+        }
+
+      shumate_vector_value_set_boolean (out, FALSE ^ inverted);
+      return TRUE;
+
+    case EXPR_ALL:
+      for (int i = 0; i < n_expressions; i ++)
+        {
+          if (!shumate_vector_expression_eval (expressions[i], scope, &value))
+            return FALSE;
+          if (!shumate_vector_value_get_boolean (&value, &boolean))
+            return FALSE;
+
+          if (!boolean)
+            {
+              shumate_vector_value_set_boolean (out, FALSE);
+              return TRUE;
+            }
+        }
+
+      shumate_vector_value_set_boolean (out, TRUE);
+      return TRUE;
+
+    case EXPR_NOT_HAS:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_HAS:
+      g_assert (n_expressions == 1);
+
+      string = shumate_vector_expression_eval_string (expressions[0], scope, NULL);
+      shumate_vector_render_scope_get_variable (scope, string, &value);
+      shumate_vector_value_set_boolean (out, !shumate_vector_value_is_null (&value) ^ inverted);
+      return TRUE;
+
+    case EXPR_GET:
+      g_assert (n_expressions == 1);
+
+      string = shumate_vector_expression_eval_string (expressions[0], scope, NULL);
+      shumate_vector_render_scope_get_variable (scope, string, &value);
+      shumate_vector_value_copy (&value, out);
+      return TRUE;
+
+    case EXPR_NOT_IN:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_IN:
+      g_assert (n_expressions >= 1);
+
+      shumate_vector_expression_eval (expressions[0], scope, &value);
+
+      for (int i = 1; i < n_expressions; i ++)
+        {
+          shumate_vector_expression_eval (expressions[i], scope, &value2);
+          if (shumate_vector_value_equal (&value, &value2))
+            {
+              shumate_vector_value_set_boolean (out, TRUE ^ inverted);
+              return TRUE;
+            }
+        }
+
+      shumate_vector_value_set_boolean (out, FALSE ^ inverted);
+      return TRUE;
+
+    case EXPR_NE:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_EQ:
+      g_assert (n_expressions == 2);
+
+      if (!shumate_vector_expression_eval (expressions[0], scope, &value))
+        return FALSE;
+      if (!shumate_vector_expression_eval (expressions[1], scope, &value2))
+        return FALSE;
+
+      shumate_vector_value_set_boolean (out, shumate_vector_value_equal (&value, &value2) ^ inverted);
+      return TRUE;
+
+    case EXPR_LE:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_GT:
+      g_assert (n_expressions == 2);
+
+      if (!shumate_vector_expression_eval (expressions[0], scope, &value))
+        return FALSE;
+      if (!shumate_vector_expression_eval (expressions[1], scope, &value2))
+        return FALSE;
+
+      if (!shumate_vector_value_get_number (&value, &number))
+        return FALSE;
+      if (!shumate_vector_value_get_number (&value2, &number2))
+        return FALSE;
+
+      shumate_vector_value_set_boolean (out, (number > number2) ^ inverted);
+      return TRUE;
+
+    case EXPR_GE:
+      inverted = TRUE;
+      G_GNUC_FALLTHROUGH;
+    case EXPR_LT:
+      g_assert (n_expressions == 2);
+
+      if (!shumate_vector_expression_eval (expressions[0], scope, &value))
+        return FALSE;
+      if (!shumate_vector_expression_eval (expressions[1], scope, &value2))
+        return FALSE;
+
+      if (!shumate_vector_value_get_number (&value, &number))
+        return FALSE;
+      if (!shumate_vector_value_get_number (&value2, &number2))
+        return FALSE;
+
+      shumate_vector_value_set_boolean (out, (number < number2) ^ inverted);
+      return TRUE;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+shumate_vector_expression_filter_class_init (ShumateVectorExpressionFilterClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ShumateVectorExpressionClass *expr_class = SHUMATE_VECTOR_EXPRESSION_CLASS (klass);
+
+  object_class->finalize = shumate_vector_expression_filter_finalize;
+  expr_class->eval = shumate_vector_expression_filter_eval;
+}
+
+
+static void
+shumate_vector_expression_filter_init (ShumateVectorExpressionFilter *self)
+{
+  self->expressions = g_ptr_array_new_with_free_func (g_object_unref);
+}
diff --git a/shumate/vector/shumate-vector-expression.c b/shumate/vector/shumate-vector-expression.c
index 14862a2..045bfd1 100644
--- a/shumate/vector/shumate-vector-expression.c
+++ b/shumate/vector/shumate-vector-expression.c
@@ -18,6 +18,7 @@
 
 #include "shumate-vector-style.h"
 #include "shumate-vector-expression-private.h"
+#include "shumate-vector-expression-filter-private.h"
 #include "shumate-vector-expression-interpolate-private.h"
 #include "shumate-vector-expression-literal-private.h"
 #include "shumate-vector-value-private.h"
@@ -50,14 +51,10 @@ shumate_vector_expression_from_json (JsonNode *json, GError **error)
     }
   else if (JSON_NODE_HOLDS_OBJECT (json))
     return shumate_vector_expression_interpolate_from_json_obj (json_node_get_object (json), error);
+  else if (JSON_NODE_HOLDS_ARRAY (json))
+    return shumate_vector_expression_filter_from_json_array (json_node_get_array (json), error);
   else
-    {
-      g_set_error (error,
-                   SHUMATE_STYLE_ERROR,
-                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
-                   "Unsupported expression type");
-      return NULL;
-    }
+    g_assert_not_reached ();
 }
 
 
diff --git a/shumate/vector/shumate-vector-layer.c b/shumate/vector/shumate-vector-layer.c
index 8bacdc6..f665e79 100644
--- a/shumate/vector/shumate-vector-layer.c
+++ b/shumate/vector/shumate-vector-layer.c
@@ -17,6 +17,7 @@
 
 #include <json-glib/json-glib.h>
 #include "shumate-vector-background-layer-private.h"
+#include "shumate-vector-expression-private.h"
 #include "shumate-vector-fill-layer-private.h"
 #include "shumate-vector-layer-private.h"
 #include "shumate-vector-line-layer-private.h"
@@ -28,6 +29,8 @@ typedef struct
   double minzoom;
   double maxzoom;
   char *source_layer;
+  ShumateVectorExpression *filter;
+
 } ShumateVectorLayerPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (ShumateVectorLayer, shumate_vector_layer, G_TYPE_OBJECT)
@@ -38,6 +41,7 @@ shumate_vector_layer_create_from_json (JsonObject *object, GError **error)
 {
   ShumateVectorLayer *layer;
   ShumateVectorLayerPrivate *priv;
+  JsonNode *filter;
   const char *type = json_object_get_string_member_with_default (object, "type", NULL);
 
   if (type == NULL)
@@ -68,6 +72,13 @@ shumate_vector_layer_create_from_json (JsonObject *object, GError **error)
   priv->maxzoom = json_object_get_double_member_with_default (object, "maxzoom", 1000000000.0);
   priv->source_layer = g_strdup (json_object_get_string_member_with_default (object, "source-layer", NULL));
 
+  filter = json_object_get_member (object, "filter");
+  if (filter != NULL)
+    {
+      if (!(priv->filter = shumate_vector_expression_from_json (filter, error)))
+        return NULL;
+    }
+
   return layer;
 }
 
@@ -79,6 +90,7 @@ shumate_vector_layer_finalize (GObject *object)
   ShumateVectorLayerPrivate *priv = shumate_vector_layer_get_instance_private (self);
 
   g_clear_pointer (&priv->source_layer, g_free);
+  g_clear_object (&priv->filter);
 
   G_OBJECT_CLASS (shumate_vector_layer_parent_class)->finalize (object);
 }
@@ -135,7 +147,8 @@ shumate_vector_layer_render (ShumateVectorLayer *self, ShumateVectorRenderScope
       for (int j = 0; j < scope->layer->n_features; j ++)
         {
           scope->feature = scope->layer->features[j];
-          SHUMATE_VECTOR_LAYER_GET_CLASS (self)->render (self, scope);
+          if (priv->filter == NULL || shumate_vector_expression_eval_boolean (priv->filter, scope, FALSE))
+            SHUMATE_VECTOR_LAYER_GET_CLASS (self)->render (self, scope);
         }
 
       cairo_restore (scope->cr);
diff --git a/shumate/vector/shumate-vector-render-scope-private.h 
b/shumate/vector/shumate-vector-render-scope-private.h
index e28c043..52242f5 100644
--- a/shumate/vector/shumate-vector-render-scope-private.h
+++ b/shumate/vector/shumate-vector-render-scope-private.h
@@ -20,6 +20,7 @@
 #include <glib-object.h>
 #include <cairo/cairo.h>
 #include "vector_tile.pb-c.h"
+#include "shumate-vector-value-private.h"
 
 typedef struct {
   cairo_t *cr;
@@ -35,3 +36,4 @@ typedef struct {
 
 gboolean shumate_vector_render_scope_find_layer (ShumateVectorRenderScope *self, const char *layer_name);
 void shumate_vector_render_scope_exec_geometry (ShumateVectorRenderScope *self);
+void shumate_vector_render_scope_get_variable (ShumateVectorRenderScope *self, const char *variable, 
ShumateVectorValue *value);
diff --git a/shumate/vector/shumate-vector-render-scope.c b/shumate/vector/shumate-vector-render-scope.c
index 58f8a1c..e544b4e 100644
--- a/shumate/vector/shumate-vector-render-scope.c
+++ b/shumate/vector/shumate-vector-render-scope.c
@@ -85,3 +85,53 @@ shumate_vector_render_scope_exec_geometry (ShumateVectorRenderScope *self)
         }
     }
 }
+
+
+void
+shumate_vector_render_scope_get_variable (ShumateVectorRenderScope *self, const char *variable, 
ShumateVectorValue *value)
+{
+  shumate_vector_value_unset (value);
+
+  if (g_strcmp0 (variable, "zoom") == 0)
+    {
+      shumate_vector_value_set_number (value, self->zoom_level);
+      return;
+    }
+
+  if (self->feature == NULL)
+    return;
+
+  if (g_strcmp0 ("$type", variable) == 0)
+    {
+      switch (self->feature->type)
+        {
+        case VECTOR_TILE__TILE__GEOM_TYPE__POINT:
+          shumate_vector_value_set_string (value, "Point");
+          return;
+        case VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING:
+          shumate_vector_value_set_string (value, "LineString");
+          return;
+        case VECTOR_TILE__TILE__GEOM_TYPE__POLYGON:
+          shumate_vector_value_set_string (value, "Polygon");
+          return;
+        default:
+          shumate_vector_value_unset (value);
+          return;
+        }
+    }
+
+  for (int i = 1; i < self->feature->n_tags; i += 2)
+    {
+      int key = self->feature->tags[i - 1];
+      int val = self->feature->tags[i];
+
+      if (key >= self->layer->n_keys || val >= self->layer->n_values)
+        return;
+
+      if (g_strcmp0 (self->layer->keys[key], variable) == 0)
+        {
+          shumate_vector_value_set_from_feature_value (value, self->layer->values[val]);
+          return;
+        }
+    }
+}
diff --git a/shumate/vector/shumate-vector-value-private.h b/shumate/vector/shumate-vector-value-private.h
index a0bfb56..33ad778 100644
--- a/shumate/vector/shumate-vector-value-private.h
+++ b/shumate/vector/shumate-vector-value-private.h
@@ -19,6 +19,7 @@
 
 #include <json-glib/json-glib.h>
 #include <gtk/gtk.h>
+#include "vector_tile.pb-c.h"
 
 #define SHUMATE_VECTOR_COLOR_BLACK ((GdkRGBA) {.red=0, .green=0, .blue=0, .alpha=1})
 
@@ -39,8 +40,10 @@ typedef struct {
 #define SHUMATE_VECTOR_VALUE_INIT ((ShumateVectorValue) {.type = 0})
 
 gboolean shumate_vector_value_set_from_g_value (ShumateVectorValue *self, const GValue *value);
+void shumate_vector_value_set_from_feature_value (ShumateVectorValue *self, VectorTile__Tile__Value *value);
 
 void shumate_vector_value_unset (ShumateVectorValue *self);
+gboolean shumate_vector_value_is_null (ShumateVectorValue *self);
 void shumate_vector_value_copy (ShumateVectorValue *self, ShumateVectorValue *out);
 
 G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (ShumateVectorValue, shumate_vector_value_unset)
diff --git a/shumate/vector/shumate-vector-value.c b/shumate/vector/shumate-vector-value.c
index 294dea8..4d1751e 100644
--- a/shumate/vector/shumate-vector-value.c
+++ b/shumate/vector/shumate-vector-value.c
@@ -62,6 +62,29 @@ shumate_vector_value_set_from_g_value (ShumateVectorValue *self, const GValue *v
   return TRUE;
 }
 
+
+void
+shumate_vector_value_set_from_feature_value (ShumateVectorValue *self, VectorTile__Tile__Value *value)
+{
+  if (value->has_int_value)
+    shumate_vector_value_set_number (self, value->int_value);
+  else if (value->has_uint_value)
+    shumate_vector_value_set_number (self, value->uint_value);
+  else if (value->has_sint_value)
+    shumate_vector_value_set_number (self, value->sint_value);
+  else if (value->has_float_value)
+    shumate_vector_value_set_number (self, value->float_value);
+  else if (value->has_double_value)
+    shumate_vector_value_set_number (self, value->double_value);
+  else if (value->has_bool_value)
+    shumate_vector_value_set_boolean (self, value->bool_value);
+  else if (value->string_value != NULL)
+    shumate_vector_value_set_string (self, value->string_value);
+  else
+    shumate_vector_value_unset (self);
+}
+
+
 void
 shumate_vector_value_unset (ShumateVectorValue *self)
 {
@@ -71,6 +94,13 @@ shumate_vector_value_unset (ShumateVectorValue *self)
 }
 
 
+gboolean
+shumate_vector_value_is_null (ShumateVectorValue *self)
+{
+  return self->type == TYPE_NULL;
+}
+
+
 void
 shumate_vector_value_copy (ShumateVectorValue *self, ShumateVectorValue *out)
 {
diff --git a/tests/data/0.pbf b/tests/data/0.pbf
new file mode 100644
index 0000000..c93ed44
Binary files /dev/null and b/tests/data/0.pbf differ
diff --git a/tests/data/tests.gresource.xml b/tests/data/tests.gresource.xml
index 6359a7a..fb3fe80 100644
--- a/tests/data/tests.gresource.xml
+++ b/tests/data/tests.gresource.xml
@@ -2,5 +2,6 @@
 <gresources>
   <gresource prefix="/org/gnome/shumate/Tests">
     <file>style.json</file>
+    <file>0.pbf</file>
   </gresource>
 </gresources>
diff --git a/tests/vector-expression.c b/tests/vector-expression.c
index 837881e..c9b33e7 100644
--- a/tests/vector-expression.c
+++ b/tests/vector-expression.c
@@ -108,6 +108,122 @@ test_vector_expression_interpolate_color (void)
 }
 
 
+static gboolean
+filter_with_scope (ShumateVectorRenderScope *scope, const char *filter)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node = json_from_string (filter, NULL);
+  g_autoptr(ShumateVectorExpression) expression = shumate_vector_expression_from_json (node, &error);
+
+  g_assert_no_error (error);
+
+  return shumate_vector_expression_eval_boolean (expression, scope, FALSE);
+}
+
+
+static gboolean
+filter (const char *filter)
+{
+  return filter_with_scope (NULL, filter);
+}
+
+
+static void
+test_vector_expression_basic_filter (void)
+{
+  g_assert_true  (filter ("true"));
+  g_assert_false (filter ("false"));
+  g_assert_false (filter ("[\"!\", true]"));
+  g_assert_true  (filter ("[\"!\", false]"));
+  g_assert_true  (filter ("[\"any\", false, true]"));
+  g_assert_false (filter ("[\"any\", false, false]"));
+  g_assert_true  (filter ("[\"none\", false, false]"));
+  g_assert_false (filter ("[\"none\", true, false]"));
+  g_assert_true  (filter ("[\"all\", true, true]"));
+  g_assert_false (filter ("[\"all\", false, true]"));
+
+  g_assert_false (filter ("[\"any\"]"));
+  g_assert_true  (filter ("[\"none\"]"));
+  g_assert_true  (filter ("[\"all\"]"));
+
+  g_assert_true  (filter ("[\"in\", 10, 20, 10, 13]"));
+  g_assert_true  (filter ("[\"!in\", 10, 20, 0, 13]"));
+
+  g_assert_true  (filter ("[\"==\", 10, 10]"));
+  g_assert_false (filter ("[\"==\", 10, 20]"));
+  g_assert_false (filter ("[\"==\", 10, \"10\"]"));
+  g_assert_false (filter ("[\"!=\", 10, 10]"));
+  g_assert_true  (filter ("[\"!=\", 10, 20]"));
+  g_assert_true  (filter ("[\"!=\", 10, \"10\"]"));
+  g_assert_true  (filter ("[\">\", 20, 10]"));
+  g_assert_false (filter ("[\">\", 10, 10]"));
+  g_assert_false (filter ("[\">\", 5, 10]"));
+  g_assert_true  (filter ("[\"<\", 10, 20]"));
+  g_assert_false (filter ("[\"<\", 10, 10]"));
+  g_assert_false (filter ("[\"<\", 10, 5]"));
+  g_assert_true  (filter ("[\">=\", 20, 10]"));
+  g_assert_true  (filter ("[\">=\", 10, 10]"));
+  g_assert_false (filter ("[\">=\", 5, 10]"));
+  g_assert_true  (filter ("[\"<=\", 10, 20]"));
+  g_assert_true  (filter ("[\"<=\", 10, 10]"));
+  g_assert_false (filter ("[\"<=\", 10, 5]"));
+}
+
+
+static void
+test_vector_expression_feature_filter (void)
+{
+  GError *error = NULL;
+  g_autoptr(GBytes) vector_data = NULL;
+  gconstpointer data;
+  gsize len;
+  ShumateVectorRenderScope scope;
+
+  vector_data = g_resources_lookup_data ("/org/gnome/shumate/Tests/0.pbf", G_RESOURCE_LOOKUP_FLAGS_NONE, 
NULL);
+  g_assert_no_error (error);
+
+  data = g_bytes_get_data (vector_data, &len);
+  scope.tile = vector_tile__tile__unpack (NULL, len, data);
+  g_assert_nonnull (scope.tile);
+
+  scope.zoom_level = 10;
+
+  g_assert_true (shumate_vector_render_scope_find_layer (&scope, "helloworld"));
+  scope.feature = scope.layer->features[0];
+
+  g_assert_true  (filter_with_scope (&scope, "[\"==\", \"name\", \"Hello, world!\"]"));
+  g_assert_false (filter_with_scope (&scope, "[\"==\", \"name\", \"Goodbye, world!\"]"));
+  g_assert_true  (filter_with_scope (&scope, "[\"has\", \"name\"]"));
+  g_assert_false (filter_with_scope (&scope, "[\"!has\", \"name\"]"));
+  g_assert_false (filter_with_scope (&scope, "[\"has\", \"name:en\"]"));
+  g_assert_true  (filter_with_scope (&scope, "[\"!has\", \"name:en\"]"));
+  g_assert_true  (filter_with_scope (&scope, "[\"==\", \"$type\", \"Point\"]"));
+  g_assert_true  (filter_with_scope (&scope, "[\"==\", \"zoom\", 10]"));
+}
+
+
+static void
+filter_expect_error (const char *filter)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node = json_from_string (filter, NULL);
+  g_autoptr(ShumateVectorExpression) expression = shumate_vector_expression_from_json (node, &error);
+
+  g_assert_error (error, SHUMATE_STYLE_ERROR, SHUMATE_STYLE_ERROR_INVALID_EXPRESSION);
+  g_assert_null (expression);
+}
+
+static void
+test_vector_expression_filter_errors (void)
+{
+  filter_expect_error ("[\"not an operator\"]");
+  filter_expect_error ("[\"in\"]");
+  filter_expect_error ("[\"==\", 0, 1, 2]");
+  filter_expect_error ("[]");
+  filter_expect_error ("[[]]");
+}
+
+
 int
 main (int argc, char *argv[])
 {
@@ -117,6 +233,9 @@ main (int argc, char *argv[])
   g_test_add_func ("/vector/expression/literal", test_vector_expression_literal);
   g_test_add_func ("/vector/expression/interpolate", test_vector_expression_interpolate);
   g_test_add_func ("/vector/expression/interpolate-color", test_vector_expression_interpolate_color);
+  g_test_add_func ("/vector/expression/basic-filter", test_vector_expression_basic_filter);
+  g_test_add_func ("/vector/expression/feature-filter", test_vector_expression_feature_filter);
+  g_test_add_func ("/vector/expression/filter-errors", test_vector_expression_filter_errors);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]