[libshumate] vector: Add expression system



commit 6e5f7c0ac69eb5c68913f182df5dc9be5ce225f4
Author: James Westman <james jwestman net>
Date:   Fri Aug 27 22:46:04 2021 -0500

    vector: Add expression system

 demos/map-style.json                               |  11 +-
 demos/shumate-demo-window.c                        |  31 ++-
 shumate/meson.build                                |   8 +
 shumate/vector/shumate-vector-background-layer.c   |  35 ++-
 ...shumate-vector-expression-interpolate-private.h |  31 +++
 .../vector/shumate-vector-expression-interpolate.c | 248 +++++++++++++++++++++
 .../shumate-vector-expression-literal-private.h    |  30 +++
 shumate/vector/shumate-vector-expression-literal.c |  71 ++++++
 shumate/vector/shumate-vector-expression-private.h |  63 ++++++
 shumate/vector/shumate-vector-expression.c         | 155 +++++++++++++
 shumate/vector/shumate-vector-fill-layer.c         |  34 ++-
 shumate/vector/shumate-vector-line-layer.c         |  45 +++-
 shumate/vector/shumate-vector-value-private.h      |  60 +++++
 shumate/vector/shumate-vector-value.c              | 206 +++++++++++++++++
 tests/meson.build                                  |   3 +
 tests/vector-expression.c                          | 122 ++++++++++
 tests/vector-value.c                               | 184 +++++++++++++++
 17 files changed, 1303 insertions(+), 34 deletions(-)
