]> git.street.me.uk Git - andy/viking.git/blob - src/bingmapsource.c
Update libjpeg utilities from exif command line tool to version 0.6.21
[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 2 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 "maputils.h"
51 #include "bbox.h"
52 #include "background.h"
53 #include "icons/icons.h"
54
55 /* Format for URL */
56 #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"
57
58 static gchar *_get_uri ( VikMapSourceDefault *self, MapCoord *src );
59 static gchar *_get_hostname ( VikMapSourceDefault *self );
60 static void _get_copyright (VikMapSource * self, LatLonBBox bbox, gdouble zoom, void (*fct)(VikViewport*,const gchar*), void *data);
61 static const GdkPixbuf *_get_logo ( VikMapSource *self );
62 static int _load_attributions ( BingMapSource *self );
63 static void _async_load_attributions ( BingMapSource *self );
64
65 struct _Attribution
66 {
67         gchar *attribution;
68         int minZoom;
69         int maxZoom;
70         LatLonBBox bounds;
71 };
72
73 typedef struct _BingMapSourcePrivate BingMapSourcePrivate;
74 struct _BingMapSourcePrivate
75 {
76         gchar *hostname;
77         gchar *url;
78         gchar *api_key;
79         GList *attributions;
80         /* Current attribution, when parsing */
81         gchar *attribution;
82 };
83
84 /* The pixbuf to store the logo */
85 static GdkPixbuf *pixbuf = NULL;
86
87 #define BING_MAP_SOURCE_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), BING_TYPE_MAP_SOURCE, BingMapSourcePrivate))
88
89 /* properties */
90 enum
91 {
92         PROP_0,
93
94         PROP_HOSTNAME,
95         PROP_URL,
96         PROP_API_KEY,
97 };
98
99 G_DEFINE_TYPE (BingMapSource, bing_map_source, VIK_TYPE_SLIPPY_MAP_SOURCE);
100
101 static void
102 bing_map_source_init (BingMapSource *self)
103 {
104         /* initialize the object here */
105         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
106
107         priv->hostname = NULL;
108         priv->url = NULL;
109         priv->api_key = NULL;
110         priv->attributions = NULL;
111         priv->attribution = NULL;
112 }
113
114 static void
115 bing_map_source_finalize (GObject *object)
116 {
117         BingMapSource *self = BING_MAP_SOURCE (object);
118         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
119
120         g_free (priv->hostname);
121         priv->hostname = NULL;
122         g_free (priv->url);
123         priv->url = NULL;
124         g_free (priv->api_key);
125         priv->api_key = NULL;
126
127         G_OBJECT_CLASS (bing_map_source_parent_class)->finalize (object);
128 }
129
130 static void
131 _set_property (GObject      *object,
132                guint         property_id,
133                const GValue *value,
134                GParamSpec   *pspec)
135 {
136         BingMapSource *self = BING_MAP_SOURCE (object);
137         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
138
139         switch (property_id)
140         {
141         case PROP_HOSTNAME:
142                 g_free (priv->hostname);
143                 priv->hostname = g_value_dup_string (value);
144                 break;
145
146         case PROP_URL:
147                 g_free (priv->url);
148                 priv->url = g_value_dup_string (value);
149                 break;
150
151         case PROP_API_KEY:
152                 priv->api_key = g_strdup (g_value_get_string (value));
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 static void
163 _get_property (GObject    *object,
164                guint       property_id,
165                GValue     *value,
166                GParamSpec *pspec)
167 {
168         BingMapSource *self = BING_MAP_SOURCE (object);
169         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
170
171         switch (property_id)
172         {
173         case PROP_HOSTNAME:
174                 g_value_set_string (value, priv->hostname);
175                 break;
176
177         case PROP_URL:
178                 g_value_set_string (value, priv->url);
179                 break;
180
181         case PROP_API_KEY:
182                 g_value_set_string (value, priv->api_key);
183                 break;
184
185         default:
186                 /* We don't have any other property... */
187                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
188                 break;
189         }
190 }
191
192
193 static void
194 bing_map_source_class_init (BingMapSourceClass *klass)
195 {
196         GObjectClass* object_class = G_OBJECT_CLASS (klass);
197         VikMapSourceDefaultClass* grandparent_class = VIK_MAP_SOURCE_DEFAULT_CLASS (klass);
198         VikMapSourceClass* base_class = VIK_MAP_SOURCE_CLASS (klass);
199         GParamSpec *pspec = NULL;
200
201         /* Overiding methods */
202         object_class->set_property = _set_property;
203         object_class->get_property = _get_property;
204         grandparent_class->get_uri = _get_uri;
205         grandparent_class->get_hostname = _get_hostname;
206         base_class->get_logo      = _get_logo;
207         base_class->get_copyright = _get_copyright;
208
209         pspec = g_param_spec_string ("hostname",
210                                      "Hostname",
211                                      "The hostname of the map server",
212                                      "<no-set>" /* default value */,
213                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
214         g_object_class_install_property (object_class, PROP_HOSTNAME, pspec);
215
216         pspec = g_param_spec_string ("url",
217                                      "URL",
218                                      "The template of the tiles' URL",
219                                      "<no-set>" /* default value */,
220                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
221         g_object_class_install_property (object_class, PROP_URL, pspec);
222
223         pspec = g_param_spec_string ("api-key",
224                                      "API key",
225                                      "The API key to access Bing",
226                                      "<no-set>" /* default value */,
227                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
228         g_object_class_install_property (object_class, PROP_API_KEY, pspec);
229
230         g_type_class_add_private (klass, sizeof (BingMapSourcePrivate));
231         
232         object_class->finalize = bing_map_source_finalize;
233
234         pixbuf = gdk_pixbuf_from_pixdata ( &bing_maps_pixbuf, TRUE, NULL );
235 }
236
237 static gchar *
238 compute_quad_tree(int zoom, int tilex, int tiley)
239 {
240         /* Picked from http://trac.openstreetmap.org/browser/applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java?rev=24486 */
241         gchar k[20];
242         int ik = 0;
243         int i = 0;
244         for(i = zoom; i > 0; i--)
245         {
246                 char digit = 48;
247                 int mask = 1 << (i - 1);
248                 if ((tilex & mask) != 0) {
249                         digit += 1;
250                 }
251                 if ((tiley & mask) != 0) {
252                         digit += 2;
253                 }
254                 k[ik++] = digit;
255         }
256         k[ik] = '\0';
257         return g_strdup(k);
258 }
259
260 static gchar *
261 _get_uri( VikMapSourceDefault *self, MapCoord *src )
262 {
263         g_return_val_if_fail (BING_IS_MAP_SOURCE(self), NULL);
264
265         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE(self);
266         gchar *quadtree = compute_quad_tree (17 - src->scale, src->x, src->y);
267         gchar *uri = g_strdup_printf ( priv->url, quadtree );
268         g_free (quadtree);
269         return uri;
270
271
272 static gchar *
273 _get_hostname( VikMapSourceDefault *self )
274 {
275         g_return_val_if_fail (BING_IS_MAP_SOURCE(self), NULL);
276
277         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE(self);
278         return g_strdup( priv->hostname );
279 }
280
281 static const GdkPixbuf *
282 _get_logo( VikMapSource *self )
283 {
284         return pixbuf;
285 }
286
287 static void
288 _get_copyright(VikMapSource * self, LatLonBBox bbox, gdouble zoom, void (*fct)(VikViewport*,const gchar*), void *data)
289 {
290         g_return_if_fail (BING_IS_MAP_SOURCE(self));
291         g_debug("%s: looking for %g %g %g %g at %g", __FUNCTION__, bbox.south, bbox.north, bbox.east, bbox.west, zoom);
292
293         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE(self);
294
295         int level = map_utils_mpp_to_scale (zoom);
296
297         /* Loop over all known attributions */
298         GList *attribution = priv->attributions;
299         if (attribution == NULL && g_strcmp0 ("<no-set>", priv->api_key)) {
300                 _async_load_attributions (BING_MAP_SOURCE (self));
301         }
302         while (attribution != NULL) {
303                 struct _Attribution *current = (struct _Attribution*)attribution->data;
304                 /* 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); */
305                 if (BBOX_INTERSECT(bbox, current->bounds) &&
306                     (17 - level) > current->minZoom &&
307                     (17 - level) < current->maxZoom) {
308                         (*fct)(data, current->attribution);
309                         g_debug("%s: found match %s", __FUNCTION__, current->attribution);
310                 }
311                 attribution = attribution->next;
312         }
313 }
314
315 /* Called for open tags <foo bar="baz"> */
316 static void
317 _start_element (GMarkupParseContext *context,
318                 const gchar         *element_name,
319                 const gchar        **attribute_names,
320                 const gchar        **attribute_values,
321                 gpointer             user_data,
322                 GError             **error)
323 {
324         BingMapSource *self = BING_MAP_SOURCE (user_data);
325         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
326         const gchar *element = g_markup_parse_context_get_element (context);
327         if (strcmp (element, "CoverageArea") == 0) {
328                 /* New Attribution */
329                 struct _Attribution *attribution = g_malloc (sizeof(struct _Attribution));
330                 priv->attributions = g_list_append (priv->attributions, attribution);
331                 attribution->attribution = g_strdup (priv->attribution);
332         }
333 }
334
335 /* Called for character data */
336 /* text is not nul-terminated */
337 static void
338 _text (GMarkupParseContext *context,
339        const gchar         *text,
340        gsize                text_len,  
341        gpointer             user_data,
342        GError             **error)
343 {
344         BingMapSource *self = BING_MAP_SOURCE (user_data);
345         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
346
347         struct _Attribution *attribution = priv->attributions == NULL ? NULL : g_list_last (priv->attributions)->data;
348         const gchar *element = g_markup_parse_context_get_element (context);
349         gchar *textl = g_strndup (text, text_len);
350         const GSList *stack = g_markup_parse_context_get_element_stack (context);
351         int len = g_slist_length ((GSList *)stack);
352
353         const gchar *parent = len > 1 ? g_slist_nth_data ((GSList *)stack, 1) : NULL;
354         
355         if (strcmp (element, "Attribution") == 0) {
356                 g_free (priv->attribution);
357                 priv->attribution = g_strdup (textl);
358         } else if (parent != NULL && strcmp (parent, "CoverageArea") == 0) {
359                 if (strcmp (element, "ZoomMin") == 0) {
360                         attribution->minZoom = atoi (textl);
361                 } else if (strcmp (element, "ZoomMax") == 0) {
362                         attribution->maxZoom = atoi (textl);
363                 }
364         } else if (parent != NULL && strcmp (parent, "BoundingBox") == 0) {
365                 if (strcmp (element, "SouthLatitude") == 0) {
366                         attribution->bounds.south = g_ascii_strtod (textl, NULL);
367                 } else if (strcmp (element, "WestLongitude") == 0) {
368                         attribution->bounds.west = g_ascii_strtod (textl, NULL);
369                 } else if (strcmp (element, "NorthLatitude") == 0) {
370                         attribution->bounds.north = g_ascii_strtod (textl, NULL);
371                 } else if (strcmp (element, "EastLongitude") == 0) {
372                         attribution->bounds.east = g_ascii_strtod (textl, NULL);
373                 }
374         }
375         if (attribution)
376                 g_debug("Current attribution %s from %d to %d %g %g %g %g",
377                         attribution->attribution,
378                         attribution->minZoom, attribution->maxZoom,
379                         attribution->bounds.south, attribution->bounds.north, attribution->bounds.west, attribution->bounds.east);
380
381         g_free(textl);
382 }
383
384 static gboolean
385 _parse_file_for_attributions(BingMapSource *self, gchar *filename)
386 {
387         GMarkupParser xml_parser;
388         GMarkupParseContext *xml_context = NULL;
389         GError *error = NULL;
390         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
391         g_return_val_if_fail(priv != NULL, FALSE);
392
393         FILE *file = g_fopen (filename, "r");
394         if (file == NULL)
395                 /* TODO emit warning */
396                 return FALSE;
397         
398         /* setup context parse (ie callbacks) */
399         xml_parser.start_element = &_start_element;
400         xml_parser.end_element = NULL;
401         xml_parser.text = &_text;
402         xml_parser.passthrough = NULL;
403         xml_parser.error = NULL;
404         
405         xml_context = g_markup_parse_context_new(&xml_parser, 0, self, NULL);
406
407         gchar buff[BUFSIZ];
408         size_t nb;
409         size_t offset = -1;
410         while (xml_context &&
411                (nb = fread (buff, sizeof(gchar), BUFSIZ, file)) > 0)
412         {
413                 if (offset == -1)
414                         /* first run */
415                         /* Avoid possible BOM at begining of the file */
416                         offset = buff[0] == '<' ? 0 : 3;
417                 else
418                         /* reset offset */
419                         offset = 0;
420                 if (!g_markup_parse_context_parse(xml_context, buff+offset, nb-offset, &error))
421                 {
422                         fprintf(stderr, "%s: parsing error: %s.\n",
423                                 __FUNCTION__, error->message);
424                         g_markup_parse_context_free(xml_context);
425                         xml_context = NULL;
426                 }
427                 g_clear_error (&error);
428         }
429         /* cleanup */
430         if (xml_context &&
431             !g_markup_parse_context_end_parse(xml_context, &error))
432                 fprintf(stderr, "%s: errors occurred while reading file: %s.\n",
433                         __FUNCTION__, error->message);
434         g_clear_error (&error);
435         
436         if (xml_context)
437                 g_markup_parse_context_free(xml_context);
438         xml_context = NULL;
439         fclose (file);
440
441         return TRUE;
442 }
443
444 static int
445 _load_attributions ( BingMapSource *self )
446 {
447         int ret = 0;  /* OK */
448
449         BingMapSourcePrivate *priv = BING_MAP_SOURCE_GET_PRIVATE (self);
450         gchar *uri = g_strdup_printf(URL_ATTR_FMT, priv->api_key);
451
452         gchar *tmpname = a_download_uri_to_tmp_file ( uri, vik_map_source_default_get_download_options(VIK_MAP_SOURCE_DEFAULT(self)) );
453
454         g_debug("%s: %s", __FUNCTION__, tmpname);
455         if (!_parse_file_for_attributions(self, tmpname)) {
456                 ret = -1;
457                 goto done;
458         }
459
460 done:
461         g_free(uri);
462         g_remove(tmpname);
463         g_free(tmpname);
464         return ret;
465 }
466
467 static int
468 _emit_update ( gpointer data )
469 {
470         gdk_threads_enter();
471         /* TODO
472         vik_layers_panel_emit_update ( VIK_LAYERS_PANEL (data) );
473         */
474         gdk_threads_leave();
475         return 0;
476 }
477
478 static int
479 _load_attributions_thread ( BingMapSource *self, gpointer threaddata )
480 {
481         _load_attributions ( self );
482         int result = a_background_thread_progress ( threaddata, 1.0 );
483         if ( result != 0 )
484                 return -1; /* Abort thread */
485
486         /* Emit update */
487         /* As we are on a download thread,
488          * it's better to fire the update from the main loop.
489          */
490         g_idle_add ( (GSourceFunc)_emit_update, NULL /* FIXME */ );
491
492         return 0;
493 }
494
495 static void
496 _async_load_attributions ( BingMapSource *self )
497 {
498         a_background_thread ( /*VIK_GTK_WINDOW_FROM_WIDGET(vp)*/NULL,
499                             _("Bing attribution Loading"),
500                             (vik_thr_func) _load_attributions_thread,
501                             self,
502                             NULL,
503                             NULL,
504                             1 );
505      
506 }
507
508 /**
509  * bing_map_source_new_with_id:
510  * @id: internal identifier.
511  * @label: the label to display in map provider selector.
512  * @key: the API key to access Bing's services.
513  *
514  * Constructor for Bing map source.
515  *
516  * Returns: a newly allocated BingMapSource GObject.
517  */
518 BingMapSource *
519 bing_map_source_new_with_id (guint16 id, const gchar *label, const gchar *key)
520 {
521         /* initialize settings here */
522         return g_object_new(BING_TYPE_MAP_SOURCE,
523                             "id", id,
524                                                 "label", label,
525                                                 "hostname", "ecn.t2.tiles.virtualearth.net",
526                                                 "url", "/tiles/a%s.jpeg?g=587",
527                                                 "api-key", key,
528                                                 "check-file-server-time", TRUE,
529                                                 "copyright", "© 2011 Microsoft Corporation and/or its suppliers",
530                                                 "license", "Microsoft Bing Maps Specific",
531                                                 "license-url", "http://www.microsoft.com/maps/assets/docs/terms.aspx",
532                                                 NULL);
533 }