Verified Commit 8c490eb2 authored by James Westman's avatar James Westman
Browse files

Split ShumateNetworkTileSource

Split the network source into separate classes for fetching data,
rendering raster tiles, and rendering vector tiles. This makes the code
much more modular and organized, especially since vector tiles are about
to get even more complicated.

ShumateDataSource is a new abstract class that retrieves tile data. It
has a built-in implementation, ShumateTileDownloader, which takes over
the network part of ShumateNetworkTileSource.

ShumateRasterRenderer is a ShumateMapSource that provides tiles by
reading image files from a ShumateDataSource. Similarly, ShumateVectorRenderer
is a ShumateMapSource that provides tiles by rendering vector tiles.

This commit also modifies the network source to use the tile URL as a
file cache key, rather than the map source ID. This is because, with
vector tiles, two different map sources may use the same set of tiles.
parent f1ac2849
Pipeline #334400 passed with stages
in 2 minutes and 39 seconds
......@@ -4,7 +4,7 @@ stages:
fedora:
stage: build
image: registry.gitlab.gnome.org/gnome/gtk/fedora:v30
image: registry.gitlab.gnome.org/gnome/gtk/fedora:v35
script:
- sudo dnf install -y vala sqlite-devel libsoup-devel gtk4-devel protobuf-c-devel
- meson _build -Db_coverage=true -Dgtk_doc=true
......
......@@ -129,7 +129,6 @@ shumate_demo_window_init (ShumateDemoWindow *self)
GtkExpression *expression;
g_autoptr(GBytes) bytes = NULL;
const char *style_json;
g_autoptr(ShumateVectorStyle) style = NULL;
GError *error = NULL;
gtk_widget_init_template (GTK_WIDGET (self));
......@@ -144,25 +143,25 @@ 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);
if (shumate_vector_style_is_supported ())
if (shumate_vector_renderer_is_supported ())
{
if (!(style = shumate_vector_style_create (style_json, &error)))
ShumateVectorRenderer *renderer = shumate_vector_renderer_new_full_from_url (
"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_json,
&error
);
if (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);
}
shumate_map_source_registry_add (self->registry, SHUMATE_MAP_SOURCE (renderer));
}
viewport = shumate_map_get_viewport (self->map);
......
libshumate_public_h = [
'shumate-coordinate.h',
'shumate-compass.h',
'shumate-data-source.h',
'shumate-file-cache.h',
'shumate-layer.h',
'shumate-license.h',
......@@ -12,12 +13,13 @@ libshumate_public_h = [
'shumate-marker-layer.h',
'shumate-marker.h',
'shumate-memory-cache.h',
'shumate-network-tile-source.h',
'shumate-path-layer.h',
'shumate-point.h',
'shumate-scale.h',
'shumate-raster-renderer.h',
'shumate-tile.h',
'shumate-vector-style.h',
'shumate-tile-downloader.h',
'shumate-vector-renderer.h',
'shumate-viewport.h',
'shumate.h',
]
......@@ -42,6 +44,7 @@ libshumate_private_h = [
libshumate_sources = [
'shumate-coordinate.c',
'shumate-data-source.c',
'shumate-compass.c',
'shumate-file-cache.c',
'shumate-kinetic-scrolling.c',
......@@ -55,12 +58,13 @@ libshumate_sources = [
'shumate-marker-layer.c',
'shumate-marker.c',
'shumate-memory-cache.c',
'shumate-network-tile-source.c',
'shumate-path-layer.c',
'shumate-point.c',
'shumate-scale.c',
'shumate-raster-renderer.c',
'shumate-tile.c',
'shumate-vector-style.c',
'shumate-tile-downloader.c',
'shumate-vector-renderer.c',
'shumate-viewport.c',
]
......@@ -163,7 +167,7 @@ if get_option('vector_renderer')
]
libshumate_c_args += [
'-DSHUMATE_VECTOR_RENDERER',
'-DSHUMATE_HAS_VECTOR_RENDERER',
]
endif
......
/*
* 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-data-source.h"
G_DEFINE_TYPE (ShumateDataSource, shumate_data_source, G_TYPE_OBJECT)
enum
{
RECEIVED_DATA,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
static GBytes *
shumate_data_source_real_get_tile_data_finish (ShumateDataSource *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
shumate_data_source_class_init (ShumateDataSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
klass->get_tile_data_async = NULL;
klass->get_tile_data_finish = shumate_data_source_real_get_tile_data_finish;
/**
* ShumateDataSource::received-data:
* @self: the [class@DataSource] emitting the signal
* @x: the X coordinate of the tile
* @y: the Y coordinate of the tile
* @zoom_level: the zoom level of the tile
* @bytes: the received data
*
* Emitted when data is received for any tile. This includes any intermediate
* steps, such as data from the file cache, as well as the final result.
*/
signals[RECEIVED_DATA] =
g_signal_new ("received-data",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
NULL,
G_TYPE_NONE,
4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_BYTES);
}
static void
shumate_data_source_init (ShumateDataSource *self)
{
}
/**
* shumate_data_source_get_tile_data_async:
* @self: a [class@DataSource]
* @x: the X coordinate to fetch
* @y: the Y coordinate to fetch
* @zoom_level: the Z coordinate to fetch
* @cancellable: a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Gets the data for the tile at the given coordinates.
*
* Some data sources may return data multiple times. For example,
* [class@TileDownloader] may return data from a cache, then return updated
* data from the network. [signal@received-data] is emitted each time
* data is received, then @callback is called after the last update.
*/
void
shumate_data_source_get_tile_data_async (ShumateDataSource *self,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (SHUMATE_IS_DATA_SOURCE (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
return SHUMATE_DATA_SOURCE_GET_CLASS (self)->get_tile_data_async (self, x, y, zoom_level, cancellable, callback, user_data);
}
/**
* shumate_data_source_get_tile_data_finish:
* @self: a [class@DataSource]
* @result: a #GAsyncResult provided to callback
* @error: a location for a #GError, or %NULL
*
* Gets the final result of a request started with
* shumate_data_source_get_tile_data_async().
*
* Returns: (transfer full)(nullable): The requested data, or %NULL if an error occurred
*/
GBytes *
shumate_data_source_get_tile_data_finish (ShumateDataSource *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE (self), FALSE);
return SHUMATE_DATA_SOURCE_GET_CLASS (self)->get_tile_data_finish (self, result, error);
}
/*
* 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 <gio/gio.h>
G_BEGIN_DECLS
#define SHUMATE_TYPE_DATA_SOURCE (shumate_data_source_get_type())
G_DECLARE_DERIVABLE_TYPE (ShumateDataSource, shumate_data_source, SHUMATE, DATA_SOURCE, GObject)
struct _ShumateDataSourceClass
{
GObjectClass parent_class;
void (*get_tile_data_async) (ShumateDataSource *self,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GBytes *(*get_tile_data_finish) (ShumateDataSource *self,
GAsyncResult *result,
GError **error);
};
void shumate_data_source_get_tile_data_async (ShumateDataSource *self,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GBytes *shumate_data_source_get_tile_data_finish (ShumateDataSource *self,
GAsyncResult *result,
GError **error);
G_END_DECLS
......@@ -79,13 +79,17 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (sqlite3_stmt, sqlite3_finalize);
static void finalize_sql (ShumateFileCache *file_cache);
static void init_cache (ShumateFileCache *file_cache);
static char *get_filename (ShumateFileCache *file_cache,
ShumateTile *tile);
int x,
int y,
int zoom_level);
static void delete_tile (ShumateFileCache *file_cache,
const char *filename);
const char *filename);
static gboolean create_cache_dir (const char *dir_name);
static void on_tile_filled (ShumateFileCache *self,
ShumateTile *tile);
int x,
int y,
int zoom_level);
static void
shumate_file_cache_get_property (GObject *object,
......@@ -470,36 +474,37 @@ shumate_file_cache_set_size_limit (ShumateFileCache *file_cache,
static char *
get_filename (ShumateFileCache *file_cache,
ShumateTile *tile)
int x,
int y,
int zoom_level)
{
ShumateFileCachePrivate *priv = shumate_file_cache_get_instance_private (file_cache);
const char *cache_key;
g_return_val_if_fail (SHUMATE_IS_FILE_CACHE (file_cache), NULL);
g_return_val_if_fail (SHUMATE_IS_TILE (tile), NULL);
g_return_val_if_fail (priv->cache_dir, NULL);
cache_key = shumate_file_cache_get_cache_key (file_cache);
char *filename = g_strdup_printf ("%s" G_DIR_SEPARATOR_S
"%s" G_DIR_SEPARATOR_S
"%d" G_DIR_SEPARATOR_S
"%d" G_DIR_SEPARATOR_S "%d.png",
priv->cache_dir,
cache_key,
shumate_tile_get_zoom_level (tile),
shumate_tile_get_x (tile),
shumate_tile_get_y (tile));
"%s" G_DIR_SEPARATOR_S
"%d" G_DIR_SEPARATOR_S
"%d" G_DIR_SEPARATOR_S "%d.png",
priv->cache_dir,
cache_key,
zoom_level,
x,
y);
return filename;
}
static char *
db_get_etag (ShumateFileCache *self, ShumateTile *tile)
db_get_etag (ShumateFileCache *self, int x, int y, int zoom_level)
{
ShumateFileCachePrivate *priv = shumate_file_cache_get_instance_private (self);
int sql_rc = SQLITE_OK;
g_autofree char *filename = get_filename (self, tile);
g_autofree char *filename = get_filename (self, x, y, zoom_level);
sqlite3_reset (priv->stmt_select);
sql_rc = sqlite3_bind_text (priv->stmt_select, 1, filename, -1, SQLITE_STATIC);
......@@ -534,7 +539,9 @@ db_get_etag (ShumateFileCache *self, ShumateTile *tile)
/**
* shumate_file_cache_mark_up_to_date:
* @self: a #ShumateFileCache
* @tile: a #ShumateTile
* @x: the X coordinate of the tile
* @y: the Y coordinate of the tile
* @zoom_level: the zoom level of the tile
*
* Marks a tile in the cache as being up to date, without changing its data.
*
......@@ -543,16 +550,17 @@ db_get_etag (ShumateFileCache *self, ShumateTile *tile)
*/
void
shumate_file_cache_mark_up_to_date (ShumateFileCache *self,
ShumateTile *tile)
int x,
int y,
int zoom_level)
{
g_autofree char *filename = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GFileInfo) info = NULL;
g_return_if_fail (SHUMATE_IS_FILE_CACHE (self));
g_return_if_fail (SHUMATE_IS_TILE (tile));
filename = get_filename (self, tile);
filename = get_filename (self, x, y, zoom_level);
file = g_file_new_for_path (filename);
info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
......@@ -570,13 +578,13 @@ shumate_file_cache_mark_up_to_date (ShumateFileCache *self,
static void
on_tile_filled (ShumateFileCache *self,
ShumateTile *tile)
int x, int y, int zoom_level)
{
ShumateFileCachePrivate *priv = shumate_file_cache_get_instance_private (self);
int sql_rc = SQLITE_OK;
g_autofree char *filename = NULL;
filename = get_filename (self, tile);
filename = get_filename (self, x, y, zoom_level);
g_debug ("popularity of %s", filename);
......@@ -793,7 +801,9 @@ static void on_get_tile_file_loaded (GObject *source_object, GAsyncResult *res,
/**
* shumate_file_cache_get_tile_async:
* @self: a #ShumateFileCache
* @tile: a #ShumateTile
* @x: the X coordinate of the tile
* @y: the Y coordinate of the tile
* @zoom_level: the zoom level of the tile
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
......@@ -801,11 +811,13 @@ static void on_get_tile_file_loaded (GObject *source_object, GAsyncResult *res,
* Gets tile data from the cache, if it is available.
*/
void
shumate_file_cache_get_tile_async (ShumateFileCache *self,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
shumate_file_cache_get_tile_async (ShumateFileCache *self,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(GFile) file = NULL;
......@@ -815,7 +827,6 @@ shumate_file_cache_get_tile_async (ShumateFileCache *self,
GetTileData *task_data = NULL;
g_return_if_fail (SHUMATE_IS_FILE_CACHE (self));
g_return_if_fail (SHUMATE_IS_TILE (tile));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
......@@ -824,7 +835,7 @@ shumate_file_cache_get_tile_async (ShumateFileCache *self,
task_data = g_new0 (GetTileData, 1);
g_task_set_task_data (task, task_data, (GDestroyNotify) get_tile_data_free);
filename = get_filename (self, tile);
filename = get_filename (self, x, y, zoom_level);
file = g_file_new_for_path (filename);
/* Retrieve modification time */
......@@ -842,10 +853,10 @@ shumate_file_cache_get_tile_async (ShumateFileCache *self,
}
task_data->modtime = g_file_info_get_modification_date_time (info);
task_data->etag = db_get_etag (self, tile);
task_data->etag = db_get_etag (self, x, y, zoom_level);
/* update tile popularity */
on_tile_filled (self, tile);
on_tile_filled (self, x, y, zoom_level);
g_file_load_bytes_async (file, cancellable, on_get_tile_file_loaded, g_object_ref (task));
}
......@@ -940,7 +951,9 @@ static void on_file_written (GObject *object, GAsyncResult *result, gpointer use
/**
* shumate_file_cache_store_tile_async:
* @self: an #ShumateFileCache
* @tile: a #ShumateTile
* @x: the X coordinate of the tile
* @y: the Y coordinate of the tile
* @zoom_level: the zoom level of the tile
* @bytes: a #GBytes
* @etag: (nullable): an ETag string, or %NULL
* @cancellable: (nullable): a #GCancellable
......@@ -951,7 +964,9 @@ static void on_file_written (GObject *object, GAsyncResult *result, gpointer use
*/
void
shumate_file_cache_store_tile_async (ShumateFileCache *self,
ShumateTile *tile,
int x,
int y,
int zoom_level,
GBytes *bytes,
const char *etag,
GCancellable *cancellable,
......@@ -965,17 +980,16 @@ shumate_file_cache_store_tile_async (ShumateFileCache *self,
StoreTileData *data;
g_return_if_fail (SHUMATE_IS_FILE_CACHE (self));
g_return_if_fail (SHUMATE_IS_TILE (tile));
g_return_if_fail (bytes != NULL);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, shumate_file_cache_store_tile_async);
filename = get_filename (self, tile);
filename = get_filename (self, x, y, zoom_level);
file = g_file_new_for_path (filename);
g_debug ("Update of %p", tile);
g_debug ("Update of tile (%d %d zoom %d)", x, y, zoom_level);
/* If needed, create the cache's dirs */
path = g_path_get_dirname (filename);
......
......@@ -26,7 +26,7 @@
#define _SHUMATE_FILE_CACHE_H_
#include <glib-object.h>
#include <shumate/shumate-tile.h>
#include <gio/gio.h>
G_BEGIN_DECLS
......@@ -77,30 +77,36 @@ gboolean shumate_file_cache_purge_cache_finish (ShumateFileCache *self,
GAsyncResult *result,
GError **error);
void shumate_file_cache_get_tile_async (ShumateFileCache *self,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GBytes *shumate_file_cache_get_tile_finish (ShumateFileCache *self,
char **etag,
GDateTime **modtime,
GAsyncResult *result,
GError **error);
void shumate_file_cache_store_tile_async (ShumateFileCache *self,
ShumateTile *tile,
GBytes *bytes,
const char *etag,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void shumate_file_cache_get_tile_async (ShumateFileCache *self,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GBytes *shumate_file_cache_get_tile_finish (ShumateFileCache *self,
char **etag,
GDateTime **modtime,
GAsyncResult *result,
GError **error);
void shumate_file_cache_store_tile_async (ShumateFileCache *self,
int x,
int y,
int zoom_level,
GBytes *bytes,
const char *etag,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean shumate_file_cache_store_tile_finish (ShumateFileCache *self,
GAsyncResult *result,
GError **error);
void shumate_file_cache_mark_up_to_date (ShumateFileCache *self,
ShumateTile *tile);
int x,
int y,
int zoom_level);
G_END_DECLS
......
......@@ -33,7 +33,8 @@
#include <gio/gio.h>
#include "shumate-network-tile-source.h"
#include "shumate-raster-renderer.h"
#include "shumate-tile-downloader.h"
struct _ShumateMapSourceRegistry
{
......@@ -162,7 +163,7 @@ shumate_map_source_registry_populate_defaults (ShumateMapSourceRegistry *self)
if (!shumate_map_source_registry_get_by_id (self, SHUMATE_MAP_SOURCE_OSM_MAPNIK))
{
g_ptr_array_add (self->map_sources,
shumate_network_tile_source_new_full (
shumate_raster_renderer_new_full_from_url (
SHUMATE_MAP_SOURCE_OSM_MAPNIK,
"OpenStreetMap Mapnik",
"Map Data ODBL OpenStreetMap Contributors, Map Imagery CC-BY-SA 2.0 OpenStreetMap",
......@@ -179,7 +180,7 @@ shumate_map_source_registry_populate_defaults (ShumateMapSourceRegistry *self)
if (!shumate_map_source_registry_get_by_id (self, SHUMATE_MAP_SOURCE_OSM_CYCLE_MAP))
{
g_ptr_array_add (self->map_sources,
shumate_network_tile_source_new_full (
shumate_raster_renderer_new_full_from_url (
SHUMATE_MAP_SOURCE_OSM_CYCLE_MAP,
"OpenStreetMap Cycle Map",
"Map data is CC-BY-SA 2.0 OpenStreetMap contributors",
......@@ -196,7 +197,7 @@ shumate_map_source_registry_populate_defaults (ShumateMapSourceRegistry *self)
if (!shumate_map_source_registry_get_by_id (self, SHUMATE_MAP_SOURCE_OSM_TRANSPORT_MAP))
{
g_ptr_array_add (self->map_sources,
shumate_network_tile_source_new_full (
shumate_raster_renderer_new_full_from_url (
SHUMATE_MAP_SOURCE_OSM_TRANSPORT_MAP,
"OpenStreetMap Transport Map",
"Map data is CC-BY-SA 2.0 OpenStreetMap contributors",
......@@ -213,7 +214,7 @@ shumate_map_source_registry_populate_defaults (ShumateMapSourceRegistry *self)
if (!shumate_map_source_registry_get_by_id (self, SHUMATE_MAP_SOURCE_MFF_RELIEF))
{