---
diff --git a/demos/map-style.json b/demos/map-style.json
index 453ce79..79d3c2c 100644
--- a/demos/map-style.json
+++ b/demos/map-style.json
@@ -12,13 +12,18 @@
       "type": "fill",
       "source-layer": "water",
       "paint": {
-        "fill-color": "#3584e4"
+        "fill-color": {
+          "stops": [
+            [0, "#3584e4"],
+            [5, "#1a5fb4"]
+          ]
+        }
       }
     },
     {
-      "id": "boundary",
+      "id": "country_boundary",
       "type": "line",
-      "source-layer": "boundary",
+      "source-layer": "admin_boundary",
       "paint": {
         "line-color": "#9a9996",
         "line-opacity": 0.5,
diff --git a/demos/shumate-demo-window.c b/demos/shumate-demo-window.c
index 1d24f24..a89369a 100644
--- a/demos/shumate-demo-window.c
+++ b/demos/shumate-demo-window.c
@@ -141,7 +141,7 @@ shumate_demo_window_init (ShumateDemoWindow *self)
   g_autoptr(GBytes) bytes = NULL;
   const char *style_json;
   g_autoptr(ShumateVectorStyle) style = NULL;
-  ShumateMapSource *map_source = NULL;
+  GError *error = NULL;
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
@@ -154,17 +154,24 @@ shumate_demo_window_init (ShumateDemoWindow *self)
 
   bytes = g_resources_lookup_data ("/org/gnome/Shumate/Demo/styles/map-style.json", 
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
   style_json = g_bytes_get_data (bytes, NULL);
-  style = shumate_vector_style_create (style_json, NULL);
-
-  map_source = SHUMATE_MAP_SOURCE (shumate_network_tile_source_new_vector_full (
-    "vector-tiles",
-    "Vector Tiles",
-    "© OpenStreetMap contributors", NULL, 0, 14, 512,
-    SHUMATE_MAP_PROJECTION_MERCATOR,
-    "https://jwestman.pages.gitlab.gnome.org/vector-tile-test-data/world_overview/#Z#/#X#/#Y#.pbf";,
-    style
-  ));
-  shumate_map_source_registry_add (self->registry, map_source);
+
+  if (!(style = shumate_vector_style_create (style_json, &error)))
+    {
+      g_warning ("Failed to create vector map style: %s", error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      ShumateMapSource *map_source = SHUMATE_MAP_SOURCE (shumate_network_tile_source_new_vector_full (
+        "vector-tiles",
+        "Vector Tiles",
+        "© OpenStreetMap contributors", NULL, 0, 5, 512,
+        SHUMATE_MAP_PROJECTION_MERCATOR,
+        "https://jwestman.pages.gitlab.gnome.org/vector-tile-test-data/world_overview/#Z#/#X#/#Y#.pbf";,
+        style
+      ));
+      shumate_map_source_registry_add (self->registry, map_source);
+    }
 
   viewport = shumate_map_get_viewport (self->map);
 
diff --git a/shumate/meson.build b/shumate/meson.build
index 53ce659..e49847f 100644
--- a/shumate/meson.build
+++ b/shumate/meson.build
@@ -27,11 +27,15 @@ libshumate_private_h = [
   'shumate-marker-private.h',
 
   'vector/shumate-vector-background-layer-private.h',
+  'vector/shumate-vector-expression-private.h',
+  'vector/shumate-vector-expression-interpolate-private.h',
+  'vector/shumate-vector-expression-literal-private.h',
   'vector/shumate-vector-fill-layer-private.h',
   'vector/shumate-vector-layer-private.h',
   'vector/shumate-vector-line-layer-private.h',
   'vector/shumate-vector-render-scope-private.h',
   'vector/shumate-vector-utils-private.h',
+  'vector/shumate-vector-value-private.h',
   'vector/vector_tile.pb-c.h',
 ]
 
@@ -59,11 +63,15 @@ libshumate_sources = [
   'shumate-viewport.c',
 
   'vector/shumate-vector-background-layer.c',
+  'vector/shumate-vector-expression.c',
+  'vector/shumate-vector-expression-interpolate.c',
+  'vector/shumate-vector-expression-literal.c',
   'vector/shumate-vector-fill-layer.c',
   'vector/shumate-vector-layer.c',
   'vector/shumate-vector-line-layer.c',
   'vector/shumate-vector-render-scope.c',
   'vector/shumate-vector-utils.c',
+  'vector/shumate-vector-value.c',
   'vector/vector_tile.pb-c.c',
 ]
 
diff --git a/shumate/vector/shumate-vector-background-layer.c 
b/shumate/vector/shumate-vector-background-layer.c
index 462398a..a1b0f7c 100644
--- a/shumate/vector/shumate-vector-background-layer.c
+++ b/shumate/vector/shumate-vector-background-layer.c
@@ -17,14 +17,15 @@
 
 #include <gtk/gtk.h>
 #include "shumate-vector-background-layer-private.h"
+#include "shumate-vector-expression-private.h"
 #include "shumate-vector-utils-private.h"
 
 struct _ShumateVectorBackgroundLayer
 {
   ShumateVectorLayer parent_instance;
 
-  GdkRGBA color;
-  double opacity;
+  ShumateVectorExpression *color;
+  ShumateVectorExpression *opacity;
 };
 
 G_DEFINE_TYPE (ShumateVectorBackgroundLayer, shumate_vector_background_layer, SHUMATE_TYPE_VECTOR_LAYER)
@@ -43,29 +44,51 @@ shumate_vector_background_layer_create_from_json (JsonObject *object, GError **e
       if (!shumate_vector_json_get_object (paint_node, &paint, error))
         return NULL;
 
-      gdk_rgba_parse (&layer->color, json_object_get_string_member_with_default (paint, "background-color", 
"#000000"));
-      layer->opacity = json_object_get_double_member_with_default (paint, "background-opacity", 1.0);
+      if (!(layer->color = shumate_vector_expression_from_json (json_object_get_member (paint, 
"background-color"), error)))
+        return NULL;
+
+      if (!(layer->opacity = shumate_vector_expression_from_json (json_object_get_member (paint, 
"background-opacity"), error)))
+        return NULL;
     }
 
   return (ShumateVectorLayer *)layer;
 }
 
 
+static void
+shumate_vector_background_layer_finalize (GObject *object)
+{
+  ShumateVectorBackgroundLayer *self = SHUMATE_VECTOR_BACKGROUND_LAYER (object);
+
+  g_clear_object (&self->color);
+  g_clear_object (&self->opacity);
+
+  G_OBJECT_CLASS (shumate_vector_background_layer_parent_class)->finalize (object);
+}
+
+
 static void
 shumate_vector_background_layer_render (ShumateVectorLayer *layer, ShumateVectorRenderScope *scope)
 {
   ShumateVectorBackgroundLayer *self = SHUMATE_VECTOR_BACKGROUND_LAYER (layer);
+  GdkRGBA color = SHUMATE_VECTOR_COLOR_BLACK;
+  double opacity;
+
+  shumate_vector_expression_eval_color (self->color, scope, &color);
+  opacity = shumate_vector_expression_eval_number (self->opacity, scope, 1.0);
 
-  gdk_cairo_set_source_rgba (scope->cr, &self->color);
-  cairo_paint_with_alpha (scope->cr, self->opacity);
+  gdk_cairo_set_source_rgba (scope->cr, &color);
+  cairo_paint_with_alpha (scope->cr, opacity);
 }
 
 
 static void
 shumate_vector_background_layer_class_init (ShumateVectorBackgroundLayerClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   ShumateVectorLayerClass *layer_class = SHUMATE_VECTOR_LAYER_CLASS (klass);
 
+  object_class->finalize = shumate_vector_background_layer_finalize;
   layer_class->render = shumate_vector_background_layer_render;
 }
 
diff --git a/shumate/vector/shumate-vector-expression-interpolate-private.h 
b/shumate/vector/shumate-vector-expression-interpolate-private.h
new file mode 100644
index 0000000..a7f5d8f
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-interpolate-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 <json-glib/json-glib.h>
+#include "shumate-vector-expression-private.h"
+
+
+G_BEGIN_DECLS
+
+#define SHUMATE_TYPE_VECTOR_EXPRESSION_INTERPOLATE (shumate_vector_expression_interpolate_get_type())
+G_DECLARE_FINAL_TYPE (ShumateVectorExpressionInterpolate, shumate_vector_expression_interpolate, SHUMATE, 
VECTOR_EXPRESSION_INTERPOLATE, ShumateVectorExpression)
+
+ShumateVectorExpression *shumate_vector_expression_interpolate_from_json_obj (JsonObject *object, GError 
**error);
+
+G_END_DECLS
diff --git a/shumate/vector/shumate-vector-expression-interpolate.c 
b/shumate/vector/shumate-vector-expression-interpolate.c
new file mode 100644
index 0000000..6597e0e
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-interpolate.c
@@ -0,0 +1,248 @@
+/*
+ * 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-interpolate-private.h"
+#include "shumate-vector-expression-literal-private.h"
+#include "shumate-vector-utils-private.h"
+
+typedef struct {
+  double point;
+  ShumateVectorExpression *expr;
+} Stop;
+
+struct _ShumateVectorExpressionInterpolate
+{
+  ShumateVectorExpression parent_instance;
+
+  ShumateVectorExpression *input;
+  double base;
+  GPtrArray *stops;
+};
+
+G_DEFINE_TYPE (ShumateVectorExpressionInterpolate, shumate_vector_expression_interpolate, 
SHUMATE_TYPE_VECTOR_EXPRESSION)
+
+
+ShumateVectorExpression *
+shumate_vector_expression_interpolate_from_json_obj (JsonObject *object, GError **error)
+{
+  g_autoptr(ShumateVectorExpressionInterpolate) self = g_object_new 
(SHUMATE_TYPE_VECTOR_EXPRESSION_INTERPOLATE, NULL);
+  JsonNode *stops_node;
+
+  self->base = json_object_get_double_member_with_default (object, "base", 1.0);
+
+  if ((stops_node = json_object_get_member (object, "stops")))
+    {
+      JsonArray *stops;
+
+      if (!shumate_vector_json_get_array (stops_node, &stops, error))
+        return NULL;
+
+      for (int i = 0, n = json_array_get_length (stops); i < n; i ++)
+        {
+          JsonNode *stop_node = json_array_get_element (stops, i);
+          JsonArray *stop_array;
+          Stop *stop;
+          JsonNode *point_node;
+          JsonNode *value_node;
+          g_auto(GValue) gvalue = G_VALUE_INIT;
+          g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+          if (!shumate_vector_json_get_array (stop_node, &stop_array, error))
+            return NULL;
+
+          if (json_array_get_length (stop_array) != 2)
+            {
+              g_set_error (error,
+                           SHUMATE_STYLE_ERROR,
+                           SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                           "Expected element of \"stops\" to have exactly 2 elements");
+              return NULL;
+            }
+
+          point_node = json_array_get_element (stop_array, 0);
+          value_node = json_array_get_element (stop_array, 1);
+
+          if (!JSON_NODE_HOLDS_VALUE (point_node)
+              || !g_value_type_transformable (json_node_get_value_type (point_node), G_TYPE_DOUBLE))
+            {
+              g_set_error (error,
+                           SHUMATE_STYLE_ERROR,
+                           SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                           "Expected element 1 of \"stops\" to be a number");
+              return NULL;
+            }
+
+          if (!JSON_NODE_HOLDS_VALUE (value_node))
+            {
+              g_set_error (error,
+                           SHUMATE_STYLE_ERROR,
+                           SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                           "Expected element 2 of \"stops\" to be a literal value");
+              return NULL;
+            }
+
+          json_node_get_value (value_node, &gvalue);
+
+          if (!shumate_vector_value_set_from_g_value (&value, &gvalue))
+            {
+              g_set_error (error,
+                           SHUMATE_STYLE_ERROR,
+                           SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                           "Could not parse literal value");
+              return NULL;
+            }
+
+          stop = g_new0 (Stop, 1);
+          stop->point = json_node_get_double (point_node);
+          stop->expr = shumate_vector_expression_literal_new (&value);
+
+          g_ptr_array_add (self->stops, stop);
+        }
+    }
+
+  return (ShumateVectorExpression *)g_steal_pointer (&self);
+}
+
+
+static void
+stop_free (Stop *stop)
+{
+  g_clear_object (&stop->expr);
+  g_free (stop);
+}
+
+
+static double
+lerp_double (double a, double b, double pos)
+{
+  return (b - a) * pos + a;
+}
+
+
+static void
+lerp (ShumateVectorValue *last_value, ShumateVectorValue *next_value, double pos, ShumateVectorValue *out)
+{
+  gdouble last_double, next_double;
+  GdkRGBA last_color, next_color;
+
+  if (shumate_vector_value_get_number (last_value, &last_double)
+      && shumate_vector_value_get_number (next_value, &next_double))
+    shumate_vector_value_set_number (out, lerp_double (last_double, next_double, pos));
+  else if (shumate_vector_value_get_color (last_value, &last_color)
+      && shumate_vector_value_get_color (next_value, &next_color))
+    {
+      GdkRGBA color = {
+        .red = lerp_double (last_color.red, next_color.red, pos),
+        .green = lerp_double (last_color.green, next_color.green, pos),
+        .blue = lerp_double (last_color.blue, next_color.blue, pos),
+        .alpha = lerp_double (last_color.alpha, next_color.alpha, pos),
+      };
+      shumate_vector_value_set_color (out, &color);
+    }
+  else
+    shumate_vector_value_unset (out);
+}
+
+
+static void
+exp_interp (double last_point,
+            double next_point,
+            ShumateVectorValue *last_value,
+            ShumateVectorValue *next_value,
+            double input,
+            double base,
+            ShumateVectorValue *dest)
+{
+  double diff = next_point - last_point;
+  double pos = input - last_point;
+
+  lerp (last_value, next_value, (pow (base, pos) - 1.0) / (pow (base, diff) - 1.0), dest);
+}
+
+
+static void
+shumate_vector_expression_interpolate_finalize (GObject *object)
+{
+  ShumateVectorExpressionInterpolate *self = (ShumateVectorExpressionInterpolate *)object;
+
+  g_clear_object (&self->input);
+  g_clear_pointer (&self->stops, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (shumate_vector_expression_interpolate_parent_class)->finalize (object);
+}
+
+
+static gboolean
+shumate_vector_expression_interpolate_eval (ShumateVectorExpression  *expr,
+                                            ShumateVectorRenderScope *scope,
+                                            ShumateVectorValue       *out)
+{
+  ShumateVectorExpressionInterpolate *self = (ShumateVectorExpressionInterpolate *)expr;
+  double zoom = scope->zoom_level;
+  Stop **stops = (Stop **)self->stops->pdata;
+  guint n_stops = self->stops->len;
+
+  if (n_stops == 0)
+    return FALSE;
+
+  if (zoom < stops[0]->point)
+    return shumate_vector_expression_eval (stops[0]->expr, scope, out);
+
+  for (int i = 1; i < n_stops; i ++)
+    {
+      Stop *last = stops[i - 1];
+      Stop *next = stops[i];
+      if (last->point <= zoom && zoom < next->point)
+        {
+          double pos_norm = (zoom - last->point) / (next->point - last->point);
+          g_auto(ShumateVectorValue) last_value = SHUMATE_VECTOR_VALUE_INIT;
+          g_auto(ShumateVectorValue) next_value = SHUMATE_VECTOR_VALUE_INIT;
+
+          if (!shumate_vector_expression_eval (last->expr, scope, &last_value))
+            return FALSE;
+          if (!shumate_vector_expression_eval (next->expr, scope, &next_value))
+            return FALSE;
+
+          if (self->base == 1.0)
+            lerp (&last_value, &next_value, pos_norm, out);
+          else
+            exp_interp (last->point, next->point, &last_value, &next_value, zoom, self->base, out);
+
+          return TRUE;
+        }
+    }
+
+  return shumate_vector_expression_eval (stops[n_stops - 1]->expr, scope, out);
+}
+
+
+static void
+shumate_vector_expression_interpolate_class_init (ShumateVectorExpressionInterpolateClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ShumateVectorExpressionClass *expr_class = SHUMATE_VECTOR_EXPRESSION_CLASS (klass);
+
+  object_class->finalize = shumate_vector_expression_interpolate_finalize;
+  expr_class->eval = shumate_vector_expression_interpolate_eval;
+}
+
+static void
+shumate_vector_expression_interpolate_init (ShumateVectorExpressionInterpolate *self)
+{
+  self->stops = g_ptr_array_new_with_free_func ((GDestroyNotify) stop_free);
+}
diff --git a/shumate/vector/shumate-vector-expression-literal-private.h 
b/shumate/vector/shumate-vector-expression-literal-private.h
new file mode 100644
index 0000000..46f0847
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-literal-private.h
@@ -0,0 +1,30 @@
+/*
+ * 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-value-private.h"
+#include "shumate-vector-expression-private.h"
+
+G_BEGIN_DECLS
+
+#define SHUMATE_TYPE_VECTOR_EXPRESSION_LITERAL (shumate_vector_expression_literal_get_type())
+G_DECLARE_FINAL_TYPE (ShumateVectorExpressionLiteral, shumate_vector_expression_literal, SHUMATE, 
VECTOR_EXPRESSION_LITERAL, ShumateVectorExpression)
+
+ShumateVectorExpression *shumate_vector_expression_literal_new (ShumateVectorValue *value);
+
+G_END_DECLS
diff --git a/shumate/vector/shumate-vector-expression-literal.c 
b/shumate/vector/shumate-vector-expression-literal.c
new file mode 100644
index 0000000..6f7bdbb
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-literal.c
@@ -0,0 +1,71 @@
+/*
+ * 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-expression-literal-private.h"
+
+struct _ShumateVectorExpressionLiteral
+{
+  ShumateVectorExpression parent_instance;
+
+  ShumateVectorValue value;
+};
+
+G_DEFINE_TYPE (ShumateVectorExpressionLiteral, shumate_vector_expression_literal, 
SHUMATE_TYPE_VECTOR_EXPRESSION)
+
+ShumateVectorExpression *
+shumate_vector_expression_literal_new (ShumateVectorValue *value)
+{
+  ShumateVectorExpressionLiteral *self = g_object_new (SHUMATE_TYPE_VECTOR_EXPRESSION_LITERAL, NULL);
+  shumate_vector_value_copy (value, &self->value);
+  return (ShumateVectorExpression *)self;
+}
+
+static void
+shumate_vector_expression_literal_finalize (GObject *object)
+{
+  ShumateVectorExpressionLiteral *self = (ShumateVectorExpressionLiteral *)object;
+
+  shumate_vector_value_unset (&self->value);
+
+  G_OBJECT_CLASS (shumate_vector_expression_literal_parent_class)->finalize (object);
+}
+
+static gboolean
+shumate_vector_expression_literal_eval (ShumateVectorExpression  *expr,
+                                        ShumateVectorRenderScope *scope,
+                                        ShumateVectorValue       *out)
+{
+  ShumateVectorExpressionLiteral *self = (ShumateVectorExpressionLiteral *)expr;
+  shumate_vector_value_copy (&self->value, out);
+  return TRUE;
+}
+
+static void
+shumate_vector_expression_literal_class_init (ShumateVectorExpressionLiteralClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ShumateVectorExpressionClass *expr_class = SHUMATE_VECTOR_EXPRESSION_CLASS (klass);
+
+  object_class->finalize = shumate_vector_expression_literal_finalize;
+  expr_class->eval = shumate_vector_expression_literal_eval;
+}
+
+static void
+shumate_vector_expression_literal_init (ShumateVectorExpressionLiteral *self)
+{
+}
diff --git a/shumate/vector/shumate-vector-expression-private.h 
b/shumate/vector/shumate-vector-expression-private.h
new file mode 100644
index 0000000..e485bfa
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression-private.h
@@ -0,0 +1,63 @@
+/*
+ * 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 <json-glib/json-glib.h>
+#include "shumate-vector-render-scope-private.h"
+#include "shumate-vector-value-private.h"
+
+G_BEGIN_DECLS
+
+#define SHUMATE_TYPE_VECTOR_EXPRESSION (shumate_vector_expression_get_type())
+G_DECLARE_DERIVABLE_TYPE (ShumateVectorExpression, shumate_vector_expression, SHUMATE, VECTOR_EXPRESSION, 
GObject)
+
+struct _ShumateVectorExpressionClass
+{
+  GObjectClass parent_class;
+
+  gboolean (*eval) (ShumateVectorExpression  *self,
+                    ShumateVectorRenderScope *scope,
+                    ShumateVectorValue       *out);
+};
+
+ShumateVectorExpression *shumate_vector_expression_from_json (JsonNode *json, GError **error);
+
+gboolean shumate_vector_expression_eval (ShumateVectorExpression  *self,
+                                         ShumateVectorRenderScope *scope,
+                                         ShumateVectorValue       *out);
+
+
+
+double shumate_vector_expression_eval_number (ShumateVectorExpression  *self,
+                                              ShumateVectorRenderScope *scope,
+                                              double                    default_val);
+
+gboolean shumate_vector_expression_eval_boolean (ShumateVectorExpression  *self,
+                                                 ShumateVectorRenderScope *scope,
+                                                 gboolean                  default_val);
+
+
+char *shumate_vector_expression_eval_string (ShumateVectorExpression  *self,
+                                             ShumateVectorRenderScope *scope,
+                                             const char               *default_val);
+
+
+void shumate_vector_expression_eval_color (ShumateVectorExpression  *self,
+                                           ShumateVectorRenderScope *scope,
+                                           GdkRGBA                  *color);
+G_END_DECLS
diff --git a/shumate/vector/shumate-vector-expression.c b/shumate/vector/shumate-vector-expression.c
new file mode 100644
index 0000000..14862a2
--- /dev/null
+++ b/shumate/vector/shumate-vector-expression.c
@@ -0,0 +1,155 @@
+/*
+ * 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-private.h"
+#include "shumate-vector-expression-interpolate-private.h"
+#include "shumate-vector-expression-literal-private.h"
+#include "shumate-vector-value-private.h"
+
+
+G_DEFINE_TYPE (ShumateVectorExpression, shumate_vector_expression, G_TYPE_OBJECT)
+
+
+ShumateVectorExpression *
+shumate_vector_expression_from_json (JsonNode *json, GError **error)
+{
+  if (json == NULL || JSON_NODE_HOLDS_NULL (json))
+    return shumate_vector_expression_literal_new (&SHUMATE_VECTOR_VALUE_INIT);
+  else if (JSON_NODE_HOLDS_VALUE (json))
+    {
+      g_auto(GValue) gvalue = G_VALUE_INIT;
+      g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+      json_node_get_value (json, &gvalue);
+      if (!shumate_vector_value_set_from_g_value (&value, &gvalue))
+        {
+          g_set_error (error,
+                       SHUMATE_STYLE_ERROR,
+                       SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                       "Unsupported literal value in expression");
+          return NULL;
+        }
+
+      return shumate_vector_expression_literal_new (&value);
+    }
+  else if (JSON_NODE_HOLDS_OBJECT (json))
+    return shumate_vector_expression_interpolate_from_json_obj (json_node_get_object (json), error);
+  else
+    {
+      g_set_error (error,
+                   SHUMATE_STYLE_ERROR,
+                   SHUMATE_STYLE_ERROR_INVALID_EXPRESSION,
+                   "Unsupported expression type");
+      return NULL;
+    }
+}
+
+
+static gboolean
+shumate_vector_expression_real_eval (ShumateVectorExpression  *self,
+                                     ShumateVectorRenderScope *scope,
+                                     ShumateVectorValue       *out)
+{
+  g_assert_not_reached ();
+}
+
+
+static void
+shumate_vector_expression_class_init (ShumateVectorExpressionClass *klass)
+{
+  klass->eval = shumate_vector_expression_real_eval;
+}
+
+
+static void
+shumate_vector_expression_init (ShumateVectorExpression *self)
+{
+}
+
+
+gboolean
+shumate_vector_expression_eval (ShumateVectorExpression  *self,
+                                ShumateVectorRenderScope *scope,
+                                ShumateVectorValue       *out)
+{
+  g_return_val_if_fail (SHUMATE_IS_VECTOR_EXPRESSION (self), FALSE);
+  return SHUMATE_VECTOR_EXPRESSION_GET_CLASS (self)->eval (self, scope, out);
+}
+
+
+double
+shumate_vector_expression_eval_number (ShumateVectorExpression  *self,
+                                       ShumateVectorRenderScope *scope,
+                                       double                    default_val)
+{
+  double result;
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+  shumate_vector_expression_eval (self, scope, &value);
+
+  if (shumate_vector_value_get_number (&value, &result))
+    return result;
+  else
+    return default_val;
+}
+
+
+gboolean
+shumate_vector_expression_eval_boolean (ShumateVectorExpression  *self,
+                                        ShumateVectorRenderScope *scope,
+                                        gboolean                  default_val)
+{
+  gboolean result;
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+  shumate_vector_expression_eval (self, scope, &value);
+
+  if (shumate_vector_value_get_boolean (&value, &result))
+    return result;
+  else
+    return default_val;
+}
+
+
+char *
+shumate_vector_expression_eval_string (ShumateVectorExpression  *self,
+                                       ShumateVectorRenderScope *scope,
+                                       const char               *default_val)
+{
+  const char *result;
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+
+  shumate_vector_expression_eval (self, scope, &value);
+
+  if (shumate_vector_value_get_string (&value, &result))
+    return g_strdup (result);
+  else
+    return g_strdup (default_val);
+}
+
+
+void
+shumate_vector_expression_eval_color (ShumateVectorExpression  *self,
+                                      ShumateVectorRenderScope *scope,
+                                      GdkRGBA                  *color)
+{
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  shumate_vector_expression_eval (self, scope, &value);
+  shumate_vector_value_get_color (&value, color);
+}
diff --git a/shumate/vector/shumate-vector-fill-layer.c b/shumate/vector/shumate-vector-fill-layer.c
index b29f6a2..080bb7f 100644
--- a/shumate/vector/shumate-vector-fill-layer.c
+++ b/shumate/vector/shumate-vector-fill-layer.c
@@ -16,15 +16,17 @@
  */
 
 #include <gtk/gtk.h>
+#include "shumate-vector-expression-private.h"
 #include "shumate-vector-fill-layer-private.h"
 #include "shumate-vector-utils-private.h"
+#include "shumate-vector-value-private.h"
 
 struct _ShumateVectorFillLayer
 {
   ShumateVectorLayer parent_instance;
 
-  GdkRGBA color;
-  double opacity;
+  ShumateVectorExpression *color;
+  ShumateVectorExpression *opacity;
 };
 
 G_DEFINE_TYPE (ShumateVectorFillLayer, shumate_vector_fill_layer, SHUMATE_TYPE_VECTOR_LAYER)
@@ -43,8 +45,11 @@ shumate_vector_fill_layer_create_from_json (JsonObject *object, GError **error)
       if (!shumate_vector_json_get_object (paint_node, &paint, error))
         return NULL;
 
-      gdk_rgba_parse (&layer->color, json_object_get_string_member_with_default (paint, "fill-color", 
"#000000"));
-      layer->opacity = json_object_get_double_member_with_default (paint, "fill-opacity", 1.0);
+      if (!(layer->color = shumate_vector_expression_from_json (json_object_get_member (paint, 
"fill-color"), error)))
+        return NULL;
+
+      if (!(layer->opacity = shumate_vector_expression_from_json (json_object_get_member (paint, 
"fill-opacity"), error)))
+        return NULL;
     }
 
   return (ShumateVectorLayer *)layer;
@@ -55,19 +60,38 @@ static void
 shumate_vector_fill_layer_render (ShumateVectorLayer *layer, ShumateVectorRenderScope *scope)
 {
   ShumateVectorFillLayer *self = SHUMATE_VECTOR_FILL_LAYER (layer);
+  GdkRGBA color = SHUMATE_VECTOR_COLOR_BLACK;
+  double opacity;
+
+  shumate_vector_expression_eval_color (self->color, scope, &color);
+  opacity = shumate_vector_expression_eval_number (self->opacity, scope, 1.0);
 
   shumate_vector_render_scope_exec_geometry (scope);
 
-  cairo_set_source_rgba (scope->cr, self->color.red, self->color.green, self->color.blue, self->opacity);
+  cairo_set_source_rgba (scope->cr, color.red, color.green, color.blue, opacity);
   cairo_fill (scope->cr);
 }
 
 
+static void
+shumate_vector_fill_layer_finalize (GObject *object)
+{
+  ShumateVectorFillLayer *self = SHUMATE_VECTOR_FILL_LAYER (object);
+
+  g_clear_object (&self->color);
+  g_clear_object (&self->opacity);
+
+  G_OBJECT_CLASS (shumate_vector_fill_layer_parent_class)->finalize (object);
+}
+
+
 static void
 shumate_vector_fill_layer_class_init (ShumateVectorFillLayerClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   ShumateVectorLayerClass *layer_class = SHUMATE_VECTOR_LAYER_CLASS (klass);
 
+  object_class->finalize = shumate_vector_fill_layer_finalize;
   layer_class->render = shumate_vector_fill_layer_render;
 }
 
diff --git a/shumate/vector/shumate-vector-line-layer.c b/shumate/vector/shumate-vector-line-layer.c
index 0c3ca5c..18fae4b 100644
--- a/shumate/vector/shumate-vector-line-layer.c
+++ b/shumate/vector/shumate-vector-line-layer.c
@@ -16,16 +16,18 @@
  */
 
 #include <gtk/gtk.h>
+#include "shumate-vector-expression-private.h"
 #include "shumate-vector-line-layer-private.h"
 #include "shumate-vector-utils-private.h"
+#include "shumate-vector-value-private.h"
 
 struct _ShumateVectorLineLayer
 {
   ShumateVectorLayer parent_instance;
 
-  GdkRGBA color;
-  double opacity;
-  double width;
+  ShumateVectorExpression *color;
+  ShumateVectorExpression *opacity;
+  ShumateVectorExpression *width;
 };
 
 G_DEFINE_TYPE (ShumateVectorLineLayer, shumate_vector_line_layer, SHUMATE_TYPE_VECTOR_LAYER)
@@ -44,24 +46,49 @@ shumate_vector_line_layer_create_from_json (JsonObject *object, GError **error)
       if (!shumate_vector_json_get_object (paint_node, &paint, error))
         return NULL;
 
-      gdk_rgba_parse (&layer->color, json_object_get_string_member_with_default (paint, "line-color", 
"#000000"));
-      layer->opacity = json_object_get_double_member_with_default (paint, "line-opacity", 1.0);
-      layer->width = json_object_get_double_member_with_default (paint, "line-width", 1.0);
+      if (!(layer->color = shumate_vector_expression_from_json (json_object_get_member (paint, 
"line-color"), error)))
+        return NULL;
+
+      if (!(layer->opacity = shumate_vector_expression_from_json (json_object_get_member (paint, 
"line-opacity"), error)))
+        return NULL;
+
+      if (!(layer->width = shumate_vector_expression_from_json (json_object_get_member (paint, 
"line-width"), error)))
+        return NULL;
     }
 
   return (ShumateVectorLayer *)layer;
 }
 
 
