1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* mateweather-location.c - Location-handling code
3  *
4  * Copyright 2008, Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This library 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. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <string.h>
26 #include <math.h>
27 #include <locale.h>
28 #include <libxml/xmlreader.h>
29 
30 #define MATEWEATHER_I_KNOW_THIS_IS_UNSTABLE
31 #include "mateweather-location.h"
32 #include "mateweather-timezone.h"
33 #include "parser.h"
34 #include "weather-priv.h"
35 
36 /**
37  * SECTION:mateweather-location
38  * @Title: MateWeatherLocation
39  *
40  * A #MateWeatherLocation represents a "location" of some type known to
41  * libmateweather; anything from a single weather station to the entire
42  * world. See #MateWeatherLocationLevel for information about how the
43  * hierarchy of locations works.
44  */
45 
46 struct _MateWeatherLocation {
47     char *name, *sort_name;
48     MateWeatherLocation *parent, **children;
49     MateWeatherLocationLevel level;
50     char *country_code, *tz_hint;
51     char *station_code, *forecast_zone, *radar;
52     double latitude, longitude;
53     gboolean latlon_valid;
54     MateWeatherTimezone **zones;
55 
56     int ref_count;
57 };
58 
59 /**
60  * MateWeatherLocationLevel:
61  * @MATEWEATHER_LOCATION_WORLD: A location representing the entire world.
62  * @MATEWEATHER_LOCATION_REGION: A location representing a continent or
63  * other top-level region.
64  * @MATEWEATHER_LOCATION_COUNTRY: A location representing a "country" (or
65  * other geographic unit that has an ISO-3166 country code)
66  * @MATEWEATHER_LOCATION_ADM1: A location representing a "first-level
67  * administrative division"; ie, a state, province, or similar
68  * division.
69  * @MATEWEATHER_LOCATION_ADM2: A location representing a subdivision of a
70  * %MATEWEATHER_LOCATION_ADM1 location. (Not currently used.)
71  * @MATEWEATHER_LOCATION_CITY: A location representing a city
72  * @MATEWEATHER_LOCATION_WEATHER_STATION: A location representing a
73  * weather station.
74  *
75  * The size/scope of a particular #MateWeatherLocation.
76  *
77  * Locations form a hierarchy, with a %MATEWEATHER_LOCATION_WORLD
78  * location at the top, divided into regions or countries, and so on.
79  * Countries may or may not be divided into "adm1"s, and "adm1"s may
80  * or may not be divided into "adm2"s. A city will have at least one,
81  * and possibly several, weather stations inside it. Weather stations
82  * will never appear outside of cities.
83  **/
84 
85 static int
sort_locations_by_name(gconstpointer a,gconstpointer b)86 sort_locations_by_name (gconstpointer a, gconstpointer b)
87 {
88     MateWeatherLocation *loc_a = *(MateWeatherLocation **)a;
89     MateWeatherLocation *loc_b = *(MateWeatherLocation **)b;
90 
91     return g_utf8_collate (loc_a->sort_name, loc_b->sort_name);
92 }
93 
94 static int
sort_locations_by_distance(gconstpointer a,gconstpointer b,gpointer user_data)95 sort_locations_by_distance (gconstpointer a, gconstpointer b, gpointer user_data)
96 {
97     MateWeatherLocation *loc_a = *(MateWeatherLocation **)a;
98     MateWeatherLocation *loc_b = *(MateWeatherLocation **)b;
99     MateWeatherLocation *city = (MateWeatherLocation *)user_data;
100     double dist_a, dist_b;
101 
102     dist_a = mateweather_location_get_distance (loc_a, city);
103     dist_b = mateweather_location_get_distance (loc_b, city);
104     if (dist_a < dist_b)
105 	return -1;
106     else if (dist_a > dist_b)
107 	return 1;
108     else
109 	return 0;
110 }
111 
112 static gboolean
parse_coordinates(const char * coordinates,double * latitude,double * longitude)113 parse_coordinates (const char *coordinates,
114 		   double *latitude, double *longitude)
115 {
116     char *p;
117 
118     *latitude = g_ascii_strtod (coordinates, &p) * M_PI / 180.0;
119     if (p == (char *)coordinates)
120 	return FALSE;
121     if (*p++ != ' ')
122 	return FALSE;
123     *longitude = g_ascii_strtod (p, &p) * M_PI / 180.0;
124     return !*p;
125 }
126 
127 static char *
unparse_coordinates(double latitude,double longitude)128 unparse_coordinates (double latitude, double longitude)
129 {
130     int lat_d, lat_m, lat_s, lon_d, lon_m, lon_s;
131     char lat_dir, lon_dir;
132 
133     latitude = latitude * 180.0 / M_PI;
134     longitude = longitude * 180.0 / M_PI;
135 
136     if (latitude < 0.0) {
137 	lat_dir = 'S';
138 	latitude = -latitude;
139     } else
140 	lat_dir = 'N';
141     if (longitude < 0.0) {
142 	lon_dir = 'W';
143 	longitude = -longitude;
144     } else
145 	lon_dir = 'E';
146 
147     lat_d = (int)latitude;
148     lat_m = (int)(latitude * 60.0) - lat_d * 60;
149     lat_s = (int)(latitude * 3600.0) - lat_d * 3600 - lat_m * 60;
150     lon_d = (int)longitude;
151     lon_m = (int)(longitude * 60.0) - lon_d * 60;
152     lon_s = (int)(longitude * 3600.0) - lon_d * 3600 - lon_m * 60;
153 
154     return g_strdup_printf ("%02d-%02d-%02d%c %03d-%02d-%02d%c",
155 			    lat_d, lat_m, lat_s, lat_dir,
156 			    lon_d, lon_m, lon_s, lon_dir);
157 }
158 
159 static MateWeatherLocation *
location_new_from_xml(MateWeatherParser * parser,MateWeatherLocationLevel level,MateWeatherLocation * parent)160 location_new_from_xml (MateWeatherParser *parser, MateWeatherLocationLevel level,
161 		       MateWeatherLocation *parent)
162 {
163     MateWeatherLocation *loc, *child;
164     GPtrArray *children = NULL;
165     const char *tagname;
166     char *value, *normalized;
167     int tagtype, i;
168 
169     loc = g_slice_new0 (MateWeatherLocation);
170     loc->parent = parent;
171     loc->level = level;
172     loc->ref_count = 1;
173     children = g_ptr_array_new ();
174 
175     if (xmlTextReaderRead (parser->xml) != 1)
176 	goto error_out;
177     while ((tagtype = xmlTextReaderNodeType (parser->xml)) !=
178 	   XML_READER_TYPE_END_ELEMENT) {
179 	if (tagtype != XML_READER_TYPE_ELEMENT) {
180 	    if (xmlTextReaderRead (parser->xml) != 1)
181 		goto error_out;
182 	    continue;
183 	}
184 
185 	tagname = (const char *) xmlTextReaderConstName (parser->xml);
186 	if (!strcmp (tagname, "name") && !loc->name) {
187 	    value = mateweather_parser_get_localized_value (parser);
188 	    if (!value)
189 		goto error_out;
190 	    loc->name = g_strdup (value);
191 	    xmlFree (value);
192 	    normalized = g_utf8_normalize (loc->name, -1, G_NORMALIZE_ALL);
193 	    loc->sort_name = g_utf8_casefold (normalized, -1);
194 	    g_free (normalized);
195 
196 	} else if (!strcmp (tagname, "iso-code") && !loc->country_code) {
197 	    value = mateweather_parser_get_value (parser);
198 	    if (!value)
199 		goto error_out;
200 	    loc->country_code = g_strdup (value);
201 	    xmlFree (value);
202 	} else if (!strcmp (tagname, "tz-hint") && !loc->tz_hint) {
203 	    value = mateweather_parser_get_value (parser);
204 	    if (!value)
205 		goto error_out;
206 	    loc->tz_hint = g_strdup (value);
207 	    xmlFree (value);
208 	} else if (!strcmp (tagname, "code") && !loc->station_code) {
209 	    value = mateweather_parser_get_value (parser);
210 	    if (!value)
211 		goto error_out;
212 	    loc->station_code = g_strdup (value);
213 	    xmlFree (value);
214 	} else if (!strcmp (tagname, "coordinates") && !loc->latlon_valid) {
215 	    value = mateweather_parser_get_value (parser);
216 	    if (!value)
217 		goto error_out;
218 	    if (parse_coordinates (value, &loc->latitude, &loc->longitude))
219 		loc->latlon_valid = TRUE;
220 	    xmlFree (value);
221 	} else if (!strcmp (tagname, "zone") && !loc->forecast_zone) {
222 	    value = mateweather_parser_get_value (parser);
223 	    if (!value)
224 		goto error_out;
225 	    loc->forecast_zone = g_strdup (value);
226 	    xmlFree (value);
227 	} else if (!strcmp (tagname, "radar") && !loc->radar) {
228 	    value = mateweather_parser_get_value (parser);
229 	    if (!value)
230 		goto error_out;
231 	    loc->radar = g_strdup (value);
232 	    xmlFree (value);
233 
234 	} else if (!strcmp (tagname, "region")) {
235 	    child = location_new_from_xml (parser, MATEWEATHER_LOCATION_REGION, loc);
236 	    if (!child)
237 		goto error_out;
238 	    if (parser->use_regions)
239 		g_ptr_array_add (children, child);
240 	    else {
241 		if (child->children) {
242 		    for (i = 0; child->children[i]; i++)
243 			g_ptr_array_add (children, mateweather_location_ref (child->children[i]));
244 		}
245 		mateweather_location_unref (child);
246 	    }
247 	} else if (!strcmp (tagname, "country")) {
248 	    child = location_new_from_xml (parser, MATEWEATHER_LOCATION_COUNTRY, loc);
249 	    if (!child)
250 		goto error_out;
251 	    g_ptr_array_add (children, child);
252 	} else if (!strcmp (tagname, "state")) {
253 	    child = location_new_from_xml (parser, MATEWEATHER_LOCATION_ADM1, loc);
254 	    if (!child)
255 		goto error_out;
256 	    g_ptr_array_add (children, child);
257 	} else if (!strcmp (tagname, "city")) {
258 	    child = location_new_from_xml (parser, MATEWEATHER_LOCATION_CITY, loc);
259 	    if (!child)
260 		goto error_out;
261 	    g_ptr_array_add (children, child);
262 	} else if (!strcmp (tagname, "location")) {
263 	    child = location_new_from_xml (parser, MATEWEATHER_LOCATION_WEATHER_STATION, loc);
264 	    if (!child)
265 		goto error_out;
266 	    g_ptr_array_add (children, child);
267 
268 	} else if (!strcmp (tagname, "timezones")) {
269 	    loc->zones = mateweather_timezones_parse_xml (parser);
270 	    if (!loc->zones)
271 		goto error_out;
272 
273 	} else {
274 	    if (xmlTextReaderNext (parser->xml) != 1)
275 		goto error_out;
276 	}
277     }
278     if (xmlTextReaderRead (parser->xml) != 1 && parent)
279 	goto error_out;
280 
281     if (children->len) {
282 	if (level == MATEWEATHER_LOCATION_CITY)
283 	    g_ptr_array_sort_with_data (children, sort_locations_by_distance, loc);
284 	else
285 	    g_ptr_array_sort (children, sort_locations_by_name);
286 
287 	g_ptr_array_add (children, NULL);
288 	loc->children = (MateWeatherLocation **)g_ptr_array_free (children, FALSE);
289     } else
290 	g_ptr_array_free (children, TRUE);
291 
292     return loc;
293 
294 error_out:
295     mateweather_location_unref (loc);
296     for (i = 0; i < children->len; i++)
297 	mateweather_location_unref (children->pdata[i]);
298     g_ptr_array_free (children, TRUE);
299 
300     return NULL;
301 }
302 
303 /**
304  * mateweather_location_new_world:
305  * @use_regions: whether or not to divide the world into regions
306  *
307  * Creates a new #MateWeatherLocation of type %MATEWEATHER_LOCATION_WORLD,
308  * representing a hierarchy containing all of the locations from
309  * Locations.xml.
310  *
311  * If @use_regions is %TRUE, the immediate children of the returned
312  * location will be %MATEWEATHER_LOCATION_REGION nodes, representing the
313  * top-level "regions" of Locations.xml (the continents and a few
314  * other divisions), and the country-level nodes will be the children
315  * of the regions. If @use_regions is %FALSE, the regions will be
316  * skipped, and the children of the returned location will be the
317  * %MATEWEATHER_LOCATION_COUNTRY nodes.
318  *
319  * Return value: (allow-none): a %MATEWEATHER_LOCATION_WORLD location, or
320  * %NULL if Locations.xml could not be found or could not be parsed.
321  **/
322 MateWeatherLocation *
mateweather_location_new_world(gboolean use_regions)323 mateweather_location_new_world (gboolean use_regions)
324 {
325     MateWeatherParser *parser;
326     MateWeatherLocation *world;
327 
328     parser = mateweather_parser_new (use_regions);
329     if (!parser)
330 	return NULL;
331 
332     world = location_new_from_xml (parser, MATEWEATHER_LOCATION_WORLD, NULL);
333 
334     mateweather_parser_free (parser);
335     return world;
336 }
337 
338 /**
339  * mateweather_location_ref:
340  * @loc: a #MateWeatherLocation
341  *
342  * Adds 1 to @loc's reference count.
343  *
344  * Return value: @loc
345  **/
346 MateWeatherLocation *
mateweather_location_ref(MateWeatherLocation * loc)347 mateweather_location_ref (MateWeatherLocation *loc)
348 {
349     g_return_val_if_fail (loc != NULL, NULL);
350 
351     loc->ref_count++;
352     return loc;
353 }
354 
355 /**
356  * mateweather_location_unref:
357  * @loc: a #MateWeatherLocation
358  *
359  * Subtracts 1 from @loc's reference count, and frees it if the
360  * reference count reaches 0.
361  **/
362 void
mateweather_location_unref(MateWeatherLocation * loc)363 mateweather_location_unref (MateWeatherLocation *loc)
364 {
365     int i;
366 
367     g_return_if_fail (loc != NULL);
368 
369     if (--loc->ref_count)
370 	return;
371 
372     g_free (loc->name);
373     g_free (loc->sort_name);
374     g_free (loc->country_code);
375     g_free (loc->tz_hint);
376     g_free (loc->station_code);
377     g_free (loc->forecast_zone);
378     g_free (loc->radar);
379 
380     if (loc->children) {
381 	for (i = 0; loc->children[i]; i++) {
382 	    loc->children[i]->parent = NULL;
383 	    mateweather_location_unref (loc->children[i]);
384 	}
385 	g_free (loc->children);
386     }
387 
388     if (loc->zones) {
389 	for (i = 0; loc->zones[i]; i++)
390 	    mateweather_timezone_unref (loc->zones[i]);
391 	g_free (loc->zones);
392     }
393 
394     g_slice_free (MateWeatherLocation, loc);
395 }
396 
397 GType
mateweather_location_get_type(void)398 mateweather_location_get_type (void)
399 {
400     static volatile gsize type_volatile = 0;
401 
402     if (g_once_init_enter (&type_volatile)) {
403 	GType type = g_boxed_type_register_static (
404 	    g_intern_static_string ("MateWeatherLocation"),
405 	    (GBoxedCopyFunc) mateweather_location_ref,
406 	    (GBoxedFreeFunc) mateweather_location_unref);
407 	g_once_init_leave (&type_volatile, type);
408     }
409     return type_volatile;
410 }
411 
412 /**
413  * mateweather_location_get_name:
414  * @loc: a #MateWeatherLocation
415  *
416  * Gets @loc's name, localized into the current language.
417  *
418  * Note that %MATEWEATHER_LOCATION_WEATHER_STATION nodes are not
419  * localized, and so the name returned for those nodes will always be
420  * in English, and should therefore not be displayed to the user.
421  * (FIXME: should we just not return a name?)
422  *
423  * Return value: @loc's name
424  **/
425 const char *
mateweather_location_get_name(MateWeatherLocation * loc)426 mateweather_location_get_name (MateWeatherLocation *loc)
427 {
428     g_return_val_if_fail (loc != NULL, NULL);
429     return loc->name;
430 }
431 
432 /**
433  * mateweather_location_get_sort_name:
434  * @loc: a #MateWeatherLocation
435  *
436  * Gets @loc's "sort name", which is the name after having
437  * g_utf8_normalize() (with %G_NORMALIZE_ALL) and g_utf8_casefold()
438  * called on it. You can use this to sort locations, or to comparing
439  * user input against a location name.
440  *
441  * Return value: @loc's sort name
442  **/
443 const char *
mateweather_location_get_sort_name(MateWeatherLocation * loc)444 mateweather_location_get_sort_name (MateWeatherLocation *loc)
445 {
446     g_return_val_if_fail (loc != NULL, NULL);
447     return loc->sort_name;
448 }
449 
450 /**
451  * mateweather_location_get_level:
452  * @loc: a #MateWeatherLocation
453  *
454  * Gets @loc's level, from %MATEWEATHER_LOCATION_WORLD, to
455  * %MATEWEATHER_LOCATION_WEATHER_STATION.
456  *
457  * Return value: @loc's level
458  **/
459 MateWeatherLocationLevel
mateweather_location_get_level(MateWeatherLocation * loc)460 mateweather_location_get_level (MateWeatherLocation *loc)
461 {
462     g_return_val_if_fail (loc != NULL, MATEWEATHER_LOCATION_WORLD);
463     return loc->level;
464 }
465 
466 /**
467  * mateweather_location_get_parent:
468  * @loc: a #MateWeatherLocation
469  *
470  * Gets @loc's parent location.
471  *
472  * Return value: (transfer none) (allow-none): @loc's parent, or %NULL
473  * if @loc is a %MATEWEATHER_LOCATION_WORLD node.
474  **/
475 MateWeatherLocation *
mateweather_location_get_parent(MateWeatherLocation * loc)476 mateweather_location_get_parent (MateWeatherLocation *loc)
477 {
478     g_return_val_if_fail (loc != NULL, NULL);
479     return loc->parent;
480 }
481 
482 /**
483  * mateweather_location_get_children:
484  * @loc: a #MateWeatherLocation
485  *
486  * Gets an array of @loc's children; this is owned by @loc and will
487  * not remain valid if @loc is freed.
488  *
489  * Return value: (transfer none) (array zero-terminated=1): @loc's
490  * children. (May be empty, but will not be %NULL.)
491  **/
492 MateWeatherLocation **
mateweather_location_get_children(MateWeatherLocation * loc)493 mateweather_location_get_children (MateWeatherLocation *loc)
494 {
495     static MateWeatherLocation *no_children = NULL;
496 
497     g_return_val_if_fail (loc != NULL, NULL);
498 
499     if (loc->children)
500 	return loc->children;
501     else
502 	return &no_children;
503 }
504 
505 
506 /**
507  * mateweather_location_free_children:
508  * @loc: a #MateWeatherLocation
509  * @children: an array of @loc's children
510  *
511  * This is a no-op. Do not use it.
512  *
513  * Deprecated: This is a no-op.
514  **/
515 void
mateweather_location_free_children(MateWeatherLocation * loc,MateWeatherLocation ** children)516 mateweather_location_free_children (MateWeatherLocation  *loc,
517 				 MateWeatherLocation **children)
518 {
519     ;
520 }
521 
522 /**
523  * mateweather_location_has_coords:
524  * @loc: a #MateWeatherLocation
525  *
526  * Checks if @loc has valid latitude and longitude.
527  *
528  * Return value: %TRUE if @loc has valid latitude and longitude.
529  **/
530 gboolean
mateweather_location_has_coords(MateWeatherLocation * loc)531 mateweather_location_has_coords (MateWeatherLocation *loc)
532 {
533     g_return_val_if_fail (loc != NULL, FALSE);
534     return loc->latlon_valid;
535 }
536 
537 /**
538  * mateweather_location_get_coords:
539  * @loc: a #MateWeatherLocation
540  * @latitude: (out): on return will contain @loc's latitude
541  * @longitude: (out): on return will contain @loc's longitude
542  *
543  * Gets @loc's coordinates; you must check
544  * mateweather_location_has_coords() before calling this.
545  **/
546 void
mateweather_location_get_coords(MateWeatherLocation * loc,double * latitude,double * longitude)547 mateweather_location_get_coords (MateWeatherLocation *loc,
548 			      double *latitude, double *longitude)
549 {
550     //g_return_if_fail (loc->latlon_valid);
551     g_return_if_fail (loc != NULL);
552     g_return_if_fail (latitude != NULL);
553     g_return_if_fail (longitude != NULL);
554 
555     *latitude = loc->latitude / M_PI * 180.0;
556     *longitude = loc->longitude / M_PI * 180.0;
557 }
558 
559 /**
560  * mateweather_location_get_distance:
561  * @loc: a #MateWeatherLocation
562  * @loc2: a second #MateWeatherLocation
563  *
564  * Determines the distance in kilometers between @loc and @loc2.
565  *
566  * Return value: the distance between @loc and @loc2.
567  **/
568 double
mateweather_location_get_distance(MateWeatherLocation * loc,MateWeatherLocation * loc2)569 mateweather_location_get_distance (MateWeatherLocation *loc, MateWeatherLocation *loc2)
570 {
571     /* average radius of the earth in km */
572     static const double radius = 6372.795;
573 
574     g_return_val_if_fail (loc != NULL, 0);
575     g_return_val_if_fail (loc2 != NULL, 0);
576 
577     //g_return_val_if_fail (loc->latlon_valid, 0.0);
578     //g_return_val_if_fail (loc2->latlon_valid, 0.0);
579 
580     return acos (cos (loc->latitude) * cos (loc2->latitude) * cos (loc->longitude - loc2->longitude) +
581 		 sin (loc->latitude) * sin (loc2->latitude)) * radius;
582 }
583 
584 /**
585  * mateweather_location_get_country:
586  * @loc: a #MateWeatherLocation
587  *
588  * Gets the ISO 3166 country code of @loc (or %NULL if @loc is a
589  * region- or world-level location)
590  *
591  * Return value: (allow-none): @loc's country code (or %NULL if @loc
592  * is a region- or world-level location)
593  **/
594 const char *
mateweather_location_get_country(MateWeatherLocation * loc)595 mateweather_location_get_country (MateWeatherLocation *loc)
596 {
597     g_return_val_if_fail (loc != NULL, NULL);
598 
599     while (loc->parent && !loc->country_code)
600 	loc = loc->parent;
601     return loc->country_code;
602 }
603 
604 /**
605  * mateweather_location_get_timezone:
606  * @loc: a #MateWeatherLocation
607  *
608  * Gets the timezone associated with @loc, if known.
609  *
610  * The timezone is owned either by @loc or by one of its parents.
611  * FIXME.
612  *
613  * Return value: (transfer none) (allow-none): @loc's timezone, or
614  * %NULL
615  **/
616 MateWeatherTimezone *
mateweather_location_get_timezone(MateWeatherLocation * loc)617 mateweather_location_get_timezone (MateWeatherLocation *loc)
618 {
619     const char *tz_hint;
620     int i;
621 
622     g_return_val_if_fail (loc != NULL, NULL);
623 
624     while (loc && !loc->tz_hint)
625 	loc = loc->parent;
626     if (!loc)
627 	return NULL;
628     tz_hint = loc->tz_hint;
629 
630     while (loc) {
631 	while (loc && !loc->zones)
632 	    loc = loc->parent;
633 	if (!loc)
634 	    return NULL;
635 	for (i = 0; loc->zones[i]; i++) {
636 	    if (!strcmp (tz_hint, mateweather_timezone_get_tzid (loc->zones[i])))
637 		return loc->zones[i];
638 	}
639 	loc = loc->parent;
640     }
641 
642     return NULL;
643 }
644 
645 static void
add_timezones(MateWeatherLocation * loc,GPtrArray * zones)646 add_timezones (MateWeatherLocation *loc, GPtrArray *zones)
647 {
648     int i;
649 
650     if (loc->zones) {
651 	for (i = 0; loc->zones[i]; i++)
652 	    g_ptr_array_add (zones, mateweather_timezone_ref (loc->zones[i]));
653     }
654     if (loc->level < MATEWEATHER_LOCATION_COUNTRY && loc->children) {
655 	for (i = 0; loc->children[i]; i++)
656 	    add_timezones (loc->children[i], zones);
657     }
658 }
659 
660 /**
661  * mateweather_location_get_timezones:
662  * @loc: a #MateWeatherLocation
663  *
664  * Gets an array of all timezones associated with any location under
665  * @loc. You can use mateweather_location_free_timezones() to free this
666  * array.
667  *
668  * Return value: (transfer full) (array zero-terminated=1): an array
669  * of timezones. May be empty but will not be %NULL.
670  **/
671 MateWeatherTimezone **
mateweather_location_get_timezones(MateWeatherLocation * loc)672 mateweather_location_get_timezones (MateWeatherLocation *loc)
673 {
674     GPtrArray *zones;
675 
676     g_return_val_if_fail (loc != NULL, NULL);
677 
678     zones = g_ptr_array_new ();
679     add_timezones (loc, zones);
680     g_ptr_array_add (zones, NULL);
681     return (MateWeatherTimezone **)g_ptr_array_free (zones, FALSE);
682 }
683 
684 /**
685  * mateweather_location_free_timezones:
686  * @loc: a #MateWeatherLocation
687  * @zones: an array returned from mateweather_location_get_timezones()
688  *
689  * Frees the array of timezones returned by
690  * mateweather_location_get_timezones().
691  **/
692 void
mateweather_location_free_timezones(MateWeatherLocation * loc,MateWeatherTimezone ** zones)693 mateweather_location_free_timezones (MateWeatherLocation  *loc,
694 				  MateWeatherTimezone **zones)
695 {
696     int i;
697 
698     g_return_if_fail (loc != NULL);
699     g_return_if_fail (zones != NULL);
700 
701     for (i = 0; zones[i]; i++)
702 	mateweather_timezone_unref (zones[i]);
703     g_free (zones);
704 }
705 
706 /**
707  * mateweather_location_get_code:
708  * @loc: a #MateWeatherLocation
709  *
710  * Gets the METAR station code associated with a
711  * %MATEWEATHER_LOCATION_WEATHER_STATION location.
712  *
713  * Return value: (allow-none): @loc's METAR station code, or %NULL
714  **/
715 const char *
mateweather_location_get_code(MateWeatherLocation * loc)716 mateweather_location_get_code (MateWeatherLocation *loc)
717 {
718     g_return_val_if_fail (loc != NULL, NULL);
719     return loc->station_code;
720 }
721 
722 /**
723  * mateweather_location_get_city_name:
724  * @loc: a #MateWeatherLocation
725  *
726  * For a %MATEWEATHER_LOCATION_CITY location, this is equivalent to
727  * mateweather_location_get_name(). For a
728  * %MATEWEATHER_LOCATION_WEATHER_STATION location, it is equivalent to
729  * calling mateweather_location_get_name() on the location's parent. For
730  * other locations it will return %NULL.
731  *
732  * Return value: (allow-none) @loc's city name, or %NULL
733  **/
734 char *
mateweather_location_get_city_name(MateWeatherLocation * loc)735 mateweather_location_get_city_name (MateWeatherLocation *loc)
736 {
737     g_return_val_if_fail (loc != NULL, NULL);
738 
739     if (loc->level == MATEWEATHER_LOCATION_CITY)
740 	return g_strdup (loc->name);
741     else if (loc->level == MATEWEATHER_LOCATION_WEATHER_STATION &&
742 	     loc->parent &&
743 	     loc->parent->level == MATEWEATHER_LOCATION_CITY)
744 	return g_strdup (loc->parent->name);
745     else
746 	return NULL;
747 }
748 
749 WeatherLocation *
mateweather_location_to_weather_location(MateWeatherLocation * gloc,const char * name)750 mateweather_location_to_weather_location (MateWeatherLocation *gloc,
751 				       const char *name)
752 {
753     const char *code = NULL, *zone = NULL, *radar = NULL, *tz_hint = NULL;
754     MateWeatherLocation *l;
755     WeatherLocation *wloc;
756     char *coords;
757 
758     g_return_val_if_fail (gloc != NULL, NULL);
759 
760     if (!name)
761 	name = mateweather_location_get_name (gloc);
762 
763     if (gloc->level == MATEWEATHER_LOCATION_CITY && gloc->children)
764 	l = gloc->children[0];
765     else
766 	l = gloc;
767 
768     if (l->latlon_valid)
769 	coords = unparse_coordinates (l->latitude, l->longitude);
770     else
771 	coords = NULL;
772 
773     while (l && (!code || !zone || !radar || !tz_hint)) {
774 	if (!code && l->station_code)
775 	    code = l->station_code;
776 	if (!zone && l->forecast_zone)
777 	    zone = l->forecast_zone;
778 	if (!radar && l->radar)
779 	    radar = l->radar;
780 	if (!tz_hint && l->tz_hint)
781 	    tz_hint = l->tz_hint;
782 	l = l->parent;
783     }
784 
785     wloc = weather_location_new (name, code, zone, radar, coords,
786 				 mateweather_location_get_country (gloc),
787 				 tz_hint);
788     g_free (coords);
789     return wloc;
790 }
791 
792 /**
793  * mateweather_location_get_weather:
794  * @loc: a %MateWeatherLocation
795  *
796  * Creates a #WeatherInfo corresponding to @loc; you can use
797  * weather_info_update() to fill it in.
798  *
799  * Return value: (transfer full): a #WeatherInfo corresponding to
800  * @loc.
801  **/
802 WeatherInfo *
mateweather_location_get_weather(MateWeatherLocation * loc)803 mateweather_location_get_weather (MateWeatherLocation *loc)
804 {
805     WeatherLocation *wloc;
806     WeatherInfo *info;
807 
808     g_return_val_if_fail (loc != NULL, NULL);
809 
810     wloc = mateweather_location_to_weather_location (loc, NULL);
811     info = weather_info_new (wloc, NULL, NULL, NULL);
812     weather_location_free (wloc);
813     return info;
814 }
815