]> git.street.me.uk Git - andy/viking.git/blob - src/bingmapsource.c
Merge branch 'i18n-launchpad'
[andy/viking.git] / src / bingmapsource.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking
4  * Copyright (C) 2011, Guilhem Bonnefille <guilhem.bonnefille@gmail.com>
5  * 
6  * viking is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  * 
11  * viking is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19  
20  /**
21   * SECTION:bingmapsource
22   * @short_description: the class for Bing Maps
23   * 
24   * The #BingMapSource class handles Bing map source.
25   * 
26   * License and term of use are available here:
27   * http://wiki.openstreetmap.org/wiki/File:Bing_license.pdf
28   * 
29   * Technical details are available here:
30   * http://msdn.microsoft.com/en-us/library/dd877180.aspx
31   */
32   
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #ifdef HAVE_MATH_H
38 #include <math.h>
39 #endif
40
41 #ifdef HAVE_STDLIB_H
42 #include <stdlib.h>
43 #endif
44 #include <glib.h>
45 #include <glib/gstdio.h>
46 #include <glib/gi18n.h>
47 #include <gdk-pixbuf/gdk-pixdata.h>
48 #include "globals.h"
49 #include "bingmapsource.h"
50 #include "bbox.h"
51 #include "background.h"
52 #include "icons/icons.h"
53
54 /* Format for URL */
55 #define URL_ATTR_FMT "http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/0,0?zl=1&mapVersion=v1&key=%s&include=ImageryProviders&output=xml"
56
57 static gchar *_get_uri ( VikMapSourceDefault *self, MapCoord *src );
58 static void _get_copyright (VikMapSource * self, LatLonBBox bbox, gdouble zoom, void (*fct)(VikViewport*,const gchar*), void *data);
59 static const GdkPixbuf *_get_logo ( VikMapSource *self );
60 static int _load_attributions ( BingMapSource *self );
61 static void _async_load_attributions ( BingMapSource *self );
62
63 struct _Attribution
64 {
65         gchar *attribution;
66         int minZoom;
67         int maxZoom;
68         LatLonBBox bounds;
69 };
70
71 typedef struct _BingMapSourcePrivate BingMapSourcePrivate;
72 struct _BingMapSourcePrivate
73 {
74         gchar *api_key;
75         GList *attributions;
76         /* Current attribution, when parsing */
77         gchar *attribution;
78 };
79
80 /* The pixbuf to store the logo */
81 static GdkPixbuf *pixbuf = NULL;
82
83 #define BING_MAP_SOURCE_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), BING_TYPE_MAP_SOURCE, BingMapSourcePrivate))
84
85 /* properties */
86 enum
87 {
88         PROP_0,
89
90         PROP_API_KEY,
91 };
92
93 G_DEFINE_TYPE (BingMapSource, bing_map_source, VIK_TYPE_SLIPPY_MAP_SOURCE);
94
95 static void
96 bing_map_source_init (BingMapSource *self)
97 {
98         /* initialize the object here */
99         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
100
101         priv->api_key = NULL;
102         priv->attributions = NULL;
103         priv->attribution = NULL;
104 }
105
106 static void
107 bing_map_source_finalize (GObject *object)
108 {
109         BingMapSource *self = BING_MAP_SOURCE (object);
110         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
111
112         g_free (priv->api_key);
113         priv->api_key = NULL;
114
115         G_OBJECT_CLASS (bing_map_source_parent_class)->finalize (object);
116 }
117
118 static void
119 _set_property (GObject      *object,
120                guint         property_id,
121                const GValue *value,
122                GParamSpec   *pspec)
123 {
124         BingMapSource *self = BING_MAP_SOURCE (object);
125         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
126
127         switch (property_id)
128         {
129         case PROP_API_KEY:
130                 priv->api_key = g_strdup (g_value_get_string (value));
131                 break;
132
133         default:
134                 /* We don't have any other property... */
135                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
136                 break;
137         }
138 }
139
140 static void
141 _get_property (GObject    *object,
142                guint       property_id,
143                GValue     *value,
144                GParamSpec *pspec)
145 {
146         BingMapSource *self = BING_MAP_SOURCE (object);
147         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
148
149         switch (property_id)
150         {
151         case PROP_API_KEY:
152                 g_value_set_string (value, priv->api_key);
153                 break;
154
155         default:
156                 /* We don't have any other property... */
157                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
158                 break;
159         }
160 }
161
162
163 static void
164 bing_map_source_class_init (BingMapSourceClass *klass)
165 {
166         GObjectClass* object_class = G_OBJECT_CLASS (klass);
167         VikMapSourceDefaultClass* grandparent_class = VIK_MAP_SOURCE_DEFAULT_CLASS (klass);
168         VikMapSourceClass* base_class = VIK_MAP_SOURCE_CLASS (klass);
169         GParamSpec *pspec = NULL;
170                 
171         /* Overiding methods */
172         object_class->set_property = _set_property;
173         object_class->get_property = _get_property;
174         grandparent_class->get_uri = _get_uri;
175         base_class->get_logo      = _get_logo;
176         base_class->get_copyright = _get_copyright;
177
178         pspec = g_param_spec_string ("api-key",
179                                      "API key",
180                                      "The API key to access Bing",
181                                      "<no-set>" /* default value */,
182                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
183         g_object_class_install_property (object_class, PROP_API_KEY, pspec);
184
185         g_type_class_add_private (klass, sizeof (BingMapSourcePrivate));
186         
187         object_class->finalize = bing_map_source_finalize;
188
189         pixbuf = gdk_pixbuf_from_pixdata ( &bing_maps_pixbuf, TRUE, NULL );
190 }
191
192 static gchar *
193 compute_quad_tree(int zoom, int tilex, int tiley)
194 {
195         /* Picked from http://trac.openstreetmap.org/browser/applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java?rev=24486 */
196         gchar k[20];
197         int ik = 0;
198         int i = 0;
199         for(i = zoom; i > 0; i--)
200         {
201                 char digit = 48;
202                 int mask = 1 << (i - 1);
203                 if ((tilex & mask) != 0) {
204                         digit += 1;
205                 }
206                 if ((tiley & mask) != 0) {
207                         digit += 2;
208                 }
209                 k[ik++] = digit;
210         }
211         k[ik] = '\0';
212         return g_strdup(k);
213 }
214
215 static gchar *
216 _get_uri( VikMapSourceDefault *self, MapCoord *src )
217 {
218         g_return_val_if_fail (BING_IS_MAP_SOURCE(self), NULL);
219
220         /* BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE(self); */
221         gchar *quadtree = compute_quad_tree (17 - src->scale, src->x, src->y);
222         gchar *uri = g_strdup_printf ( "/tiles/a%s.%s?g=587", quadtree, "jpeg");
223         g_free (quadtree);
224         return uri;
225
226
227 static const GdkPixbuf *
228 _get_logo( VikMapSource *self )
229 {
230         return pixbuf;
231 }
232
233 static void
234 _get_copyright(VikMapSource * self, LatLonBBox bbox, gdouble zoom, void (*fct)(VikViewport*,const gchar*), void *data)
235 {
236         g_return_if_fail (BING_IS_MAP_SOURCE(self));
237         g_debug("%s: looking for %g %g %g %g at %g", __FUNCTION__, bbox.south, bbox.north, bbox.east, bbox.west, zoom);
238
239         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE(self);
240
241         int level = vik_slippy_map_source_zoom_to_scale (zoom);
242
243         /* Loop over all known attributions */
244         GList *attribution = priv->attributions;
245         if (attribution == NULL) {
246                 _async_load_attributions (BING_MAP_SOURCE (self));
247         }
248         while (attribution != NULL) {
249                 struct _Attribution *current = (struct _Attribution*)attribution->data;
250                 /* g_debug("%s %g %g %g %g %d %d", __FUNCTION__, current->bounds.south, current->bounds.north, current->bounds.east, current->bounds.west, current->minZoom, current->maxZoom); */
251                 if (BBOX_INTERSECT(bbox, current->bounds) &&
252                     (17 - level) > current->minZoom &&
253                     (17 - level) < current->maxZoom) {
254                         (*fct)(data, current->attribution);
255                         g_debug("%s: found match %s", __FUNCTION__, current->attribution);
256                 }
257                 attribution = attribution->next;
258         }
259 }
260
261 /* Called for open tags <foo bar="baz"> */
262 static void
263 _start_element (GMarkupParseContext *context,
264                 const gchar         *element_name,
265                 const gchar        **attribute_names,
266                 const gchar        **attribute_values,
267                 gpointer             user_data,
268                 GError             **error)
269 {
270         BingMapSource *self = BING_MAP_SOURCE (user_data);
271         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
272         const gchar *element = g_markup_parse_context_get_element (context);
273         if (strcmp (element, "CoverageArea") == 0) {
274                 /* New Attribution */
275                 struct _Attribution *attribution = g_malloc (sizeof(struct _Attribution));
276                 priv->attributions = g_list_append (priv->attributions, attribution);
277                 attribution->attribution = g_strdup (priv->attribution);
278         }
279 }
280
281 /* Called for character data */
282 /* text is not nul-terminated */
283 static void
284 _text (GMarkupParseContext *context,
285        const gchar         *text,
286        gsize                text_len,  
287        gpointer             user_data,
288        GError             **error)
289 {
290         BingMapSource *self = BING_MAP_SOURCE (user_data);
291         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
292
293         struct _Attribution *attribution = priv->attributions == NULL ? NULL : g_list_last (priv->attributions)->data;
294         const gchar *element = g_markup_parse_context_get_element (context);
295         gchar *textl = g_strndup (text, text_len);
296         const GSList *stack = g_markup_parse_context_get_element_stack (context);
297         int len = g_slist_length ((GSList *)stack);
298
299         const gchar *parent = len > 1 ? g_slist_nth_data ((GSList *)stack, 1) : NULL;
300         
301         if (strcmp (element, "Attribution") == 0) {
302                 g_free (priv->attribution);
303                 priv->attribution = g_strdup (textl);
304         } else if (parent != NULL && strcmp (parent, "CoverageArea") == 0) {
305                 if (strcmp (element, "ZoomMin") == 0) {
306                         attribution->minZoom = atoi (textl);
307                 } else if (strcmp (element, "ZoomMax") == 0) {
308                         attribution->maxZoom = atoi (textl);
309                 }
310         } else if (parent != NULL && strcmp (parent, "BoundingBox") == 0) {
311                 if (strcmp (element, "SouthLatitude") == 0) {
312                         attribution->bounds.south = g_ascii_strtod (textl, NULL);
313                 } else if (strcmp (element, "WestLongitude") == 0) {
314                         attribution->bounds.west = g_ascii_strtod (textl, NULL);
315                 } else if (strcmp (element, "NorthLatitude") == 0) {
316                         attribution->bounds.north = g_ascii_strtod (textl, NULL);
317                 } else if (strcmp (element, "EastLongitude") == 0) {
318                         attribution->bounds.east = g_ascii_strtod (textl, NULL);
319                 }
320         }
321         if (attribution)
322                 g_debug("Current attribution %s from %d to %d %g %g %g %g",
323                         attribution->attribution,
324                         attribution->minZoom, attribution->maxZoom,
325                         attribution->bounds.south, attribution->bounds.north, attribution->bounds.west, attribution->bounds.east);
326
327         g_free(textl);
328 }
329
330 static gboolean
331 _parse_file_for_attributions(BingMapSource *self, gchar *filename)
332 {
333         GMarkupParser xml_parser;
334         GMarkupParseContext *xml_context = NULL;
335         GError *error = NULL;
336         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
337         g_return_val_if_fail(priv != NULL, FALSE);
338
339         FILE *file = g_fopen (filename, "r");
340         if (file == NULL)
341                 /* TODO emit warning */
342                 return FALSE;
343         
344         /* setup context parse (ie callbacks) */
345         xml_parser.start_element = &_start_element;
346         xml_parser.end_element = NULL;
347         xml_parser.text = &_text;
348         xml_parser.passthrough = NULL;
349         xml_parser.error = NULL;
350         
351         xml_context = g_markup_parse_context_new(&xml_parser, 0, self, NULL);
352
353         gchar buff[BUFSIZ];
354         size_t nb;
355         size_t offset = -1;
356         while (xml_context &&
357                (nb = fread (buff, sizeof(gchar), BUFSIZ, file)) > 0)
358         {
359                 if (offset == -1)
360                         /* first run */
361                         /* Avoid possible BOM at begining of the file */
362                         offset = buff[0] == '<' ? 0 : 3;
363                 else
364                         /* reset offset */
365                         offset = 0;
366                 if (!g_markup_parse_context_parse(xml_context, buff+offset, nb-offset, &error))
367                 {
368                         fprintf(stderr, "%s: parsing error: %s.\n",
369                                 __FUNCTION__, error->message);
370                         g_markup_parse_context_free(xml_context);
371                         xml_context = NULL;
372                 }
373                 g_clear_error (&error);
374         }
375         /* cleanup */
376         if (xml_context &&
377             !g_markup_parse_context_end_parse(xml_context, &error))
378                 fprintf(stderr, "%s: errors occurred while reading file: %s.\n",
379                         __FUNCTION__, error->message);
380         g_clear_error (&error);
381         
382         if (xml_context)
383                 g_markup_parse_context_free(xml_context);
384         xml_context = NULL;
385         fclose (file);
386
387         return TRUE;
388 }
389
390 static int
391 _load_attributions ( BingMapSource *self )
392 {
393         int ret = 0;  /* OK */
394
395         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
396         gchar *uri = g_strdup_printf(URL_ATTR_FMT, priv->api_key);
397
398         gchar *tmpname = a_download_uri_to_tmp_file ( uri, vik_map_source_default_get_download_options(VIK_MAP_SOURCE_DEFAULT(self)) );
399
400         g_debug("%s: %s", __FUNCTION__, tmpname);
401         if (!_parse_file_for_attributions(self, tmpname)) {
402                 ret = -1;
403                 goto done;
404         }
405
406 done:
407         g_free(uri);
408         g_remove(tmpname);
409         g_free(tmpname);
410         return ret;
411 }
412
413 static int
414 _emit_update ( gpointer data )
415 {
416         gdk_threads_enter();
417         /* TODO
418         vik_layers_panel_emit_update ( VIK_LAYERS_PANEL (data) );
419         */
420         gdk_threads_leave();
421         return 0;
422 }
423
424 static int
425 _load_attributions_thread ( BingMapSource *self, gpointer threaddata )
426 {
427         _load_attributions ( self );
428         int result = a_background_thread_progress ( threaddata, 1.0 );
429         if ( result != 0 )
430                 return -1; /* Abort thread */
431
432         /* Emit update */
433         /* As we are on a download thread,
434          * it's better to fire the update from the main loop.
435          */
436         g_idle_add ( (GSourceFunc)_emit_update, NULL /* FIXME */ );
437
438         return 0;
439 }
440
441 static void
442 _async_load_attributions ( BingMapSource *self )
443 {
444         a_background_thread ( /*VIK_GTK_WINDOW_FROM_WIDGET(vp)*/NULL,
445                             _("Bing attribution Loading"),
446                             (vik_thr_func) _load_attributions_thread,
447                             self,
448                             NULL,
449                             NULL,
450                             1 );
451      
452 }
453
454 /**
455  * bing_map_source_new_with_id:
456  * @id: internal identifier.
457  * @label: the label to display in map provider selector.
458  * @key: the API key to access Bing's services.
459  *
460  * Constructor for Bing map source.
461  *
462  * Returns: a newly allocated BingMapSource GObject.
463  */
464 BingMapSource *
465 bing_map_source_new_with_id (guint8 id, const gchar *label, const gchar *key)
466 {
467         /* initialize settings here */
468         return g_object_new(BING_TYPE_MAP_SOURCE,
469                             "id", id,
470                                                 "label", label,
471                                                 "hostname", "ecn.t2.tiles.virtualearth.net",
472                                                 "api-key", key,
473                                                 "check-file-server-time", TRUE,
474                                                 "copyright", "© 2011 Microsoft Corporation and/or its suppliers",
475                                                 "license", "Microsoft Bing Maps Specific",
476                                                 "license-url", "http://www.microsoft.com/maps/assets/docs/terms.aspx",
477                                                 NULL);
478 }