+static void
+shumate_vector_line_layer_finalize (GObject *object)
+{
+  ShumateVectorLineLayer *self = SHUMATE_VECTOR_LINE_LAYER (object);
+
+  g_clear_object (&self->color);
+  g_clear_object (&self->opacity);
+  g_clear_object (&self->width);
+
+  G_OBJECT_CLASS (shumate_vector_line_layer_parent_class)->finalize (object);
+}
+
+
 static void
 shumate_vector_line_layer_render (ShumateVectorLayer *layer, ShumateVectorRenderScope *scope)
 {
   ShumateVectorLineLayer *self = SHUMATE_VECTOR_LINE_LAYER (layer);
+  GdkRGBA color = SHUMATE_VECTOR_COLOR_BLACK;
+  double opacity;
+  double width;
+
+  shumate_vector_expression_eval_color (self->color, scope, &color);
+  opacity = shumate_vector_expression_eval_number (self->opacity, scope, 1.0);
+  width = shumate_vector_expression_eval_number (self->width, scope, 1.0);
 
   shumate_vector_render_scope_exec_geometry (scope);
 
-  cairo_set_source_rgba (scope->cr, self->color.red, self->color.green, self->color.blue, self->opacity);
-  cairo_set_line_width (scope->cr, self->width * scope->scale);
+  cairo_set_source_rgba (scope->cr, color.red, color.green, color.blue, opacity);
+  cairo_set_line_width (scope->cr, width * scope->scale);
   cairo_stroke (scope->cr);
 }
 
