1 /*
2  * Copyright (C) 2010-2013 Jiri Techet <techet@gmail.com>
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, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 /**
20  * SECTION:champlain-map-source
21  * @short_description: A base class for map sources
22  *
23  * #ChamplainTile objects come from map sources which are represented by
24  * #ChamplainMapSource.  This is should be considered an abstract
25  * type as it does nothing of interest.
26  *
27  * When loading new tiles, #ChamplainView calls champlain_map_source_fill_tile()
28  * on the current #ChamplainMapSource passing it a #ChamplainTile to be filled
29  * with the image.
30  *
31  * Apart from being a base class of all map sources, #ChamplainMapSource
32  * also supports cooperation of multiple map sources by arranging them into
33  * chains. Every map source has the #ChamplainMapSource:next-source property
34  * that determines the next map source in the chain. When a function of
35  * a #ChamplainMapSource object is invoked, the map source may decide to
36  * delegate the work to the next map source in the chain by invoking the
37  * same function on it.
38 
39  * To understand the concept of chains, consider for instance a chain
40  * consisting of #ChamplainFileCache whose next source is
41  * #ChamplainNetworkTileSource whose next source is an error tile source
42  * created with champlain_map_source_factory_create_error_source ().
43  * When champlain_map_source_fill_tile() is called on the first object of the
44  * chain, #ChamplainFileCache, the cache checks whether it contains the
45  * requested tile in its database. If it does, it returns the tile; otherwise,
46  * it calls champlain_map_source_fill_tile() on the next source in the chain
47  * (#ChamplainNetworkTileSource). The network tile source loads the tile
48  * from the network. When successful, it returns the tile; otherwise it requests
49  * the tile from the next source in the chain (error tile source).
50  * The error tile source always generates an error tile, no matter what
51  * its next source is.
52  */
53 
54 #include "champlain-map-source.h"
55 
56 #include <math.h>
57 
58 struct _ChamplainMapSourcePrivate
59 {
60   ChamplainMapSource *next_source;
61   ChamplainRenderer *renderer;
62 };
63 
64 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ChamplainMapSource, champlain_map_source, G_TYPE_INITIALLY_UNOWNED)
65 
66 enum
67 {
68   PROP_0,
69   PROP_NEXT_SOURCE,
70   PROP_RENDERER,
71 };
72 
73 static void
champlain_map_source_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)74 champlain_map_source_get_property (GObject *object,
75     guint prop_id,
76     GValue *value,
77     GParamSpec *pspec)
78 {
79   ChamplainMapSourcePrivate *priv = CHAMPLAIN_MAP_SOURCE (object)->priv;
80 
81   switch (prop_id)
82     {
83     case PROP_NEXT_SOURCE:
84       g_value_set_object (value, priv->next_source);
85       break;
86 
87     case PROP_RENDERER:
88       g_value_set_object (value, priv->renderer);
89       break;
90 
91     default:
92       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
93     }
94 }
95 
96 
97 static void
champlain_map_source_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)98 champlain_map_source_set_property (GObject *object,
99     guint prop_id,
100     const GValue *value,
101     GParamSpec *pspec)
102 {
103   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE (object);
104 
105   switch (prop_id)
106     {
107     case PROP_NEXT_SOURCE:
108       champlain_map_source_set_next_source (map_source,
109           g_value_get_object (value));
110       break;
111 
112     case PROP_RENDERER:
113       champlain_map_source_set_renderer (map_source,
114           g_value_get_object (value));
115       break;
116 
117     default:
118       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
119     }
120 }
121 
122 
123 static void
champlain_map_source_dispose(GObject * object)124 champlain_map_source_dispose (GObject *object)
125 {
126   ChamplainMapSourcePrivate *priv = CHAMPLAIN_MAP_SOURCE (object)->priv;
127 
128   if (priv->next_source)
129     {
130       g_object_unref (priv->next_source);
131 
132       priv->next_source = NULL;
133     }
134 
135   if (priv->renderer)
136     {
137       g_object_unref (priv->renderer);
138 
139       priv->renderer = NULL;
140     }
141 
142   G_OBJECT_CLASS (champlain_map_source_parent_class)->dispose (object);
143 }
144 
145 
146 static void
champlain_map_source_finalize(GObject * object)147 champlain_map_source_finalize (GObject *object)
148 {
149   G_OBJECT_CLASS (champlain_map_source_parent_class)->finalize (object);
150 }
151 
152 
153 static void
champlain_map_source_constructed(GObject * object)154 champlain_map_source_constructed (GObject *object)
155 {
156   if (G_OBJECT_CLASS (champlain_map_source_parent_class)->constructed)
157     G_OBJECT_CLASS (champlain_map_source_parent_class)->constructed (object);
158 }
159 
160 
161 static void
champlain_map_source_class_init(ChamplainMapSourceClass * klass)162 champlain_map_source_class_init (ChamplainMapSourceClass *klass)
163 {
164   GObjectClass *object_class = G_OBJECT_CLASS (klass);
165   GParamSpec *pspec;
166 
167   object_class->finalize = champlain_map_source_finalize;
168   object_class->dispose = champlain_map_source_dispose;
169   object_class->get_property = champlain_map_source_get_property;
170   object_class->set_property = champlain_map_source_set_property;
171   object_class->constructed = champlain_map_source_constructed;
172 
173   klass->get_id = NULL;
174   klass->get_name = NULL;
175   klass->get_license = NULL;
176   klass->get_license_uri = NULL;
177   klass->get_min_zoom_level = NULL;
178   klass->get_max_zoom_level = NULL;
179   klass->get_tile_size = NULL;
180   klass->get_projection = NULL;
181 
182   klass->fill_tile = NULL;
183 
184   /**
185    * ChamplainMapSource:next-source:
186    *
187    * Next source in the loading chain.
188    *
189    * Since: 0.6
190    */
191   pspec = g_param_spec_object ("next-source",
192         "Next Source",
193         "Next source in the loading chain",
194         CHAMPLAIN_TYPE_MAP_SOURCE,
195         G_PARAM_READWRITE);
196   g_object_class_install_property (object_class, PROP_NEXT_SOURCE, pspec);
197 
198   /**
199    * ChamplainMapSource:renderer:
200    *
201    * Renderer used for tiles rendering.
202    *
203    * Since: 0.8
204    */
205   pspec = g_param_spec_object ("renderer",
206         "Tile renderer",
207         "Tile renderer used to render tiles",
208         CHAMPLAIN_TYPE_RENDERER,
209         G_PARAM_READWRITE);
210   g_object_class_install_property (object_class, PROP_RENDERER, pspec);
211 }
212 
213 
214 static void
champlain_map_source_init(ChamplainMapSource * map_source)215 champlain_map_source_init (ChamplainMapSource *map_source)
216 {
217   ChamplainMapSourcePrivate *priv = champlain_map_source_get_instance_private (map_source);
218 
219   map_source->priv = priv;
220 
221   priv->next_source = NULL;
222   priv->renderer = NULL;
223 }
224 
225 
226 /**
227  * champlain_map_source_get_next_source:
228  * @map_source: a #ChamplainMapSource
229  *
230  * Get the next source in the chain.
231  *
232  * Returns: (transfer none): the next source in the chain.
233  *
234  * Since: 0.6
235  */
236 ChamplainMapSource *
champlain_map_source_get_next_source(ChamplainMapSource * map_source)237 champlain_map_source_get_next_source (ChamplainMapSource *map_source)
238 {
239   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
240 
241   return map_source->priv->next_source;
242 }
243 
244 
245 /**
246  * champlain_map_source_get_renderer:
247  * @map_source: a #ChamplainMapSource
248  *
249  * Get the renderer used for tiles rendering.
250  *
251  * Returns: (transfer none): the renderer.
252  *
253  * Since: 0.8
254  */
255 ChamplainRenderer *
champlain_map_source_get_renderer(ChamplainMapSource * map_source)256 champlain_map_source_get_renderer (ChamplainMapSource *map_source)
257 {
258   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
259 
260   return map_source->priv->renderer;
261 }
262 
263 
264 /**
265  * champlain_map_source_set_next_source:
266  * @map_source: a #ChamplainMapSource
267  * @next_source: the next #ChamplainMapSource in the chain
268  *
269  * Sets the next map source in the chain.
270  *
271  * Since: 0.6
272  */
273 void
champlain_map_source_set_next_source(ChamplainMapSource * map_source,ChamplainMapSource * next_source)274 champlain_map_source_set_next_source (ChamplainMapSource *map_source,
275     ChamplainMapSource *next_source)
276 {
277   g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
278 
279   ChamplainMapSourcePrivate *priv = map_source->priv;
280 
281   if (priv->next_source != NULL)
282     g_object_unref (priv->next_source);
283 
284   if (next_source)
285     {
286       g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (next_source));
287 
288       g_object_ref_sink (next_source);
289     }
290 
291   priv->next_source = next_source;
292 
293   g_object_notify (G_OBJECT (map_source), "next-source");
294 }
295 
296 
297 /**
298  * champlain_map_source_set_renderer:
299  * @map_source: a #ChamplainMapSource
300  * @renderer: the renderer
301  *
302  * Sets the renderer used for tiles rendering.
303  *
304  * Since: 0.8
305  */
306 void
champlain_map_source_set_renderer(ChamplainMapSource * map_source,ChamplainRenderer * renderer)307 champlain_map_source_set_renderer (ChamplainMapSource *map_source,
308     ChamplainRenderer *renderer)
309 {
310   g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
311   g_return_if_fail (CHAMPLAIN_IS_RENDERER (renderer));
312 
313   ChamplainMapSourcePrivate *priv = map_source->priv;
314 
315   if (priv->renderer != NULL)
316     g_object_unref (priv->renderer);
317 
318   g_object_ref_sink (renderer);
319   priv->renderer = renderer;
320 
321   g_object_notify (G_OBJECT (map_source), "renderer");
322 }
323 
324 
325 /**
326  * champlain_map_source_get_id:
327  * @map_source: a #ChamplainMapSource
328  *
329  * Gets map source's id.
330  *
331  * Returns: the map source's id.
332  *
333  * Since: 0.4
334  */
335 const gchar *
champlain_map_source_get_id(ChamplainMapSource * map_source)336 champlain_map_source_get_id (ChamplainMapSource *map_source)
337 {
338   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
339 
340   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_id (map_source);
341 }
342 
343 
344 /**
345  * champlain_map_source_get_name:
346  * @map_source: a #ChamplainMapSource
347  *
348  * Gets map source's name.
349  *
350  * Returns: the map source's name.
351  *
352  * Since: 0.4
353  */
354 const gchar *
champlain_map_source_get_name(ChamplainMapSource * map_source)355 champlain_map_source_get_name (ChamplainMapSource *map_source)
356 {
357   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
358 
359   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_name (map_source);
360 }
361 
362 
363 /**
364  * champlain_map_source_get_license:
365  * @map_source: a #ChamplainMapSource
366  *
367  * Gets map source's license.
368  *
369  * Returns: the map source's license.
370  *
371  * Since: 0.4
372  */
373 const gchar *
champlain_map_source_get_license(ChamplainMapSource * map_source)374 champlain_map_source_get_license (ChamplainMapSource *map_source)
375 {
376   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
377 
378   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_license (map_source);
379 }
380 
381 
382 /**
383  * champlain_map_source_get_license_uri:
384  * @map_source: a #ChamplainMapSource
385  *
386  * Gets map source's license URI.
387  *
388  * Returns: the map source's license URI.
389  *
390  * Since: 0.4
391  */
392 const gchar *
champlain_map_source_get_license_uri(ChamplainMapSource * map_source)393 champlain_map_source_get_license_uri (ChamplainMapSource *map_source)
394 {
395   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);
396 
397   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_license_uri (map_source);
398 }
399 
400 
401 /**
402  * champlain_map_source_get_min_zoom_level:
403  * @map_source: a #ChamplainMapSource
404  *
405  * Gets map source's minimum zoom level.
406  *
407  * Returns: the miminum zoom level this map source supports
408  *
409  * Since: 0.4
410  */
411 guint
champlain_map_source_get_min_zoom_level(ChamplainMapSource * map_source)412 champlain_map_source_get_min_zoom_level (ChamplainMapSource *map_source)
413 {
414   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
415 
416   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_min_zoom_level (map_source);
417 }
418 
419 
420 /**
421  * champlain_map_source_get_max_zoom_level:
422  * @map_source: a #ChamplainMapSource
423  *
424  * Gets map source's maximum zoom level.
425  *
426  * Returns: the maximum zoom level this map source supports
427  *
428  * Since: 0.4
429  */
430 guint
champlain_map_source_get_max_zoom_level(ChamplainMapSource * map_source)431 champlain_map_source_get_max_zoom_level (ChamplainMapSource *map_source)
432 {
433   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
434 
435   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_max_zoom_level (map_source);
436 }
437 
438 
439 /**
440  * champlain_map_source_get_tile_size:
441  * @map_source: a #ChamplainMapSource
442  *
443  * Gets map source's tile size.
444  *
445  * Returns: the tile's size (width and height) in pixels for this map source
446  *
447  * Since: 0.4
448  */
449 guint
champlain_map_source_get_tile_size(ChamplainMapSource * map_source)450 champlain_map_source_get_tile_size (ChamplainMapSource *map_source)
451 {
452   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
453 
454   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_tile_size (map_source);
455 }
456 
457 
458 /**
459  * champlain_map_source_get_projection:
460  * @map_source: a #ChamplainMapSource
461  *
462  * Gets map source's projection.
463  *
464  * Returns: the map source's projection.
465  *
466  * Since: 0.4
467  */
468 ChamplainMapProjection
champlain_map_source_get_projection(ChamplainMapSource * map_source)469 champlain_map_source_get_projection (ChamplainMapSource *map_source)
470 {
471   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), CHAMPLAIN_MAP_PROJECTION_MERCATOR);
472 
473   return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_projection (map_source);
474 }
475 
476 
477 /**
478  * champlain_map_source_get_x:
479  * @map_source: a #ChamplainMapSource
480  * @zoom_level: the zoom level
481  * @longitude: a longitude
482  *
483  * Gets the x position on the map using this map source's projection.
484  * (0, 0) is located at the top left.
485  *
486  * Returns: the x position
487  *
488  * Since: 0.4
489  */
490 gdouble
champlain_map_source_get_x(ChamplainMapSource * map_source,guint zoom_level,gdouble longitude)491 champlain_map_source_get_x (ChamplainMapSource *map_source,
492     guint zoom_level,
493     gdouble longitude)
494 {
495   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
496 
497   longitude = CLAMP (longitude, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);
498 
499   /* FIXME: support other projections */
500   return ((longitude + 180.0) / 360.0 * pow (2.0, zoom_level)) * champlain_map_source_get_tile_size (map_source);
501 }
502 
503 
504 /**
505  * champlain_map_source_get_y:
506  * @map_source: a #ChamplainMapSource
507  * @zoom_level: the zoom level
508  * @latitude: a latitude
509  *
510  * Gets the y position on the map using this map source's projection.
511  * (0, 0) is located at the top left.
512  *
513  * Returns: the y position
514  *
515  * Since: 0.4
516  */
517 gdouble
champlain_map_source_get_y(ChamplainMapSource * map_source,guint zoom_level,gdouble latitude)518 champlain_map_source_get_y (ChamplainMapSource *map_source,
519     guint zoom_level,
520     gdouble latitude)
521 {
522   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
523 
524   latitude = CLAMP (latitude, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);
525 
526   /* FIXME: support other projections */
527   return ((1.0 - log (tan (latitude * M_PI / 180.0) + 1.0 / cos (latitude * M_PI / 180.0)) / M_PI) /
528           2.0 * pow (2.0, zoom_level)) * champlain_map_source_get_tile_size (map_source);
529 }
530 
531 
532 /**
533  * champlain_map_source_get_longitude:
534  * @map_source: a #ChamplainMapSource
535  * @zoom_level: the zoom level
536  * @x: a x position
537  *
538  * Gets the longitude corresponding to this x position in the map source's
539  * projection.
540  *
541  * Returns: the longitude
542  *
543  * Since: 0.4
544  */
545 gdouble
champlain_map_source_get_longitude(ChamplainMapSource * map_source,guint zoom_level,gdouble x)546 champlain_map_source_get_longitude (ChamplainMapSource *map_source,
547     guint zoom_level,
548     gdouble x)
549 {
550   gdouble longitude;
551 
552   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);
553   /* FIXME: support other projections */
554   gdouble dx = (gdouble) x / champlain_map_source_get_tile_size (map_source);
555   longitude = dx / pow (2.0, zoom_level) * 360.0 - 180.0;
556 
557   return CLAMP (longitude, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);
558 }
559 
560 
561 /**
562  * champlain_map_source_get_latitude:
563  * @map_source: a #ChamplainMapSource
564  * @zoom_level: the zoom level
565  * @y: a y position
566  *
567  * Gets the latitude corresponding to this y position in the map source's
568  * projection.
569  *
570  * Returns: the latitude
571  *
572  * Since: 0.4
573  */
574 gdouble
champlain_map_source_get_latitude(ChamplainMapSource * map_source,guint zoom_level,gdouble y)575 champlain_map_source_get_latitude (ChamplainMapSource *map_source,
576     guint zoom_level,
577     gdouble y)
578 {
579   gdouble latitude;
580 
581   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);
582   /* FIXME: support other projections */
583   gdouble dy = (gdouble) y / champlain_map_source_get_tile_size (map_source);
584   gdouble n = M_PI - 2.0 * M_PI * dy / pow (2.0, zoom_level);
585   latitude = 180.0 / M_PI *atan (0.5 * (exp (n) - exp (-n)));
586 
587   return CLAMP (latitude, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);
588 }
589 
590 
591 /**
592  * champlain_map_source_get_row_count:
593  * @map_source: a #ChamplainMapSource
594  * @zoom_level: the zoom level
595  *
596  * Gets the number of tiles in a row at this zoom level for this map source.
597  *
598  * Returns: the number of tiles in a row
599  *
600  * Since: 0.4
601  */
602 guint
champlain_map_source_get_row_count(ChamplainMapSource * map_source,guint zoom_level)603 champlain_map_source_get_row_count (ChamplainMapSource *map_source,
604     guint zoom_level)
605 {
606   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
607   /* FIXME: support other projections */
608   return (zoom_level != 0) ? 2 << (zoom_level - 1) : 1;
609 }
610 
611 
612 /**
613  * champlain_map_source_get_column_count:
614  * @map_source: a #ChamplainMapSource
615  * @zoom_level: the zoom level
616  *
617  * Gets the number of tiles in a column at this zoom level for this map
618  * source.
619  *
620  * Returns: the number of tiles in a column
621  *
622  * Since: 0.4
623  */
624 guint
champlain_map_source_get_column_count(ChamplainMapSource * map_source,guint zoom_level)625 champlain_map_source_get_column_count (ChamplainMapSource *map_source,
626     guint zoom_level)
627 {
628   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
629   /* FIXME: support other projections */
630   return (zoom_level != 0) ? 2 << (zoom_level - 1) : 1;
631 }
632 
633 
634 #define EARTH_RADIUS 6378137.0 /* meters, Equatorial radius */
635 
636 /**
637  * champlain_map_source_get_meters_per_pixel:
638  * @map_source: a #ChamplainMapSource
639  * @zoom_level: the zoom level
640  * @latitude: a latitude
641  * @longitude: a longitude
642  *
643  * Gets meters per pixel at the position on the map using this map source's projection.
644  *
645  * Returns: the meters per pixel
646  *
647  * Since: 0.4.3
648  */
649 gdouble
champlain_map_source_get_meters_per_pixel(ChamplainMapSource * map_source,guint zoom_level,gdouble latitude,G_GNUC_UNUSED gdouble longitude)650 champlain_map_source_get_meters_per_pixel (ChamplainMapSource *map_source,
651     guint zoom_level,
652     gdouble latitude,
653     G_GNUC_UNUSED gdouble longitude)
654 {
655   g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);
656 
657   /*
658    * Width is in pixels. (1 px)
659    * m/px = radius_at_latitude / width_in_pixels
660    * k = radius of earth = 6 378.1 km
661    * radius_at_latitude = 2pi * k * sin (pi/2-theta)
662    */
663 
664   gdouble tile_size = champlain_map_source_get_tile_size (map_source);
665   /* FIXME: support other projections */
666   return 2.0 *M_PI *EARTH_RADIUS *sin (M_PI / 2.0 - M_PI / 180.0 *latitude) /
667          (tile_size * champlain_map_source_get_row_count (map_source, zoom_level));
668 }
669 
670 
671 /**
672  * champlain_map_source_fill_tile:
673  * @map_source: a #ChamplainMapSource
674  * @tile: a #ChamplainTile
675  *
676  * Fills the tile with image data (either from cache, network or rendered
677  * locally).
678  *
679  * Since: 0.4
680  */
681 void
champlain_map_source_fill_tile(ChamplainMapSource * map_source,ChamplainTile * tile)682 champlain_map_source_fill_tile (ChamplainMapSource *map_source,
683     ChamplainTile *tile)
684 {
685   g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
686 
687   CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->fill_tile (map_source, tile);
688 }
689