Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2021 James Westman <james@jwestman.net>
3 : : *
4 : : * This library is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * This library is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : : * Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with this library; if not, see <https://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include "shumate-vector-renderer-private.h"
19 : : #include "shumate-tile-downloader.h"
20 : : #include "shumate-tile-private.h"
21 : : #include "shumate-profiling-private.h"
22 : : #include "shumate-vector-reader.h"
23 : : #include "shumate-vector-reader-iter.h"
24 : :
25 : : /**
26 : : * ShumateVectorRenderer:
27 : : *
28 : : * A [class@MapSource] that renders tiles from a given vector data source.
29 : : */
30 : :
31 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
32 : : #include <json-glib/json-glib.h>
33 : : #include <cairo/cairo.h>
34 : :
35 : : #include "vector/shumate-vector-render-scope-private.h"
36 : : #include "vector/shumate-vector-symbol-info-private.h"
37 : : #include "vector/shumate-vector-utils-private.h"
38 : : #include "vector/shumate-vector-layer-private.h"
39 : : #include "vector/shumate-vector-index-private.h"
40 : : #endif
41 : :
42 : : struct _ShumateVectorRenderer
43 : : {
44 : : ShumateMapSource parent_instance;
45 : :
46 : : char *source_name;
47 : : ShumateDataSource *data_source;
48 : :
49 : : ShumateVectorSpriteSheet *sprites;
50 : : GMutex sprites_mutex;
51 : :
52 : : GThreadPool *thread_pool;
53 : :
54 : : char *style_json;
55 : :
56 : : GPtrArray *layers;
57 : :
58 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
59 : : ShumateVectorIndexDescription *index_description;
60 : : #endif
61 : : };
62 : :
63 : :
64 : : static gboolean begin_render (ShumateVectorRenderer *self,
65 : : GTask *task,
66 : : GBytes *tile_data,
67 : : ShumateGridPosition *source_position);
68 : :
69 : : static void shumate_vector_renderer_initable_iface_init (GInitableIface *iface);
70 : :
71 [ + + + - ]: 30 : G_DEFINE_TYPE_WITH_CODE (ShumateVectorRenderer, shumate_vector_renderer, SHUMATE_TYPE_MAP_SOURCE,
72 : : G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, shumate_vector_renderer_initable_iface_init))
73 : :
74 : : enum {
75 : : PROP_0,
76 : : PROP_STYLE_JSON,
77 : : PROP_SPRITE_SHEET,
78 : : N_PROPS
79 : : };
80 : :
81 : : static GParamSpec *properties [N_PROPS];
82 : :
83 : :
84 : : /**
85 : : * shumate_vector_renderer_new:
86 : : * @id: an ID for the map source
87 : : * @style_json: a vector style
88 : : * @error: return location for a #GError, or %NULL
89 : : *
90 : : * Creates a new [class@VectorRenderer] from the given JSON style.
91 : : *
92 : : * The stylesheet should contain a list of tile sources. Tiles will be
93 : : * downloaded using [class@TileDownloader]s.
94 : : *
95 : : * See the [MapLibre Style Specification](https://maplibre.org/maplibre-gl-js-docs/style-spec/)
96 : : * for details on @style_json, but be aware that libshumate does not support
97 : : * every feature of the specification.
98 : : *
99 : : * Returns: (transfer full): a newly constructed [class@VectorRenderer], or %NULL if @error is set
100 : : */
101 : : ShumateVectorRenderer *
102 : 6 : shumate_vector_renderer_new (const char *id,
103 : : const char *style_json,
104 : : GError **error)
105 : : {
106 [ + - ]: 6 : g_return_val_if_fail (id != NULL, NULL);
107 [ - + ]: 6 : g_return_val_if_fail (style_json != NULL, NULL);
108 : :
109 : 6 : return g_initable_new (SHUMATE_TYPE_VECTOR_RENDERER, NULL, error,
110 : : "id", id,
111 : : "style-json", style_json,
112 : : NULL);
113 : : }
114 : :
115 : :
116 : : /**
117 : : * shumate_vector_renderer_is_supported:
118 : : *
119 : : * Checks whether libshumate was compiled with vector tile support. If it was
120 : : * not, vector renderers cannot be created or used.
121 : : *
122 : : * Returns: %TRUE if libshumate was compiled with `-Dvector_renderer=true` or
123 : : * %FALSE if it was not
124 : : */
125 : : gboolean
126 : 0 : shumate_vector_renderer_is_supported (void)
127 : : {
128 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
129 : 0 : return TRUE;
130 : : #else
131 : : return FALSE;
132 : : #endif
133 : : }
134 : :
135 : :
136 : : static void
137 : 6 : shumate_vector_renderer_finalize (GObject *object)
138 : : {
139 : 6 : ShumateVectorRenderer *self = (ShumateVectorRenderer *)object;
140 : :
141 [ + - ]: 6 : g_clear_pointer (&self->layers, g_ptr_array_unref);
142 [ + - ]: 6 : g_clear_pointer (&self->style_json, g_free);
143 [ + - ]: 6 : g_clear_pointer (&self->source_name, g_free);
144 [ + - ]: 6 : g_clear_object (&self->data_source);
145 [ + + ]: 6 : g_clear_object (&self->sprites);
146 [ + - ]: 6 : g_clear_pointer (&self->index_description, shumate_vector_index_description_free);
147 : :
148 [ - + ]: 6 : if (self->thread_pool)
149 : 0 : g_thread_pool_free (self->thread_pool, FALSE, FALSE);
150 : :
151 : 6 : g_mutex_clear (&self->sprites_mutex);
152 : :
153 : 6 : G_OBJECT_CLASS (shumate_vector_renderer_parent_class)->finalize (object);
154 : 6 : }
155 : :
156 : : static void
157 : 0 : shumate_vector_renderer_get_property (GObject *object,
158 : : guint prop_id,
159 : : GValue *value,
160 : : GParamSpec *pspec)
161 : : {
162 : 0 : ShumateVectorRenderer *self = SHUMATE_VECTOR_RENDERER (object);
163 : :
164 [ # # # ]: 0 : switch (prop_id)
165 : : {
166 : 0 : case PROP_STYLE_JSON:
167 : 0 : g_value_set_string (value, self->style_json);
168 : 0 : break;
169 : 0 : case PROP_SPRITE_SHEET:
170 : 0 : g_value_set_object (value, shumate_vector_renderer_get_sprite_sheet (self));
171 : 0 : break;
172 : 0 : default:
173 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
174 : : }
175 : 0 : }
176 : :
177 : : static void
178 : 6 : shumate_vector_renderer_set_property (GObject *object,
179 : : guint prop_id,
180 : : const GValue *value,
181 : : GParamSpec *pspec)
182 : : {
183 : 6 : ShumateVectorRenderer *self = SHUMATE_VECTOR_RENDERER (object);
184 : :
185 [ + - - ]: 6 : switch (prop_id)
186 : : {
187 : 6 : case PROP_STYLE_JSON:
188 : : /* Property is construct only, so it should only be set once */
189 [ + - ]: 6 : g_assert (self->style_json == NULL);
190 [ - + ]: 6 : self->style_json = g_strdup (g_value_get_string (value));
191 : 6 : break;
192 : 0 : case PROP_SPRITE_SHEET:
193 : 0 : shumate_vector_renderer_set_sprite_sheet (self, g_value_get_object (value));
194 : 0 : break;
195 : 0 : default:
196 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
197 : : }
198 : 6 : }
199 : :
200 : : static void shumate_vector_renderer_fill_tile_async (ShumateMapSource *map_source,
201 : : ShumateTile *tile,
202 : : GCancellable *cancellable,
203 : : GAsyncReadyCallback callback,
204 : : gpointer user_data);
205 : :
206 : : static gboolean shumate_vector_renderer_fill_tile_finish (ShumateMapSource *map_source,
207 : : GAsyncResult *result,
208 : : GError **error);
209 : :
210 : : static void
211 : 7 : shumate_vector_renderer_class_init (ShumateVectorRendererClass *klass)
212 : : {
213 : 7 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
214 : 7 : ShumateMapSourceClass *map_source_class = SHUMATE_MAP_SOURCE_CLASS (klass);
215 : :
216 : 7 : object_class->finalize = shumate_vector_renderer_finalize;
217 : 7 : object_class->get_property = shumate_vector_renderer_get_property;
218 : 7 : object_class->set_property = shumate_vector_renderer_set_property;
219 : :
220 : 7 : map_source_class->fill_tile_async = shumate_vector_renderer_fill_tile_async;
221 : 7 : map_source_class->fill_tile_finish = shumate_vector_renderer_fill_tile_finish;
222 : :
223 : : /**
224 : : * ShumateVectorRenderer:style-json:
225 : : *
226 : : * A map style, in [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/)
227 : : * format.
228 : : *
229 : : * Note that not all features of the specification are supported.
230 : : */
231 : 14 : properties[PROP_STYLE_JSON] =
232 : 7 : g_param_spec_string ("style-json",
233 : : "Style JSON",
234 : : "Style JSON",
235 : : NULL,
236 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
237 : :
238 : : /**
239 : : * ShumateVectorRenderer:sprite-sheet:
240 : : *
241 : : * The sprite sheet used to render icons and textures.
242 : : *
243 : : * Since: 1.1
244 : : */
245 : 14 : properties[PROP_SPRITE_SHEET] =
246 : 7 : g_param_spec_object ("sprite-sheet",
247 : : "sprite-sheet",
248 : : "sprite-sheet",
249 : : SHUMATE_TYPE_VECTOR_SPRITE_SHEET,
250 : : G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
251 : :
252 : 7 : g_object_class_install_properties (object_class, N_PROPS, properties);
253 : 7 : }
254 : :
255 : :
256 : : static gboolean
257 : 6 : shumate_vector_renderer_initable_init (GInitable *initable,
258 : : GCancellable *cancellable,
259 : : GError **error)
260 : : {
261 : 6 : SHUMATE_PROFILE_START ();
262 : :
263 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
264 : 6 : ShumateVectorRenderer *self = (ShumateVectorRenderer *)initable;
265 : 6 : g_autoptr(JsonNode) node = NULL;
266 : 6 : JsonNode *layers_node;
267 : 6 : JsonNode *sources_node;
268 : 6 : JsonObject *object;
269 : 6 : const char *style_name;
270 : :
271 [ + - ]: 6 : g_return_val_if_fail (SHUMATE_IS_VECTOR_RENDERER (self), FALSE);
272 [ - + ]: 6 : g_return_val_if_fail (self->style_json != NULL, FALSE);
273 : :
274 [ - + ]: 6 : if (!(node = json_from_string (self->style_json, error)))
275 : : return FALSE;
276 : :
277 [ + - ]: 6 : if (!shumate_vector_json_get_object (node, &object, error))
278 : : return FALSE;
279 : :
280 [ + - ]: 6 : if (!shumate_vector_json_get_string_member (object, "name", &style_name, error))
281 : : return FALSE;
282 [ - + ]: 6 : if (style_name != NULL)
283 : 0 : shumate_map_source_set_name (SHUMATE_MAP_SOURCE (self), style_name);
284 : :
285 [ - + ]: 6 : if ((sources_node = json_object_get_member (object, "sources")) == NULL)
286 : : {
287 : 0 : g_set_error (error,
288 : : SHUMATE_STYLE_ERROR,
289 : : SHUMATE_STYLE_ERROR_UNSUPPORTED,
290 : : "a data source is required");
291 : 0 : return FALSE;
292 : : }
293 : : else
294 : : {
295 : 6 : JsonObject *sources;
296 : 6 : JsonObjectIter iter;
297 : 6 : const char *source_name;
298 : 6 : JsonNode *source_node;
299 : 6 : int minzoom = 30, maxzoom = 0;
300 : :
301 [ + - ]: 6 : if (!shumate_vector_json_get_object (sources_node, &sources, error))
302 : 0 : return FALSE;
303 : :
304 [ - + ]: 6 : if (json_object_get_size (sources) > 1)
305 : : {
306 : : /* TODO: Support multiple data sources */
307 : 0 : g_set_error (error,
308 : : SHUMATE_STYLE_ERROR,
309 : : SHUMATE_STYLE_ERROR_UNSUPPORTED,
310 : : "ShumateVectorRenderer does not currently support multiple data sources");
311 : 0 : return FALSE;
312 : : }
313 [ - + ]: 6 : else if (json_object_get_size (object) == 0)
314 : : {
315 : 0 : g_set_error (error,
316 : : SHUMATE_STYLE_ERROR,
317 : : SHUMATE_STYLE_ERROR_UNSUPPORTED,
318 : : "a data source is required");
319 : 0 : return FALSE;
320 : : }
321 : :
322 : 6 : json_object_iter_init (&iter, sources);
323 [ + + ]: 12 : while (json_object_iter_next (&iter, &source_name, &source_node))
324 : : {
325 : 6 : JsonObject *source_object;
326 : 6 : const char *source_type;
327 : 6 : const char *url;
328 : 6 : JsonArray *tiles;
329 : 6 : const char *url_template;
330 : 6 : ShumateDataSource *data_source;
331 : :
332 [ + - ]: 6 : if (!shumate_vector_json_get_object (source_node, &source_object, error))
333 : 0 : return FALSE;
334 : :
335 [ + - ]: 6 : if (!shumate_vector_json_get_string_member (source_object, "type", &source_type, error)
336 [ + - ]: 6 : || !shumate_vector_json_get_string_member (source_object, "url", &url, error)
337 [ - + ]: 6 : || !shumate_vector_json_get_array_member (source_object, "tiles", &tiles, error))
338 : 0 : return FALSE;
339 : :
340 [ - + ]: 6 : if (g_strcmp0 (source_type, "vector") != 0)
341 : : {
342 : 0 : g_set_error (error,
343 : : SHUMATE_STYLE_ERROR,
344 : : SHUMATE_STYLE_ERROR_UNSUPPORTED,
345 : : "ShumateVectorRenderer currently only supports vector sources.");
346 : 0 : return FALSE;
347 : : }
348 : :
349 [ - + ]: 6 : if (url != NULL)
350 : : {
351 : 0 : g_set_error (error,
352 : : SHUMATE_STYLE_ERROR,
353 : : SHUMATE_STYLE_ERROR_UNSUPPORTED,
354 : : "ShumateVectorRenderer does not currently support TileJSON links. "
355 : : "Please embed the TileJSON data directly into the style.");
356 : 0 : return FALSE;
357 : : }
358 : :
359 [ + - - + ]: 6 : if (tiles == NULL || json_array_get_length (tiles) == 0)
360 : : {
361 : 0 : g_set_error (error,
362 : : SHUMATE_STYLE_ERROR,
363 : : SHUMATE_STYLE_ERROR_MALFORMED_STYLE,
364 : : "Expected 'tiles' array to contain at least one element");
365 : 0 : return FALSE;
366 : : }
367 : :
368 [ + - ]: 6 : if (!shumate_vector_json_get_string (json_array_get_element (tiles, 0),
369 : : &url_template,
370 : : error))
371 : : return FALSE;
372 : :
373 : 6 : minzoom = json_object_get_int_member_with_default (source_object, "minzoom", 0);
374 : 6 : maxzoom = json_object_get_int_member_with_default (source_object, "maxzoom", 30);
375 : :
376 : 6 : data_source = SHUMATE_DATA_SOURCE (shumate_tile_downloader_new (url_template));
377 : 6 : shumate_data_source_set_min_zoom_level (data_source, minzoom);
378 : 6 : shumate_data_source_set_max_zoom_level (data_source, maxzoom);
379 : :
380 [ - + ]: 6 : self->source_name = g_strdup (source_name);
381 : 6 : self->data_source = data_source;
382 : : }
383 : :
384 : 6 : maxzoom = MAX (maxzoom, 18);
385 : :
386 [ + - ]: 6 : if (minzoom < maxzoom)
387 : : {
388 : 6 : shumate_map_source_set_min_zoom_level (SHUMATE_MAP_SOURCE (self), minzoom);
389 : 6 : shumate_map_source_set_max_zoom_level (SHUMATE_MAP_SOURCE (self), maxzoom);
390 : : }
391 : : }
392 : :
393 : 6 : self->layers = g_ptr_array_new_with_free_func (g_object_unref);
394 [ + - ]: 6 : if ((layers_node = json_object_get_member (object, "layers")))
395 : : {
396 : 6 : JsonArray *layers;
397 : :
398 [ + - ]: 6 : if (!shumate_vector_json_get_array (layers_node, &layers, error))
399 : 0 : return FALSE;
400 : :
401 [ + + ]: 30 : for (int i = 0, n = json_array_get_length (layers); i < n; i ++)
402 : : {
403 : 24 : JsonNode *layer_node = json_array_get_element (layers, i);
404 : 24 : JsonObject *layer_obj;
405 : 24 : const char *layer_id;
406 : 24 : ShumateVectorLayer *layer;
407 : 24 : ShumateVectorExpression *filter;
408 : :
409 [ + - ]: 24 : if (!shumate_vector_json_get_object (layer_node, &layer_obj, error))
410 : 0 : return FALSE;
411 : :
412 [ + - ]: 24 : if (!shumate_vector_json_get_string_member (layer_obj, "id", &layer_id, error))
413 : : return FALSE;
414 : :
415 [ - + ]: 24 : if (!(layer = shumate_vector_layer_create_from_json (layer_obj, error)))
416 : : {
417 : 0 : g_prefix_error (error, "layer '%s': ", layer_id);
418 : 0 : return FALSE;
419 : : }
420 : :
421 : 24 : g_ptr_array_add (self->layers, layer);
422 : 24 : filter = shumate_vector_layer_get_filter (layer);
423 [ - + ]: 24 : if (filter != NULL)
424 : 0 : shumate_vector_expression_collect_indexes (filter, shumate_vector_layer_get_source_layer (layer), self->index_description);
425 : : }
426 : : }
427 : :
428 : : /* According to the style spec, this is not configurable for vector tiles */
429 : 6 : shumate_map_source_set_tile_size (SHUMATE_MAP_SOURCE (self), 512);
430 : :
431 : 6 : return TRUE;
432 : : #else
433 : : g_set_error (error,
434 : : SHUMATE_STYLE_ERROR,
435 : : SHUMATE_STYLE_ERROR_SUPPORT_OMITTED,
436 : : "Libshumate was compiled without support for vector tiles, so a "
437 : : "ShumateVectorRenderer may not be constructed. You can fix this "
438 : : "by compiling libshumate with `-Dvector_renderer=true` or by "
439 : : "checking `shumate_vector_renderer_is_supported ()` before trying "
440 : : "to construct a ShumateVectorRenderer.");
441 : : return FALSE;
442 : : #endif
443 : : }
444 : :
445 : :
446 : : static void
447 : 7 : shumate_vector_renderer_initable_iface_init (GInitableIface *iface)
448 : : {
449 : 7 : iface->init = shumate_vector_renderer_initable_init;
450 : 7 : }
451 : :
452 : :
453 : : /* An item in the thread pool queue to render a tile from received data. */
454 : : typedef struct {
455 : : GTask *task;
456 : : GCancellable *cancellable;
457 : : gulong cancellable_handle;
458 : : GBytes *data;
459 : : ShumateGridPosition source_position;
460 : :
461 : : GdkPaintable *paintable;
462 : : GPtrArray *symbols;
463 : : } RenderJob;
464 : :
465 : :
466 : : static void
467 : 0 : render_job_unref (RenderJob *job)
468 : : {
469 [ # # ]: 0 : if (job->cancellable_handle != 0)
470 : 0 : g_cancellable_disconnect (g_task_get_cancellable (job->task), job->cancellable_handle);
471 [ # # ]: 0 : g_clear_object (&job->cancellable);
472 [ # # ]: 0 : g_clear_object (&job->task);
473 [ # # ]: 0 : g_clear_pointer (&job->data, g_bytes_unref);
474 [ # # ]: 0 : g_clear_object (&job->paintable);
475 [ # # ]: 0 : g_clear_pointer (&job->symbols, g_ptr_array_unref);
476 : 0 : g_free (job);
477 : 0 : }
478 : :
479 : : /* The data associated with a shumate_vector_renderer_fill_tile_async() task. */
480 : : typedef struct {
481 : : ShumateTile *tile;
482 : : RenderJob *current_job;
483 : : ShumateDataSourceRequest *req;
484 : : guint8 completed : 1;
485 : : } TaskData;
486 : :
487 : : static void
488 : 0 : task_data_free (TaskData *data)
489 : : {
490 [ # # ]: 0 : g_clear_object (&data->tile);
491 [ # # ]: 0 : g_clear_object (&data->req);
492 : 0 : g_free (data);
493 : 0 : }
494 : :
495 : :
496 : : static void
497 : 6 : shumate_vector_renderer_init (ShumateVectorRenderer *self)
498 : : {
499 : 6 : g_mutex_init (&self->sprites_mutex);
500 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
501 : 6 : self->index_description = shumate_vector_index_description_new ();
502 : : #endif
503 : 6 : }
504 : :
505 : :
506 : : /**
507 : : * shumate_vector_renderer_get_style_json:
508 : : * @self: a [class@VectorStyle]
509 : : *
510 : : * Gets the JSON string from which this vector style was loaded.
511 : : *
512 : : * Returns: (nullable): the style JSON, or %NULL if none is set
513 : : */
514 : : const char *
515 : 0 : shumate_vector_renderer_get_style_json (ShumateVectorRenderer *self)
516 : : {
517 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_VECTOR_RENDERER (self), NULL);
518 : :
519 : 0 : return self->style_json;
520 : : }
521 : :
522 : :
523 : : /**
524 : : * shumate_vector_renderer_set_sprite_sheet_data:
525 : : * @self: a [class@VectorRenderer]
526 : : * @sprites_pixbuf: a [class@GdkPixbuf.Pixbuf]
527 : : * @sprites_json: a JSON string
528 : : * @error: return location for a #GError, or %NULL
529 : : *
530 : : * Sets the sprite sheet used by the style JSON to render icons and textures.
531 : : *
532 : : * The existing [property@VectorRenderer:sprite-sheet] property will be replaced
533 : : * with a new instance of [class@VectorSpriteSheet].
534 : : *
535 : : * Returns: whether the sprite sheet was loaded successfully
536 : : *
537 : : * Deprecated: 1.1: Use the methods of [property@VectorRenderer:sprite-sheet] instead.
538 : : */
539 : : gboolean
540 : 0 : shumate_vector_renderer_set_sprite_sheet_data (ShumateVectorRenderer *self,
541 : : GdkPixbuf *sprites_pixbuf,
542 : : const char *sprites_json,
543 : : GError **error)
544 : : {
545 : 0 : g_autoptr(ShumateVectorSpriteSheet) sprites = NULL;
546 [ # # ]: 0 : g_autoptr(GdkTexture) texture = NULL;
547 : :
548 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_VECTOR_RENDERER (self), FALSE);
549 [ # # # # : 0 : g_return_val_if_fail (GDK_IS_PIXBUF (sprites_pixbuf), FALSE);
# # # # ]
550 [ # # ]: 0 : g_return_val_if_fail (sprites_json != NULL, FALSE);
551 : :
552 : 0 : sprites = shumate_vector_sprite_sheet_new ();
553 : :
554 : 0 : texture = gdk_texture_new_for_pixbuf (sprites_pixbuf);
555 [ # # ]: 0 : if (!shumate_vector_sprite_sheet_add_page (sprites, texture, sprites_json, 1, error))
556 : : return FALSE;
557 : :
558 : 0 : shumate_vector_renderer_set_sprite_sheet (self, sprites);
559 : 0 : return TRUE;
560 : : }
561 : :
562 : :
563 : : /**
564 : : * shumate_vector_renderer_get_sprite_sheet:
565 : : * @self: a [class@VectorRenderer]
566 : : *
567 : : * Gets the sprite sheet used to render icons and textures.
568 : : *
569 : : * Returns: (transfer none): the [class@VectorSpriteSheet]
570 : : *
571 : : * Since: 1.1
572 : : */
573 : : ShumateVectorSpriteSheet *
574 : 0 : shumate_vector_renderer_get_sprite_sheet (ShumateVectorRenderer *self)
575 : : {
576 : 0 : g_autoptr(GMutexLocker) locker = NULL;
577 : :
578 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_VECTOR_RENDERER (self), FALSE);
579 : :
580 : 0 : locker = g_mutex_locker_new (&self->sprites_mutex);
581 : :
582 [ # # ]: 0 : if (self->sprites == NULL)
583 : 0 : self->sprites = shumate_vector_sprite_sheet_new ();
584 : :
585 : 0 : return self->sprites;
586 : : }
587 : :
588 : :
589 : : /**
590 : : * shumate_vector_renderer_set_sprite_sheet:
591 : : * @self: a [class@VectorRenderer]
592 : : * @sprites: a [class@VectorSpriteSheet]
593 : : *
594 : : * Sets the sprite sheet used to render icons and textures.
595 : : *
596 : : * Since: 1.1
597 : : */
598 : : void
599 : 0 : shumate_vector_renderer_set_sprite_sheet (ShumateVectorRenderer *self,
600 : : ShumateVectorSpriteSheet *sprites)
601 : : {
602 : 0 : g_autoptr(GMutexLocker) locker = NULL;
603 : :
604 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_VECTOR_RENDERER (self));
605 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_VECTOR_SPRITE_SHEET (sprites));
606 : :
607 : 0 : locker = g_mutex_locker_new (&self->sprites_mutex);
608 : :
609 [ # # ]: 0 : if (g_set_object (&self->sprites, sprites))
610 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SPRITE_SHEET]);
611 : : }
612 : :
613 : :
614 : : /**
615 : : * shumate_vector_renderer_set_data_source:
616 : : * @self: a [class@VectorRenderer]
617 : : * @name: the name of the data source
618 : : * @data_source: a [class@DataSource]
619 : : *
620 : : * Adds a data source to the renderer.
621 : : *
622 : : * Currently, [class@VectorRenderer] only supports one data source
623 : : * and throws an error if the style does not contain exactly one
624 : : * data source. However, support for multiple sources may be added
625 : : * in the future, so this method accepts a name parameter. If the
626 : : * name does not match the one expected by the style, this method
627 : : * will have no effect.
628 : : *
629 : : * Since: 1.2
630 : : */
631 : : void
632 : 0 : shumate_vector_renderer_set_data_source (ShumateVectorRenderer *self,
633 : : const char *name,
634 : : ShumateDataSource *data_source)
635 : : {
636 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_VECTOR_RENDERER (self));
637 [ # # ]: 0 : g_return_if_fail (name != NULL);
638 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_DATA_SOURCE (data_source));
639 : :
640 [ # # ]: 0 : if (g_strcmp0 (name, self->source_name) == 0)
641 : 0 : g_set_object (&self->data_source, data_source);
642 : : }
643 : :
644 : :
645 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
646 : : static GdkTexture *
647 : 3 : texture_new_for_surface (cairo_surface_t *surface)
648 : : {
649 : 6 : g_autoptr(GBytes) bytes = NULL;
650 : 3 : GdkTexture *texture;
651 : :
652 [ + - ]: 3 : g_return_val_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE, NULL);
653 [ - + ]: 3 : g_return_val_if_fail (cairo_image_surface_get_width (surface) > 0, NULL);
654 [ - + ]: 3 : g_return_val_if_fail (cairo_image_surface_get_height (surface) > 0, NULL);
655 : :
656 : 3 : bytes = g_bytes_new_with_free_func (cairo_image_surface_get_data (surface),
657 : 3 : (gsize) cairo_image_surface_get_height (surface)
658 : 3 : * (gsize) cairo_image_surface_get_stride (surface),
659 : : (GDestroyNotify) cairo_surface_destroy,
660 : 3 : cairo_surface_reference (surface));
661 : :
662 : 3 : texture = gdk_memory_texture_new (cairo_image_surface_get_width (surface),
663 : : cairo_image_surface_get_height (surface),
664 : : GDK_MEMORY_B8G8R8A8_PREMULTIPLIED,
665 : : bytes,
666 : 3 : cairo_image_surface_get_stride (surface));
667 : :
668 [ - + ]: 3 : return texture;
669 : : }
670 : : #endif
671 : :
672 : :
673 : : static void
674 : 0 : get_source_coordinates (ShumateVectorRenderer *self,
675 : : int *x,
676 : : int *y,
677 : : int *zoom_level)
678 : : {
679 : : /* Figure out which tile from the data source should be used to render the
680 : : * given tile (which will be different if we're overzooming). */
681 : :
682 : 0 : int maxzoom = shumate_data_source_get_max_zoom_level (self->data_source);
683 [ # # ]: 0 : if (*zoom_level > maxzoom)
684 : : {
685 : 0 : *x >>= (*zoom_level - maxzoom);
686 : 0 : *y >>= (*zoom_level - maxzoom);
687 : 0 : *zoom_level = maxzoom;
688 : : }
689 : 0 : }
690 : :
691 : : static void
692 : 0 : on_request_notify (ShumateDataSourceRequest *req,
693 : : GParamSpec *pspec,
694 : : gpointer user_data)
695 : : {
696 : 0 : GTask *task = user_data;
697 : 0 : GBytes *data;
698 : 0 : ShumateVectorRenderer *self = g_task_get_source_object (task);
699 : :
700 [ # # ]: 0 : if ((data = shumate_data_source_request_get_data (req)))
701 : : {
702 : 0 : begin_render (
703 : : self,
704 : : task,
705 : : data,
706 : 0 : &SHUMATE_GRID_POSITION_INIT (
707 : : shumate_data_source_request_get_x (req),
708 : : shumate_data_source_request_get_y (req),
709 : : shumate_data_source_request_get_zoom_level (req)
710 : : )
711 : : );
712 : : }
713 : 0 : }
714 : :
715 : :
716 : : static void
717 : 0 : return_from_task (GTask *task, ShumateDataSourceRequest *req)
718 : : {
719 : 0 : GError *error;
720 : 0 : TaskData *data = g_task_get_task_data (task);
721 : :
722 : 0 : shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
723 : :
724 [ # # ]: 0 : if ((error = shumate_data_source_request_get_error (req)))
725 : 0 : g_task_return_error (task, g_error_copy (error));
726 : : else
727 : 0 : g_task_return_boolean (task, TRUE);
728 : 0 : }
729 : :
730 : : static void
731 : 0 : on_request_notify_completed (ShumateDataSourceRequest *req,
732 : : GParamSpec *pspec,
733 : : gpointer user_data)
734 : : {
735 : 0 : g_autoptr(GTask) task = user_data;
736 : 0 : TaskData *data = g_task_get_task_data (task);
737 : :
738 [ # # ]: 0 : if (data->current_job != NULL)
739 : 0 : data->completed = TRUE;
740 : : else
741 : 0 : return_from_task (task, req);
742 : 0 : }
743 : :
744 : : static void
745 : 0 : shumate_vector_renderer_fill_tile_async (ShumateMapSource *map_source,
746 : : ShumateTile *tile,
747 : : GCancellable *cancellable,
748 : : GAsyncReadyCallback callback,
749 : : gpointer user_data)
750 : : {
751 : 0 : ShumateVectorRenderer *self = (ShumateVectorRenderer *)map_source;
752 : 0 : g_autoptr(GTask) task = NULL;
753 : 0 : int x, y, zoom_level;
754 [ # # ]: 0 : g_autoptr(ShumateDataSourceRequest) req = NULL;
755 : 0 : GBytes *data;
756 : 0 : TaskData *task_data;
757 : :
758 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_VECTOR_RENDERER (self));
759 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_TILE (tile));
760 [ # # # # : 0 : g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
# # # # ]
761 : :
762 : 0 : task = g_task_new (self, cancellable, callback, user_data);
763 [ # # ]: 0 : g_task_set_source_tag (task, shumate_vector_renderer_fill_tile_async);
764 : :
765 : 0 : task_data = g_new0 (TaskData, 1);
766 : 0 : task_data->tile = g_object_ref (tile);
767 : 0 : g_task_set_task_data (task, task_data, (GDestroyNotify)task_data_free);
768 : :
769 : 0 : x = shumate_tile_get_x (tile);
770 : 0 : y = shumate_tile_get_y (tile);
771 : 0 : zoom_level = shumate_tile_get_zoom_level (tile);
772 : :
773 : 0 : get_source_coordinates (self, &x, &y, &zoom_level);
774 : :
775 : 0 : req = shumate_data_source_start_request (self->data_source, x, y, zoom_level, cancellable);
776 : 0 : task_data->req = g_object_ref (req);
777 : :
778 [ # # ]: 0 : if ((data = shumate_data_source_request_get_data (req)))
779 : : {
780 : 0 : begin_render (
781 : : self,
782 : : task,
783 : : data,
784 : 0 : &SHUMATE_GRID_POSITION_INIT (x, y, zoom_level)
785 : : );
786 : : }
787 : :
788 [ # # ]: 0 : if (shumate_data_source_request_is_completed (req))
789 : 0 : on_request_notify_completed (req, NULL, g_steal_pointer (&task));
790 : : else
791 : : {
792 : 0 : g_signal_connect_object (req, "notify::data", (GCallback)on_request_notify, task, 0);
793 : 0 : g_signal_connect_object (req, "notify::completed", (GCallback)on_request_notify_completed, g_object_ref (task), 0);
794 : : }
795 : : }
796 : :
797 : : static gboolean
798 : 0 : shumate_vector_renderer_fill_tile_finish (ShumateMapSource *map_source,
799 : : GAsyncResult *result,
800 : : GError **error)
801 : : {
802 : 0 : ShumateVectorRenderer *self = (ShumateVectorRenderer *)map_source;
803 : :
804 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_VECTOR_RENDERER (self), FALSE);
805 [ # # ]: 0 : g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
806 : :
807 : 0 : return g_task_propagate_boolean (G_TASK (result), error);
808 : : }
809 : :
810 : : void
811 : 3 : shumate_vector_renderer_render (ShumateVectorRenderer *self,
812 : : ShumateTile *tile,
813 : : GBytes *tile_data,
814 : : ShumateGridPosition *source_position,
815 : : GdkPaintable **paintable,
816 : : GPtrArray **symbols)
817 : : {
818 : 3 : SHUMATE_PROFILE_START ();
819 : :
820 : : #ifdef SHUMATE_HAS_VECTOR_RENDERER
821 : 3 : ShumateVectorRenderScope scope;
822 : 3 : cairo_surface_t *surface;
823 : 6 : g_autoptr(GPtrArray) symbol_list = g_ptr_array_new_with_free_func ((GDestroyNotify)shumate_vector_symbol_info_unref);
824 : 3 : int texture_size;
825 [ + - ]: 3 : g_autofree char *profile_desc = NULL;
826 : 3 : g_autoptr(ShumateVectorSpriteSheet) sprites = NULL;
827 [ + - ]: 3 : g_autoptr(ShumateVectorReader) reader = NULL;
828 : :
829 [ + - ]: 3 : g_assert (SHUMATE_IS_VECTOR_RENDERER (self));
830 [ - + ]: 3 : g_assert (SHUMATE_IS_TILE (tile));
831 : :
832 : 3 : g_mutex_lock (&self->sprites_mutex);
833 [ + - ]: 3 : if (self->sprites == NULL)
834 : 3 : self->sprites = shumate_vector_sprite_sheet_new ();
835 : 3 : sprites = g_object_ref (self->sprites);
836 : 3 : g_mutex_unlock (&self->sprites_mutex);
837 : :
838 : 3 : texture_size = shumate_tile_get_size (tile);
839 : 3 : scope.scale_factor = shumate_tile_get_scale_factor (tile);
840 : 3 : scope.target_size = texture_size;
841 : 3 : scope.tile_x = shumate_tile_get_x (tile);
842 : 3 : scope.tile_y = shumate_tile_get_y (tile);
843 : 3 : scope.zoom_level = shumate_tile_get_zoom_level (tile);
844 : 3 : scope.symbols = symbol_list;
845 : 3 : scope.sprites = sprites;
846 : 3 : scope.index = NULL;
847 : 3 : scope.index_description = self->index_description;
848 : :
849 [ - + ]: 3 : if (scope.zoom_level > source_position->zoom)
850 : : {
851 : 0 : float s = 1 << ((int)scope.zoom_level - source_position->zoom);
852 : 0 : scope.overzoom_x = (scope.tile_x - (source_position->x << ((int)scope.zoom_level - source_position->zoom))) / s;
853 : 0 : scope.overzoom_y = (scope.tile_y - (source_position->y << ((int)scope.zoom_level - source_position->zoom))) / s;
854 : 0 : scope.overzoom_scale = s;
855 : : }
856 : : else
857 : : {
858 : 3 : scope.overzoom_x = 0;
859 : 3 : scope.overzoom_y = 0;
860 : 3 : scope.overzoom_scale = 1;
861 : : }
862 : :
863 : 6 : surface = cairo_image_surface_create (
864 : : CAIRO_FORMAT_ARGB32,
865 : : texture_size * scope.scale_factor,
866 : 3 : texture_size * scope.scale_factor
867 : : );
868 : 3 : scope.cr = cairo_create (surface);
869 : 3 : cairo_scale (scope.cr, scope.scale_factor, scope.scale_factor);
870 : :
871 : 3 : reader = shumate_vector_reader_new (tile_data);
872 : 3 : scope.reader = shumate_vector_reader_iterate (reader);
873 : :
874 [ + - ]: 3 : if (scope.reader != NULL)
875 [ + + ]: 15 : for (scope.layer_idx = 0; scope.layer_idx < self->layers->len; scope.layer_idx ++)
876 : 12 : shumate_vector_layer_render ((ShumateVectorLayer *)self->layers->pdata[scope.layer_idx], &scope);
877 : :
878 : 3 : *paintable = GDK_PAINTABLE (texture_new_for_surface (surface));
879 : 3 : *symbols = g_ptr_array_ref (scope.symbols);
880 : :
881 : 3 : cairo_destroy (scope.cr);
882 : 3 : cairo_surface_destroy (surface);
883 [ + - ]: 3 : g_clear_object (&scope.reader);
884 : :
885 [ - + ]: 3 : g_clear_pointer (&scope.index, shumate_vector_index_free);
886 : :
887 : 3 : profile_desc = g_strdup_printf ("(%d, %d) @ %f", scope.tile_x, scope.tile_y, scope.zoom_level);
888 [ + - ]: 3 : SHUMATE_PROFILE_END (profile_desc);
889 : :
890 : : #else
891 : : g_return_if_reached ();
892 : : #endif
893 : 3 : }
894 : :
895 : : static gboolean
896 : 0 : render_job_finish (RenderJob *job)
897 : : {
898 : 0 : TaskData *data = g_task_get_task_data (job->task);
899 : :
900 [ # # ]: 0 : if (!g_cancellable_is_cancelled (job->cancellable))
901 : : {
902 : 0 : shumate_tile_set_paintable (data->tile, job->paintable);
903 : 0 : shumate_tile_set_symbols (data->tile, job->symbols);
904 : : }
905 : :
906 [ # # ]: 0 : if (data->current_job == job)
907 : : {
908 : 0 : data->current_job = NULL;
909 : :
910 [ # # ]: 0 : if (data->completed)
911 : 0 : return_from_task (job->task, data->req);
912 : : }
913 : :
914 : 0 : render_job_unref (job);
915 : 0 : return G_SOURCE_REMOVE;
916 : : }
917 : :
918 : : static void
919 : 0 : thread_func (RenderJob *job)
920 : : {
921 : 0 : ShumateVectorRenderer *self = g_task_get_source_object (job->task);
922 : 0 : TaskData *data = g_task_get_task_data (job->task);
923 : :
924 [ # # ]: 0 : if (!g_cancellable_is_cancelled (job->cancellable))
925 : : {
926 : 0 : shumate_vector_renderer_render (
927 : : self,
928 : : data->tile,
929 : : job->data,
930 : : &job->source_position,
931 : : &job->paintable,
932 : : &job->symbols
933 : : );
934 : : }
935 : :
936 : 0 : g_idle_add ((GSourceFunc)render_job_finish, job);
937 : 0 : }
938 : :
939 : : static void
940 : 0 : chain_cancel (GCancellable *source,
941 : : GCancellable *dest)
942 : : {
943 : 0 : g_cancellable_cancel (dest);
944 : 0 : }
945 : :
946 : : static gboolean
947 : 0 : begin_render (ShumateVectorRenderer *self,
948 : : GTask *task,
949 : : GBytes *tile_data,
950 : : ShumateGridPosition *source_position)
951 : : {
952 : 0 : g_autoptr(GError) error = NULL;
953 : 0 : RenderJob *job = NULL;
954 : 0 : TaskData *data = (TaskData *)g_task_get_task_data (task);
955 : :
956 [ # # ]: 0 : if (data->current_job != NULL)
957 : 0 : g_cancellable_cancel (data->current_job->cancellable);
958 : :
959 : 0 : job = g_new0 (RenderJob, 1);
960 : 0 : job->cancellable = g_cancellable_new ();
961 : 0 : job->task = g_object_ref (task);
962 : 0 : job->data = g_bytes_ref (tile_data);
963 : 0 : job->source_position = *source_position;
964 : 0 : data->current_job = job;
965 : :
966 : : /* If the input cancellable is cancelled, stop the render job. */
967 [ # # ]: 0 : if (g_task_get_cancellable (task) != NULL)
968 : : {
969 : 0 : job->cancellable_handle = g_cancellable_connect (
970 : : g_task_get_cancellable (task),
971 : : G_CALLBACK (chain_cancel),
972 : : job, NULL
973 : : );
974 : : }
975 : :
976 [ # # ]: 0 : if (self->thread_pool == NULL)
977 : : {
978 : 0 : self->thread_pool = g_thread_pool_new_full (
979 : : (GFunc)thread_func,
980 : : NULL,
981 : : (GDestroyNotify)render_job_unref,
982 : 0 : g_get_num_processors () - 1,
983 : : FALSE,
984 : : &error
985 : : );
986 [ # # ]: 0 : if (self->thread_pool == NULL)
987 : : {
988 : 0 : g_critical ("Failed to create thread pool: %s", error->message);
989 : 0 : return FALSE;
990 : : }
991 : : }
992 : :
993 [ # # ]: 0 : if (!g_thread_pool_push (self->thread_pool, job, &error))
994 : : {
995 : 0 : g_critical ("Failed to push job to thread pool: %s", error->message);
996 : 0 : return FALSE;
997 : : }
998 : :
999 : : return TRUE;
1000 : : }
1001 : :
1002 : :
1003 : : /**
1004 : : * shumate_style_error_quark:
1005 : : *
1006 : : * Returns: a #GQuark
1007 : : */
1008 [ + + ]: 67 : G_DEFINE_QUARK (shumate-style-error-quark, shumate_style_error);
|