@@ -69,8 +96,10 @@ shumate_vector_line_layer_render (ShumateVectorLayer *layer, ShumateVectorRender
 static void
 shumate_vector_line_layer_class_init (ShumateVectorLineLayerClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   ShumateVectorLayerClass *layer_class = SHUMATE_VECTOR_LAYER_CLASS (klass);
 
+  object_class->finalize = shumate_vector_line_layer_finalize;
   layer_class->render = shumate_vector_line_layer_render;
 }
 
diff --git a/shumate/vector/shumate-vector-value-private.h b/shumate/vector/shumate-vector-value-private.h
new file mode 100644
index 0000000..a0bfb56
--- /dev/null
+++ b/shumate/vector/shumate-vector-value-private.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <json-glib/json-glib.h>
+#include <gtk/gtk.h>
+
+#define SHUMATE_VECTOR_COLOR_BLACK ((GdkRGBA) {.red=0, .green=0, .blue=0, .alpha=1})
+
+typedef struct {
+  int type;
+
+  union {
+    double number;
+    gboolean boolean;
+    struct {
+      char *string;
+      GdkRGBA color;
+      int color_state;
+    };
+  };
+} ShumateVectorValue;
+
+#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_unset (ShumateVectorValue *self);
+void shumate_vector_value_copy (ShumateVectorValue *self, ShumateVectorValue *out);
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (ShumateVectorValue, shumate_vector_value_unset)
+
+void shumate_vector_value_set_number (ShumateVectorValue *self, double number);
+gboolean shumate_vector_value_get_number (ShumateVectorValue *self, double *number);
+
+void shumate_vector_value_set_string (ShumateVectorValue *self, const char *string);
+gboolean shumate_vector_value_get_string (ShumateVectorValue *self, const char **string);
+
+void shumate_vector_value_set_boolean (ShumateVectorValue *self, gboolean boolean);
+gboolean shumate_vector_value_get_boolean (ShumateVectorValue *self, gboolean *boolean);
+
+void shumate_vector_value_set_color (ShumateVectorValue *self, GdkRGBA *color);
+gboolean shumate_vector_value_get_color (ShumateVectorValue *self, GdkRGBA *color);
+
+gboolean shumate_vector_value_equal (ShumateVectorValue *a, ShumateVectorValue *b);
diff --git a/shumate/vector/shumate-vector-value.c b/shumate/vector/shumate-vector-value.c
new file mode 100644
index 0000000..294dea8
--- /dev/null
+++ b/shumate/vector/shumate-vector-value.c
@@ -0,0 +1,206 @@
+/*
+ * 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-value-private.h"
+
+enum {
+  TYPE_NULL,
+  TYPE_NUMBER,
+  TYPE_BOOLEAN,
+  TYPE_STRING,
+  TYPE_COLOR,
+};
+
+enum {
+  COLOR_UNSET,
+  COLOR_SET,
+  COLOR_INVALID,
+};
+
+gboolean
+shumate_vector_value_set_from_g_value (ShumateVectorValue *self, const GValue *value)
+{
+  g_auto(GValue) tmp = G_VALUE_INIT;
+
+  if (value == NULL)
+    shumate_vector_value_unset (self);
+  else if (g_value_type_transformable (G_VALUE_TYPE (value), G_TYPE_DOUBLE))
+    {
+      g_value_init (&tmp, G_TYPE_DOUBLE);
+      g_value_transform (value, &tmp);
+      shumate_vector_value_set_number (self, g_value_get_double (&tmp));
+    }
+  else if (g_value_type_transformable (G_VALUE_TYPE (value), G_TYPE_BOOLEAN))
+    {
+      g_value_init (&tmp, G_TYPE_BOOLEAN);
+      g_value_transform (value, &tmp);
+      shumate_vector_value_set_boolean (self, g_value_get_boolean (&tmp));
+    }
+  else if (g_value_type_transformable (G_VALUE_TYPE (value), G_TYPE_STRING))
+    {
+      g_value_init (&tmp, G_TYPE_STRING);
+      g_value_transform (value, &tmp);
+      shumate_vector_value_set_string (self, g_value_get_string (&tmp));
+    }
+  else
+    return FALSE;
+
+  return TRUE;
+}
+
+void
+shumate_vector_value_unset (ShumateVectorValue *self)
+{
+  if (self->type == TYPE_STRING)
+    g_clear_pointer (&self->string, g_free);
+  self->type = TYPE_NULL;
+}
+
+
+void
+shumate_vector_value_copy (ShumateVectorValue *self, ShumateVectorValue *out)
+{
+  shumate_vector_value_unset (out);
+  *out = *self;
+
+  if (self->type == TYPE_STRING)
+    out->string = g_strdup (out->string);
+}
+
+
+void
+shumate_vector_value_set_number (ShumateVectorValue *self, double number)
+{
+  shumate_vector_value_unset (self);
+  self->type = TYPE_NUMBER;
+  self->number = number;
+}
+
+
+gboolean
+shumate_vector_value_get_number (ShumateVectorValue *self, double *number)
+{
+  if (self->type != TYPE_NUMBER)
+    return FALSE;
+
+  *number = self->number;
+  return TRUE;
+}
+
+
+void
+shumate_vector_value_set_boolean (ShumateVectorValue *self, gboolean boolean)
+{
+  shumate_vector_value_unset (self);
+  self->type = TYPE_BOOLEAN;
+  self->boolean = boolean;
+}
+
+
+gboolean
+shumate_vector_value_get_boolean (ShumateVectorValue *self, gboolean *boolean)
+{
+  if (self->type != TYPE_BOOLEAN)
+    return FALSE;
+
+  *boolean = self->boolean;
+  return TRUE;
+}
+
+
+
+void
+shumate_vector_value_set_string (ShumateVectorValue *self, const char *string)
+{
+  shumate_vector_value_unset (self);
+  self->type = TYPE_STRING;
+  self->string = g_strdup (string);
+  self->color_state = COLOR_UNSET;
+}
+
+
+gboolean
+shumate_vector_value_get_string (ShumateVectorValue *self, const char **string)
+{
+  if (self->type != TYPE_STRING)
+    return FALSE;
+
+  *string = self->string;
+  return TRUE;
+}
+
+
+void
+shumate_vector_value_set_color (ShumateVectorValue *self, GdkRGBA *color)
+{
+  shumate_vector_value_unset (self);
+  self->type = TYPE_COLOR;
+  self->color = *color;
+}
+
+
+gboolean
+shumate_vector_value_get_color (ShumateVectorValue *self, GdkRGBA *color)
+{
+  if (self->type == TYPE_STRING)
+    {
+      if (self->color_state == COLOR_UNSET)
+        {
+          if (gdk_rgba_parse (&self->color, self->string))
+            self->color_state = COLOR_SET;
+          else
+            self->color_state = COLOR_INVALID;
+        }
+
+      if (self->color_state == COLOR_SET)
+        {
+          *color = self->color;
+          return TRUE;
+        }
+      else
+        return FALSE;
+    }
+
+  if (self->type != TYPE_COLOR)
+    return FALSE;
+
+  *color = self->color;
+  return TRUE;
+}
+
+gboolean
+shumate_vector_value_equal (ShumateVectorValue *a, ShumateVectorValue *b)
+{
+  if (a->type != b->type)
+    return FALSE;
+
+  switch (a->type)
+    {
+    case TYPE_NULL:
+      return TRUE;
+    case TYPE_NUMBER:
+      return a->number == b->number;
+    case TYPE_BOOLEAN:
+      return a->boolean == b->boolean;
+    case TYPE_STRING:
+      return g_strcmp0 (a->string, b->string) == 0;
+    case TYPE_COLOR:
+      return gdk_rgba_equal (&a->color, &b->color);
+    default:
+      g_assert_not_reached ();
+    }
+}
diff --git a/tests/meson.build b/tests/meson.build
index a12e451..20b9dfb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -14,7 +14,9 @@ tests = [
   'marker-layer',
   'memory-cache',
   'network-tile-source',
+  'vector-expression',
   'vector-style',
+  'vector-value',
   'viewport',
 ]
 
@@ -32,6 +34,7 @@ testutils_dep = declare_dependency(
   link_with: testutils_lib,
   dependencies: libshumate_deps,
   sources: test_utils_sources,
+  include_directories: include_directories('../shumate')
 )
 
 foreach test : tests
diff --git a/tests/vector-expression.c b/tests/vector-expression.c
new file mode 100644
index 0000000..837881e
--- /dev/null
+++ b/tests/vector-expression.c
@@ -0,0 +1,122 @@
+#include <gtk/gtk.h>
+#include <shumate/shumate.h>
+#include "shumate/vector/shumate-vector-expression-literal-private.h"
+#include "shumate/vector/shumate-vector-expression-interpolate-private.h"
+
+
+static void
+test_vector_expression_parse (void)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node1 = json_from_string ("{\"stops\": [[12, 1], [13, 2], [14, 5], [16, 9]]}", NULL);
+  g_autoptr(JsonNode) node2 = json_from_string ("1.0", NULL);
+  g_autoptr(ShumateVectorExpression) expr1 = NULL;
+  g_autoptr(ShumateVectorExpression) expr2 = NULL;
+  g_autoptr(ShumateVectorExpression) expr3 = NULL;
+
+  expr1 = shumate_vector_expression_from_json (node1, &error);
+  g_assert_no_error (error);
+  g_assert_true (SHUMATE_IS_VECTOR_EXPRESSION_INTERPOLATE (expr1));
+
+  expr2 = shumate_vector_expression_from_json (node2, &error);
+  g_assert_no_error (error);
+  g_assert_true (SHUMATE_IS_VECTOR_EXPRESSION_LITERAL (expr2));
+
+  expr3 = shumate_vector_expression_from_json (NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (SHUMATE_IS_VECTOR_EXPRESSION_LITERAL (expr3));
+}
+
+
+static void
+test_vector_expression_literal (void)
+{
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  g_autoptr(ShumateVectorExpression) expr = NULL;
+  double result;
+
+  shumate_vector_value_set_number (&value, 3.1415);
+  expr = shumate_vector_expression_literal_new (&value);
+
+  result = shumate_vector_expression_eval_number (expr, NULL, -10);
+  g_assert_cmpfloat (3.1415, ==, result);
+}
+
+
+static void
+test_vector_expression_interpolate (void)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node = json_from_string ("{\"stops\": [[12, 1], [13, 2], [14, 5], [16, 9]]}", NULL);
+  g_autoptr(ShumateVectorExpression) expression;
+  ShumateVectorRenderScope scope;
+
+  expression = shumate_vector_expression_from_json (node, &error);
+  g_assert_no_error (error);
+
+  /* Test that exact stop values work */
+  scope.zoom_level = 12;
+  g_assert_cmpfloat (1.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+  scope.zoom_level = 13;
+  g_assert_cmpfloat (2.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+  scope.zoom_level = 14;
+  g_assert_cmpfloat (5.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+  scope.zoom_level = 16;
+  g_assert_cmpfloat (9.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+
+  /* Test that outlier values work */
+  scope.zoom_level = 1;
+  g_assert_cmpfloat (1.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+  scope.zoom_level = 100;
+  g_assert_cmpfloat (9.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+
+  /* Test that in-between values work */
+  scope.zoom_level = 12.5;
+  g_assert_cmpfloat (1.5, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+  scope.zoom_level = 15;
+  g_assert_cmpfloat (7.0, ==, shumate_vector_expression_eval_number (expression, &scope, -10000.0));
+}
+
+
+static void
+test_vector_expression_interpolate_color (void)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node = json_from_string ("{\"stops\": [[12, \"#00224466\"], [13, \"#88AACCEE\"]]}", 
NULL);
+  g_autoptr(ShumateVectorExpression) expression;
+  ShumateVectorRenderScope scope;
+  GdkRGBA color, correct_color;
+
+  expression = shumate_vector_expression_from_json (node, &error);
+  g_assert_no_error (error);
+
+  /* Test that exact stop values work */
+  scope.zoom_level = 12;
+  shumate_vector_expression_eval_color (expression, &scope, &color);
+  gdk_rgba_parse (&correct_color, "#00224466");
+  g_assert_true (gdk_rgba_equal (&color, &correct_color));
+
+  scope.zoom_level = 12.5;
+  shumate_vector_expression_eval_color (expression, &scope, &color);
+  gdk_rgba_parse (&correct_color, "#446688AA");
+  g_assert_true (gdk_rgba_equal (&color, &correct_color));
+
+  scope.zoom_level = 13;
+  shumate_vector_expression_eval_color (expression, &scope, &color);
+  gdk_rgba_parse (&correct_color, "#88AACCEE");
+  g_assert_true (gdk_rgba_equal (&color, &correct_color));
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/vector/expression/parse", test_vector_expression_parse);
+  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);
+
+  return g_test_run ();
+}
diff --git a/tests/vector-value.c b/tests/vector-value.c
new file mode 100644
index 0000000..1a5880c
--- /dev/null
+++ b/tests/vector-value.c
@@ -0,0 +1,184 @@
+#include <gtk/gtk.h>
+#include <shumate/shumate.h>
+#include "shumate/vector/shumate-vector-value-private.h"
+
+static void
+test_vector_value_literal (void)
+{
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  double number;
+  gboolean boolean;
+  const char *string;
+
+  shumate_vector_value_unset (&value);
+
+  shumate_vector_value_set_number (&value, 3.1415);
+  g_assert_true (shumate_vector_value_get_number (&value, &number));
+  g_assert_cmpfloat (3.1415, ==, number);
+
+  shumate_vector_value_set_boolean (&value, TRUE);
+  g_assert_true (shumate_vector_value_get_boolean (&value, &boolean));
+  g_assert_true (boolean);
+
+  shumate_vector_value_set_boolean (&value, FALSE);
+  g_assert_true (shumate_vector_value_get_boolean (&value, &boolean));
+  g_assert_false (boolean);
+
+  shumate_vector_value_set_string (&value, "Hello, world!");
+  g_assert_true (shumate_vector_value_get_string (&value, &string));
+  g_assert_cmpstr ("Hello, world!", ==, string);
+}
+
+
+static void
+test_vector_value_from_gvalue (void)
+{
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  g_auto(GValue) gvalue = G_VALUE_INIT;
+  double number;
+  gboolean boolean;
+  const char *string;
+
+  shumate_vector_value_unset (&value);
+
+  g_value_init (&gvalue, G_TYPE_DOUBLE);
+  g_value_set_double (&gvalue, 3.1415);
+  g_assert_true (shumate_vector_value_set_from_g_value (&value, &gvalue));
+  g_assert_true (shumate_vector_value_get_number (&value, &number));
+  g_assert_cmpfloat (3.1415, ==, number);
+  g_value_unset (&gvalue);
+
+  g_value_init (&gvalue, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&gvalue, TRUE);
+  g_assert_true (shumate_vector_value_set_from_g_value (&value, &gvalue));
+  g_assert_true (shumate_vector_value_get_boolean (&value, &boolean));
+  g_assert_true (boolean);
+
+  g_value_set_boolean (&gvalue, FALSE);
+  shumate_vector_value_set_from_g_value (&value, &gvalue);
+  g_assert_true (shumate_vector_value_get_boolean (&value, &boolean));
+  g_assert_false (boolean);
+  g_value_unset (&gvalue);
+
+  g_value_init (&gvalue, G_TYPE_STRING);
+  g_value_set_string (&gvalue, "Hello, world!");
+  g_assert_true (shumate_vector_value_set_from_g_value (&value, &gvalue));
+  g_assert_true (shumate_vector_value_get_string (&value, &string));
+  g_assert_cmpstr ("Hello, world!", ==, string);
+  g_value_unset (&gvalue);
+}
+
+static void
+test_vector_value_get_color (void)
+{
+  g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
+  GdkRGBA color, correct_color;
+
+  gdk_rgba_parse (&correct_color, "goldenrod");
+  shumate_vector_value_set_string (&value, "goldenrod");
+
+  g_assert_true (shumate_vector_value_get_color (&value, &color));
+  g_assert_true (gdk_rgba_equal (&color, &correct_color));
+
+  /* Try again to make sure caching works */
+  g_assert_true (shumate_vector_value_get_color (&value, &color));
+  g_assert_true (gdk_rgba_equal (&color, &correct_color));
+
+  shumate_vector_value_set_string (&value, "not a real color");
+  g_assert_false (shumate_vector_value_get_color (&value, &color));
+  /* Try again to make sure caching works */
+  g_assert_false (shumate_vector_value_get_color (&value, &color));
+}
+
+
+static void
+test_vector_value_equal (void)
+{
+  g_auto(ShumateVectorValue) value1 = SHUMATE_VECTOR_VALUE_INIT;
+  g_auto(ShumateVectorValue) value2 = SHUMATE_VECTOR_VALUE_INIT;
+  GdkRGBA color;
+
+  /* Both are initialized to NULL so they should be equal */
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_number (&value1, 1.0);
+  shumate_vector_value_set_number (&value2, 1.0);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_number (&value1, 1.0);
+  shumate_vector_value_set_number (&value2, 2.0);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_boolean (&value1, TRUE);
+  shumate_vector_value_set_boolean (&value2, TRUE);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_number (&value1, FALSE);
+  shumate_vector_value_set_number (&value2, TRUE);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_string (&value1, "Hello, world!");
+  shumate_vector_value_set_string (&value2, "Hello, world!");
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_string (&value1, "Hello, world!");
+  shumate_vector_value_set_string (&value2, "Goodbye, world!");
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  gdk_rgba_parse (&color, "magenta");
+  shumate_vector_value_set_color (&value1, &color);
+  shumate_vector_value_set_color (&value2, &color);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_color (&value1, &color);
+  gdk_rgba_parse (&color, "purple");
+  shumate_vector_value_set_color (&value2, &color);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_string (&value1, "Hello, world!");
+  shumate_vector_value_set_number (&value2, 1.0);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_number (&value1, TRUE);
+  shumate_vector_value_set_boolean (&value2, 1.0);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_unset (&value1);
+  shumate_vector_value_set_number (&value2, 0.0);
+  g_assert_false (shumate_vector_value_equal (&value1, &value2));
+}
+
+
+static void
+test_vector_value_copy (void)
+{
+  g_auto(ShumateVectorValue) value1 = SHUMATE_VECTOR_VALUE_INIT;
+  g_auto(ShumateVectorValue) value2 = SHUMATE_VECTOR_VALUE_INIT;
+  GdkRGBA color;
+
+  shumate_vector_value_copy (&value1, &value2);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  gdk_rgba_parse (&color, "red");
+  shumate_vector_value_set_color (&value1, &color);
+  shumate_vector_value_copy (&value1, &value2);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+
+  shumate_vector_value_set_string (&value1, "Hello, world!");
+  shumate_vector_value_copy (&value1, &value2);
+  g_assert_true (shumate_vector_value_equal (&value1, &value2));
+}
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/vector/value/literal", test_vector_value_literal);
+  g_test_add_func ("/vector/value/from-gvalue", test_vector_value_from_gvalue);
+  g_test_add_func ("/vector/value/get-color", test_vector_value_get_color);
+  g_test_add_func ("/vector/value/equal", test_vector_value_equal);
+  g_test_add_func ("/vector/value/copy", test_vector_value_copy);
+
+  return g_test_run ();
+}


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