gpscorrelate-1.6.1/0000755000175000017500000000000011335415005013460 5ustar danieldanielgpscorrelate-1.6.1/exif-gps.h0000644000175000017500000000300710636114130015351 0ustar danieldaniel/* exif-gps.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains the prototypes for the functions * in exif-gps.cpp. * These are declared extern "C" to prevent name * mangling, as C++ has a habit of doing. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef __cplusplus extern "C" { #endif char* ReadExifDate(char* File, int* IncludesGPS); char* ReadExifData(char* File, double* Lat, double* Long, double* Elevation, int* IncludesGPS); char* ReadGPSTimestamp(char* File, char* DateStamp, char* TimeStamp, int* IncludesGPS); int WriteGPSData(char* File, struct GPSPoint* Point, char* Datum, int NoChangeMtime, int DegMinSecs); int WriteFixedDatestamp(char* File, time_t TimeStamp); int RemoveGPSExif(char* File, int NoChangeMtime); #ifdef __cplusplus } #endif gpscorrelate-1.6.1/main-command.c0000644000175000017500000004532511335414761016205 0ustar danieldaniel/* main-command.c * Written by Daniel Foote. * Started Feb 2005. * * Command line program to match GPS data and Photo EXIF timestamps * together, to figure out where you were at the time. * Writes the output back into the GPS exif tags. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #ifndef WIN32 #include #endif #include "gpsstructure.h" #include "exif-gps.h" #include "unixtime.h" #include "gpx-read.h" #include "correlate.h" /* Command line options structure. */ static struct option program_options[] = { { "gps", required_argument, 0, 'g' }, { "timeadd", required_argument, 0, 'z'}, { "no-interpolation", no_argument, 0, 'i'}, { "help", no_argument, 0, 'h'}, { "verbose", no_argument, 0, 'v'}, { "datum", required_argument, 0, 'd'}, { "no-write", no_argument, 0, 'n'}, { "max-dist", required_argument, 0, 'm'}, { "show", no_argument, 0, 's'}, { "machine", no_argument, 0, 'o'}, { "remove", no_argument, 0, 'r'}, { "ignore-tracksegs", no_argument, 0, 't'}, { "no-mtime", no_argument, 0, 'M'}, { "version", no_argument, 0, 'V'}, { "fix-datestamps", no_argument, 0, 'f'}, { "degmins", no_argument, 0, 'p'}, { "photooffset", required_argument, 0, 'O'}, { 0, 0, 0, 0 } }; /* Function to print the version - near the top for easy modification. */ void PrintVersion(char* ProgramName) { printf("%s, v 1.6.1, 13 February 2010. Daniel Foote, 2005-2010. GNU GPL.\n", ProgramName); } /* Function to print the usage info. */ void PrintUsage(char* ProgramName) { printf("Usage: %s --gps|-g file.gpx [options] file1.jpg file2.jpg ...\n", ProgramName); printf("--gps or -g file.gpx: required, specifies GPX file with GPS data.\n"); printf("--timeadd or -z +/-XX[:XX]: time to add to GPS data to make it match photos.\n"); printf(" GPS data is in UTC; photos are not likely to be in UTC. Enter the\n"); printf(" timezone used when taking the photos: eg, +8 for Perth.\n"); printf("--no-interpolation or -i: disable interpolation between points.\n"); printf(" Interpolation is linear, points rounded if disabled.\n"); printf("--verbose or -v: show what has been selected.\n"); printf("--datum or -d datum: specify measurement datum. If not set, WGS-84 used.\n"); printf("--no-write or -n: do not write the exif data. Useful with --verbose.\n"); printf("--max-dist or -m time: max time outside points that photo will be matched.\n"); printf(" Time is in seconds.\n"); printf("--show or -s: Just show the GPS data from the given files, if it exists.\n"); printf("--machine or -o: Just show the GPS data from the given files, machine readable output.\n"); printf("--remove or -r: Strip GPS tags from the given files, and then quit.\n"); printf("--ignore-tracksegs or -t: Interpolate between track segments too.\n"); printf("--no-mtime or -M: Don't change mtime of modified files.\n"); printf("--fix-datestamps or -f: Fix broken GPS datestamps written with versions < 1.5.2\n"); printf("--degmins or -p: Write location as DD MM.MM as was default before < 1.5.3.\n"); printf("--photooffset or -O : Offset added to photo time to make it match the GPS.\n"); printf("--help or -h: display usage/help message.\n"); printf("--version or -V: display version information.\n"); } /* Display the information from an existing file. */ void ShowFileDetails(char* File, int MachineReadable) { double Lat, Long, Elev; int IncludesGPS = 0; char* Time = NULL; Lat = Long = Elev = 0; Time = ReadExifData(File, &Lat, &Long, &Elev, &IncludesGPS); if (Time) { if (IncludesGPS) { /* Display the data as CSV if we want * it machine readable. */ if (MachineReadable) { printf("\"%s\",\"%s\",%f,%f,%f\n", File, Time, Lat, Long, Elev); } else { printf("%s: %s, Lat %f, Long %f, Elevation %f.\n", File, Time, Lat, Long, Elev); } } else { /* Don't display anything if we want machine * readable data and there is no data. */ if (!MachineReadable) { printf("%s: %s, No GPS Data.\n", File, Time); } } } else { /* Say that there was no data, except if we want * machine readable output */ if (!MachineReadable) { printf("%s: No EXIF data.\n", File); } } free(Time); } /* Remove all GPS exif tags from a file. Not really that useful, but... */ /* BUG: presently removes tags even on read-only files. Hmm. */ void RemoveGPSTags(char* File, int NoChangeMtime) { if (RemoveGPSExif(File, NoChangeMtime)) { printf("%s: Removed GPS tags.\n", File); } else { printf("%s: Tag removal failure.\n", File); } } /* Fix GPSDatestamp tags, if they were incorrect, as found with versions * earlier than 1.5.2. */ void FixDatestamp(char* File, int AdjustmentHours, int AdjustmentMinutes, int NoWriteExif) { /* Read the timestamp data. */ char DateStamp[12]; char TimeStamp[12]; char CombinedTime[24]; int IncludesGPS = 0; char* OriginalDateStamp = NULL; OriginalDateStamp = ReadGPSTimestamp(File, DateStamp, TimeStamp, &IncludesGPS); if (OriginalDateStamp == NULL) { printf("%s: No EXIF data.\n", File); } else if (IncludesGPS == 0) { printf("%s: No GPS data.\n", File); } else { /* Check the timestamp. */ time_t PhotoTime = ConvertToUnixTime(OriginalDateStamp, EXIF_DATE_FORMAT, AdjustmentHours, AdjustmentMinutes); strcpy(CombinedTime, DateStamp); strcat(CombinedTime, " "); strcat(CombinedTime, TimeStamp); time_t GPSTime = ConvertToUnixTime(CombinedTime, EXIF_DATE_FORMAT, 0, 0); if (PhotoTime != GPSTime) { /* Timestamp is wrong. Fix it. * Should be photo time - this also corrects * GPSTimestamp, which was wrong too. */ if (!NoWriteExif) { WriteFixedDatestamp(File, PhotoTime); } char PhotoTimeFormat[100]; char GPSTimeFormat[100]; char *tmp = ctime(&PhotoTime); strncpy(PhotoTimeFormat, tmp, 100); tmp = ctime(&GPSTime); strncpy(GPSTimeFormat, tmp, 100); printf("%s: Wrong timestamp:\n Photo: %s GPS: %s Corrected: %s", File, PhotoTimeFormat, GPSTimeFormat, PhotoTimeFormat); } else { /* Inside the range. Do nothing! */ printf("%s: Timestamp is OK: Photo %s (localtime), GPS %s (UTC).\n", File, OriginalDateStamp, CombinedTime); } } free(OriginalDateStamp); } int main(int argc, char** argv) { /* Say hello. */ printf("EXIF-GPS Photo matching program.\n"); printf("Daniel Foote, 2005.\n\n"); /* If you didn't pass any arguments... */ if (argc == 1) { PrintUsage(argv[0]); exit(EXIT_SUCCESS); } /* Parse our command line options. */ /* But first, some variables to store stuff in. */ int c; char* GPSData = NULL; /* Filename of the file with the GPS data. */ char* TimeAdjustment = NULL; /* Time adjustment, as passed to program. */ int TimeZoneHours = 0; /* Integer version of the timezone. */ int TimeZoneMins = 0; char* Datum = NULL; /* Datum of input GPS data. */ int Interpolate = 1; /* Do we interpolate? By default, yes. */ int NoWriteExif = 0; /* Do we not write to file? By default, no. */ int ShowDetails = 0; /* Do we show lots of details? By default, no. */ int FeatherTime = 0; /* The "feather" time, in seconds. 0 = disabled. */ int ShowOnlyDetails = 0; int MachineReadable = 0; int RemoveTags = 0; int DoBetweenTrackSegs = 0; int NoChangeMtime = 0; int FixDatestamps = 0; int DegMinSecs = 1; int PhotoOffset = 0; while (1) { /* Call getopt to do all the hard work * for us... */ c = getopt_long(argc, argv, "g:z:ihvd:m:nsortMVfO:", program_options, 0); if (c == -1) break; /* Determine what getopt saw. */ switch (c) { case 'g': /* This parameter specifies the GPS data. * It must be present. */ if (optarg) { GPSData = malloc((sizeof(char) * strlen(optarg)) + 1); strncpy(GPSData, optarg, strlen(optarg)+1); } break; case 'z': /* This parameter specifies the time to add to the * GPS data to make it match the timezone for * the photos. */ /* We only store it here, convert it to numbers later. */ if (optarg) { TimeAdjustment = malloc((sizeof(char) * strlen(optarg)) + 1); strncpy(TimeAdjustment, optarg, strlen(optarg)+1); } break; case 'O': PhotoOffset = atoi(optarg); break; case 'i': /* This option disables interpolation. */ Interpolate = 0; break; case 'v': /* This option asks us to show more info. */ #ifdef WIN32 printf("--verbose does not work on win32\n"); #else ShowDetails = 1; #endif break; case 'd': /* This option specifies a Datum, if other than WGS-84. */ if (optarg) { Datum = malloc((sizeof(char) * strlen(optarg)) + 1); strncpy(Datum, optarg, strlen(optarg)+1); } break; case 'n': /* This option specifies not to write to file. */ NoWriteExif = 1; break; case 'm': /* This option gives us the allowable "feather" time. */ FeatherTime = atoi(optarg); break; case 'h': /* Display the help/usage information. And then quit. */ PrintUsage(argv[0]); exit(EXIT_SUCCESS); break; case 'V': /* Display version information, and then quit. */ PrintVersion(argv[0]); exit(EXIT_SUCCESS); break; case 'f': /* Fix Datestamps. */ FixDatestamps = 1; break; case 's': /* Show the data in the photos. Mark this. */ ShowOnlyDetails = 1; break; case 'o': /* Show the data in the photos, machine readable. */ ShowOnlyDetails = 1; MachineReadable = 1; break; case 'r': /* Remove GPS tags from the file. Mark this. */ RemoveTags = 1; break; case 't': /* Interpolate between track segments. */ DoBetweenTrackSegs = 1; break; case 'M': NoChangeMtime = 1; break; case 'p': /* Write in old DegMins format. */ DegMinSecs = 0; break; case '?': /* Unrecognised option. Or, missing argument. */ /* We should inform the user and let them correct this. */ printf("Next time, please pass a parameter with that!\n"); exit(EXIT_FAILURE); break; default: /* Unrecognised code that getopt returned anyway. * Oops... */ break; } /* End switch(c) */ } /* End While(1) */ /* Check to see if the user passed some files work with. Not much * good if they didn't. */ if (optind < argc) { /* You passed some files. Handy! */ } else { /* Hmm. It seems there were no other files... that doesn't work. */ printf("Nice try! However, next time, pass a few JPEG files to match!\n"); exit(EXIT_SUCCESS); } /* If we only wanted to display info on the passed photos, do so now. */ if (ShowOnlyDetails) { while (optind < argc) { ShowFileDetails(argv[optind++], MachineReadable); } exit(EXIT_SUCCESS); } /* If we wanted to delete tags, do this now. */ if (RemoveTags) { while (optind < argc) { RemoveGPSTags(argv[optind++], NoChangeMtime); } exit(EXIT_SUCCESS); } /* If we wanted to fix datestamps, do this now. * This is to fix incorrect GPSDateStamp values written by versions * earlier than 1.5.2. */ if (FixDatestamps) { if (TimeAdjustment == NULL) { printf("You must give a time adjustment for the photos with -z to fix photos.\n"); exit(EXIT_SUCCESS); } /* Break up the adjustment and convert to numbers. */ if (strstr(TimeAdjustment, ":")) { /* Found colon. Split into two. */ sscanf(TimeAdjustment, "%d:%d", &TimeZoneHours, &TimeZoneMins); if (TimeZoneHours < 0) TimeZoneMins *= -1; } else { /* No colon. Just parse. */ TimeZoneHours = atoi(TimeAdjustment); } while (optind < argc) { FixDatestamp(argv[optind++], TimeZoneHours, TimeZoneMins, NoWriteExif); } exit(EXIT_SUCCESS); } /* Set up any other command line options... */ if (!Datum) { Datum = malloc((sizeof(char) * strlen("WGS-84")) + 1); strcpy(Datum, "WGS-84"); } if (TimeAdjustment) { /* Break up the adjustment and convert to numbers. */ if (strstr(TimeAdjustment, ":")) { /* Found colon. Split into two. */ sscanf(TimeAdjustment, "%d:%d", &TimeZoneHours, &TimeZoneMins); if (TimeZoneHours < 0) TimeZoneMins *= -1; } else { /* No colon. Just parse. */ TimeZoneHours = atoi(TimeAdjustment); } /* printf("Time zone: %d : %d.\n", TimeZoneHours, TimeZoneMins); */ } /* See if the user did pass any GPS data. * If not, don't continue. */ if (GPSData == NULL) { printf("You need to pass some GPS data! Otherwise, nothing to match from!\n"); exit(EXIT_FAILURE); } /* Read the XML file into memory and extract the "points". */ /* The returned pointer is the start of a singly-linked list. */ printf("Reading GPS Data..."); struct GPSPoint* Points = ReadGPX(GPSData); printf("\n"); if (Points) { /* GPS Data was read correctly. */ } else { /* GPS Data was not read correctly... */ /* Tell the user we are bailing. * Not really required, seeing as ReadGPX should * inform the user anyway... but, doesn't hurt! */ printf("Failure reading/processing GPS data.\n"); exit(EXIT_FAILURE); } /* Print a legend for the matching process. * If we're not being verbose. Otherwise, this would be pointless. */ if (!ShowDetails) { printf("Legend: . = Ok, / = Interpolated, < = Rounded, - = No match, ^ = Too far.\n"); printf(" w = Write Fail, ? = No exif date, ! = GPS already present.\n"); } /* Set up our options structure for the correlation function. */ struct CorrelateOptions Options; Options.NoWriteExif = NoWriteExif; Options.NoInterpolate = (Interpolate ? 0 : 1); Options.TimeZoneHours = TimeZoneHours; Options.TimeZoneMins = TimeZoneMins; Options.FeatherTime = FeatherTime; Options.Datum = Datum; Options.DoBetweenTrkSeg = DoBetweenTrackSegs; Options.NoChangeMtime = NoChangeMtime; Options.DegMinSecs = DegMinSecs; Options.PhotoOffset = PhotoOffset; Options.MinTime = 0; Options.MaxTime = 0; Options.Points = Points; /* Twig with the terminal settings to make the single character * output stuff work right. */ #ifndef WIN32 struct termios initial_settings, new_settings; if (!ShowDetails) { tcgetattr(fileno(stdout),&initial_settings); new_settings = initial_settings; new_settings.c_lflag &= ~ICANON; new_settings.c_cc[VMIN] = 1; new_settings.c_cc[VTIME] = 0; new_settings.c_lflag &= ~ISIG; if(tcsetattr(fileno(stdout), TCSANOW, &new_settings) != 0) { /* Oops. Oh well. Didn't work. */ printf("Debug: Broken tty set.\n"); } } #endif /* Make it all look nice and pretty... so the user knows what's going on. */ printf("\nCorrelate: "); if (ShowDetails) printf("\n"); /* A few variables that we'll require later. */ struct GPSPoint* Result; char* File; /* Including stats on what happenned. */ int MatchExact = 0; int MatchInter = 0; int MatchRound = 0; int NotMatched = 0; int WriteFail = 0; int TooFar = 0; int NoDate = 0; int GPSPresent = 0; /* Now it is time to correlate the photos. Feed one in at a time, and * see what happens.*/ /* We already checked to make sure that files were passed on the * command line, so just go for it... */ /* printf("Remaining non-option arguments: %d.\n", argc - optind); */ while (optind < argc) { File = argv[optind++]; /* Pass the file along to Correlate and see what happens. */ Result = CorrelatePhoto(File, &Options); /* Was result NULL? */ if (Result) { /* Result not null. But what did happen? */ if (Options.Result == CORR_OK) { MatchExact++; if (ShowDetails) { printf("%s: Exact match: ", File); } else { printf("."); } } if (Options.Result == CORR_INTERPOLATED) { MatchInter++; if (ShowDetails) { printf("%s: Interpolated: ", File); } else { printf("/"); } } if (Options.Result == CORR_ROUND) { MatchRound++; if (ShowDetails) { printf("%s: Rounded: ", File); } else { printf("<"); } } if (Options.Result == CORR_EXIFWRITEFAIL) { WriteFail++; if (ShowDetails) { printf("%s: Exif write failure: ", File); } else { printf("w"); } } if (ShowDetails) { /* Print out the "point". */ printf("Lat %f, Long %f, Elev %f.\n", Result->Lat, Result->Long, Result->Elev); } /* Ok, that's all from this part... */ } else { /* We got nothing back. One of a few errors. */ if (Options.Result == CORR_NOMATCH) { NotMatched++; if (ShowDetails) { printf("%s: No match.\n", File); } else { printf("-"); } } if (Options.Result == CORR_TOOFAR) { TooFar++; if (ShowDetails) { printf("%s: Too far from nearest point.\n", File); } else { printf("^"); } } if (Options.Result == CORR_NOEXIFINPUT) { NoDate++; if (ShowDetails) { printf("%s: No date exif tag present.\n", File); } else { printf("?"); } } if (Options.Result == CORR_GPSDATAEXISTS) { GPSPresent++; if (ShowDetails) { printf("%s: GPS Data already present.\n", File); } else { printf("!"); } } /* Handled all those errors, now... */ } /* End if Result. */ /* And, once we've got here, we've finished with that file. * We can now do the next one. Now wasn't that too easy? */ } /* End while parse command line files. */ /* Right, so now we're done. That really wasn't that hard. Right? */ /* Add a new line if we were doing the not-show-details thing. */ #ifndef WIN32 if (!ShowDetails) { printf("\n"); tcsetattr(fileno(stdout), TCSANOW, &initial_settings); } #endif /* Print details of what happenned. */ printf("\nCompleted correlation process.\n"); printf("Matched: %5d (%d Exact, %d Interpolated, %d Rounded).\n", MatchExact + MatchInter + MatchRound, MatchExact, MatchInter, MatchRound); printf("Failed: %5d (%d Not matched, %d Write failure, %d Too Far,\n", NotMatched + WriteFail + TooFar + NoDate + GPSPresent, NotMatched, WriteFail, TooFar); printf(" %d No Date, %d GPS Already Present.)\n", NoDate, GPSPresent); /* Clean up! */ FreePointList(Points); if (GPSData) free(GPSData); if (TimeAdjustment) free(TimeAdjustment); if (Datum) free(Datum); return 0; } gpscorrelate-1.6.1/gpsstructure.h0000644000175000017500000000221310476247144016415 0ustar danieldaniel/* gpsstructure.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains the structure that stores * GPS data points. Placed here so that several things * could share it. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This data structure describes a single point. */ struct GPSPoint { double Lat; double Long; double Elev; time_t Time; int EndOfSegment; struct GPSPoint* Next; }; gpscorrelate-1.6.1/gpx-read.c0000644000175000017500000001634711165546332015357 0ustar danieldaniel/* gpx-read.c * Written by Daniel Foote. * Started Feb 2005. * * This file contains routines to read the XML GPX files, * containing GPS data. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "gpx-read.h" #include "unixtime.h" #include "gpsstructure.h" struct GPSPoint* FirstPoint; struct GPSPoint* LastPoint; void ExtractTrackPoints(xmlNodePtr Start) { /* The pointer passed to us should be the start * of a heap of trkpt's. So walk though them, * extracting what we need. */ xmlNodePtr Current = NULL; xmlNodePtr CCurrent = NULL; xmlAttrPtr Properties = NULL; char* Lat; char* Long; char* Elev; char* Time; for (Current = Start; Current; Current = Current->next) { if ((Current->type == XML_ELEMENT_NODE) && (strcmp((char *)Current->name, "trkpt") == 0)) { /* This is indeed a trackpoint. Extract! */ /* Reset the vars... so we don't get * the data from last run. */ Lat = NULL; Long = NULL; Elev = NULL; Time = NULL; /* To get the Lat and Long, we have to * extract the properties... another * linked list to walk. */ for (Properties = Current->properties; Properties; Properties = Properties->next) { if (strcmp((char *)Properties->name, "lat") == 0) { Lat = (char *)Properties->children->content; } if (strcmp((char *)Properties->name, "lon") == 0) { Long = (char *)Properties->children->content; } } /* Now, grab the elevation and time. * These are children of trkpt. */ /* Oh, and what's the deal with the * Node->children->content thing? */ for (CCurrent = Current->children; CCurrent; CCurrent = CCurrent->next) { if (strcmp((char *)CCurrent->name, "ele") == 0) { if (CCurrent->children) Elev = (char *)CCurrent->children->content; } if (strcmp((char *)CCurrent->name, "time") == 0) { if (CCurrent->children) Time = (char *)CCurrent->children->content; } } /* Check that we have all the data. If we're missing something, * then skip this point... */ if (Time == NULL || Long == NULL || Lat == NULL) { /* Missing some data. */ /* TODO: Really should report this upstream... */ continue; } /* Right, now we theoretically have all the data. * Allocate ourselves some memory and go for it... */ if (FirstPoint) { /* Ok, adding to the list... */ LastPoint->Next = malloc(sizeof(struct GPSPoint)); LastPoint = LastPoint->Next; LastPoint->Next = NULL; } else { /* This is the first one. */ FirstPoint = malloc(sizeof(struct GPSPoint)); LastPoint = FirstPoint; } /* Clear the structure first... */ LastPoint->Lat = 0; LastPoint->Long = 0; LastPoint->Elev = 0; LastPoint->Time = 0; LastPoint->EndOfSegment = 0; /* Write the data into LastPoint, which should be a new point. */ LastPoint->Lat = atof(Lat); LastPoint->Long = atof(Long); if (Elev) { LastPoint->Elev = atof(Elev); } LastPoint->Time = ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0, 0); /* Debug... printf("TrackPoint. Lat %s (%f), Long %s (%f). Elev %s (%f), Time %d.\n", Lat, atof(Lat), Long, atof(Long), Elev, atof(Elev), ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0, 0)); */ } } /* End For. */ /* Return control to the recursive function... */ } void FindTrackSeg(xmlNodePtr Start) { /* Go recursive till we find a tag. */ xmlNodePtr Current = NULL; for (Current = Start; Current; Current = Current->next) { if ((Current->type == XML_ELEMENT_NODE) && (strcmp((char *)Current->name, "trkseg") == 0)) { /* Found it... the children should * all be trkpt's. */ ExtractTrackPoints(Current->children); /* Mark the last point as being the end * of a track segment. */ if (LastPoint) LastPoint->EndOfSegment = 1; } /* And again, with children of this node. */ FindTrackSeg(Current->children); } /* End For */ } struct GPSPoint* ReadGPX(char* File) { /* Init the libxml library. Also checks version. */ LIBXML_TEST_VERSION xmlDocPtr GPXData; /* Read the GPX data from file. */ GPXData = xmlParseFile(File); if (GPXData == NULL) { fprintf(stderr, "Failed to parse GPX data from %s.\n", File); return 0; } /* Now grab the "root" node. */ xmlNodePtr GPXRoot; GPXRoot = xmlDocGetRootElement(GPXData); if (GPXRoot == NULL) { fprintf(stderr, "GPX file has no root. Not healthy.\n"); return 0; } /* Check that this is indeed a GPX - the root node * should be "gpx". */ if (strcmp((char *)GPXRoot->name, "gpx") == 0) { /* Ok, it is a GPX file. */ } else { /* Not valid. */ fprintf(stderr, "Invalid GPX file.\n"); return 0; } /* Now comes the messy part... walking the tree to find * what we want. * I've chosen to do it with two functions, one of which * is recursive, rather than a clever inside-this-function * walk the tree thing. * * We start by calling the recursive function to look for * tags, and then that function calls another * when it has found one... this sub function then * hauls out the tags with the actual data. * Messy, convoluted, but it seems to work... */ /* As to where to store the data? Again, its messy. * We maintain two global vars, FirstPoint and LastPoint. * FirstPoint points to the first GPSPoint done, and * LastPoint is the last point done, used for the next * point... we use this to build a singly-linked list. */ /* (I think I'll just be grateful for the work that libxml * puts in for me... imagine having to write an XML parser! * Nasty.) */ /* Before we go into this function, we also setlocale to "C". * The GPX def indicates that the decimal seperator should be * ".", but certain locales specify otherwise. Which has caused issues. * So we set the locale for this function, and then revert it. */ FirstPoint = NULL; LastPoint = NULL; char* OldLocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); FindTrackSeg(GPXRoot); setlocale(LC_NUMERIC, OldLocale); /* Clean up stuff for the XML library. */ xmlFreeDoc(GPXData); xmlCleanupParser(); return FirstPoint; }; void FreePointList(struct GPSPoint* List) { /* Free the memory associated with the * GPSPoint list... */ struct GPSPoint* NextFree = NULL; struct GPSPoint* CurrentFree = List; while (1) { if (CurrentFree == NULL) break; NextFree = CurrentFree->Next; free(CurrentFree); CurrentFree = NextFree; } }; gpscorrelate-1.6.1/gui.h0000644000175000017500000000311510636505725014431 0ustar danieldaniel/* gui.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains the function prototypes for the * stuff in gui.c */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ GtkWidget* CreateMatchWindow(void); gboolean DestroyWindow(GtkWidget *Widget, GdkEvent *Event, gpointer Data); void AddPhotosButtonPress( GtkWidget *Widget, gpointer Data ); void AddPhotoToList(char* Filename); void RemovePhotosButtonPress( GtkWidget *Widget, gpointer Data ); void SetListItem(GtkTreeIter* Iter, char* Filename, char* Time, double Lat, double Long, double Elev, char* PassedState, int IncludesGPS); void SetState(GtkTreeIter* Iter, char* State); void SelectGPSButtonPress( GtkWidget *Widget, gpointer Data ); void CorrelateButtonPress( GtkWidget *Widget, gpointer Data ); void StripGPSButtonPress( GtkWidget *Widget, gpointer Data ); void GtkGUIUpdate(void); gpscorrelate-1.6.1/gpscorrelate.desktop0000644000175000017500000000043211335414554017554 0ustar danieldaniel[Desktop Entry] Type=Application Version=1.0 Name=GPSCorrelate GenericName=Geotagging tool Comment=Add coordinates to jpeg images using gpx files TryExec=gpscorrelate-gui Exec=gpscorrelate-gui Terminal=false Categories=Graphics;ImageProcessing;Geography;GTK; Icon=gpscorrelate-gui gpscorrelate-1.6.1/RELEASES0000644000175000017500000001172111335414761014620 0ustar danieldanielRelease History: v1.0: 24 Feb 2005 Initial release. v1.1: 1 Mar 2005 Instead of ignoring track segments, we record them now, and by default don't interpolate between them. This can be disabled, ie, match between track segments. v1.2: (Not released until 1.3) Added --machine/-o option. This outputs the tags from the passed files in a machine-readable CSV output. v1.3: 25 April 2006 It would appear that the Exiv2 API changed somewhat. And gpscorrelate didn't work. Reported to me by a friendly chap. Now fixed to work correctly with the latest Exiv2 v0.9.1. v1.4: 28 May 2006 Added option to preserve mtime on input photos. Patch submitted by Russell Steicke. (http://adelie.cx/). Also added patch to make GPX read correctly in non-C locales - would interpret "." as thousands seperator in some locales. v1.5: 24 Feb 2007 Fixed very silly bug where it would segfault on certain GPX files. Turns out those GPX files don't have time data on the trackpoints, and this is due to that track coming from certain parts of the GPS memory (where the timestamps get stripped to save space on the GPS device itself). This is something gpscorrelate should have handled. v1.5.1: 3rd April 2007 Included patch from Marc Horowitz (an MIT one) to correctly remove all GPS tags when using the "remove GPS tags" feature. It seems my original code missed two. The patch instead iterates over the tags and removes anything starting with "Exif.GPSInfo". Thanks! v1.5.2: 6 June 2007 - Fixed bug where program would die with uncaught exception if input files were not JPEGs at all. Now the exception is caught. - Fixed very silly bug where timestamps were incorrectly calculated: in struct tm, I didn't realise that tm_mon was 0-based, and didn't decrement it. This caused failures on dates spanning months with different numbers of days. Because the timestamps inside EXIF data and the timestamps from GPX data were converted the same way, the matching still worked. The date part is written as GPSDateStamp, which is wrong, and thus a --fix-datestamp option is provided. - Turns out GPS Timestamp wasn't correct either. This time was out by the local timezone. This did not affect matches. --fix-timestamps will fix this as well. - Added a --version option. v1.5.3: 20 June 2007 - GPS coordinates, including altitude, are not written as Rational values instead of Signed Rational values, this now meets the EXIF specifications. - Default format for writing coordinates is now DD MM SS.SS. The old behaviour can be forced with the --degmins parameter. - If altitude is negative, the correct sea level reference value is now written. v1.5.4: 22 June 2007 - Added Photo Offset time, as a fine adjustment between photo time and GPS time. Read the docs to understand it. - GUI now has extra settings, and a "Strip GPS tags" button. - GUI Now remembers settings on exit, into ~/.gpscorrelaterc. These are reloaded next time the GUI is started. v1.5.5: 20 August 2007 - Made altitude data in GPX files optional. This should have been the case since the beginning, but it seems it was not. v1.5.6: 1 October 2007 - Incorporated patch from Marc Horowitz that allows gpscorrelate to correctly calculate negative timezone adjustments. Previously, the minutes were not subtracted from the timezone adjustment. v1.5.7: 21 September 2008 - Fixed a bug where altitude data was not read correctly if the value was negative - instead it would read positive. It was always written correctly, though. Thanks to Andrzej Novak for pointing this one out. - Added an 'install' target to the makefile provided by Till Maas. Thanks! - Update the GUI to remember the last directory for Photos and GPX data when using the file chooser dialog. This also persists across program invocations. This was suggested by Till Maas. - Added Makefile.mingw32 contributed by Julio Castillo, which allows cross compliation on Win32. This also included some cross-platform patches for the code, which is greatly appreciated. v1.5.8: 1 November 2008 The 'Till Maas' release. - Added gpscorrelate.desktop contributed by Till. - Added patches for the Makefile by Till, to improve the installation. - Added manpage, originally from Debian, but converted to XML by Till. - Added patches for the Makefile by Till, to configure and install the manpages. - Added patches by Till to remove compilation warnings. - Thanks for your work! v1.5.9: 4 April 2009 Incorporated patches from the new Debian maintainer: - Fixes crash on empty tags - Fixes writing of negative altitudes. - Fixes display of negative altitudes. - Fixes invalid use of Exiv2 toRational(). Thanks Eugeniy for organising all these fixes; you did all the work - I just applied the patches you supplied. v1.6.0: 5 April 2009 Added another patch that I forgot to include in 1.5.9. Thanks again Eugeniy. v1.6.1: 13 February 2010 - Added desktop icon created by Till Maas. - Added a patch to fix future build issues on Fedora. gpscorrelate-1.6.1/gpscorrelate2.glade0000644000175000017500000016452110700137305017242 0ustar danieldaniel GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GPSCorrelate 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 2 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Remove Data 0 1 2 1 2 GTK_FILL True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Load Data 0 1 2 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Preview</b> True label_item 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Loaded Files</b> True label_item True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GDK_EXTENSION_EVENTS_ALL True Load GPS data to match to photos. GPS Data tab False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 3 2 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Strip GPS Tags 0 2 3 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-orientation-landscape 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Preview</b> True label_item 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Photos</b> True label_item True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Remove Photos 0 1 2 1 2 GTK_FILL True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Add Photos 0 1 2 GTK_FILL 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Choose Photos to match GPS data to Photos tab 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>GPS Data</b> True label_item False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Photos</b> True label_item True True False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Map View</b> True label_item False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-orientation-landscape 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Photo Preview</b> True label_item True True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Assign Point 0 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Clear Point 0 1 False 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Match Photos to specific waypoints Manual Correlation tab 2 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>GPS Data</b> True label_item True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Interpolate 0 True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Don't change mtime 0 True 1 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Between Segments 0 True 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 4 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 6 WGS-84 1 2 3 4 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 0 1 2 2 3 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 +8:00 1 2 1 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 0 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GPS Datum: 3 4 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Photo Offset: 2 3 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Time Zone: 1 2 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Max gap time: GTK_FILL 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Options</b> True label_item 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Dry Run 0 False False 2 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Correlate & Save 0 False False 3 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_SHADOW_IN True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 12 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Photos</b> True label_item True True 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Automatic Correlation tab 3 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Export tab 4 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Set Advanced GPScorrelate options Options tab 5 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False GPSCorrelate v2.0 Daniel Foote, (C) 2007. Licenced under the GNU GPL v3. 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK About tab 6 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 False 1 gpscorrelate-1.6.1/correlate.h0000644000175000017500000000546010636505725015632 0ustar danieldaniel/* correlate.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains the options structure and prototypes for * functions in correlate.c. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* A structure of options to pass to the correlate function. * Not really sure if this is needed, but... */ struct CorrelateOptions { int NoWriteExif; int NoInterpolate; int NoChangeMtime; int TimeZoneHours; /* To add to photos to make them UTC. */ int TimeZoneMins; int FeatherTime; char* Datum; /* Datum of the data; when writing. */ int DoBetweenTrkSeg; /* Match between track segments. */ int DegMinSecs; /* Write out data as DD MM SS.SS (more accurate than in the past) */ int Result; time_t MinTime; /* Calculated on first pass. Used to */ time_t MaxTime; /* determine when to throw photos out. */ int PhotoOffset; /* Offset applied to Photo time. This is ADDED to PHOTO TIME to make it match GPS time. In seconds. This is (GPS - Photo) */ struct GPSPoint* Points; /* Points to use... */ }; /* Return codes in order: * _OK - all ok. Correlated exactly. * _INTERPOLATED - all ok, interpolated point. * _ROUND - point rounded to nearest. * _NOMATCH - could not find a match - photo timestamp outside GPS data * (This could be due to timezone of photos not set/set wrong). * Returns NULL for Point. * _TOOFAR - point outside "feather" time. Too far from any point. * Returns NULL for Point. * _EXIFWRITEFAIL - unable to write EXIF tags. * _NOEXIFINPUT - The source file contained no EXIF tags, or not the one we wanted. Hmm. * Returns NULL for Point. * _GPSDATAEXISTS - There is already GPS data in the photo... you probably don't want * to fiddle with it. * Returns NULL for Point. */ #define CORR_OK 1 #define CORR_INTERPOLATED 2 #define CORR_ROUND 3 #define CORR_NOMATCH 4 #define CORR_TOOFAR 5 #define CORR_EXIFWRITEFAIL 6 #define CORR_NOEXIFINPUT 7 #define CORR_GPSDATAEXISTS 8 struct GPSPoint* CorrelatePhoto(char* Filename, struct CorrelateOptions* Options); gpscorrelate-1.6.1/gpx-read.h0000644000175000017500000000176710476247144015367 0ustar danieldaniel/* gpx-read.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains prototypes for the functions * in gpx-read.c. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ struct GPSPoint* ReadGPX(char* File); void FreePointList(struct GPSPoint* List); gpscorrelate-1.6.1/COPYING0000644000175000017500000004313110476247144014531 0ustar danieldaniel GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. gpscorrelate-1.6.1/unixtime.h0000644000175000017500000000216310476247144015511 0ustar danieldaniel/* unixtime.h * Written by Daniel Foote. * Started Feb 2005. * * This file contains prototypes and other things for * the Unix time functions. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define EXIF_DATE_FORMAT "%d:%d:%d %d:%d:%d" #define GPX_DATE_FORMAT "%d-%d-%dT%d:%d:%dZ" time_t ConvertToUnixTime(char* StringTime, char* Format, int TZOffsetHours, int TZOffsetMinutes); gpscorrelate-1.6.1/Makefile.mingw320000644000175000017500000000135711065335320016414 0ustar danieldaniel# Makefile for gpscorrelate # Written by Daniel Foote. CC = i486-mingw32-gcc CXX = i486-mingw32-g++ COBJS = main-command.o unixtime.o gpx-read.o correlate.o exif-gps.o GOBJS = main-gui.o gui.o unixtime.o gpx-read.o correlate.o exif-gps.o CFLAGS = -mms-bitfields -Wall $(shell pkg-config --cflags libxml-2.0 gtk+-2.0 exiv2) OFLAGS = -Wall $(shell pkg-config --libs exiv2 libxml-2.0 gtk+-2.0) -lm -liconv -lexpat all: gpscorrelate.exe gpscorrelate-gui.exe gpscorrelate.exe: $(COBJS) $(CXX) -o $@ $(COBJS) $(OFLAGS) gpscorrelate-gui.exe: $(GOBJS) $(CXX) -o $@ $(GOBJS) $(OFLAGS) .c.o: $(CC) $(CFLAGS) -c -o $*.o $< .cpp.o: $(CXX) $(CFLAGS) -c -o $*.o $< clean: rm -f *.o gpscorrelate{,.exe} gpscorrelate-gui{,.exe} gpscorrelate-1.6.1/correlate.c0000644000175000017500000002314010636505725015620 0ustar danieldaniel/* correlate.c * Written by Daniel Foote. * Started Feb 2005. * * The functions in this file match the timestamps on * the photos to the GPS data, and then, if a match * is found, writes the GPS data into the EXIF data * in the photo. For future reference... */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "gpsstructure.h" #include "exif-gps.h" #include "correlate.h" #include "unixtime.h" /* Internal functions used to make it work. */ void Round(struct GPSPoint* First, struct GPSPoint* Result, struct CorrelateOptions* Options, time_t PhotoTime); void Interpolate(struct GPSPoint* First, struct GPSPoint* Result, struct CorrelateOptions* Options, time_t PhotoTime); /* This function returns a GPSPoint with the point selected for the * file. This allows us to do funky stuff like not actually write * the files - ie, just correlate and keep into memory... */ struct GPSPoint* CorrelatePhoto(char* Filename, struct CorrelateOptions* Options) { /* Read out the timestamp from the EXIF data. */ char* TimeTemp; int IncludesGPS = 0; TimeTemp = ReadExifDate(Filename, &IncludesGPS); if (!TimeTemp) { /* Error reading the time from the file. Abort. */ /* If this was a read error, then a seperate message * will appear on the console. Otherwise, we were * returned here due to the lack of exif tags. */ Options->Result = CORR_NOEXIFINPUT; return 0; } if (IncludesGPS) { /* Already have GPS data in the file! * So we can't do this again... */ Options->Result = CORR_GPSDATAEXISTS; return 0; } /* Now convert the time into Unixtime. */ time_t PhotoTime = ConvertToUnixTime(TimeTemp, EXIF_DATE_FORMAT, Options->TimeZoneHours, Options->TimeZoneMins); /* Add the PhotoOffset time. This is to make the Photo time match * the GPS time - ie, it is (GPS - Photo). */ PhotoTime += Options->PhotoOffset; /* Free the memory for the time string - it won't otherwise * be freed for us. */ free(TimeTemp); /* Check to see if MinTime and MaxTime are filled in. * If not, fill them in. */ if (Options->MinTime == 0 && Options->MaxTime == 0) { /* Alright, fill them in! * Requires us to go through the list and keeping * the biggest and smallest. The list should, * however, be sorted. But we do it this way anyway. */ struct GPSPoint* Fill = NULL; Options->MinTime = Options->Points->Time; for (Fill = Options->Points; Fill; Fill = Fill->Next) { /* Ignore trackseg markers... */ if (Fill->Lat == 1000 && Fill->Long == 1000) continue; /* Check the Min time */ if (Fill->Time < Options->MinTime) Options->MinTime = Fill->Time; /* Check the Max time */ if (Fill->Time > Options->MaxTime) Options->MaxTime = Fill->Time; } } /* Check that the photo is within the times that * our tracks are for. Can't really match it if * we were not logging when it was taken. */ /* Note: photos taken between logging sessions of the * same file will still make it inside of this. In * some cases, it won't matter, but if it does, then * keep this in mind!! */ if ((PhotoTime < Options->MinTime) || (PhotoTime > Options->MaxTime)) { /* Outside the range. Abort. */ Options->Result = CORR_NOMATCH; return 0; } /* Time to run through the list, and see if our PhotoTime * is in between two points. Alternately, it might be * exactly on a point... even better... */ struct GPSPoint* Search; struct GPSPoint* Actual = malloc(sizeof(struct GPSPoint)); Options->Result = CORR_NOMATCH; /* For convenience later */ for (Search = Options->Points; Search; Search = Search->Next) { /* Sanity check: we need to peek at the next point. * Make sure we can. */ if (Search->Next == NULL) break; /* Sanity check: does this point have the same * timestamp as the next? If so, skip onward. */ if (Search->Time == Search->Next->Time) continue; /* Sanity check: does this point have a later * timestamp than the next point? If so, skip. */ if (Search->Time > Search->Next->Time) continue; if (Options->DoBetweenTrkSeg) { /* Righto, we are interpolating between segments. * So simply do nothing! Simple! */ } else { /* Don't check between track segments. * If the end of segment marker is set, then simply * "jump" over this point. */ if (Search->EndOfSegment) { continue; } } /* Sanity check / track segment fix: is the photo time before * the current point? If so, we've gone past it. Hrm. */ if (Search->Time > PhotoTime) { Options->Result = CORR_NOMATCH; break; } /* Sort of sanity check: is this photo inside our * "feather" time? If not, abort. */ if (Options->FeatherTime) { /* Is the point between these two? */ if ((PhotoTime > Search->Time) && (PhotoTime < Search->Next->Time)) { /* It is. Now is it too far * from these two? */ if (((Search->Time + Options->FeatherTime) < PhotoTime) && ((Search->Next->Time - Options->FeatherTime) > PhotoTime)) { /* We are inside the feather * time between two points. * Abort. */ Options->Result = CORR_TOOFAR; free(Actual); return NULL; } } } /* endif (Options->Feather) */ /* First test: is it exactly this point? */ if (PhotoTime == Search->Time) { /* This is the point, exactly. * Copy out the data and return that. */ Actual->Lat = Search->Lat; Actual->Long = Search->Long; Actual->Elev = Search->Elev; Actual->Time = Search->Time; Options->Result = CORR_OK; break; } /* Second test: is it between this and the * next point? */ if ((PhotoTime > Search->Time) && (PhotoTime < Search->Next->Time)) { /* It is between these points. * Unless told otherwise, we interpolate. * If not interpolating, we round to nearest. * If points are eqidistant, we round down. */ if (Options->NoInterpolate) { /* No interpolation. Round. */ Round(Search, Actual, Options, PhotoTime); Options->Result = CORR_ROUND; break; } else { /* Interpolate away! */ Interpolate(Search, Actual, Options, PhotoTime); Options->Result = CORR_INTERPOLATED; break; } } } /* End for() loop to search. */ /* Did we actually match it at all? */ if (Options->Result == CORR_NOMATCH) { /* Nope, no match at all. */ /* Return with nothing. */ free(Actual); return NULL; } /* Write the data back into the Exif info. If we're allowed. */ if (Options->NoWriteExif) { /* Don't write exif tags. Just return. */ return Actual; } else { /* Do write the exif tags. And then return. */ if (WriteGPSData(Filename, Actual, Options->Datum, Options->NoChangeMtime, Options->DegMinSecs)) { /* All ok. Good! Return. */ return Actual; } else { /* Not good. Return point, but note failure. */ Options->Result = CORR_EXIFWRITEFAIL; return Actual; } } /* Looks like nothing matched. Free the prepared memory, * and return nothing. */ free(Actual); return NULL; }; void Round(struct GPSPoint* First, struct GPSPoint* Result, struct CorrelateOptions* Options, time_t PhotoTime) { /* Round the point between the two points - ie, it will end * up being one or the other point. */ struct GPSPoint* CopyFrom = NULL; /* Determine the difference between the two points. * We're using the scale function used by interpolate. * This gives us a good view of where we are... */ double Scale = (double)First->Next->Time - (double)First->Time; Scale = ((double)PhotoTime - (double)First->Time) / Scale; /* Compare our scale. */ if (Scale <= 0.5) { /* Closer to the first point. */ CopyFrom = First; } else { /* Closer to the second point. */ CopyFrom = First->Next; } /* Copy the numbers over... */ Result->Lat = CopyFrom->Lat; Result->Long = CopyFrom->Long; Result->Elev = CopyFrom->Elev; Result->Time = CopyFrom->Time; /* Done! */ } void Interpolate(struct GPSPoint* First, struct GPSPoint* Result, struct CorrelateOptions* Options, time_t PhotoTime) { /* Interpolate between the two points. The first point * is First, the other First->Next. Results into Result. */ /* Calculate the "scale": a decimal giving the relative distance * in time between the two points. Ie, a number between 0 and 1 - * 0 is the first point, 1 is the next point, and 0.5 would be * half way. */ double Scale = (double)First->Next->Time - (double)First->Time; Scale = ((double)PhotoTime - (double)First->Time) / Scale; /* Now calculate the Lattitude. */ Result->Lat = First->Lat + ((First->Next->Lat - First->Lat) * Scale); /* And the longitude. */ Result->Long = First->Long + ((First->Next->Long - First->Long) * Scale); /* And the elevation. If elevation wasn't set, it should be zero. * Which works quite fine for us. */ Result->Elev = First->Elev + ((First->Next->Elev - First->Elev) * Scale); /* The time is not interpolated, but matches photo. */ Result->Time = PhotoTime; /* And that should have fixed us... */ } gpscorrelate-1.6.1/README0000644000175000017500000000075710476247144014365 0ustar danieldanielGPSCorrelate Daniel Foote, 2005. This program correlates digital camera photos with GPS data in GPX format. The documentation is in the doc/ directory. It is in HTML format. To build, you will need: The Exiv2 library (C++ EXIF tag handling): http://www.exiv2.org/ libxml GTK+ (if compiling the GUI). You can build the command line version and the GUI together simply with "make". There is presently no "make install". Queries, comments, information: http://freefoote.dview.net/ Have fun! gpscorrelate-1.6.1/unixtime.c0000644000175000017500000000431110631421054015464 0ustar danieldaniel/* unixtime.c * Written by Daniel Foote. * Started Feb 2005. * * This file contains a function that converts a string * to a unix time, given a format string. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include "unixtime.h" time_t ConvertToUnixTime(char* StringTime, char* Format, int TZOffsetHours, int TZOffsetMinutes) { /* Read the time using the specified format. * The format and string being read from must * have the most significant time on the left, * and the least significant on the right: * ie, Year on the left, seconds on the right. */ /* Sanity check... */ if (StringTime == NULL || Format == NULL) { return 0; } /* Define and set up our structure. */ struct tm Time; Time.tm_wday = 0; Time.tm_yday = 0; Time.tm_isdst = -1; /* Read out the time from the string using our format. */ sscanf(StringTime, Format, &Time.tm_year, &Time.tm_mon, &Time.tm_mday, &Time.tm_hour, &Time.tm_min, &Time.tm_sec); /* Adjust the years for the mktime function to work. */ Time.tm_year -= 1900; Time.tm_mon -= 1; /* Add our timezone offset to the time. * We don't check to see if it overflowed anything; * mktime does this and fixes it for us. */ /* Note also that we SUBTRACT these times. We want the * result to be in UTC. */ Time.tm_hour -= TZOffsetHours; Time.tm_min -= TZOffsetMinutes; /* Calculate and return the unix time. */ return mktime(&Time); }; gpscorrelate-1.6.1/doc/0000755000175000017500000000000011335415005014225 5ustar danieldanielgpscorrelate-1.6.1/doc/gpscorrelate-manpage.xml.in0000644000175000017500000002717011102570417021464 0ustar danieldaniel Stefano"> Zacchiroli"> 30 Oct 2008"> 1"> zack@debian.org"> GPSCORRELATE"> Debian"> GNU"> GPL"> ]>
&dhemail;
&dhfirstname; &dhsurname;, Till Maas 2006, 2008 &dhusername; &dhemail;, Till Maas &dhdate;
&dhucpackage; &dhsection; &dhpackage; correlates digital images with GPS data filling EXIF fields &dhpackage; -z --timeadd +/-HH[:MM] -O --photooffset seconds -i --no-interpolation -v --verbose -d --datum datum -n --no-write -m --max-dist time -t --ignore-tracksegs -M --no-mtime -f --fix-timestamps -p --degmins -g file.gpx image.jpg &dhpackage; -s --show -o --machine image.jpg &dhpackage; -M --no-mtime -r --remove image.jpg &dhpackage; -V --version -h --help &dhpackage;-gui DESCRIPTION This manual page documents briefly the &dhpackage; and &dhpackage;-gui commands. There is an extended documenation available in HTML format; see below. &dhpackage; is a program that acts on digital images in JPEG format filling EXIF (Exchangeable Image File Format) fields related to GPS (Global Positioning System) information. Source for the GPS data is a record of GPS information encoded in GPX (GPS Exchange Format) Format. The act of filling those fields is referred to as correlation. If GPS data are available at the precise moment the image was taken (with a 1-second granularity) the GPS data are stored unmodified in EXIF fields. If they are not linear interpolation of GPS data available at moments before and after the image was taken can be used. &dhpackage; is a command line tool implementing correlation whereas &dhpackage;-gui is the corresponding GTK+ graphical user interface. OPTIONS These programs follow the usual &gnu; command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. For a complete description, see the HTML documentation. , file.gpx correlate using the specified GPX file with GPS data , only show the GPS data of the given images , only show the GPS data of the given images in a machine readable output, if there is any , only remove GPS EXIF data from the given images , +/-XX[:XX] time to add to GPS data to make it match the timestamps of the images. GPS data is in UTC; images are not likely to be in UTC. Enter the timezone used when taking the images: eg, +8 for Perth , seconds time in seconds to add to the photo timestamp to make it match the GPS timestamp. To determine the amount of seconds needed, just create a picture of your GPS device showing the current time and compare it with the timestamp of your photo file. , disable interpolation between points. Interpolation is linear, points are rounded if disabled , show which GPS data has been selected for each image , datum specify measurement datum. If not set, WGS-84 used , do not write the exif data. Useful with --verbose , time max time outside points that image will be matched. Time is in seconds , Interpolate between track segments, too , Do not change mtime of modified files , Fix broken GPS datestamps written with versions < 1.5.2 , Write location as DD MM.MM as was default before < 1.5.3 , Only show summary of options , Only print the version SEE ALSO gpsd (1), gpsbabel (1), gpxlogger (1), cgpxlogger (1). The documentation of &dhpackage; and &dhpackage;-gui in HTML format are available on the filesystem at @DOCDIR@. AUTHOR This manual page was initially written by &dhusername; &dhemail; for the &debian; system. It was extended by Till Maas opensource@till.name. Permission is granted to copy, distribute and/or modify this document under the terms of the &gnu; General Public License, Version 2 any later version published by the Free Software Foundation. On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL.
gpscorrelate-1.6.1/doc/corr.png0000644000175000017500000025642310636505725015730 0ustar danieldaniel‰PNG  IHDRpúe pHYsÄÄ•+tIME×$´ÃÖ{ IDATxÚìw\GÇ8‘vÈÑ„£AI¤ ¶Á‚[Ä®)–HGPºhÄW1¢Æ†% ö‚Á+½ƒ4¥`¡íöýcÍærÁ >ßú™{vfvæ™ÙÙßÎÎî „Bžž;Ðßôôô à„Þ³' !4!9wî\„ÀM !¬±±‘L–ݱÃKÀÓsÇwß}§ @×}âõëš+W®Œ@‘ɲ†G€>A&Ë"„B#FŒw}—‘#B¼§'ß¿_^^‘™™ùøñãêêW_ý•©©‰¾¾™,˼$øBe%»‰Á`´··WWWgdd>~ü¸¤¤´»»{üxƒY³¬Ç—––vâÄ©ÆF𢢒™™‰‰‰‰ššš¸¸¸   xà ”,3”¡¡a<èìì255>}†——·ŽŽÎÈ‘$„B66Ö VYYùôé“ßOöôôîììüöÛ™>>ÞàM€/ÓŠ·nÝ’––éîî*--mhh`0#FŒÀÕ$B!AAÁ#Fttt64Ô·µµÉÉÉýö[¸àË„Ã-oÁ°°0MMÌÌÌ[·n­Y³ZXXä›ofÌ;O]]ý÷ß“®^½V^^>~¼Á’%K"##KKË\]]Á•üçÄ_ü!´lÉ÷PS€¾Â`0ššš¥¤$™’éééin~#--Å{q#çç»1 “••ýæ›o¾ùæ›®®®/^üöÛo;vì(//_°`ÁêÕ«LMM%$$ðÈeee3éì쬩©}ÿþ=BhôèÑмˆˆ?õéîîÎÉÉEèÃèð¹ÐÕÕ™_ÿêÕ«Ö¶V„‰D#1ÆÊr ³úûªU@LLLYYÅ@Ï¡ètú³Ïêêj{zz„…EF‰üv¦5!Å-ŸÿDœõôôUVW¶´´ôôôH¢’c$­,§‚îà?$33‹£ÝÈÈ[’¦¦æêêj­ACC`ÝÝÝ%%%tzBHVV¦WAÉþuœVU ›˜˜Lšd2y²åîÝ»~üñGÎÓø÷©¥«¨¨˜øŠã›7oÞ¿¯­­-,,Üë9©´ôƒB-))UW§ ‹@·€!NggGRrÞþµñ× J!­¡¸¤ˆedX¶d99#ëEAA~gGç×_MB=yú¸¦¶ÆÒÂJ^žòæMs~a§q©÷|8G|ÓŸT]]]¤üþöÝ» FÆãTT…šš ‹ û[†A*3| €Þ))IF§Óq†*--£Ó;H$’””$ï‘dBˆås‹ °$À£qø6£€€Àß[ÿáõë×===JJJ¡êêêwïÞ½~ýZMM•ç9©«´´ŒN§ã?étzii™†††ˆˆ0´? err³[ZÞmüµªªnQPPPPP`‰†$ÉxÂWåååUU•&“LBuõu!Y™#„dee­d§ðÞ·|p**_æåçµ´´Œ-ñõW_É‘Ç"„0 +(È/.-¡ÓÛH$1 u ]]_â/à©â/þ‚Z¾ÌŽGdö’äæå6¿ycd8AKS·Œ•;Vnì‡KsîùàûýjâWùùíôöï—.g· „ ‹ ŠKŠ[[[G’Fjhhêéê±û!TT\ðüÅ QQQŠÂÄ ÆÂ«Ökž &NœÀbyñ"ƒÇG¶…544JJJètzaa‘  `gg'‰DÒÐÐèuBë-ï¬~§[YYׂÊÊʹ¹¹¸‘/_¾¤Óé#GŽlooGá—/_jkkA·€¡ÌëׯBJJÊ|§ø× #""B§Óïܽ­¨ (##K¡PÄFŠõ#œ†††Y3mêêkRÓR?yLIþƒ€€†aœ.ÅØ#3§b· àv"À-9ް°ðèÑ£UUUñ5”åååBBB¼SðŸÓNoG‘H$âh½ðëy<`÷ý –¢££##3!¤¢¢‚[ÌL-^¼xööÝÛ²—ee/ËL&™PÕÔ¹íŽ[>8ãÇŠˆ+)*#„ZZZðM%¥Å!]]Ý#Fèêè—–èéésÄzÌRñ‘$1ŽÃT¯ù±ŒÔÌ<¹¾ž°°°Ž¶vNnvqI±®Ž.{™õõ ðUúøÉ_¯k^3—‡½j<ò`x ÀQ›õª©ÿ–m‚ü$AÜf(9^ô÷‰1c$ššš+**UT”B••U†#Á;~Ãý=E*""¬¥¥ }†>b#G¶¶µµ·ÓÅÄF:’Д¸E@@`äH1=]½ñÖ†Säå)³ç¶¶¶¼®©ÉÊÎêììÈÈÌä!(¹åówaÄØ/_é¸ä%áÂ!D§·qËŸÿÈxÅéí§T{Íg$i$KfK{{BèúÍk„¥­­•}/õ õYÙ™ošßtuwá–ŽŽ®ÒŸ¿<øÜáx·¹×[Ð]%%% „PgggII ?“”Üf(û&q9ËlTRRzÿ¾åýû÷¹¹y¸EXXXII‰Ï¹F<LLÀç‚’’JaQÁËòRýñœ†ˆ¬X¾’G&â⣵4GËËË߸y½»»‹ÇÀ;ö1 !D"lkkëèìI‰/Ô&ý{Z‘9Ükd–ŠWVVrœäëÓNÙ-#GŽjmmY´p1.F¹Õ4-=•N§O›2BQ`0¿&üÂ’OŸò`òüù ‹±ñÄ^åY_g(»ººKKKétº˜ØHMMM„Pqqq{{{ii©¦¦&óâ¾åßV O"Å.""¢§§[]ýŠxm’’b¯+: $%%APÀg„‘¡a]}]NnŽ€€UM}äÈ‘mmm샷ƒú·ßÓÒÔ+7VDDäíÛ·!Š<…ÇÀ{pà((©jԜܜüü‚ FFù!-M |“ðá®î®ÖÖVqqq< È+ž•),,¬ªª*( ØÔÔ˜›Ÿ7mÊ4~òá-(u´už=š‘õâk㯠5Ò ò§O›Á1²ˆˆHO#+;“y{ÕzÍ€!ˆ€€`Ÿ†A<É‹™ìF ß¾}‹?Ê¢­­…¿6H[[«°°ˆN§¿}û–L–í› hl¤uuu à4Ø1ëND§Ó«ªª8 ^*U­ŽÓÔÔ€ÞŸ""¢6³lò ò+*+rrs †°°°”””´´4?‚RxĈŒÌçíítƒ!**J¥R¿šøÕÀ J#à †½|ù²°(_l¤˜‘¡‘¾Þx|Óøñ†Ù9ÙW¯'"„V­XÍ;2ÇŠæ—={þ/¿””T¯;åGPêêèŽ1¢°¨ðâå‹#„„deåttôØ“O¶°|úìIRrÒÈ‘#‰Å”ܪÖkž A¾újâ'H"##-((()9†x ¸°°°ŽŽö›7o¥¤${x==wxxx0›|||¯_¿nhh¸|ùò æKJ~SRR‚‚oß¾ƒ‹Éæææ›7o^¸ðKuuõŠ+ÜÜàc9_à0Cæêºùúõ±±±;wîüöÛo—/_nnnŽßïêêÌÏÏÿõ×ø;wnËÊ’—-[jmm-##Þø2Á_lÎzc^EEeófû¼¼¼k×®mÙâ!,,¢­­]UUµr媒’ ‹ýû÷Á²n”\W#‘H$cccccãíÛ·?zôèÊ•DYY™©S§FFF(**‚ïDÌPò^-)9ÆÆÆÚÆÆüàïšüøo-_øÇÁ?<Ž¿÷ü;à .‰É‚ý8ðÏèÑ£ÿ”ø`¡¦æUnnø†úú†ʇǬÏ_:Í‹ײXF€7äæféë‚`Xžå A‰Úì°­×$Ož?|üü!»%ÀoŸ'àÚ\~ï+h£aÐXzz†ìõª¶ŠÁèaüCχ¹e8´%•ª*++éÇÖ~g ð£&—.]õ…;!// ?ðÀà7ð¸€6¥§7w ù”¸&Ãé«2;uêôîÝÁ¡ÀÀk×®<%ú¡J#FÈËËÛÚÎsww$uûÂãº\€ßÀ{à ÚèK†_A‰ë*fÝÆ'†={NHH!wvÍšÕƒöj"¼MMMáá?ýt¤¹¹9,,Úx €—•‚+Àoà=pmôåÐÜܼÓ/p箉1&(ûMZZZYY™ BØ;wÓÓÓ'OžLl=qâÄÉ“§ëëëÇç켑%-ï­Ü––ö÷÷»téò7™åÅ‹—~úé§êêW***ÁÁ»ÍÌLñ«¨ØØ£çÏ_¨««;v¬Ýrgç‚‚‚„tfž§ä!ô×_##÷`fbbbo¿~Ê”)0@ÀX €ßÀ{à h#às„N§oõø!';gÇ6¯ÿÅD ‹ô¢»@gÎÄ!„Ö®]³fÍâ'ΩS§CBÂÆ7xø0í—_Î'%%1'ä½µ]ùÉ“§×¯_ûßÿ”––úøøàÆ#Gb£¢öÚØØdggÚØØìÝ»/6ö(bºÓ]VVB„¹EFyxlÉÈÈøñǽÏÝÜ6Ÿ={nX_2à ðx\@ ¿ÆâHOO—_NvB(''w§£‡Ñçʤ¤ÛDxæÌÙ#A^½z•œ|_[[ÛÔÔ!¤¥¥•œ|ÿõë× ¡S§N!„||¼¥¥¥BÞÞÞwîÜe”¼¶òž¡ˆØƒš7o.³}Û¶-bbb3fLGUVVáÆóç/ „œœDEEíþùçóç/lÚäÌ1g‘[[[EDD( B_ýõ×_ÍîÌÏ–…ÕkbÀà7ð¸€6þD„íIùãñ3õÏ´ý?غݣo‚’YI477~LÎ;Ï`0Ö¬Yÿ\»vŸŸÿÙ³ç<=w „^¿®Aáâ!¤ @aNË{+GðÛÓBBBòòòÎÎ=<þUó±cÇ"„„……™§0ëëëB222ÄÿuuuÜòçyÛ¶­{öD}÷Ýb!!!}}½~Ønii‰>ç‡ÝÎöㆼ<¥¶¶îæ 6Ÿ©Ÿ‡þ]°¡ìXÞÞûB½Ï¥#ÐFŸ;~>>~^ì¯ ê³ üx)‰êèèŒO@ùùùûùù3)•QQJeeÕëׯ••• IÀ{+GúñD¶¬¬lmmmcc#™Lnll$tg_#¯_¿nÅ »‚‚‚‡íÙµuë¶'Oˆ?‹‚BQ¨©yýåŒ#_Gf}9ãõ'(ó0vl¯¼’¼Wÿ“±A |J>v %•ªÁíÑï7n455Mž<_†ˆÿMž<¹±±éæÍ›¡uëÖ!„ÂÃ#šššš››#""˜“óÞ:P,ZôBèèÑãÇŽýŒZ¾ü{|“¸¸8bº9Î;²‹Ëæ‚‚]]]ss3„¨(iX†Q( ̸ñõëWIJŒ/gyQ}~`‰Lx¬O™0æ ñ[_fŽíõ$ª9°%çÖ]‡Ž+Øý€—y(–å½ñÝ»w;MLL••UtuõìíRSÓút„êp5°ôç=”|¾ 1.î,BhÙ²¥ÌÆeË–¦¥¥9·hÑwëÖ­íéé>yò´™™…ŠŠÊ¦MÎÌ«$yo(<<Ü Æ•+‰§N’““Ûºu ±€ÒÍÍ5&æð´iÓ‰Êòˆ¼dÉ’°°ðÌÌ,ccc__ïa|ÅùêU5ËÐ/j†²O•å¹û¬g(?Mᇙc9€ƒçÕ¡ÜÁ0 #œ ¨¨D„aVl¨u×ÇS‰ÿUQQñÍ›7ééüqŸ……yŸÚ šu¸ÍP2Ï22?øÌf!1ñrYY‰­í#Ϙ1ý×_)(ÈËÏϽx1ÞØØx¸ÎPrœQTTbÙÚÝÝfhh¤®®±i“KKKËð»øæèŠòòŠuëÖijjQ©ê+V¬¬¯¯ÇýƒŸùGánvvÇÒéô­[·jhhN˜0ñðáŸ>#?óé7:¾cÇmmmmooo:NxàÌ™8SS3UU5kk›¼¼<ÜÞÞÞîîîA8d¸:–Oïñ>ôš››Ç7|óæ ¹¹¹ÙÈh›7oúÔ]ûÑLŸà b–ŸÌe>s挹¹¹šuúô=JHH°²š¢¦F5Ë:??¸ŽTCê`OOO÷óóSVV”––ž7onBBÇ>ÆgW„öìÊ'Ͼ~]][[S__G£5455¾yóæÝ»w­--ímíôŽ®Î®Áºå |IWœ üÁ=<ÀüǾõàÁƒÙÙÙ·oßÊÈx.**žêóúãÓëׯwppÈÌ|ñâÅ3 õÀÀ@ cTUU „ªª*ªª*˜Ór³³;vïÞ}4ZãÇi÷îÝyø0ý3ò3Ÿ~Û³gOmmퟦþWüå »vÙÙ­ðññÂ7………JII!„6nt:pàxÚ«W¯^¼˜€Û~ÿýwö̇™c9€ìÇ Ç’oذ~ÍšuBBBÝÝÝgÏž»x1ð¾v×¾6Ó§YÌÀígDD8^$û={¢BCCðŸNNŽÑѾ„‘ê?ï®GÆÆÆõ÷(--•––™3göÖ­[FÍÞp|vEh¯Aeªå q’„         `ýYXš ‚€A¼¢â%‰€êêê¦Oÿ†ˆ& 0œÖÁðF/^d„……egç´··³TüceCMII +))}¦~æá7­QYY·«¨¨ÐhDIII<,**ÚÝÝM8DAA+**òèŠÃƱ@>=uuuuuõ»wïΞ=ûæÍ[_}eL¡P0 ëkwík3ý·‚’¹HÜJ8¼Gªÿ¼»Ž=ú‡¶ÿðÃv ÃJJJccc]Ξ=ÃÞp|vEh¯¡ÌG ÊššW¹¹YàÄá¾¾!…¢Ø?5À¾•L&'&^f~Ó"(7ùùùN›6U\\¼­­M_|Ÿåˆ#ÚÛÛI$B¨¹¹™Ø*++[YY©ªªŠªªªúLýÌÃo222D+**eddxûMFF¦ººZEE…Å!ÃØ±½>|ûä6¬?t(ÆÆÆæÄ‰!!Á¸±¯Ýµ¯Íôß J~¢ ï‘j(tW õàà݆†86Ÿ]ÚkØ ÊÜܬÏ÷ÅÝ3yyY¹¹Y((W­Zéåå´SQQ±´´4&æðÿþwàK”¢¢¢Â•••QQ{‰8cÆŒ)--¥R©,™°Øõôtcc:99¾}ûv÷î`"Úwß- ݳ'ðݻC>S?óð›­í¼ààÐÈÈp CAA»,˜Ï[ÌŸo†aXpp({æÃϱ| Jn%·²²Ü½;8..nÔ¨Qzzzxä¾v×¾6ÓДÃ{¤úÏ»ëòå+V­Zijj*--E£ÑŽ;nddıñÙ¡½†­ ÄáýætàKPQì[7ntB­\¹º¾¾žJ¥ººº ËJ55uf{YYÉž=‘!!!..¯)Š““ã7 ‡ØÚ.hkk#^ÀÑêíí}ðà!2™ìì¼ñÖ­Ûx´-[<üý,,,Gíà`Ÿœœü9ú™‡ß~øaûÎAS§NGÍ›7oÛ¶­¼eÁ¶m[}||q‡¬_¿îÁƒì=p˜9–‡÷ø<ôÖ¯_tâÄqÂÒ×îÚ×fú‚rxTÿywõðp?}ú´¿@kk«ŒŒÌ”)V119ö1>»"´×PFÀÓs‡··³))éöÒ¥«øùÄ ³§§üø¹SX˜›››Åüév¼qÎêëâSÑMM _²‹jkkðàŠ¡æ·²²² þø#¼ €6øo,ü,Oœú_ÕUñÿPŽâXeæ #""`†®¾¨+N\1Düæêº¹««+,,ÂÚÚzØ7 ô:p´0”A ð ƒ ®B~SQ7kÖìööö™3gº¹¹ûF^®€6†½ „5”pÅ ®>µßV¬°[±ÂîËièuà h#`Ø ÊáÙoäå)µµ5üÛa€Wà7ð¸€6A ý†ßª}™‡ à ðx\@ (ÿáðáŸÂÃÃ}||\\6 ä»¡am¸üÞWÐFJ„B ãÌ™3»ví:vìØÆN‚‚‚ ({%/>‰®¿÷À´‚òo’“ïKKK¯[·6!!áþýû3fÌÀí>>>ׯßwrr"ªÆÍþ倿ŠŽ}}C„¸üÞWÐF㱆š üÌf¶OŸ>µfÍj c¬]»úÔ©SÓ§OÃí{÷î£Ñ>LÃ0lûöˆªq³9èéêéýÓó‚‚‚ :„a˜««ëÝ»wñ#;::zzzŽ7î?þpqq)))A…„„¼xñâàÁƒâââQQQÁÁÁ!)))< 3ìö“'O^¸p!..ðիW¯^½zÍš5¡)S¦Ì™3ÇÅÅðŸ~úéöíÛ))),90¬­­ñ*lÞ¼Y__?00!äïï_^^~àÀ ÃÜÜÜîܹÃ^$fW}êEàðÞÀº"&&æÈ‘#—.]ÒÒÒbŽÃm”àf···ÿá‡455«««½½½ÉdòÁƒYFÁH‹JJJŠŠŠº{÷n¯çs$äg€urrÚ¾}»ººzccãÁƒ333¯_¿>€efn#–ÂÄÆÆfff^¸p7úùù•••½8í@¥¤&ßLJ¼v÷râí„Ë7½xý¯‰g/\>sîâɸøŸOÿrìĹ#Çã=}0%5™=9¼‡øèèèfÑyì?Gû©S§ÒÓÓÿúë/ ÃV¯^·fÍ„ÐöíÛI$RNNBÈÇÇçܹs+W®WÀ0&&&ÆÅÅåìÙ³mmmQQQW®\á1Jp³ósµ9i™=z´ººšÇ•0Ÿ#!;ëׯ %,‡vuurs3ãããØ7>>.77“e:“N§»¸¸P(--­HJJâöß~ûÍÌÌŒL&?þäÉ“¸±³³Ó××W]]}ܸq‡Â0L’ ælY~r³ûí·)))x8%%ÅÚÚ+**677ãᦦ&YIJJîß¿_SS“B¡lÞ¼™N§ãöööö7²W €!N[[ÛØ±cyÜì<ŽtbÓ`¤%èìì466~ýú5 ò9²Ä,**244looçVÎöövÂoܪÃoyûíÛ·“&M***bß NwwwWVVVVVÞºu+1³ä˜˜øí·ß–™3g^½z•ÈäåË—Ë—/WTT;vì¢E‹êêê8žhØOFx´'NŒ?žL&O™2%''çK;p˜Oý‡ŽíÃ0¬º¦²òÕËòªÒ²Šâ’—…E¥yÅ9¹…YÙù/2sŸ½È~òøYú¡cûX¡§ç޹åÍ€¿Ïý¯O-N£Ñ²²²RSSSSS »³³³OUUÕ­[·^¼xA\çå奤¤dddTUUWÛÍÍÍý»]PP0qâD<;;;555==®Và3ºý=iÒ$Þ£7;BˆJ¥ÊÉÉMš4iÿþý===ü<™'>>ÞÜÜœB¡|üHÈ‚¿¿¿··7‰D⸵­­íÈ‘#S§Nýxßò@BB"::zãÆ]]],›BBBjjjž={öôéÓòòòððpŽ9ØÚÚ677§¥¥!„RSSß¾};oÞãÆ YYY\\þöÛo¸D"ÕÖÖ644())8p7þòË/‰‰‰ŠŠŠøñüñ{omm%nvˆ‹‹·´´àáo¿ývçλwïF´¶¶òÎ'""‚L&ã "„.]ºtëÖ-„PXXØÝ»w¡¹`è“Ï{”àfÇ5GOOOAAOCCCXXËíìÁH‹Ã`08pöìÙ ™÷›’’RSSóý÷ßsÌ¿‡N&“ïܹÞ¶¯¾å……Å”)SÂÃÃwîÜÉl¿xñâ7ð¡822rÁ‚AAAìÉÝÜÜöïß?yòäèèh777AÁ¦Ã>|HœƒvîÜ9~üxŽeàv2úñÇ¥¥¥BnnnQQQp4õx(èóC9õõõãÆÃÃD!tæÌ™û÷ïO›6ÍÀÀàæÍ›¸±®®NMMm»ì¨Q£±ØÒÒ"..އ÷íÛ×Üܬ««kjjª¢¢‚< J®ªªZ__OTMYYs[ À"--mÕªU'NœÐÐÐà=Jp³ã éëë?~üüùóü<ŸöÚµkêêêZZZ2²LO3k/fš››«ªªœœœ\]]?Þ·½âëë{ÿþýG1TUUñ°ššZCC·äË—/ÏËË;þ|^^‹D~úôéœ9s¥¤¤ššš8æÀídDœ,FŽÙÝÝ JV(üOCCsÙ²e……E JPÊÉÉUTTàáÊÊJÂnll|áÂ…’’’èèèmÛ>,ì;vlyyùvYâ~zFF†ŽŽ–‘‘‰‹‹«©©),,TTT´²²âQ…ŠŠ 999’9m_}ËÚÚÚ6lðôô$,‹-òóóÇbooï%K–ðHîææF£ÑØçSét:‰D­¨¨`^)))Y\\Ì\ò=_ Äâââ7n,..Ævww‡……©«klÚäÒÒÒò÷3ÂJgΜ177WS£NŸ>ãÑ£G VVSÔÔ¨³fYççç¤íرC[[G[[ÇÛÛ›N§777oøæÍb×ÍÍÍFFÞ¼yÃm_Ÿ» ôññ‘––600˜qrr"–!„dee'Nœhll¬¨¨Ø¿Ëø˜˜%%¥… ZZZvéÓ§ãçd| žž;¼½½˜MII·—.]ÕÜÜØkbüá¥S.ü²kW¼üX111ooÏ;wîñ#"Â)yIÔÁÁ¾­­-44ÿéää˜GKLL Ü)###++»kWЕ+—1 Û°aýéÓqÝÝ݆uuu={ÎÁÁžÇ¾àK9ÿ YYYÄZIøúú¾y󦾾> `þüù0Ü@¯ ç×UT¼Ä0¬²²ÒÓÓ+''Y@]]Ýôéßqˆòã¯?E‰ŠŠ²üÄÅ"BˆFkTVVÆÃ***4Z#†aêêêêêêwïÞ={öÍ›·¾úʘB¡`Æc_À§ÇÆÆ7o¨Tª‰‰I[[Ûܹs‰[á|¡‚’}DÏ›gknn6zôh2™œ˜xyìØ±ìåg©ÇŸ222•••øk***eddpû† ëб±±9qâDHH0nä±/àÓS[[K„yÜïvpppppwÿ ÿ‡r0 ÿapõêU ÃV­ZéååS^^ÞÕÕUPPàææÎŸÇO[ÛyÁÁ¡4­¡´kÁ‚ù¸ÝÊʲ©©)..nÔ¨Qzzz¸‘Û¾à–7Éá?C‰óý÷Ë¢¢ö®X±bãF'„ÐÊ•«ëëë©Tª««KŸf(øaûÎAS§NGÍ›7oÛ¶­D´õë×8qœ°ðØJf¥Å‚+--b.˜¥ådKËÉÆ@ÎÎNÎÎN,åg‰Ïí§ˆˆpDDhDD({õíì¾·³ûžÙÂm_ (Y%̺€ ü ” (?˜¡A ‚A |¶‚ž\AÙ_òò²À‰ (û‰¾¾an.Êa‚¾¾aÿJIIñø’!GRSS_½zÕׄ0Pôãü Š ¤P)Ep"ÐW###çÎ c ÁÀ§'77wÖ¬YÌ:‡]ÿñoÇ0,((HUUUMMm×®]Ÿ5 ÉäääE‹ÉËË«««;;;766ò ¸Ù¥þ Ç}}‚´***Ü®„û7âá466êëë3'¿wïÞܹsååå©Tª½½}MM ÿ¾}ûö­½½½¢¢¢††FDD?—ñK–,a)7GqËÁÌÌŒ9 ÃLMM{ͤO{@PŸ%ÂÂÂÌ:£Ôë“ýÔ©Séééýõ×ÇSSSãââÀÏ0¼‰‰‰qqq)++û믿ƌãààÀ{4à1J43Áq_ƒ‘–9addäºuë8&ÿÈ/""býúõ̖Ç»ººeeeikk¯]»–ßnß¾D"åää êk*+_½ä93÷Ù‹ì'Ÿ¥:¶E:zzîà0CÙÜÜÚàA}}ý¸qãð0@9s&**jÏž=$‰xˆ»®®NMMí”jÔ¨Q­­­£GFµ´´ˆ‹‹óŽO”\UU¿E‚WMYYs[ À"--ÍÅÅåôéÓ¼GÞ£„¾¾þñãÇMLLÂÂÂú4Â|LÚk×®©««kii øˆçïï"(Èym[sssKKË‘#G\]]oݺŧo÷íÛ·eË]]] {{{iii> ìëëkmm=kÖ,333ÂØÐРªªŠ‡ÕÔÔ¸%_¾|yDDÄùóçóòòXÖ <}útçΙ™™mmm!Ž9p;U9rdww7Pýž¡„5”@Ÿ‘““«¨¨ÀÕ••„ÝØØøÂ… %%%ÑÑÑÛ¶}Xl1vìØòòòOP*/^àጌ Þñ‰*TTTÈÉÉáa2™\UUÅ^5†&—/_vtt<}úô„ z ø% FÿF˜þ¥Ý¿?óŠÀñrrr.\H¬#d_...îêêúüùsþ}+##WSSSXX¨¨¨heeÅgEDDŽ9âááÑÒÒBÉd21———“ÉdÉ7mÚäîîîââ"""¼iíÚµùùùMMMø-2Ž9|²“Ñð k(AP}fÙ²e~~~4Í××—°;::wvv2ŒžžÜhggçååõúõëwïÞ ^©ìììBBBêêêjkkƒƒƒW¬XAlâøl¯¯/FkhhðññY¶ln\¼x±¿¿?^5???hkÒ“(11‰‰‰ÌЇÇhÀÍnooŸŸŸßÝÝ]^^îææ¶`ÁöÑc0Ò"„’’’H$’©©é`Œx,Ÿ œœ »»»ëêêBBBˆõ‘Ìi¹ùvóæÍÕÕÕt:ýæÍ›^^^ü—Y[[{Æ žžž„eÑ¢E~~~øPìíí½dÉÉÝÜÜh4ËF!:N"‘DEE+**ÜÝÝ »¤¤$ó“@Ÿìd4L®ã?²ÿñN2¼ôŸmÛ¶HHHlÞ¼ùîÝ»¸ÝÚÚÚÎή¢¢BCC#667zyyíܹÓÒÒÃ0æq„懯™G½>Ù×­[W^^ŽËëׯ_½z5ïZ˜››[XX´´´|÷ÝwÞÞÞ¸ÑÏÏÏÃïš³³3¬Î€¡Œ¿¿?BˆYUWW5ŠÛhÀÍnkkëèèXXXH&“¿ûî;Žjc0Ò"„öíÛçááÁ»š2â13gÎ{{ûÂÂB))©™3gž8q‚ßš˜˜Ìœ9óÝ»w¦¦¦çÎÓÔÔìS“999-]º”ø°cÇüé"\\öïºÂ××wíÚµŠŠŠîîîW®\ÁíÓ§OommÅ=ÆÏÉøçÊÓ|dVÞsI<=wx{óºÂÈÈȸxñRzzúë×5‚‚‚TªÚüùó7lX?b_bôÝ»w?ýtäÞ½ß^½z…’••ÕÔÔéôf‡m¯j«Œžk·.³G¶²˜Æ`0º:»?¸bñ¿^_Ù»( úî»ï6lX¯¨¨ÔÔÔý¿ˆˆÈ—/ËÃÃC{MÛÔÔ´t鲺ºúÐÐo¿‰aسgÏNž<Í sMMMáá?ýt¤¹¹9,,!äééõÇ)GÆZYYäÿôÓÞ»+--îì쬨¨8þÂöí?$&^ýITTz €ÊÊÊ"ÖJòÀ×××ÓÓ³³³3 `þüùà7àK£3”½¯¡¼z5qݺµT*UTT„B¡íD]½z•Ÿíßýòe¹··×‚óÅÄÄF5eÊbz’iii?„Ð7q þ&U##CQQ##£#G~êu"""ššš;7ntzðàÁÿ¼vûĉ“Tª†††–‰‰™··ïû÷ïÓ)•ªÁ<]Ê12ðYcccü¼†T*ÕÄÄÄØØx̘1Ä­pør¨¨.{YYRZ^T\–_X’›W”SÐËýº>¯¡|òä BˆÏ÷¯&%ý޲±±æ3s–‡³F–èè Í™3wÆŒo&Nœ0}ú´±cÇò™ÕÊ•+މ½~ý†§çÜÒÞÞ~÷îíqãÆ]½zÕË˧»»kïÞ¨²²Ž·¼9F†öYS[[K„yÜïvpp >2?Œà¤ÿùb&qg§ºº:(h—   _37MMMèßÏØ³€ìk›››#"ö „æÍ›‹[öí‹ -))IHHHHHÞE<Ëüæ&ñ~A„ÐæÍ.x`Ñ¢E^^>ü‘Â#9·ÈÌÎö,]º Žú#(™•óKÎããッCétzxxØÌ™ß𓻬¬lMMMCCƒ¼¼<¡#™ï,3«L!!!yyygçÄ#oVVV÷îÝ©ªªzðàÏ}û~|óæÍÞ½ûø”uuu„¬D=~üdß¾}ùù­­­ø<(9*‘¿Ÿßà£%‹”lhhðòòþã”ñã ÂÂÂôõõøÌÝÆÆúäÉS ÝÜ\yDãý„µ²²òÊ•+ÌÍÍfΜõî¿kÏ;²µýð¹OWW7vüø1++Kƒ¡««Ïíݧ¼#Ãg„Xèý¡œ7nX[Ï~üø‰¿¿ßåË—XÔ$˳,,xx¸ëèè òÏ«‚øyq£„„ÄåËÿùÖ­Û‡ÿÔÝÝ-..®««c``ÀOáÄÄÄ"#£êëë»»»¥¥¥/^Ä{í¦ºº¦°°°”””¦¦fTÔžï¾[H¼‡rÿþ}AA»W®\%''·i“3s*77ט˜ÃÓ¦M'*Å#2ÐgAÉ[8ö*+I$’«ëfW×ÍýHþóÏÇù¬F¯Å˜Vc€ \"""wòó"L`h’››;kÖ,f‡Ã®ÿÄNðé%,ƒGrrò¢E‹äååÕÕÕ?|É Ã°   UUU55µ]»vŸ:ãf—ú7÷õ Òª¨¨p»fÏœ[žì466êëë3'¿wïÞܹsååå©Tª½½}MM {*nqø,3sá—,YÂR¼>]ÛKII™™™1ç€a˜©©i¯™À Âç*(ñOæP©†«W¯))DµWPP0}ú´Oé&*Ucݺ ,‡ÄЙ"%œod4ÑÕÕ½±±iˆw»ŽŽaaafÇQê ”€aILLŒ‹‹KYYÙ_ý5ḟo>uêTzzú_ýõðáÃÔÔÔ¸¸8Þvbôà1† FZæ„‘‘‘ëÖ­ãv%Ìž3ò°MÁD¬_¿žÙrøðaWW×¢¢¢¬¬,mmíµkײ§â‡Ï23#--}üøñih11±Û·o?oݺ5jÔ(èÿÃVP"„ÊÊJÊÊJ>L355ݲeëຳ³“YŽ|¤¤$ãâÎÙ†ÄÿûïI#G’üýý[nÞ¼YAAA[[ûÿûaOJJ277—““344 àüùó¼íü ƒš¶««+66vÓ¦Mü×GžÌWqqñ½{÷\]]™Ó&&&Ξ=[BBB\\ÜÝÝ=++‹=-8}-óÞ½{;V\\Ìñlâáá¹ IDAT¡¢¢¢¢¢²mÛ6b(fÁÃÃ#::šø½eËâgyy¹’’’¼¼üâÅ‹ëëë9žhØOF8'Ož444”““›:ujnn.VCBPâŒ=ÚÑѱ¤¤ÿÙÓÓ³gOÔ¤I&ººúîî[Z[[q{ee•££“¡®®ÞÚµëi4ÚßÝ«ÓÓÓKOÏÀÄÄìèÑcìùãó‚Ä7Ä©TŸþÙÂÂR]]ך>>~†† 'øûttt©Î;?uêt=üÓä—/_ùæ›outôæÍ³-,,dÉŸÝ»w9WVVÆQàrÛéù󬬦jkëÚÚÎ/((àmçæ“””këÙÚÚºVVS/\ø…‡óeeeüýýþü3uP;Mxx8FËÊÊJMMMMýg_ÎÎÎ>>>UUU·nÝzñâqqœ———’’’‘‘QUU…þ¾ïüŸÏ&¦§§§¥¥eddÔÖÖFDDàÆÐÐÐ÷ïßggg§¦¦¦§§ÃŸ ÉÉÉ“&MÂÃ'NÄÃ&L ÆXnv„•J•““›4iÒþýû{zzØó¤´8ñññæææ …ÿúöš'Ž¿¿¿··7‰D⸵­­íÈ‘#S§Nå±#nqø/³„„DttôÆ»ººX6…„„ÔÔÔ<{öìéÓ§åååááás°µµmnnNKKC¥¦¦¾}ûvÞ¼yÄV;;;—’’’¢¢"---Ž'ö“q†½sçNyyùܹsÝÝÝáPB‚òýû÷GÕÕÕÅ>üSNNîõëמ>}L"‰FDDâvGG§ 6<{öäñã¿44Ôwï&®<ÿüóÁ­[7=zÄq*Ž˜Ã-YYÙ7n\+--FíÝ»¯®®69ù÷ää¤ÊÊ*ækš´´´_~9Ÿ‘ñ|þ|Û ì““ïŸ={&#ã¹µµµ?B9,,tÛ¶íì‡Ϧ'$üš‘ñüÛo¿õööåmçæ“íÛwlÝê‘••ù믲²²{-*Å4B|||xx¸¬¬,™Lf>þI$RmmmCCƒ’’Òpã/¿ü²gÏEEEIIIB· """Èd²œœ\DDD||<1á*##C&“ÃÂÂ`€€Ï‚ìì쀀€ýû÷ã?[[[‰[¢âââ---¼íÍÍÍeee555§Nºÿ~@@‘3!G#-ƒÁ8pà@_¥ <‰ý¦¤¤ÔÔÔ|ÿý÷Ü&P>Ì<Œ³\çsŒÓ2[XXL™2…]/^¼x1<<Š###‰¡˜U© º¹¹áííææ&(øzyøð¡••‰D’عsgJJ ÇL¸Œ~üñG111777ö‰Xà¿”Ä2¾sçÎ//E"?}útΜ9ŠŠŠRRR MMœŸ"àv2"ôÃÈ‘#»»»á€‚²¬¬¤´´øþýd55Õ¼¼|¢ gΜ…kM3â)¼ŒŒŒï¿_®¯?žJÕÐÓ3 ®Šh4š²²&¼!“ÉLŠªQEåA@ì1- Á'ÿ™ò߇¶nÝ’ššúôéÓË8¾vʼŽvn>ù駘ÔÔÔùóNžluïÞo<Ôüôé3Z[ÛBCCµÓÈÉÉUTTàáÊÊJÂnll|áÂ…’’’èèèmÛ¶áÆ±cÇ–——Á®OT¡¢¢BNNŽèKÄ­æª04¹|ù²££ãéÓ§'L˜@uttˆU7:::¼íÌ0 vûà¥Ý¿?óŠ@>á§<999 .$Ö²/ wuu}þü9qŒÓ2‹ˆˆ9rÄÃÃy2•L&Cqyy9óÙœ=ù¦M›ÜÝÝ]\\DDD˜7­]»ÖÁÁ!??¿©©©ªªŠÛ=º!{2AÉqãT¢£÷ûû¼ÿï.¦áw¨qʼnÇtqq]½zÕ£G饥ÅÙÙ™D••­ªªÆÃÕÕ¯úZ"yUU•ŒŒÌÀVPDDdß¾½>>~Ä¥áî”›O ;úôéã°°ÿnj¾¬¬$++ãðáC²²²ƒÚi–-[æççרØH£Ñ|}ÿ¹ïèèX\\ÜÙÙÉ`0ˆ…Dvvv^^^¯_¿~÷îŸSÎÇgk|}}i4ZCCƒÏ²eËpãâÅ‹ýýýñªùùùÁC™˜˜˜ÀÀÀÄÄDf5‰;!!!uuuµµµÁÁÁ+V¬àm···ÏÏÏïîî.//wss[°`ûè1iBIII$ÉÔÔ´¯uç‘'±_–ÇωI ''§ÂÂÂîîºbí)sZqú]fmmí 6xzz–E‹ùùùáC±··÷’%Kx$wss£Ñhìó©t:D"‰ŠŠVTT0ß…—””d~h(œŒ@Pö™±cÇNš4éڵ롕+WøøøVTTvwwº»¸¦éèè%‰ˆˆVUU3/.\¸pAhhhSSSccSHHŸgÚæÍ›ÚØØD£5îÚ<¾m_sèõe@+W® Ú5€;åí“-[¶–••uvv2ÇË_>«Ã-ÜW|||¤¥¥ &OžlaaAØ­­­íììcccq£———–––¥¥¥‘‘‘‚‚7ÁÇñÑï²sÄÜÜÜÂÂÂÈȈL&{{{ãF???111¼j&&&Ÿþeð¿¿uu5þJBüRݺuæææ¦¦¦fff–––«W¯Æãs³ÛÚÚ:::R(”9s樫«GEE±ïk0Ò"„öíÛçááÑë%1ûÈÆ#Ï^™3g޽½=…B±²²jjj:qâDŸâðSfn899KŒB²²²'N466VTTìße|LLŒ¿¿¿’’ÒÂ… --- »‡‡ÇôéÓ ñs2>jJÑÓs‡··³))éöÒ¥«š›û*\˜WþùçŸQQ{¯]»Ê`0bcž?¡¾¾žJ¥ºººÌ;!ôûïÉ!!!¯^½¦P(NNŽ^TÞÙÙéïpýúÑ£G;8ØGDD²/jdÞË~;::vî ºuë®óví ÄçÆY¢ñøÉ²‰£ðõëí¹víúªªªÕÔÔüü|¦L™Â»xüøŠGænpV_ßPOÏpôuƒA&“ñ RRR½>c^\\¼téÒŒŒ &€aIBÂÙ™3gãáó—NovØöª¶ŠÁèaüCχ»:»?¸bñ¿^_9`‚f ?A™‘‘agg—ŸŸÏ[Púúúzzzvvvº»»kiiíÞ½:‚’· Þ¾lll{F¥RMLLÚÚÚæÎKÜ €aÏ“çû%ð¥P[[K„yÜïvpp >à_APýfªå q’„         `ýYXš;À‚²¦æUn.¼V~˜ ¯oH¡(‚è+%(ss³–.]Näåeåæf àS Jœ¾¾Aù/¸}æA ‚A yA k(@P~” ü f(åå)µµ5ÐÞ (‡9>/D¨ Š‹KuuõÆSµ±™}íÚ5Ü>ÔÊ9Ôø˜ %%Õ×$©©©ýHœ†@PræåËòÅ‹[XX¤¦þYP ‚òÊ~ÉãƒÚ0vÊÿ†½{÷nÚä¼víIII‘‰'ÆÆÆ‚éÌ™8SS3UU5kk›¼¼<ÜN§ÓwìØ¡­­£­­ãííM§ÓyÛ“““§OŸ¡ªªfjjw7vww‡……©«klÚäÒÒÒ‚’7¹¹¹³fÍbÖy8ìú{rrò¢E‹äååÕÕÕÙ÷ûé%,ƒ·£ð   UUU55µ]»vC7»Ô¿áv¦ì´***Ü®„Ù3ç–';úúúÌÉïÝ»7wî\yyy*•joo_SSÃZ~êËRø%K–°¯O×öRRRfffÌ9`fjjÚk&0ƒð9 JÆùKMM?ß–ã&„Pjꟗ/_ÊÍͶ¶žµcÇܾgÏžÚÚÚ?ÿLyðàŠŠŠ½{÷ñ¶{xlÙ¾}[~~îÅ‹ ™™™¸ñàÁƒÙÙÙ·oßÊÈx.**2t|Âçß'îvÿgï¼Ã¢8Þ¾@„‰ÒŽrGQª5¶€(5b‰H‘b‰Àq‡`XÀ$Æ.$XBŒ¢1ü4˜ƒA½ ÄC õ¬ôòûc“Í}¯,{ƒú~ž¹wgÞywvæÝ™ÙÙîîî &ðúyB]=±äéééÁÁÁµµµ………S¦Lñ÷÷‡ÛÞnDÝõ'Nœ((((,,¼}û6“É<}ú4¾kUDµ9c”–7aJJŠÐäB5ãä‡äää 6ðJ<RUUU\\ldd´~ýzâi‰”—EEÅ£GŽäBËÊÊ^½zû™““3iÒ$¨ÿ0B9†´··+**ŠÛ½;QMM•D"mÜXRRŠÊ³³³ccw*)))++ïÚ÷Ãñå$’Lss‡Ã¡PÔSR’Pafæw»vÅ©©©ÊÊÊFEE\»öóÛ=BÙÝݽyóf …bddôÅ_`òÜÜ\{{{ssó'N ÂÞÞ^:n`` ££“žžŽ=5|º%Î… æÏŸ/++«¬¬wûömœV…´´4CCC …ÒÝÝÊ»ºº‚‚‚°¢Á.ŒgDÝõ™™™ CUUUMM-&&&##_Nd”kLÓööö>|xÓ¦MÄËŽ£“·áª®®¾~ýzHHoÚììì?üpòäÉrrraaaÅÅÅÄÓƒ½{÷9r¤ººZho®¥¥¥¥¥µmÛ6¬)æ#<<<-- û™––¶eËì'›ÍöööÖÐÐPSSóòòjiiÚÑvF(Ç777WQQqqq)--…Û ÊÁÁÁA.·M”Ã$//†eddúúúÐ0‡ÃÕÔÔDÃZZZ_~èÐ×·nýæêºÄÖÖs›››çÎý@KKGKKgæLk‡óv;”III§¸¸˜Éd2™LLD£Ñêëësrrîß¿=à–••ååå±X¬úúzäŸygâO·ÃàÆÖÖÖøq òóóY,VSSSrr2*LLL|ñâŃ˜LfAA4ð¦À{×WTT̘1 [ZZVTTàËÑÓÓSQQ±¶¶NMMíïïÔ?FiQ²²²ìííÕÕÕ‰—wH( #**ŠD" =ÚÑÑqèÐ!±ÒY^>&Ožœ––¶qãÆÞÞ^¾C wïÞ-**b³ÙIIIB5¸»»···ççç#Âd2Ÿ={¶dÉ쨷·wpppMMMUU•¡¡!FÚÑvF(yyy×®]c³Ùnnnaaap+ ›·jÛ Ù³~úé’¯¯/‘|¢?•””?~¬££ƒ H]Ýc%%%|¹¹¹ùÑ£Gùõ×¼;v,\¸A2™œ}QUUuÚd,ÈÊʺ|ù²²²2ê\þòË/¨œD"555µ¶¶jhh8p~÷ÝwÙÙÙT*½Ÿ_Cö7·¨¨ÈÕÕ•J¥*((P(”¶¶6¡DuF¨7‰ Èĉûúúà†‡õÒ´33ÏÞºõ›‹Ë\ssKƒñᇋñÊO?Ý®¨¨èâ2wΜ¹êêêÛ¶mÅ—Ï›77 ÀÔtzRRÊþý{QáÆÖÖ³Ö¬Ygbb¾uá‚' ¿q¥ŠŠJ]]~üø1&·²²ÊÌ̬©©IKKÛ¶m*TUUe³Ù¯§6_¼x1 àäÉ“–––CFÆŠPWW§¢¢‚†Éd26Â[4Æ'BïzccclÕ ‹Å266Æ—ó"!!10 ä=űK›ššÊ»" DòSRRâáá­#\.''rïÞ½a¤Å)¯P¤¥¥:Î;˜J&“±¦˜Íf£C•¢’oÚ´),,,88XZZš÷ÐúõëýýýËËËÛÚÚêëëEõh¯³3‡rØå8z[YOO÷àÁ¯îß/*//¹t)ÛÍÍ•?|XÅ û)-=!99±¸ø~qñýÝ»ã'Lx_îîî–›{½²²ìÚµ+NNލPB ¼uëfEEiNÎO®® ž'üƽå½råJ:Îår9Ntt4&¨®®îééÀÖx{{GFF644<þ<&&fìªrzzzlllvv¶ 7)´)ŒŽŽæp8­­­4måÊ•¨ÐËË‹Á` E£ÓéÐ@ÀxFÔ]ïííÐÜÜÜÔÔ¿zõj|¹ŸŸ_yyy__›Í ]¶l™`ë1iÉÍÍ%‘H¶¶¶â–G'v^¾×±±éìÀÀÀÊÊʾ¾¾æææ„„ÞçC¦Å)ïùúúFDD`OOO:Ž6ÅQQQË—/ÇIÊápÇS»ººH$’ŒŒL]]ï"HyyyÞ7^[gåÛ0B ¼žJ¦¨¨hff6{ölL¾hÑ"ooo*•{øðaTihhèèèhaaA¡P„*ä}ÜðŒ œÁ`>>ööö¶¶¶vvvŽŽŽëÖ­Cã‹’»»»¨««»ººêëë ]H7iÙ·o_xx8~1…¶x8:‡ÄÕÕÕÏÏO]]ÝÉÉ©­­íرcÄÓ)/Ø#Abbb”••g̘aeeE¥R‡÷ŸžžÎ`0444<<<1yxxøÜ¹s1‹錀‘ ±#**’W”›{uÅŠµííÜ!£1[[›ÁŽo:­­Í¥¥ÅóçÈwqÏ;cjjnbbþ”q``€L&£[+(( ùŽyuuõŠ+X,Tà­äܹ3X×ÿWs½i²””¤¤¤”¤¤¤¤¤¤”àÿYù°”ªªÉ«'99å­zËp(..ÆÖJâÑÓÓ³téR° 8”À»ÂâŋѽðÑÓÓ³±±éèèpssæÂ‡¦¦&,Œ3ßíïïŸm€×ïP€À¡&eeÅ`Dp(‡‰©©yi)8”o ¦¦æ`^·C©®NUW§‚Þe$Á8”8”À›‰ð5”çÎÓÃt(W¬X v†ïP¶·sÁ.A` %0RÄMÂd2ÍÌ̆‘þÃþ ‡GÄÆÆ¦¤¤à|PÚ‡òAOÏ@OÏêÁk¦´´táÂ…¼~Š ÿG\~ýúu777555===??¿ÆÆFÁó¾~€±ãÆžžžjjjúúúAAA\îßëµãââttttuuwíÚ588ˆ/Wø_„žë5¤ÕÒÒõ$,¨\”NA¸\®©©)or"­¥¨r)/ŸžåË—óeO¬g{;;;^ ƒƒƒ¶¶¶C*„ñèPÞºukÃ?ssKCCãÙ³"""›ššˆ$äp8ÁÁ›§O·˜:ÕhÖ,w÷¥ÿ­“îã8¡»»{„ ¼~žPWO,ùÁƒCBBªªªŠ‹‹ŒŒÖ¯_v€·›ôôôàààÚÚÚÂÂÂ)S¦øûû£ò'NÞ¾}›Édž>}_޵*¢Úœ1JË›0%%ÅÇÇGhr¡šqòÃGrrò† †ÑZŠ*×ååCQQñèÑ£#¹Ð²²²W¯^Å~æääLš4 êÿéPž8qÒßßïÎßïÜùý£<Ο¿°qc‘„‘×®ýœšº¿¤äÁ·ß¡RÇÝwkkkjkk ˆënÞ¼™B¡}ñŘ<77×ÞÞ^EEÅÜÜüĉ¨°··—N§èè褧§cOŸn‰“ýá‡Nž®©©zø°zÕªåååH¥™W2¤Ú€€€'OgffÈËË[X˜£‡ ååå32Î46þuýúµå˽ˆÛsüÿee.-ýsð™6mÚÇÑpMM¼¼<655=|øp}}=od“ªª*> X’!…âÊÑ+5uêT,‡B“ÈËËcª««MLL°¢=zô ?|øPÔ©WϘ1£ººý©¨¨Øß߆ûûû•””ðå(}}}%%%îîî4Mðc—¶¿¿ßÚÚº²²¿Œ|;N”_ýÕÉÉ &Øšá·–øåÂ/¯ÐlÇÆÆ¢k=y…ÆÆÆ555h¸ªªjÚ´i¢4œ>±¦æï9å &ÄÇïZ¹r%‚ ÎÎsžvi³³³¿ÿþûÌÌÌ!'Ðy›#"ùqrrJHH@ …6˜8­%‘r‰’ ÍvOOÏ¢E‹’’’ììì0!_“‹5Å‚zzz,-- FBB‹Å’––Æ”íܹóÏ?ÿìèè@DBB¢­­M°ÈD:#QÝÊ[̹sg0Oï¯æz9Òd))III)IIIIII))4Àÿ³òa)UU“WOrrÊ{¢< 4À»ÉymmÍË—/Ož<µoßþ°°-çÏg ™Q''§ëׯÕ××ߺõÛ¾}ûŸ>}ºwï>Ô¡D_ë™3g.ù¯¿þ"X~t…„’’ö¿¹¹™7æìR(ê‚444ŽŠZUUUÔ-F§ìQá¶m[?ÿ|ÏGyIII™šš|úévGGGäÍÿàÎç7UTTêêêôôôyüø1&·²²BÛÄÜÜÜÍ›7£¥ªª*›Í60x}ïBÉÉÉ…„„ìÝ»?V„ºº:TH&“ëëëuttøŠÀøäâÅ‹ ###ó&166¾ÿ¾³³3‚ ,ËØØ_΋„„¶Ø‰—±K›ššúù矋[p"ù)))ñððÀñ–¶–¢Ê%J.iiéC‡}òÉ'ÿ÷ÿ‡ Éd2Ö³Ùl2™Œ“|Ó¦MaaaqqqÒÒÒ¼‡Ö¯_?þü÷ßÿÕ«Wèp’ ¯¿3z×¹†²½+øÉ999ô5º’’âçÐÔÔ\³f5ê€>þ¢‹EîÜù}-¦¶¶¦¦¦Š BeeeAÐ"Ðÿ¨«‡ÑÐÐðO s+G®V(6ø°X÷~øáÂöíÛŠ‹lݺ׀oîN‘W®\I§Ó¹\.‡Ã‰ŽŽÆäÕÕÕ===ØÂooïÈÈȆ††çÏŸÇÄÄŒ]U ¬¬¬ìëëknnNHHÀVS!"–™GGGs8œÖÖV†>ä âååÅ`0ТÑéth `<“žž›ÍëM¢ÍNBBBsssSSS||üêÕ«ñå~~~ååå}}}l6;44tÙ²e‚­ÇX¤E¿I$’­­­¸eÇщ—ïulÌ›$ÒZŠ*Ny‡ÄÈÈÈ××7""“xzzÒét´)ŽŠŠZ¾|9NòÐÐP‡Ã÷‚‚ ]]]$IFF¦®®Žw¤¼¼<ï›@¯­3‡??ÿÛ·ïîî~ùòå‘#G±±±AáoÄãí½úÊ•+§§§§ªª A''GôÏzARR>þüyGGÇo¿ýæã㋹­‚<~\/J­§çG‚|óÍÑîîî#G¾EdÕªy#$%%·µµµ··£/[øúÒ<¤Z¡o®¨¨˜6m𽽂 22¤·¾ÒÐh4EEE33³Ù³g;88`òE‹y{{S©ÔØØØÃ‡£ÂÈÈHCCCGGG QË$°ñ„nxFPîêêêçç§®®îääÔÖÖvìØ1üRØÛÛ;88XXXÉ䨨(TH§ÓeeeÑ¢ÙØØðîmÀxƒÁ`EE-JQQ=š™ùÝ©S§=z$--=sæL__'''AŽ9šž~ðùóç¢4÷öö¦¦¦ýðC6‡ÃQQQYµêãÍ›ƒ%%%±üìÙóù×_ª¯¯'“ɾ¾|}ÿÞ‚KP3oþ‡T‹e†÷ç7>üçŸÅ¦¦¦ÑÑQVVVoAÍ@«Ð5”o(d2{&²P¦ººzÅŠ, š à­d×Pr(ß †|¡xgJ‹åíí]^^ŽïPFGGGDDôôô„……~öÙgPp(ñJø–7ð®°xñb"{ŒéééÙØØXYYM™2› ‡÷ÀÀ;ï÷Bqæ»ýýý±¸ð.:”0Ù ðš)oJJJà]d^ÊÉͽŠÛâh|¢¨¨ÜÖÆªðŸ9”‚¬X±VÔ¡sç΀•ÞbÆjÊ›ïãËBQTTa„×Ã8ÉÀ;äPéJàP q"Q?ó&q>F‡¢¢òñã',,f¨ªªÏ™3·´´ùg\PQQ ìïïÿì³xCC# EÃÏ/àåË—Xòƒš™™+)‘ÑŸ|addL¥j†††uw÷ Ñº»»Ã÷jkëjkënÛ¶“c°Ùu«W¯ÑÔÔVW§xy­hii+àPŽŽ[)®7‰’—wëêÕ+=tuu Û‚ ú¢L[{cfÿþTëÏ_½YUU1q").n–üþ}V^ÞM.·ýYPPÀd2Y¬{MMÍÉÉɨ0!!±©©±¨è¢¢;lv]RR_V¯^³iÓ¦êêÊÊÊ ##Ãèhº¸Ùx7Æ€4“É433ƒ‘làÍê¿€×äPÏ›Ddÿþ½ EVV644äÁƒBãœ9s6%%‰B¡ÈÉÉÅÆîüé§ËØ¡„„x%%%ìgRR™¬L&“““wŸ;w^¸pq÷îݨüóÏ“19FAÓÉÉ‘D"Mž<9&†‘—wKÜl‰MIIF=¶Þ ‡rx^‚¢¢"˜8qb__ŸÐ8¶¶öè쳑Ñ4çß½~TUUycjkkýÐFg®immÕÑÑFÃ:::­­­|ú‹ŠŠÜÜ–hhh)**S©šmmmâf HiiéÂ… yý<¡oq‰+G„ËåšššŠr_¿ ÀØqãÆ OOO555}}ý   .—‹ÊãââttttuuwíÚ588ˆ/Wø_„žë5¤ÕÒÒõ$,¨\”Nâ­"~k9’òòe~ùòå|ÙëÙ^AAÁÎÎŽWÃàà ­­íJ`áÍv(Ç´ÃVUU--}€Î>·µq° nAêêc4L&“yäud2™/•¯¿¿_YY —Ûúø1[ÔýI<€(º»»'L˜À[m„Öqå‚$''oذ, ïéééÁÁÁµµµ………S¦Lñ÷÷Gå'Nœ((((,,¼}û6“É<}ú4¾kUpÚ–±HË›0%%ÅÇÇGTÇ*¨'?[EüÖr$å0:zôèH.´¬¬ìÕ«ÿîx““3iÒ$¨ÿo­CI¼nG^^¾ººû¹aƒÏ–-[=b÷õõ•••ùùˆJH§Ó9nk+'::zÅŠå¨ð£>b0¨<**ÚËË“/UWW—Œ IF†TW÷]Ç9Âl¼ÅîàæÍ›)Š‘‘Ñ_|ÉsssíííUTTÌÍÍOœ8 {{{étºŽŽNzz:öÔHðéV,ª««¯_¿2ä«‚‚BZZš¡¡!…B éîîÆê@PPV4xÀ€ñÌ… æÏŸ/++«¬¬wûömTž™™É`0TUUÕÔÔbbb222ðå8`À˜¦ííí=|øð¦M›ˆ—G'oÃ%ªU%IyE±wïÞ#GŽðv£¼½Ixx¸–––––Ö¶mÛ°¦˜ððð´´4ìgZZÚ–-ÿöÑl6ÛÛÛ[CCCMMÍËË –ìh;#”ãÇ›››«¨¨¸¸¸ ïÿ¥CyîÜQ#Q:oÞ|ìõê-[Âíìì<<>¢R5ƒÜÝÝD%´··Ÿ={¶¥å 2™‰ cbèJJÊVV3gΜE¡Pèôh¾T_}õeLLŒ¦¦ÖGy::Îy6ÞV’’’8Nqq1“Éd2™˜<((ˆF£Õ××çääÜ¿{.++ËËËc±XõõõÈ?ÃØcñÂ`0¢¢¢H$‘Èùùù,«©© {s+11ñÅ‹<`2™Ð@À›Â7¬­­ÑpEEÅŒ3а¥¥eEE¾A===kkëÔÔÔþþ~Aýc”%++ËÞÞ^]]xy‡Ô‰ß*ÙZޤ¼|Lž<9--mãÆ½½½|‡ïÞ½[TTÄf³_–EqwwoooÏÏÏG„Éd>{ölÉ’%ØQooïàààšššªª*CCC&´£ìŒPòòò®]»Æf³ÝÜÜÂÂÂàV6;0—ëŸq¦«+V¬mo羡E‚O)Ž h58w©¹‰‰9ï!“Ë—/ëéé!òðáÃY³f¡w¬™™YXX˜«««††ÙÔÔ4;;{êÔ©|Á‚Þ¤P!qy^^^LL̯¿þ*))9d…»wï¢E¨©©Y¶lú`jbb’““£££ƒ HmmíÌ™3aÙ%Œ‚ ÕÕÕË–-+++ªáÔ©S—.]:þüòåË—-[¶nÝ:¡9ïììœ>}zMM ÅDuF>DßâèììÔÒÒ|¿âíæÜ¹3Ø÷±ÿj®—#M–’’”””’””””””’Bü?+–RU5yõ$'§HBKˆKKK‹¶ö߯7aAN:uóæÍ9s昙™]¹r677ëê꾆\1Œøøx´ù#–sìÍ­––MÍ¿oQ äWäçç¯]»öرc¨7‰ ȤI“^½z…†_¾|)''‡/G‘’’255=zô¨ÐéݱK{éÒ%}}}|oR\ø­"‘Ör$åJttôÍ›7ÿýw^akk+ú ˆ®®.Ž3·jÕª²²²ŒŒŒ²²²?þ˜÷PQQ‘««+•JUPP P(¢^¨Õy' 8”€Ø¨¨¨ÔÕÕ¡áÇcr++«ÌÌÌššš´´´mÛ¶¡BUUU6›ýrURRâáá!¸Á¾(°"ÔÕÕñ¾¹…M…ð €ñÉÅ‹NžŒ ### -,,(ŠP…¼ʼΟ¸r±°··wpp°°° “ÉQQQ¨N§ËÊÊ¢E³±±áÝÛ€ñƒÁxòä º%! :Këããcoookkkggçèè¸nÝ:4¾(¹»»{@@€ººº«««¾¾þž={Ï5iÙ·o_xx8~1…¶x8:G…‘”‡ÀÀ@l‰‚ 111ÊÊÊ3f̰²²¢R©Ã{ŒOOOg0ŽŽŽ˜<<<|îܹ˜ÅˆtFÀHx _ÊFœ—rÞPÈd2ºõ±¨uè¼TWW¯X±‚ÅbAeÞJज़ââbl­$ÑÑÑOŸ>mii‰‰‰Yºt)Ø †Jà]añâÅDöÓÓÓ³±±±²²š2e 6ï €w„¦¦&,Œ3ßíïï}À "À%0"Fa„27÷*~l½'¥pV¬X+êÐ?çý_ßo ÈXMyÙ PQQYQQYI‰¬­­ëâ2g×®Ï8œáïU¤¨¨Ì ˜79*ÚÀ¡Û›$î·q¹­åå¥_|ñEgg§³³ |ïàu(¿ :äÆÑ²²²æÉÉIŸ|²n÷î$TØÝݾU[[W[[wÛ¶íÝÝ=¨\QQùøñ3TUÕçÌ™[ZZÊ« PDÇ>ùÎ2sæ¬ŠŠ 4üÝwߣòòò™3g¡IÃó&QQQÁÒ^¸pq÷îÝd²2™LþüóäsçÎcÑöïßK¡PdeeCCCøà:=fÅŠå‚,_î‰- $¨mß-[¶>zÄîëë+++óó €:Dü¥~ &“iff6Œ„ðö_€(Fgcóaï^®¨¨,!!!++«««3wîÜß~ËÃF"cbè;vDZYÍDK:=š Î°°Ðyóæ¿zõJè{9‰‰»=<<ñòòŠÝ…- $¨mË–ð¾ððø¨©©iêÔ©Ÿ~º êÐ0ˆMIIqss{ýmÇÑD""bGTT$¯(7÷êŠkÛÛ¹`w´œ;wÆÔÔÜÄÄ|t½455µúúú &ð=#òéKÎ÷  Ž#¼õܸq㫯¾*((˜4iÒ‚ Ñ+wíÚuâÄ Ÿ;wJHHàȉ´¯!íûï¿/t'f¡-ž(‚p¹\gg熆¾¼‰’ãŸ÷úõë¸{÷®¬¬ìܹsÔÕÕñ;ˆ>øàܹs¼Ù«×PPP022º}û6¦appÐÎήªª _ ‘³À(ùsg°ïcÿÕ\/Gš,%%)))%))))))%…øV>,¥ªjòêINN‘„& xýtwwcÞ$"z½Äðä#Y}ÀDzzzpppmmmaaá”)SüýýQù‰' oß¾Íd2OŸ>/'ÒzŒEZÞ„)))>>>B“ ÕŒ“>’““7lØ@\ŽÞƒ†„„TUU­_¿~Èˤ¨¨xôèÑ‘\hYYÙ«W¯b?srr&Mšõ¼%0wpóæÍ ÅÈÈè‹/¾Àä¹¹¹ööö***æææ'Nœ@…½½½t:ÝÀÀ@GG'=={ê%òqÎQ;£‚‚BZZš¡¡!…B éîîFå]]]AAAXÑ`y Œg.\¸0þ|YYYee常¸Û·o£òÌÌLƒ¡ªªª¦¦ƒîé#'ÒhŒiÚÞÞÞÇoÚ´‰xÙqtò6\ÕÕÕׯ_ áK.J>d£—ýá‡Nž}ºÐ<ˆêŒöïßnnºgϸ›† ŒPbÓÒÒ¢­ý÷žóXAS§NݼysΜ9fffW®\A…ÍÍͺºº¯-oRRR¦¦¦GrB Ë¹ŽŽ:E‚MSóï…ÆZZZp­`ü“ŸŸ¿víÚcÇŽ ’I“&½zõ ¿|ùRNN_N¤õ»´—.]Ò××744«Ôø:Q F||<¯ï…/ë¾¾¾>00PpÒ\ÑÑÑ7oÞüý÷ßy…­­­:::hXWW—÷ &|¬Zµª¬¬,##£¬¬ŒÏ.**ruu¥R©  ¥­­M¨QÑð>•€C Œ***uuuh˜÷D++«ÌÌÌššš´´´mÛþÞPIUU•Íf¿æJHH àÇÁŠPWW§¢¢‚†Éd26"ôuKÆ/^ 8yò¤¥¥%&466ÆVݰX,ccc|9‘ÖcìÒ¦¦¦ò®$‘ü”””xxx`ë±Õ„¢äb!''rïÞ=‚ñ¥¥¥:Î;˜J&“±¦˜Ífã|ÁDZZzÓ¦MaaaÁÁÁÒÒÒ¼‡Ö¯_ïïï_^^ÞÖÖV__?88(TÃÒC x¬\¹’N§s¹\‡ýïþ ÕÕÕ===ØB"ooïÈÈȆ††çÏŸÇÄÄŒ]®üüüÊËËûúúØlvhhè²e˰CB›Ëèèh‡ÓÚÚJ£ÑV®\‰ ½¼¼ Z4:ׯ3ééé±±±ÙÙÙ¼Þ$Úì$$$477755ÅÇǯ^½_N¤õ‹´‚äææ’H$[[[qËŽ£“w»Þ×ϱélQr"Îe```eee___sssBB¶n•FFF¾¾¾˜ÄÓÓ“N§£MqTTÔòåËq’‡††r8Á1Ñ®®.‰$##SWWÇ»’ï»$¯­3‡ˆB£ÑÍÌÌfÏžíàà€É-ZäííM¥Rccc>Œ ### -,,(ŠP…¼ʼ͙Xrww÷€€uuuWWW}}ý!—ÂØÛÛ;88XXXÉ䨨(TH§ÓeeeÑ¢ÙØØðîmÀxƒÁ`ÑÓ3¨­­ZõÖS\\Œ­•Ä!:::SL IDAT""¢§§'&&féÒ¥`7’ÑÙ6hÅŠµ8Î/X\áñÀâŋѽ†²ªžMGG‡››6À˜;”Ãö‡‘˜4i’––¦³³³ŸŸŸ’’⽫·ØÍâ+x“bÑÔÔ„…qæ»ýýý±¸0ÞJÌ%êìì|ø°öÂ… nnKΟÏÒÐЀ ð¦0.Þòž8q¢™™ilìÎU«>Þ¿?öôôÐhtssKssK#¦»»•ëéddd:9¹Msw_Ê÷½)tÔSOÏ ðÑÛÛ›°{Ö,KK«o¿ý“ Uøøq}@@ ™™ù´i&ë×oàp8øèîîÞ¾ýS3»o¾9‚e ¿¿ÿóÏ÷X[ÛL›f¶ÛŠ–/ÛgÏf¸¸Ì566Y´èÃ;wþ¸xñ‡>X`ll²d‰{ee¥Ð¢ñ†LN0'o¤C‰ññÇ+™Ì|4¼wï¾ææ¦7þïÆÜÇëy? ŸŸ_pîÜ÷,Ö½ DEEój@‡SSS›8qb``@YY¯+‰ÏÅ‹?ìÚ«®®>eÊ”;cðþüóU{{;™÷ßÇŽO nãÇÿé§Ët:]QQQYY‰Á``‘³²ÎÅÅíTSS›4iRDÄŽŸ¾.4o‰‰ êêê$ÉÏÏ·££#>~ú3 À¿¤¤dÈ¢IN0'bñÞ¸ÊMkkë”)SÐ0—ËÕÒú÷«Êèö(Ø>¥$I¬/o¶¶¶ ý@³P…,+))¹¤¤´³³A üø‡Jý{¯T *¹¹¹yþü…ØO^=¢ò0Œ2IN0'o°Cùý÷Y³gÿýå%%¥úú'::Ú‚Ô××+))\?ú¥f¡Ÿ‡$88$::ÊÅÅENN®££cút üøJJJýÕ€:ÁOžüÅ{Ò‹Ï«ªªþçæ?9àmb\Lywuu•””~öY|fæw[·nE…K–,ILLärÛ8î®]ñK—ºÔ6eÊ”ÚÚZ¡‡¼¼<ãâ>kjjzñâÅîÝIøzº»»edHÒÒ2õõOøVj ÅÝÝ}÷îÝmmm\n[bb"&_³f5]W÷¸¯¯¯²²2,l˰ …S4"ŒbN0Fg„rØ»—ëéHHHLœ8Q[[ËÉÉ)'ç²²²2zhÇŽí;wÆÍ™3u.·oßFPçÆK—zttt®ª MNNqu]288‚¯çóÏS6oQWW ¸rå ~üíÛ·Òhѳg;½ÿþû6øÜºõ* Úxø°ÄÚµëZZZôôôBB‚‡mgœ¢as€ßòjkk7lðËË»ùæáíû–7¼Œâ·¼%Áš£HBBâ³gÏ8ÎîÝI‹/ƒð.åh¢­­½`ÁÂ9sæMžÆ¥K—°ý¶ž?N£Ñ,,,Èd²žžÞ'Ÿ|òÛo¿‚¦§§gccsùòe"Î=ñ‹ËWX¡ kjj<==) …Bñôô¬©©Á7 N…­ )Ê€ ˆe=œê4ä- êr‹²°¸ñq®ÔV¢L188§£££««»k×®ÁÁA|9‘ öÒ Ý&Y”éDéFë!VžŸ={æççG¥R ’““G>¢6N†ÜF1 vvv¼WdppÐÖÖöM18”À›Dww÷„ xý<¡®žXòššš5kÖøøøÔÔÔüòË/………¯¿\ ©ªª*..622Z¿~=*OOO®­­-,,œ2eŠ¿¿¿Ðä¢Ê‹ Hrrò† #£¤¤¤øøøàçA”}DÅçåÚµk¼Ãɾ¾¾½½½—.]jjjºs玧§gJÊ¿Ÿñôðð¸zõê¶ÞEÇ1‘¯¯¯MyyyYYÙÌ™3ýüüð ˆSaF¥Bâ¯8¼i‰XOTu"r à\n¡ù7þòѽãD™âĉ………·oßf2™§OŸÆ—©`c‘VÔ]L¤‚áägÈÖc$yÞ¾};‰D*))ùã?ØlöÙ³g¡GDVV–÷FÎÉÉ™4iÒÛ_숈mmÞ¿¬¬Óƒƒƒ|Bø{×þÐj•uº´ôÏÁÿ¥««+88X]]ÝÐÐðÀòòò¨ü—_~±³³#“ÉÓ§O?~ü8*ìé鉎ŽÖ×××ÖÖþꫯåyàUË÷S\¹ŸŸ_FFÆ .XyyùÔÔÔ©S§ª««oÞ¼¹«« •wvvnܸQ°hà³³SUUUPÞÑÑ!TŽSÞªª*ssóÎÎN¡ùééé±²²jhhÀÏûˆÊ³¹¹ymm-öSEEååË—¢”Ÿ!"bŒPb“””ÄápŠ‹‹™L&“ÉÄäAAA4­¾¾>''çþýûØÃqYYY^^‹Åª¯¯ÇæG}¸â·ß~kjj266¦P(6lxúô)~ü‚‚‚üü|‹ÕÔÔ„MÜ$&&¾xñâÁƒL&³  `Ø™éèè8tè‹‹‹ÐI:kkk±´1Œ¨¨(Qëê²²²ìííÕÕÕñó0¤}pòÜÜÜÌ«ßÖÖ6,,¬°°ðÕ«W‚‘©TjSSÓ¨×:===kkëÔÔÔþþ~Á|ðÁ_|ñüùógÏž¥¥¥ÍŸ?߀âV˜‘Àg@ĵou«D‚—ßÂÄãy¥øøõ×_¯\¹Âf³—/_¾råÊŸþ9;;›Íf»»»‡‡‡Ï3fÌ@Ö––ør"Ù£´øw1CêÄo=†g¾¹õòòrâyÎËË»ví›Ívss Úìß¿ÿÏ?ÿÌËË«®®&‘H±±±Xòû÷ïߺu«­­ § OHHhll¼{÷nQQ›ÍNJâÿ‚‰··wpppMMMUU•¡¡!F+¢º?>ÜÝÝÛÛÛóóóa2™Ïž=[²d‰¸yì=E™F(áïM¡œ6mÚÇÑpMM ö8ejjzøðáúúzÞÈ&&&UUUD†1F8 ¤¤¤ÌårÛÚÚ6nÜèëë‹ÿ¬Œ¡ººÚÄÄ+Ú£GÐðLJ7B‰>bN:;FqqñŒ3ª««‰Cüúë¯NNNýýýBMÑßßomm]YY9dð탓çÁÁA55µŽŽìçÓ§Oãã㜜ÔÔÔÌÌÌ¢££Ÿ={†íîîVWWÝJlÌ ¤¤ÄÝÝF£ mhh˜>}:ZéÓ§766âpÈ 3Š#”|”`xÖã«NDnüË-ÊÂâÆÇ¹R‚#”\.bäû©¬¬<}:Öe477bÑšššxÕ mÃkjj°ÑÙiÓ¦áÜžúúúbeCT÷'Xä“'Ozyy zyy:ujyÕ{ïJ`<ÒÒÒ¢­­†±‚ §Nºyóæœ9sÌḬ̀ 577üxúÈ׬$&&****(($%% ùF-–s––¬hššoÖ*j¢âó²xñ✜ìç’%K~øá‡–––þþþÆÆÆ„„„™3gbGüñÇ?üpt/®ŸŸ_yyy__›Í ]¶l™ ŒŒRSSÑ5”©©©FFFøÄ©0£Ÿq b=QÕ‰È- êr‹²°¸ñq®ÔX ÊÞÞÞ ÍÍÍMMMñññ«W¯Æ—©`c‘VÔ]LØyEUþ‘äyóæÍOž<éêêºråJrrrddä/¢¼¼|uu5öÓ××7<<üÑ£G}}}eee|Û5 Ù†{zzÒétTµ|ùr¾T]]]$IFF¦®®Žw"Álˆêþ„Êáp§}æáµõžãË¡zzØßŒ37m æ]v**ɘá/^ÄÇ'8;Ï144¶²š¼ùöíßÇÕå|=ö¡ÑhŠŠŠfff³gÏvppÀä‹-òöö¦R©±±±‡F…‘‘‘†††ŽŽŽ E¨BÞhÁÍÒÊ×®]K¡PfÍšebbÒÑѱwï^üRØÛÛ;88 ›)FEE¡B:.++‹ÍÆÆ†wo#"¸ººúùù©««;99µµµ;v •3Œ'Ož û¡}ETyE±oß>Á·DåA”}DÅçeéÒ¥سrTTÔÅ‹mmmÕÔÔ>øàƒgÏž8q=„nà»ü\b]\ww÷€€uuuWWW}}ý={ö*wî\¬\[¶l±··_¶l…B À¹„¶á111ÊÊÊ3f̰²²¢R©‚ãééé CCCÃÃÃÃÑÑQÜlˆêþÄ}"’"½ç¸B""bGTÔÿ™S]]½bÅ ‹cÒ‚¤¦¦Þ»wg¯;ÌK›5kÖ0æïÀ€`= |öl´àuÒþj®—#M–’’”””’””””””’Bü?+–RU5yõ$'§Œ‹—r$$$”••·l Ç{ûûû?ÿ|µµÍ´i¦aa[ЇNtø ÑDdîÜyXü‹@UUUsçÎ¥G®§g‘‘éääbd4ÍÝ}©Ð-~ÿý÷¨¨HMMM)))%%EWWWÌ›¥¶»»{ûöOMLÌllì¾ùæ6‚¨§gpöl†‹Ë\cc“E‹>¼sç‹øàƒÆÆ&K–¸WVV#·|öA$//oÑ¢Œ¦99¹df~÷Žß6ÅÅÅØ:¢££Ÿ>}ÚÒÒ³téRhnP¶nÝ:¤7‰ È™3gÀ¶Áz¼¹Œ—·¼¹Ü¶/¿ü [sðà×%%¥?ýt©¨è‰$“œœ‚ :öV[[ƒœœœîÜùA†††¸¸]]]]‚Þqvv¥GŽ H~~Á¹sß³X÷,X%dmÄÌ™VQQ´»wïvttð¥vß¾ý/_¾d2oåä\¹sço’üüüï¾Ë`±î-]êîëëwãÆÍ3gN±X÷-ZD£Ñ‡‘[>û ²}ûŽ­[Ë‹ÿüþûÌââïx]_¼x1‘-»ÐÏßYYYM™2›F`ü:”ØJkk›³g3âã?CåYYçââvª©©Mš4)"bÇÏ?_Lëìì\XxAK—~’‘‘¾r%‡×¡¥GsBÂgjjj'N (++<ã×_ÔÖÖŽ›9ÓÚÑÑ9!a÷‹/ðÕþôÓe:®¨¨¨¬¬Ä`0xµ%&&¨««“H$??ßŽŽŽøø]èÏ€ÿ’’’‘çA™ææ.—C¡P’’ßñºÞÔÔ´iÓ&4Œ3Wâïï_UUõäɓÇËÊÊB0Þ€ùîqÈ{ÿíé±±´çÏŸŸ=›·ë̙ӂ477ÏŸ¿‹&tÿ û¸¸]‚dgÿ˜’’räÈ//Ïû÷ïïÙ“‚£G3¶–D" ÝÛiòäÉÛ·oÛ¾}ºëõ7ßÙ¼9ôÔ©8j9•ú÷ZZ *¯6ÞÓ‰:ûHr‹ È×_§ùåW_~ù•ŒŒLlìÎ… @àms(y}5__ß¾@’Éä‹Ïóíbʇ¬¬¬––&úÍõyóæ8pàÆ›::Úè4Ñ<$Ÿ}¶ËÒÒ _­’’Ò_5hii"òäÉ_âžh„¹577?räAòòòv숇€±`¼¬¡|ñâűcÇôõõПkÖ¬¦Ñ¢ëê÷õõUVV†…ý½P}Ê”)µµµX*ggç„„ÝËYºÔ=66ïÆÑ JNoïÕW®\áp8ýýýÍÍÍ{÷î·°0ÇWëîî¾{÷î¶¶6.·-1QìIgqsËgŸ-[¶ÖÖÖöôô ¾††x7ÊsçÎ /!ö>²¬¬ì¬Y³¾üòïÊ  ‡K¬]»®¥¥EOO/$$•oܸt©GGG:Wîì켪««+‚ K—.MNþs(Ei%'BxxøÉ“'Œ¯^½RRRrvvJOÿ _íöí[i´èÙ³Þÿý |nÝúM,ûˆ›[>ûÌ›7/ °¾þ‰®®îþý{¡º0ŒÂ>”Ajkk7lðËË»ùFäöíÛ‡^Þ¶}(ßnŸ={ÆápvïNZ¼x€· p(Çmmí Ι3oòäÉ[¶„ƒAxËxL0Ö¬[·vݺµ`ÞV`„‡‡xsÁ¾ÙC&“iff6Œ„ÀEjjêÚµC¯ÜX³fMZZ˜kxëÀøé§þCµãêŒàPãšØØØ”””×ÿ1Vpa‡Gggç×_?dÌøøøƒvuuцa@°o.àPÿ¥¥¥ .äõóPý?âr…ÿEKKKð¼cíÂ^¿~ÝÍÍMMMMOOÏÏϯ±±•߸qÃÓÓSMMM__?((ˆËåŠòw…–A.—kjjÊ{HTyEåATüÁÁÁ¸¸8]]Ý]»v  žýÒ¥KºººèÏçÏŸÓh4 2™¬§§÷É'ŸüöÛß;öëééÙØØ\¾|™ˆs?ì‹+TaMM§§'…B¡P(žžž555øD¤¤¤dùòåT*ÕÔÔ4##?¢ EäâòPA±¬‡sFQ%"’V¨•ž={æççG¥R ’““‡¬ê¢¬‡$ˆ*ލ‹%JN¤‚½†´B[-q+ä0Z±nRümñãä9³¡  `ggÇ{EmmmßS€C ¼ItwwO˜0×Ïêê‰%oç!%%ÅÇÇçõ—ëàÁƒ!!!UUUÅÅÅFFFëׯGåéééÁÁÁµµµ………S¦Lñ÷÷š\TyINNÞ°a‘òŠÊƒ¨ø'Nœ((((,,¼}û6“É<}ú´àÙ¯]»æææ†ýôõõííí½téRSSÓ;w<==SRR°£W¯^ÒVûè8&òõõµ±±)///++›9s¦ŸŸ¾kjjÖ¬YãããSSSóË/¿âçA”¡ˆ\\>ò‡÷\D¬'êŒ8%2­(+mß¾D"•””üñÇl6ûìÙ³øÕL”õpâQÅu±pjûl,ÒlµÄªDZ"yu^ü @‘••彑srr&Mšôö;"bG[‡÷/+ëôàà Ÿç/+ë4þqUð7~þÐj•uº´ôÏÁÿ¥««+88X]]ÝÐÐðÀòòò¨ü—_~±³³#“ÉÓ§O?~ü8*ìé鉎ŽÖ×××ÖÖþꫯåyàUË÷sØòžž++«††œ$òòò©©©S§NUWWß¼ysWW*ïììܸq£`цAgg§ªªª ¼££C¨§\UUUæææBóƒS^¡yà‹¿`Á‚¼¼<4œ——·hÑ"A=æææµµµØO•—/_ŠÊÿ£G,-- Z‰àÅ%r!TUU±ëØÕÕÅ[p¡ôóóËøöÞ=®©#ÿÿ¸µxù®A’‘û5\Då¢í+P/XPÄ 7µVÁº]‚V±ÝU«bu«]EWP«kEEAäžA\’áªÈï³=¿Ó“ÌÉI…~æùàáãœ93sÞï×Ì™™33'ž;GÞ62B ' ;j©‡»£Jˆ­Uª›ÍnooGŽÛÚÚ<<_[[;!!M^\\|ïÞ½¶¶6‚6<99Y$=~ü¸¨¨H(¦¤¤àlX³fMXX˜@ ¨©©±°°ˆ‰‰QË P÷‡ÃÛÛ»½½=??ŸB¡ðx¼ÎÎN///umPì=A2þ¡f(•ŽÐ‘Q6h†’N§‰DMè©§çgè±Hô_M·¦¦tG …¢î|âì¸qãþßÿûvv¶Û¶Eaó'¾ŠËÇÒÒR*mAC¤Ò Ô$•°BaÝ–-›g̘ñ§?ýIGGÇÛÛëÊ•l•—†7 f(­­­kkk‘c@€¾Nq8œï¾û®±±ÙÆÆ¦¦¦†ÌìŰÌP 899UWW«|WF]àóù666¨kõõõÈqmm­f3”Hå777GoRZZêèèÈçóÉÏCܽ{×ÍÍm``@©Ë A6(ÆŸ6m’9rUWWWÑ$&“ÙÓÓƒžvtt$%%¹¹¹1™L[[ÛØØØÎÎNôêëׯY,ÖðÎP¢seeeÞÞÞ111ŠW›ššìììÇíììD"±€ºººaaaR©´­­móæÍÁÁÁÄ6¨Š pqRÐL=ÜUzD¤Òúõë###‘N.<<œF£‘¬ê gŸJ¥J¥RtŠwŠ»yw@…E\ˆÄläÒ·ZWH•­±Í û‚423”J —¿ÚeH$ 4šX,Æf«´ ·²²è쬵µ5A…ìéé155UË P÷§èòéÓ§ýüüär¹ŸŸß™3g4°Ô{jöŒŒ™J 6–:99ݸñ¿ׯßhnnFNsrri4Ýáµ ÛUV–>|¸··wÁ‚…ÏŸ?'yõ÷{#&^¿~³7âúäɓԊ€ÙŒ²ñÍ›7W®\‰þ[Xø«¯ïò¯¾úJå¥áÍAš›› ‘cô€B¡œ9sæ—_~ùðÃmmmsrrйô[„·ÀÕ«WMMM‘<1¨åFFFhÝknn60øßxÚ «êŠ›ÖÛÛÛ7mÚ ÏÏÏ >>ŠÏ—JmPßÊÊ ÝŸPRRbee¥˜•££ã£GÃÇgiiùõ×_#+;h[‰®ÄãÆT:ªØ¹sçÔ©S©Tê—_~‰šÃáà²%¾/P* $ "$ÕSzG’)M RIWW7++K$UWW³Ùl7772ÕŒuãk&¨°ÈÔvP¹´ä[-uí!n=ˆm&¾+}І ==½ŠŠ ´i ¶Úp4\(Òét\ªõë×oܸ±²²²­­ YOSË P÷§È„ ¶nÝ6aÂÍlx›½ç(Pâæ&UnûôÓEOŸ–¶¶J)ʯ¿~üñÇ¿þúy-(++ûôÓE¿í™(ÉËûE*mÁN%b×—)Jz:·¤äéÝ»¿ÔÔTMœ¨˜¸ŸŒ©k×Þ½›§ÁU//¯¶¶¶üü …Âãåwttà¾ßTÅÅÅyÛ¶íJ÷¢\Þ4`ÕªU{÷î•J¥­­­±±±hxhh(ŸÏïëëD7å¬Y³fÏž=MMM]]]ñññ#Z›oß¾­­­íââBfÊ<66¶µµµ¥¥%&&fÕªUH ŸŸ_\\âÚÞ½{Õ5`Ó¦MÕÕÕýýý‰$99ÝÑuôèÑ„„„ììlŇÊé|Ü<öÉRê/ÈPü5kÖ$''K$±Xœ””ôùçŸ+Úàáá‘››‹­á—/_nnn‰DÉÉɳgÏF¯^¹reÉ’%Ã[²!!!•••ýýýB¡022rÙ²eŠZZZr¹\dk—˵´´$0 >>¾£££££#..nñâÅÄ6€„"(\€QtGP•@iA*…‡‡¿xñB&“åä䤦¦îÙ³Ge5SëÑ" w@… 'SÁF"-A«¥‚<±±Òb%c3Ѓ¦1T*•Ï磧ÁÁÁÛ¶m«¯¯ïïﯨ¨ øŠ\iîëë»wï^$<::zÅŠ¸T2™L[[ûý÷ßohhÀn@$i¨ûSJdddkk«âü=IÞfï9< ×JЧ»_yoÙ²ùàÁmm­óçÏûùç³fÍjkkÝ·/~Ë–Íèx±²²\që$å÷û  ÇÕÕ•t:̶K‰DôÞ{ª˜Ï¡CÜO>ù¤­­õã?>|8CÑ6‚Ø¿úúÚ;¿°³³ÕÖÖž>}úÖ­[…Â:•—†7¿òf2™¸ÏÜ.^¼8{ölæêêzûömô;µèèhccc##£ÌÌLÐ7¼Jëºá999Ä›xЃŒŒ ä Á°°0ôKÀžžžÐÐPĵôôt:®Öf”Ë—/ÏŸ?ŸF£™››oݺµ¹¹´gýJšŒ_ ÍLJýÙŠ?88¸oß>CCCCCÃÄÄÄÁÁA¥»|ÌÍÍÑÝ¥÷ïß 422¢ÑhÖÖÖáááÈWŠÈÆSssóÞÞ^’;MI.ꔵµull,64NUU•··7“ÉÔÓÓóòòªªªR)àÁƒMMMY,VPPú-3ÈP… ´‡’¤zw$ðˆ¼µX•N:eiiÉb±|||ž>}J¦š— â£1”¯ûAî€ N¦‚DZ‚VKåÃB§ÊÖƒŒÍ û’yЈ›_Ü)—Ëe³ÙèéÀÀ@zz:òó¼yó.]ºJ®´ ïí툈˜>}úôéÓ£¢¢¿ò¾~ýº££#Fspp8q℺f€º?µ¶†“´dï9zöPŽÛ½{WtôžßOó\_¹2°½]JzZèúÊ• ¹É‹Ϻ»+á~ö¬,22òÖ­Ÿ.ü¨ €7wîü;wnøáÇÇÿÃÎÎY×F§!±§¸pƒ‰ÝF0nÜ8ìŒ&.-Ê‹/ÜÝ?­ªªPyU1Ÿ¾¾>GÇÙ{÷Æ8p°¸øñ„ p¶DP ²}ø›oŽ45‰.]ú‰ä¥áÍAi±^¼x–ñ·±±§Œ}ét:òÓÇ:::*'Ñù|þÊ•+KJJ( …Ëå>yò„à·îçÌ™£Áúª¨µúöÿ¯µ?$ØAÚ%S´ÿ<~¼––Öx-----­ñã‘üium9[Ï›OjjÚŸ†×2µ ØÎÎV.—ÿøãÈz™³³óßÿþ÷ߟ€Œ&É£§§÷óÏ7ÔúÁ …’•uváš]0aÂÖ­[¶mÛž˜˜€ÛA2d/Ú_ÿú•±±)ùKÛÞÒÒRtŸ ±±±»wïîëë‹_ºt)lnvìØA&ÚÙ³g¡V Õƒ@ c—aÛC©ÙÏ úûûïߟäæö…Bùàƒùýë×Èç8ÄàölØ´}ûŽúzáo{B Òööö>}Z{úôäÇŸÈ_ÅÞÒ" Ó8…Bñö^šÝÒÒòÛ^´ƒ³gÏRy‰‚ùINsø?ˆ‡‡™ŸìBþû»Y³fM:5::¶¨dxf(/^ÔðÅzåÊ•‰‰û?ø`>…B™?Þ›7oV®\©2UTTäÇ»¿zõ YDÞ¾}Û¡C‡}|–‹Åbssó/¿üBiªiÓhãÆ›4i’±±ÑG}tÿ~öû/â«#Äž={þþ÷¿ñÅ—/_¾¤ÓéüÑ÷ߟTyixsø¿öw[^~6nÜú¯!2€ëÝ£aØC ùCòÇÛC @ Ë0î¡Ô‚jB @†PB @à€@ ”@ 8 „@ ü_äOP‚aD$úoyyé5žÃ±g±Ø°!À廤¼¼”ñ»ÆÃ%@ 8 ŒÑ_m»s«@Þ-p%@ (!ïu“ðx<[[[ BÞ\.700Pe´€€€ŒŒ (—fBõ ÑÓO½ÃlGÕá€2ªIHHHKK{ûÿ+ÂjFooï±cÇ’’’TÆLJJÊÌÌ”ÉdP4 „êA ± PBÞåååŸ~ú)vœ‡ 8þ#.|}}õõõõõõ}}}â}GzûóÏ?{zz2™L“‘H„„ß¹sÇ××—ÉdšššnÙ²E*•‚Æ»Jý¥P(R©”Ãá`/éüž3fÛŠOF·«W¯Î›7ÏØØ9íêꊉ‰qpp Óé&&&ëÖ­»ÿ>rÉÄÄÄÙÙùÚµkd÷ä g¼Ò ‰QB¡”••­X±‚Ífs8œsçÎ!r¹<11ÑÈÈÈØØxÿþýr¹ ïìì a³Ùfff©©©$ N©€: ¨¥AuRê… òT_5Tª¤ ³A… 'SÁÞBZô©$#(O Zµ´%(zÍ^ãGÉ{þ0š¡££ãêêŠ-¹\îââ2V¤€JM011ƒc»wÂëׯß{ï=ì8OéPO­ðàà`ggçÊÊÊŠŠŠÙ³g‡„„¼}¿233#""jjjJKK---ׯ_„=z4,,¬®®®°°pêÔ©7nTšä/…BIMMݰaƒbd„´´´   b@ñÉèvãÆ OOO¬ÔoÞ¼¹zõªX,~øð¡¯¯oZZzÕÇÇçúõë*µÒ¬Ð $"vDQ@@$nݺUXXˆ„Ÿ:uª   °°ðÁƒ</++ ß¹s§¶¶vYYÙ£G„Bá?ü@¦à” ˆs›–Œz êòˆŒÂUTi| ÔP©’€Ì"(œL‰´ §’Œtö¨l=ÈØ Ò– è!(“&MÂ>ȹ¹¹“'Oþ㻽{÷®¶¶Vìß… Yr¹ú£R©sæÌ‘J[Щ´eöìÙT*•doóo¤­ºp!«¼ü©ü7 ŒŒ壘 ²«Î#™LÆb±,,,:D¥R‘ð[·n¹ººÒét;;»ï¿ÿ ìëë‹555544^ÿâÅó¦¦bqSK‹D*miooëììèîîêéy%“õöõõ=«,ÆvïÞ¥5#ñ‰·oÿ=½uëöäÉ“à ŠZoc‹”””ÖÖÖÒÒRÇãñÐð-[¶ÄÄÄ466æææ£/Çyyy%%%èºó°OW|òÉ'‡îêêêììÌÈÈpww'Ž_PPŸŸ_RR"‹Ñ…›tww?{öŒÇãhlLOOÏ·ß~»páB¥ INNNjå­­­­ôê… æÎËb±HÚ€‹OF7‰D‚ÍßÅÅ%**ª°°ðÕ«WŠ‘Ùl¶X,õƒáääÄårÔªJ¼ÿ¾X,¶²²Ò××ß°aCGG^UUåèèˆÏœ9³ªª ]´Â&¯¬¬$o_[[;!!M^\\|ïÞ½¶¶6‚6<99Y$=~ü¸¨¨H(¦¤¤àlX³fMXX˜@ ¨©©±°°ˆ‰‰QË P÷‡ÃÛÛ»½½=??ŸB¡ðx¼ÎÎN///umPì=A2þqf(ø!ë£>BC>üðÃsç΢s%%Å~~¾úúúzz ooïššª¶¶Öðð°ï¾;†&ùöÛÌðð0\Î"Ñ7lb2™fff©©)Ä"–¤P(¿þZØÕÕ…ýÞ"4tSppðãÇ>,433ýË_’(ʾ}ñ—.]¾v-‡B¡üûß×._ÎÞ·/—íßþ–þòåKï^nnÎlj3Dxøðannî½{w%’æC‡á2¬« ÿ"8òóóÿùÏs%%O–.õ¹sç—³gÏ””Ÿß××788ˆnÊY³fÍž={šššºººâããGÎ*KKK.—‹l¡ãr¹–––Š…‹%66¶µµµ¥¥%&&fÕªUH ŸŸ_\\âÚÞ½{ÕµaÓ¦MÕÕÕýýý‰$99ÝutôèÑ„„„ììì™3g‚*Ü<¶Âß¾}[[[ÛÅÅ…Œ  øº¡xxxäææ¢§^^^—/_nnn‰DÉÉɳgÏF¯^¹reÉ’%Ã[¸!!!•••ýýýB¡022rÙ²eŠ‚ ßÑÑÑÑÑ·xñb´Æ&''K$±Xœ””ôù矣3 /^¼Éd999©©©{öì!o?N@ȨªN ÈT3‚* ZÐXad6¨Aád*ØH¤=•d Ƚ/¨ò“±¤í°=•Jåóùèippð¶mÛêëëûûû+**~¯Ciîëë»wï^$<::zÅŠ¸T2™L[[ûý÷ßohhÀn@$i¨ûSJdddkk+n™ˆ¼ o­÷]3”mm­ii©ººº_}•† ·²²:sæTCC½TÚÒØØ€†?xoeeõÃYØ™H쟅……ÒJP†¿Ÿ¡,Rw†’L4kkëÊÊrò_yÿQ÷P"Ÿ¹1™LÜgn/^œ={6Fsuu½}û6úZtt´±±±‘‘Qff&™I8Í«ªª¼½½™L¦žžž——WUUÁ«0•JÍÈÈ@¾ Cg¶zzzBCC×ÒÓÓétºZ^¾|yþüù4ÍÜÜ|ëÖ­ÍÍÍ yô+i2þ‚^F=<¿¦¦¦ÚÇgYtôE« Ñ5k ”ÉÉI>>ËŠ‹Ÿ47‹<È $þ(çäÉ“‹-‹Å"‘ÈÝÝýôéÓŒåe```Ú´iäšš9D.—Ëåééé*£p¹\(—fBõ !î–Œù%o¾ú*íàÁƒvvök×®suý߬þ¾} k×"§îîŸ,[¶4>>—pçÎ'Nœ?ßmÉ’ÏfÍš…þ¶Ò œœœ–,Y²`ÁB¶}»’ߛؼyÓÒ¥>ÿžù–-›œœ×r8vÛ·±xñ"âøAAAsçÎuqqquuýàƒÖ®]K¼;JKK †Êh±±±ÍÍÍñññK—.…º!ìØ±ƒÌï^={vûöíP.Í„êA ±Ë¸Ý»wEGÿnÄíÛ×W® lo—Ž+ëêê6lÉËû… މ‰™ÒÏ·ß&·o_çpìmlìÇbU¸xñ¬»û\5¸áÑ«“ IDATxñìØõ“ÉLHHغu+²Oô+˜Çÿꫯzzz<==¹\î¤IðGU!dtAІC4îúÿ+iœ¢ýçñãµ´´ÆkiiiiiàO«kËÙzØ|RSÓþ4šýLN>ñæÍ›ƒS<<‡ ìï¶´D7ný׈ ÀÑä(dTÿ_Þ†††‹}úá‡ÿùÏVº„ @ yçŒêʵk×® $ù¯wC üßD J@ J@ PB @Æ&‚ #Ž}yyiyyé5– @  €3”@ !g(‡“òòÒ±;ÏW^^Êb±a!B ÊwÏýeÆèJ=@ w\ò†@ À%䢣££ngkk«ABÈ»‚Ëåªþ_222 \š Õƒ@FO?õ³Uw„JȨ&!!!--ííÿg¬p«½½½ÇŽKJJR3)))33S&“AÑ4ª@Æ.p@ y”——úé§Øq‚âø|xgggHH›Í633KMMUzß‘ÂþüóÏžžžL&ÓÄÄ$$$D$!áwîÜñõõe2™¦¦¦[¶l‘J¥ ñ®R)ŠT*åp8ØK:¿gÆŒÄ6___}}}}}}___@ 4¥†]½zuÞ¼yÆÆÆÈiWWWLLŒƒƒN711Y·nÝýû÷‘K&&&ÎÎÎ×®]#3¸'_¸dŒ9B¡”••­X±‚Ífs8œsçÎ!r¹<11ÑÈÈÈØØxÿþýr¹\eþĆ) ¨£€ZêT'¥ayGRy´šîET C$ÈMP8™ öÒâä".‚¢T·õP«5#~Ð4x%ïùÃh†ŽŽŽ««+¶Där¹‹‹ËX‘bt (MLÌà˜ BÀëׯß{ï=ì8OéPO­ð;wjkk—••=zôH(þðÃo߯ÌÌ̈ˆˆšššÒÒRKKËõë×#áG «««+,,œ:uêÆAã]Ð755uÆ Š‘ÒÒÒ‚‚‚ˆmvvv®¬¬¬¨¨˜={vHHˆÒ¬”ÞýÆžžžèippð›7o®^½*‹>|èëë›––†^õññ¹~ýºJ­4+t# T* @  ·nÝ*,,DÂO:UPPPXXøàÁ—••ER@gqâÜÁ¦%£¨:<Âò䨚îŠ*…!’ä¦J÷ Šr$ÒËE\4ö¨l=ÈØ¬AKA™4iöAÎÍÍùòå,‹Åb-_¾œÏç+ÆLHH044422JLL$ט ²«Rd2YXX‹Å²°°8tèêÚ­[·\]]étºÝ÷ßöõõÅÆÆššš9r‘E¥DäÃÙlv{{;rÜÖÖæááA„J¥r¹\sss‹.“ÉðÞÞÞÍ›7+º¦½½½zzzŠá===Jà ü­©©±··ïííUjO__߬Y³šššˆmÐÓÓCÝ”Édh8íííëêêÐSƒñòåKPäúúú™3g’T‰dá’1ä HÀsçÎ)æ³hÑ¢¼¼<ä8//oñâÅ*óWi$N@‚˜j©‡«N Èx§Ò6\5Sy/Åj©´¨Tê‰' †««k~~þùóççÌ™Ã`0ÜÜÜÊËË5“ä&(œ DÐK#‘–ÌS *‚<±1A²«õH’iITfH¥ROžÜÕÕÕÙÙ™‘‘áîîŽYR0a0NNN\.w``@ñ‰›¿‹‹KTTTaaá«W¯#³Ùl±X<ë*Œ$pP©€÷ïß‹ÅVVVúúú6lèèè@«ªª‘ã™3gVUU©Ì_%8 PW=luy„äJpÕL彫%¨ß½{7''G(®X±bÕªU7oÞÌÎÎ …ÞÞÞÛ¶mÓL ›`#”–ø)&€dQ‚dWi³-‰Jòòònܸ! ===£¢¢”véééOŸ>ÍËËãóùÚÚÚ hòâââ{÷îµµµ´áÉÉÉ"‘èñãÇEEEB¡0%%gÚ5kÂÂÂAMM……ELLŒZf€º?ÞÞÞíííùùù …Çãuvvzyy©kƒbï ’ñ0C‰ªTzJ¥R¿ùæ°Rqoܸ~üø? Ƽys<ÈG¢µ´H¢£÷˜ššèéé666¨¼cddDzúßÐS±¸ióæÍÓ§OŸ>}zXØV‘¨ 5àÈ‘o8NŸ?^AOå‡2ɪî %Á«žÊW[•¯¼#7Cimm][[‹ ÔT‡óÝwß566b#ÛØØÔÔÔQcˆ3”ëׯŒŒDžÆððpFü®ŒºÀçómllP×êëë‘ãÚÚZÍf(‘WLsssô(¥¥¥ŽŽŽJ§¢A~ݽ{×ÍÍm``@©NNNÕÕÕ*mhjj²³³CÂíììD"6~YY™··wLLŒ¢IL&³§§=íèèHJJrssc2™¶¶¶±±±èÕׯ_³X¬á¡$c$ÈA€ºººaaaR©´­­móæÍÁÁÁHø´iÓȈ¼ºººd$žõÁ ¨¸°£™z¸êò È;b/«ñ½ãƒJJ¥J¥RtŠwªô)&#ÈMb÷‰+ØÈ¥=ÅÄE£²(U¶Ä6kÖ’·ºJ g˜ÚeH$ 4šX,Æf«´ ·²²è쬵µ5AÅîéé155UË P÷§èòéÓ§ýüüär¹ŸŸß™3g4°Ô{jöŒŒöJ2äççÿóŸçJJž,]êrçÎ/gÏž))y²xñ☘½¿íÕ8VVVþï_-*z¨­ý~jjqž_}õ×iÓ¦­GC¾þúo‰øÎÿܹsûùóFìOoäç\¼øcIÉ“E‹EGÇjvǂ̫ž¯Ú#Mss³¡¡!rŒP(”3gÎüòË/~ø¡­­mNN:7ƒ~‹0¢üíokoo·¶¶vqq™1cÆ´iӈ㣖577£® Ç  òX”îjoooollÜ´iSDDîY ûŒÍfëèèèëë£ó$ÍuŠøûûWTTœ;w®¢¢bõêÕØê=5xFÞ#> 

êBCCƒÁ@Žét:º²€u µUºv?eÊ”ˆˆˆ'Ož !—.] =}úôÌ™3Õò«¬¬ÌÇÇ[‹°W¹\îöíÛ•&ÄÙŸŸ¿sçΩS§R©Ô/¿üY‹Á1nܸÁÁAÅpGGÇG)oiiùõ×_cs+**B_uF‘ Ar8\¶È••ºa£¤¤ÄÊÊŠ¼€ @*BR=¥Õ äwÄ(V3â{)Æ'®ÆCA© 7ɸª`#—–à)&€Œ=ddÙ<”–d(èééUTT M+h°EІ£áB¡N§ãR­_¿~ãÆ•••mmmÈzšZf€º?E&L˜°uëÖ¨¨¨°°° &hfÃÛé=ÇÌ€­ÁÈì):²–H$î˜˜™˜˜9;»‚~T…B¡œ<ù}CCC||.\*•ΘñÿO,asâ‡nBqB‚Ì«žf¯Ú#ʪU«öîÝ+•J[[[cccÑðÐÐP>Ÿß××788ˆnÊY³fÍž={šššºººâããGΪððð/^Èd²œœœÔÔÔ={ö(Ö,±±±­­­---111«V­Býüüâââ×öîÝ«® ›6mª®®îïï—H$ÉÉÉ莮£G&$$dgg+Ž&Uö¬Šµ½tûömmmm26XZZr¹\d:œËåZZZ"á!!!•••ýýýB¡022rÙ²eŠ6xxxäææ¢§^^^—/_nnn‰DÉÉɳgÏF¯^¹reÉ’%Ã[¸F¢‚ ßÑÑÑÑÑ·xñb´Æ&''K$±Xœ””ôùçŸçOœ€QT@aUyG€ÒjFp/¥ñ ªñPIrN¦‚DZ\d Ƚ/Hv26«Û’h •Jåóùèippð¶mÛêëëûûû+**¾"WÚ†ûúúîÝ» ŽŽ^±b.•L&ÓÖÖ~ÿý÷°Išêþ”ÙÚÚŠ[ª"oÃ[ë=‡‘ÞCI&šµµuee¹Ê»df]¹rEs³Xñ’……Å“'‘ãÇYZZËGz%ƒÁPù¹Ü(ÜC‰|æÆd2qŸ¹]¼xqöìÙ4ÍÕÕõöíÛèwjÑÑÑÆÆÆFFF™™™ ox•î'S+üÔ©S–––,ËÇÇçéÓ§ M<èAFFò…`XXZ ===¡¡¡ˆkééét:]- /_¾<þ|fnn¾uëÖææfО9ô+i2þ‚j‘‡‡GNNIªªª¼½½™L¦žžž——WUU.¾µµullloo¯Ò]>æææèîÒû÷ï!©ÂÃѯ‘§æææJ3Qº7‹dá‰Æ9H àÁƒMMMY,VPPúƒƒƒûöí344444Äþ~Aþ* ' h%Iõª“R°Žƒ¼#ðBi5#¸(¾ÒR þœ_å&f 7Aád*ØH¤%#¨hˆ‹’Xv26«Û’ÙC©4œËå²Ùlôt`` ==ùŠyÞ¼y—.]%WÚ†÷ööFDD TDEE)~å}ýúuGGGæààpâÄ uÍujm 'iÉÞsôì¡·{÷®èè=¿½¾re`{»“v&&fØ•bôŽ;ÍÌHm*•zâÄ ƒáêꚟŸþüù9sæ0 77·òòrͤ"(œÀkôÒH¤%S©@EC'6¦Òb"cóPZP«{òäI;;;:¾`Á‚²²2¥ÝAbb¢™™“É îîîF“9rÄÆÆFGG‡  —ÉdQQQ;vì@ÃÑüëëëýýýÙl¶žžž¯¯¯D"QË P÷‡s9;;{Ñ¢Ehˆ»»û•+WÔµA±÷É8\]ÿ³ÊâúúÚçÏë_¼xÞÔôB,nji‘H¥-íímÝÝ]==¯d²Þ¾¾¾g•ŸñÏîÝ»†a†²®NPW'øÏnOœ¨÷Ö†ÂBaC`àÚE‹Üýµà×_ -r \+6 WûúúÐ90ìñÛd4Ì™)))­­­¥¥¥<Çã¡á[¶l‰‰‰illÌÍÍ-..F_p+**òòòJJJÑug•oÆ$¼œœœãªª*GGGäxæÌ™UUUÄi òóóKJJÄb1ºöwàÀîîîgÏžñx¼‚‚ ëééùöÛo.\Hl3Iââ⢣£µµµ•^½páÂܹsY,± ¸±ÊÊJò¶I$lþ...QQQ………¯^½RŒÌf³Åbñ°×:ƒáääÄår#|òÉ'‡îêêêììÌÈÈpww'ðþýûb±ØÊÊJ__Æ $+Hpò ®z¸;ªT‰L5 Y¥‰«%.>Hm …r÷îÝœœ¡P¸bÅŠU«Vݼy3;;[(z{{oÛ¶mx[‚ÂU)Ý¥UY©@lñ@ŤÒf[òòònܸ! ===£¢¢”véééOŸ>ÍËËãóùÚÚÚ hòâââ{÷îµµµ´áÉÉÉ"‘èñãÇEEEB¡0%%gÚ5kÂÂÂAMM……ELLŒZf€º?ÞÞÞíííùùù …Çãuvvzyy©kƒbï ’ñ3C‰×××êëë£áGŽ|Ãápètúüùó xH¸XÜ´yóæéÓ§OŸ>=,l«HÔ„æDsS ÷·~ýº””ƒØ””ƒAAëqâ2¿|ù_NNNt:Ãá=zIØÒ"‰ŽÞcjj¢§§ØØØ Ô*•s–¸ù<2sfcq†ÒÚÚº¶¶9èë‡Ãùî»ï±‘mlljjjÈÌ‚¨;CYZZêèèÈçó‘ÓiÓ¦ ǺººÄïʨ |>߯Æu­¾¾9®­­Õl†©3æææè-@6“ñ÷îÝ»nnnˆkŠö 899UWW«´aýúõ‘‘‘HkN£ÑÈÛÆd2{zzÐÓŽŽŽ¤¤$777&“ikkÛÙÙ‰^}ýú5‹ÅÞJtΠ¬¬ÌÛÛ;&&FñjSS“â¸H$"PWW7,,L*•¶µµmÞ¼988˜LE ®–€ŠM“fê)½#±J*«èÙÄU'âj©¤6•J•J¥è#îTÑ<’R€ ‘¸p‰¥¹´•Š hÈ´xÄÅDl³f-q««´pq™ÛÙÙ¡]†D"±°°@£‰Åbl¶JÛp+++@€Î°Z[[TìžžSSSµÌuŠ.Ÿ>}ÚÏÏO.—ûùù9sF@½§fÏÈ[˜¡æ%‹ÅBÃ׬YSQQößÿ6&&&,X° ÿòË>>Ëjjªkjª¼¼¼öìÙ­t Іý333«¬,džTV–›™™)fˆ=6119wî¬HÔTVVºyóf$pÿþD//¯ŠŠ²ÆÆ†CÂÃÃ@ÃGµ”]]]Èqgg§¡¡ác@©««Ûßß¿yó}?~ìïïojjÊáp®]»†F~óæÍ°(y<ž½½}qq±Zjc›6¬ è3 rx쨭»»û¯ýë’%Kˆm&ãï|p÷î]—/_ö÷÷WšΆÖÖÖÀÀ@&“iaa‘––†¶dlsppÀ®Øb7uTUU………-_¾»hëèè8J´qWZ¸Ë—/?pà@GGG{{{rr²¯¯/±€ØÅ_6›M¦"WK@â%o’êߤq5 ° Wˆ«¥b|ÚÄ{H¾Ñ‘o È´ éF.-ÁSL {TÍ·<­.™p†}×B¸•&'Ó†+[=z´dÉ}}}âüAfé#Àׯ_[[[ÿðÃÖÖÖ¯_¿ÖÀ2½çPöeÆ%ïß¾kKN>ðÁóÑää¿0™Ì‰'nÚZQQ^½úïøøxM—F£%&îËξ¢472ÑÚÛÛuuu±!ºººØ•¥¼ÿþûI³TÚª¯¯Ÿ’rà·©û‹‰‰û˜LæäÉ“wïÞuóæÏëûä5yõêºwÊ”)/_¾üc,y3Œ††ÿm-xþü9>kÖ¬óçÏ ‚ŒŒŒ/¾øø€Ç‹ IDAT ÔÓÓ …ÃkÀ¥K—BCCOŸ>=sæL4ÐÊÊ ]g/))±²²"Îu¡¡¡Á` Çt:]YÀºFüe‰âÚý”)S"""žzôH1|ܸq–––_ý5²²ƒPTT„®ÄãÆT ÏÏÏß¹sçÔ©S©Tê—_~‰šÃáà²%S‘” ®±€ŠTOåA*TbpÕ‰¸Z*Æ©ý–[2­Hº‘KKð@Æ•ÅD`³Æ-ÏÑÓÓ«¨¨@›Vt›|ކ …B:ŽKµ~ýú7VVV¶µµ!ëij™êþ™0aÂÖ­[£¢¢ÂÂÂ&L˜ ™ ÃÞ{Ž(Ã0 411311ûè£_½ê9p  G럶¶vÿoãNéŒÈñŒ3@¿ŸB&šŽ.•J©T*±µÇŽåñxK—úÌŸïöóÏ·ÐNîîŸ"Ž8;»‚¬R—É“'£›Ì^¾|9eÊ”?Æ€rÕªU{÷î•J¥­­­±±±hxhh(ŸÏïëëD7å¬Y³fÏž=MMM]]]ñññC¿ûÑ£G²³³q]éš5k’““%‰X,NJJúüóÏ«"–ØØØÖÖÖ–––˜˜˜U«V!~~~qqqˆk{÷îU×¶M›6UWW÷÷÷K$’äädtGÈf ‰ß‰À}Œ‰¼Þ¾}[[[ÛÅÅ…Œ ááá/^¼Éd999©©©{öìQiЇ‡Gnn.zêååuùòåæææ‘H”œœ<{ölôê•+W–,Y2¼U.$$¤²²²¿¿_(FFF.[¶LQ@KKK.—‹ì¡är¹–––ÄÄÇÇwtttttÄÅÅ-^¼XeER*¸@F=ÐɨªêViª ø µßrk '#ÝH¤U*2iñ@ÅDÆfu[¡R©|>= Þ¶m[}}}EEîçT¶á¾¾¾{÷îE£££W¬XK%“É´µµßÿý††ìD’f€º?¥DFF¶¶¶FDDhfð÷žci%A8zjaañäÉcäøñãG–––JモaÿÖ® TÜC¹nÝZâ%oôïòå¡ëãÖÖÖ¸ÕóaùÊûº‡ùÌ Y5Ã~ævñâÅÙ³gÓh4WW×Û·o£ß©EGGeff‚&í•î'S®¸ÿ ùâxpppß¾}††††††¸oê×V¨TjFFò…`XXú%`OOOhh(âZzz:NWKÃË—/ÏŸ?ŸF£™››oݺµ¹¹™Øfœ @«999$m8uꔥ¥%‹Åòññyúô)Á~>Å/¸{zzÌÍÍÑÝ¥÷ïß 422¢ÑhÖÖÖáááÈWŠÈÆSssóÞÞ^’;MI:ꔵµull,64NUU•··7“ÉÔÓÓóòòªªªR)àÁƒMMMY,VPPº KP‘” ®€ =”$ÕÝ‘ŒJ j Ry\u©J_©ÚC\òV·5…“‘n$Ò‚*™‡…L‹§²˜lV·åÑxÉ›Ëå²Ùlôt`` ==ùŠyÞ¼y—.]%WÚ†÷ööFDD `DEE)~å}ýúuGGGæààpâÄ uÍujíä!iÉÞsô,yÛ½{Wtôï^Roß¾¾re`{;©Y:3¥«À¸pô49ù`Cƒ055U.—ùå.KK‹˜˜h …âè8û_ÿºhbbòÛr¹òh¿_}®[½ÚÇŽK—z#«ä\.÷Â…‘_Æ€=Þ¾}GTTäôéÓóó víÚ]TôB¡df+**JHH`³õkkk=vøp†¢Uþ¢Òq8ö66öÈé÷ßþüù¬¬,¹\¾víÚµk×®[·nÔ¾Z\¼xÖÝ} ®\¼xëјfppN§#ÓÏ:::*¿1çóù+W®,))¡@(.—ûäÉ•¿T8gΜZ ûà Õƒ@HB¦ ‡¨ÛõÿWÒ8EûÏãÇkii×ÒÒÒÒÒ?9ÀŸV×–³õ °ù¤¦¦ýé-›¾k×Î}û?üð#dálçÎÿí´Û¼yÓÒ¥>===Èp í÷cV“¬¬3©©_8pB¡899eeAÿ/ üqhè¦ÆÆÆÆÆéé_#[¶lþî»qk›››MLL""”Z¥.AAAB¡YذaÃÚµkaÝ}‡”––¢ûlˆÝ½{w___||üÒ¥K¡n;vì íìÙ³P+„êA ±ËPg(!8éÆî|Þ~†’Éd&$$lݺ•øíöøñã_}õUOO§§'—Ë4i¬Ø2ª€3”#Ñõ±Jä]ý½h‚–hãÆ ÿ@ £8š…hA @ p@ @ J@ PB @à€@ Qø•÷pÂáØ———–——ŽQãa B Ñ8C @ p†r8)//»ó|åå¥,"@ 8 |÷ŒÑÿWfŒ®ÔC yçÀ%o@ PBÞ):::ê&áñx¶¶¶$„¼+¸\n`` ÊhP.Í„êA £§Ÿz‡ÙŽª;Â%dT“––ööÿ3V8„ÕŒÞÞÞcÇŽ%%%©Œ™”””™™)“É hÕƒ@ c8 „¼ÊËË?ýôSì8Aqü§~çÎ___&“ijjºeË©Tª2”‘ÂþüóÏžžžL&ÓÄÄ$$$D$©´Y¥¿R©”Ãá`/éüž3fÇ—Ë剉‰FFFÆÆÆû÷ï—ËåÄñ±\½zuÞ¼yÆÆÆÈiWWWLLŒƒƒN711Y·nÝýû÷‘K&&&ÎÎÎ×®]#3¸'_è8g•f(|}}õõõõõõ}}}JËÊÊV¬XÁf³9ιs爅Ù@Æ6œ€: ¨¥¨:‘±Tm@y¨ªÖãIÆ6 ÝTˆê.–·÷KMüDkVùÉäjå4~%ïùÃh†ŽŽŽ««+¶Där¹‹‹ËX‘(!c‰×¯_¿÷Þ{ØqžÒ¡žÒð£G†……ÕÕÕN:uãÆ*óykdffFDDÔÔÔ”––ZZZ®_¿^¥Í*ýEHMMݰaƒbd„´´´   âø§N*(((,,|ðàÇËÊÊ"ŽåÆžžžèippð›7o®^½*‹>|èëë›––†^õññ¹~ýºJ­Ô*tœ¿J3 vvv®¬¬¬¨¨˜={vHH±ƒ ((H ܺu«°°P¥P TÚ†—›ŠŒz*K@Õ”'ªš=žÃû‚n*D —̤qZ⧘Xjâ'Z³ÊO&P+Á2iÒ$샜››;yòä?¾Û»wïjkkÅþ]¸%—Ëq55Uá¶¶¶t:ÝÈÈhùrŸììKÈ%êo|þùç|~ ÞÐP¿cÇv[[[fddäï¿:'çšbÎHZéÓ§Ï›77:zšÁ•J%oüðþ]¸U^þT.—·´´˜››Ëd2ùo Ç2™ÌÌ̬¥¥E.—K$’;wÚÛÛÓétccãU«Vݾ}‰ÖÙÙmooO£ÑŒ×®]{ïÞ=¹2ø|þòåËY,‹ÅZ¾|9ŸÏÇ^mmmµ±±¡R©rB.\ÈR¬¨GXd2YXX‹Å²°°8tèšó­[·\]]étºÝ÷ßöõõÅÆÆššš9rD.—S1`³YH`yOOžžùøè%*•ÊårÍÍÍY,Vxx8ZR½½½›7oVtMz{{mÙLlMM½½}oo¯R{úúúfÍšÕÔÔDÑ¢EyyyÈq^^ÞâÅ‹Iæ/—ËíííëêêÐSƒñòåKýõõõ3gÎ$©ÉB'Szzzh9Êd2¬ÈJ 9wîœb> ¡4¨Ÿ  ’¨¥®:©U]«Ò< TUëñ$Pïĉ ÃÕÕ5??ÿüùósæÌa0nnnåååšI*Du {i$Ò’)ÔybcªUùÕ²™ •µº'Ož´³³£Óé ,(++SÚô÷÷'&&š™™1™Ìàààîîn4ù‘#GllltttÚp™Le`````°cÇ4Í¿¾¾ÞßߟÍfëééùúúJ$µÌu8—³³³-Z„†¸»»_¹rE]{OŒCÛõ?«,®¯¯}þ¼þÅ‹çMM/Ä⦖‰TÚÒÞÞÖÙÙÑÝÝÕÓóJ&ëíëë{VYŒÿìÞ½k¨3”‰Ä×wƒÁ8þ‡òògÿùÏ­€€€“'O¡êêuu‚ÿüçöĉÚqqqH`dä¶7oÞüðCVeeù­[?{yy>|Xiþuu‚ÚZ~aáƒÔÔÔÞÞ^OO¯/^Œþa:FsrrB_P:;;¯_¿ÞÜÜŒœæää8;;Óh4‘H´hÑ"&“yíÚµ¦¦¦¢¢¢àààcÇŽ‘™ÊÜÌIIIimm---åñx< ß²eKLLLcccnnnqq1z÷ŠŠŠ¼¼¼’’’ÆÆFtÝyX&*îܹãää¤YÚ‚‚‚üüü’’±XœššŠ8p »»ûÙ³g<¯  @cÃzzz¾ýöÛ… ‹ÍqqqÑÑÑÚÚÚJ¯^¸paîܹ,‹8~UU•££#r}š——ÇçóµµµÐäÅÅÅ÷îÝkkk#hÓ““E"ÑãÇ‹ŠŠ„BaJJ Ά5kÖ„…… ‚šš ‹˜˜µÌu8¼½½ÛÛÛóóó) ÇëììôòòR×ÅÞ$ãd†244ôàÁd& ëëkõõõ‘cƒñâÅs æ÷í‹ Z—”ûùùêëëëé1¼½½kjª°s¢hZ\&Øðo¾9lgg‡47n\?~üŽŽŽ cÞ¼¹äk0å‰Ï»víÚêÕ«Ñ©;*•zåÊäÔÏÏïÚµkr¹<""‚Ëå‚Þˆç†273ÄJkkëÚÚZäX  9s8œï¾û®±±ÙÆÆ¦¦¦†Ì¬†º3@¥¥¥ŽŽŽ¸éXò3”¨ |>߯Æu­¾¾9®­­Õl†©~æææè-TÚL`ÿÝ»wÝÜÜ”º600àääT]]­2þ´iÓ@$•®®.™ü˜LfOOzÚÑÑ‘””äææÆd2mmmccc;;;Ñ«¯_¿f±XÃ;C‰Î”••y{{ÇÄÄ(^mjj²³³CÄ·³³‰DÄêêꆅýìy\Çÿÿ¶ÊGm B!"Ê‘pŠr© µbÁEP¡99U9Å(R­^hµ*Ÿj^¥Zû±¢€Å Dn‚ GC€„SÃ!ßûùìw?If³ `ÕÎó‘?vggfßóš÷ÎÌÎìnB…Ba[[ÛÆð…·ß6 ÉR(§žLw·ä6 ø#H饗×Ú²²<^cJÊ7ºººëÖ­Cv¿þz×gŸ}6Ìe¿‰‰ ²®ýÍ7ßxxxÄÇÇ# 4ƒÁèïï‹ÅL&gÃÕÕ5 à?þ;¬\·nÝwß}×ÙÙÙÑѱ{÷nìòº§§'²´1‚JMMÍdûÍ›7hÎOŸ>õòò244477GFÌHä7oÞŒø€2//ÏÒÒ²¨¨H¡%HlÓ†-zM‚І?v”ŽÖÝݽgÏž%K–´Ç~‡{÷îŠvåÊ///"ñõôôºººÐ§)ôõõ‰ä0cÆ ìŠ-ÊÐÐPeeehhèªU«°‹¶VVV£1 DwÔx,«V­JKKëèèhooOMMuww—+H{{;²ÝÖÖF§Óñ…"b(\B@ü%o‚êá»ÈÛ€ò©ªôå)aþ³ ïè¤OªÄáTîè¥U¾ÔDìQÔùÊ_f+‡ßê ×ÒÒÂÞk! Ü2“iÃ¥‡­?^²d‰®®.~þ 3ˆôH`__Ÿ©©é¹sçLMMûúú”°Hï9œç²Þ¹%ïöövMMMÌÌ¿ò“zˬ-55ÍÁa²{äÈa}}ýää]³gÛ88ÌOMý¶»»›àRrgg'²}ûö/sæØ7î“O>Ù¾}[AÁCEOKK¥Ñhjjj"‘(%åkd788¨´´»ì®„2ü±››ÛÏ?ÿL"‘ ccc‘Ÿúé'wwwä•¡PH¡P°oxa_åËÎΞ6mÚöíÛŒŒ,,,ºººdžkïÞ½ÙÙÙúúúS§N=wîÜÞ½{щñ¦¦¦µk׎ì¬6•J­««C¶ëëëÑðY³f]¸pÍfïß¿ëÖ­H ¶¶6—ËY._¾|úôé™3g* Z„ºº:*•ŠlS(te[4ü7K¤×î'NœþìÙ³áÛ\ZZêææ†:†Äk€,kóæÍD⛘˜ Ï!›˜˜ÉÁÊÊêñãÇÒá***L&sïÞ½ÈÊ“'OЕ²Ñ@EEehhH:?%%uBœüQ\\\rrrÐÝåË—_¹r¥¥¥epp°©©)55uöìÙèÑk×®-Y²dd].00°¢¢b``€ËåFDD¬\¹RZ@&“Éb±§ýX,“ÉÄ/ ORRRGGGGGGbb¢³³3¾P pl ˆõ@îDD%Û€ò©:‚¶FkªDE++Ýh¤UpòDÏ«¨ó±ÔÊ gQSSƒ}% **ŠÃá ”——K¼ · wwwOHH@ÂcccW¯^-‘ª··WMMmܸquuuØ šêþd!ÂÃÕ³aÄ{ÏwýÊÀÀ€´´TÐR5ÁÅb¯‘J¥YòNJJôóû Ù6119sæT]G(lmh¨TKK‹ÇkD¶kkkä>[9œWÅ¥ˆÏœ9.‹###Y,–ƒƒztÓ¦Mûöí#¸Ø'‰¨T*èiK™ÏPâ<ª5ü·¼utt$^s»téÒìÙ³µ´´ìííÑwÕûûûccc§M›6uêÔǃ&íe)3\ºPè#8ùHoìß¿yC044UO$#EÛ·o…BQhíàÊ•+óæÍÓÒÒ266Þ´iSKK ›åêò —›7o\#Ú¹s§¾¾¾¾¾þ®]»†††ˆ/4‹D"cccôéÒøúúN:UKKËÔÔ4,, yKyðÔØØ¸§§‡à“¦+ÖÔÔ4>>›?§²²ÒÕÕUGGG[[{ùòå•••r øí·ßÒh4t$ÈÛ@‚.L‚ê܉ˆJ ·剣ªB—'ȶa.yƒNªDE+kÃh¤%rƒ¤ÆÉ“Hë!Óù*¯D+§ô’7‹Å¢Óéèîààà¾}û·˜çÎ{ùòePr™mxOOOxxøäÉ“'Ož)ý–÷/¿übee¥¥¥5cÆŒ'N(j¨ûSèI‚6ì=?œg(_¼xÎ`0’“w>^ÔÒÂol¬¿zõ²Ü¥³óÙÙ§««+[[›+*ʶnݺx±΀’Çk¼?wóæ(##£ââÿcÚ´i?ýôϦ&^qq‘Y__ÿÉ“Gh&óç;îÚ•Ìã5VT”y{{+1 TîJ„ÇþôÓOb±øâÅ‹ººº¨[ˆÅâúúz“Ý»ws¹Ü7oÞ¼zõê·ß~CýcÙ²e—/_nnnàñxñññ §UÓÓÓ‘g(ÓÓÓçÏŸ¯ÄÇMˆ(ßS'MšDü"¬®®ž1c†"‹Åâ}ûöùúúÊæããƒóžªŒÃHAÞ¹g(étúåË?óx¼5kÖš™Mwt\púô™S§Nâ§ŠŠŠºqãæâÅΦ¦ænnîÝÝ]‡|+ÄÈÐÐØÚÚvÇŽcÇŽÍɹ1eŠrè»ï2¾ýö[ Ë/¿üÊÞþÿÖ 6nܰb…úgZZÚ;w,-gº»¯ÆF{;¬Y³¦««ËÁÁD"Í›7ïÍ›7kÖ¬Aêééݹs§¡¡ÁÅÅ…F£YZZ;v yì’D"ÅÆÆ^¾|ÙÎÎNGGgÑ¢E§N’y–ãÇ0™L&“™ŸŸìØ1DŠ’’ô9âãã;::ZZZ’’’V¬XuCزe Η“QΞ=«ÄÓ`P@¨yßùhøYèêêfdì–yô:‹½½‘±þÛ0‹}¾hÑçè®Ïžð Ù² 777û׿®£»_~é+3sœ]å^ÊAÐÒÒB??©««‹n£Lž<ùàAÙƒid$*&“yýúu¹âýÍ}ÝÅÅûI3Èß߉D¢eË–ÅÆÆÂ6@ ·1 „@Þ °ß‹Æ^þ@ ïpŠäþ—7@ (!@ p@ @ J@ PB @ ßòIÌÍ-ËÊJÊÊJÞSãa B Ê¿N£Ñ¡@þVÀ%o@ PB @à€@ òž"ûÊK—ÎBi ÑÐÐPô_°òòòBBBþüóOø÷Yï ,ëéÓ§gÏÊi|||lll6oÞ SB@¨òîôSa¶ïÔ‡5 \³Æ:dTINNÎÈÈX¶lهѦ|ðôôô9räöíÛrc¦¤¤¸¸¸„„„¨©©Aݪ@Þ_d (ÛÛ…PȨRVVöÅ_`Çyÿõ½v‰ñŸtøÝ»w8J-Aõ@îDD%µ |ø­±Xloo_]]ý^H¡4ðJÈ_@__ßÇŒm‚e^E2Ã:úòåËÂÂBuuõ   üð·ÉáÇÃÃë««KJJ˜L¦ŸŸŸB¶t ‘H»wï^¿~½td„ŒŒ üøŠêŒåÖ­[Øé䀀€7oÞ\¿~Ïç?zôÈÝÝ=##=êææöË/¿ÈÕJQc°å•™a@@€­­mEEEyyùìÙ³ña³Ù>>>þþþl6ûÎ;………Hø©S§ >|˜———­\>8J[""êḓ\•@Ö‚ò¹4HmP8¾»* ÈlP%âT®\éF#-AYdæŒï¨ÃwZE[`–ñãÇc/䜜œ &|øÅމÙÞÖ&€?ø“ù»x1»¬ì¹øéíí ¥Ñhõ:¸] IDAT #33“L&#áwîܱ··§P(?þø#Øßßohh¨¯¯ðàA±XLÆ€ÍVbWn¸X,‰DÚÚÚÄÃѬÈd2‹Å266¦Ñhaaa½½½HxOOÏÆ¥‹¦=== Ù†SÞêêjKKËžž™öô÷÷Ïš5‹Ç㌯„Ζ––/_¾Dw©Tê«W¯@‘9ÎÌ™3 ªDÐ"¡­­Öcoo/Vd™‚ž?^:ŸÅ‹çææ"Û¹¹¹ÎÎÎÊåƒ# NqROˆ¨DÄZ‹b]¤6N-ÈtW2™|âĉ3fP©T{{ûüüü .X[[S©TGGDz²2å¤U"(G:ôÐh¤Å¹Šå^8ybc*ê´ÒÅQ¢µº'Ož´°° P(óçÏ/--•Ù ìÚµËÈÈHGG'  »»M~ðàA333 œ6¼··722ROOOOOoË–-h8š?‡Ãñòò¢ÓéÚÚÚîîîÍÍÍ ™êþ$Š|õêÕÅ‹£!NNN×®]SÔéÞ$ãp¸x1íñ_Tq8µõõœÆÆz¯‘Ïçµ¶6 …­íímÝÝ]"ÑëÞÞžþþþE£…˜˜íæ ¥‘¼I%ÒÓÓAIII^^^^^×ÐГ“STT„Þ———çææ744 «'rgÅ.xÙØØÇRPPŸŸ_\\ÌçówïÞ¦¥¥uww¿xñ"//¯  @iÃD"ÑÑ£G,X œm$&&ÆÆÆ‚ž«»xñâœ9s…H"ñ• ¹¹›¿]dddaaáëׯ¥#Óét>Ÿ? µ•Jµ±±a±XƒƒƒÒ-ZtàÀ®®®ÎÎÎýû÷;99á òàÁ>Ÿobb¢««»~ýúŽŽ$¼²²ÒÊÊ Ùž9sfee¥rùàˆƒ¢êI¸“\•ˆX+ÓE%\¤6N-€ÜõÞ½{7oÞär¹«W¯öôô¼}ûöÕ«W¹\®««kTT”rR€*§råJ7JiA²ÈEnžÃtÚá´À rssoݺÅår—-[)³;Ø·oßóçÏssskjjÔÔÔ’““ÑäEEE÷ïßokkÃiÃSSS›ššž>}úäÉ.—›žž.aƒ··whh(›Í®®®f0qqq ™êþ$puumooÏÏÏ'‘HyyyË—/WÔéÞ$ã{9CyäÈ!KK dM0 ŸÏKKK;wŽŽŽŽ¦¦&ƒÁX±bÅhÏ«´P¡‚(ÿC¡455­­­E¶Ùl6z;ennž••ÕÐЀlffV]]Mä–WÑ™³’’++«šš‚á÷ÊhjjjÌÌÌТq8d»¶¶V¹JÄOŒÑS± TÞ{÷î9::Ê”bppÐÆÆ¦ªªŠ`|åf(uttD"ºÛÑÑ‘’’âè訣£3}úôøøøÎÎNôh__FÙJtΠ´´ÔÕÕ5..Nú(dz°øOëdaaÑÔÔ„/ˆ¦¦fhh¨P(lkkÛ¸qc@@>iÒ$$2"¯¦¦¦rùàH–B9õdº¾Jr­•™§´KƒÔ…ƒÜ•L& …BtºKbWKKK9)@• '"Ý襕–…ÈEŸçðVé§Õ•Y¹ù[XX ]Fss3ƒÁ@£ñù|l¶2Ûp6›ÎΚššâ”B$*d¨û“.òéÓ§=<<Äb±‡‡Ç™3g”°Ô{*w¼[3”999û÷8}ú4ñ$ÝÝÝ«8°nݺ?þ((--9p sìØá$ß{MKK‹¾¾>²nH¤3gÎüþûïŸ}öÙôéÓoÞ¼‰ÎÍ ï"Œ ùùù¾¾¾'OžÄ¾x. jùÔ©S[ZZТééé!ÛS¦L‘k†Æ‘x𨡡aÆ áááÊÙ&1Á’’¢ª*ûR½~ýº¡¡!ƒÁ _9´µµ±Ófêêꉉ‰÷ïßçñx?ÿüsGGöÙ/§££3Ž7fÌssó~øáüùóÒGü¼¼¸\.‡ÃY»vmXX¾ ãÇOKK›4i’††Fzz:ú ö„ Й×W¯^Mœ8Q¹|pÄy†’¸z wÂW ßZPžÒ. RrW‰4iÒ$dãÿø‡ÄîÀÀ€rR€*NDºÑK+S¹àç9|§UÎýð!R¹MMM¶¶¶H»Êd2[[[±×‘Ü6¼µµuêÔ©Èö´iÓ°Éž|dhh»f}æLö¼yŽÆÆL™!b±øÄ‰ ~ÎdšÎ›çxôh–X,–iÆÉ“?1lmíccã»»»‘ ¥×ljçùw†J¥ÖÕÕ!ÛØwgÍšuáÂ6›½ÿþ­[·¢M—ËY._¾|úôé™3g — Z„ºº:*•ŠlS(teÈ[¨ 'ñ'NœþìÙ3ålÃRZZêææ†[%¯,Kâ³…øñ•ÃÊÊêñãÇÒá***L&sïÞ½ÈÊÚV¢+q£ŠŠ ÚPHôpÑÑÑêêêd2yÛ¶m¨I AÌÍÍ%²E6LLLÐ6Š‹‹MLL”ˇˆ€ÒTO®;T±?O —© ¹ë(µ J…‘nôÒ*' {†ã´Ãq¿aÞ¾–——£M+h°…Ó†£á\.—B¡H¤òóó ª¨¨hkkCÖÓ2ÔýI3vìØM›6EFF†††Ž;V9F¼÷üë”Ïž=‹ŽÞvìØQ&“©Pî¿ür‹D"­\¹BæÑ£G³öìÙëââòâÅs—½{¿ÏÊ:†P^^qçÎíšš*™!Çÿ––nggÿüy‘§çšï¾ÛsâÄI™'êéé¹}û—òòÒíÛ£/^¼øõ×ßH¤—/ÙÈÑ—/Ùè6~žcÆŒùè£à€ÒÓÓ3!!A( ‚øøx4<88¸¦¦¦¿¿hh}ÈÛÛ{ÇŽ<¯««+))iøg?tèPrròÕ«W%Ú2P8hP/Z[[ãââ<==‘@ÄÄD¤h ŠÚ¶aƪªªæææÔÔTôé"EmMe‘þ÷û¿ýö›šššÁøJãââ’““ƒî._¾üÊ•+---ƒƒƒMMM©©©³gÏF^»vmÉ’%#ër\.7""båÊ•Ò2™L‹…<½Çb±ÐÆ $ˆORRRGGGGGGbb¢³³3걩©©ÍÍÍ|>?%%ýœŠ¢ùàˆõ@îDD%µ q·uëÖéÓ§úé§aaaèr‰³³³··w]]‘‘QVÖæ¡wìØ±sçN±X#3C´ C6¤?µˆ OLL$‘HØö·±±q„  pPéæÌ™3wîÜW¯^­Zµ*66 LHHˆŠŠBŠòïÿ[!Å–.]XUU¥¡¡áäätòäîFÚÒÄ÷߯Ð[ éŒeÅŠIII\.YKŠÍÊÊÚºuë«W¯(ÊçŸ~êÔ©ÿÞž½,,,<~üøÈãêê\UUE¡PV­Z%³m=~üøöíÛ333Åb±Í±cÇð ðõõmhh°¶¶‰DÎÎÎ, ÷÷÷çr¹He­_¿þË/¿T.I€TäNDTY ÊäÒ µqjAQw%ÈlP%‚‰H7i Ê"ó¢PÔQuZE[`¥+1**jáÂ…¯_¿Fòß¼ysffæÊ•+ù|¾±±ñ¶mÛjÓ’’¶oߎLó#ƒKéq||¼ŸŸNŒŒ¼råŠBf€º?Eo„ˆØ@¤÷|§P‰‰Ù»Cb@‰Ž$œƒÞ˜F'ö@Ì›çØÔÔôða¾Ä£ †ÉÀÀ›]­ªª:88hlÌü裪«+Ñ3bó—A’ÿÏt«ª*›]-ùÑ£Çßÿ}EEåëׯ‘‰e•ÚÚEó$‘HkH$Òÿû—þ{i~À£ÉK—ήYã{éÒYssK33Ë DCCC E(’ˆ}?¶¦¦fÍš5ÅÅÅpNšD"±X¬gÏžá|ëí¨¬­­áŸ*' T!~{ ÿöl¤úzdêD"ýÙÜ0QíÓ1cTUUǨªªªªªŽƒlHîVՖѵõ°ùìÞ\ÀÅþ_ŽÄÀQzÂÅÅùÇO]¿þ¯à`ß>ÕÒÒâóùB¡íæeŽ;AÐh´†††GþÐÒÒ‰!~øá¸££ÃÐЩ©9Îc‘øy¢CIi‰ ï>%%%ès68ÄÇÇÇÄÄô÷÷'%%­X±ꆰeË"ÑäþÙ7ª@>H†û*(þ£¢"MLL¾ÿ~ß… ÿ|õêU__ßãÇO6l؈uw_E"‘Žû¡¯¯ïøñ$ÉËk-ñSûûû‘H¤ŒŒïºººD"уüý@‘?ùä“þþþ={öb‘×âêëæ ?où^ãââBä“]ÈßßÍš5K]]]F@ ‚Ãè¾bòé§Ÿ^¾üó‰'Ïž=ûÍ7)dSt¸944tåÊÕS§NQ©Ô-[6c ”ËúõþjjjgÎdÛÚÚ;vöìÙ륣±XßïÚõ/•J•È?""üСß}¶ôß W‚yBÞG°ßmÁY+ ‚&@ ï2p½ûø %\Ïý›ƒ¸Á‡ô %@ ,#ø ¥*T@ 2à€@ ”@ ¿Ža½”ÓÔôgYY ñÃÀÜÜ’F£C ¼ÕeYY èÏT ïåå%ee%p@ @ äm(††† Ž@ p@©<8<@ J8 „@ À%@ dÔωÅCð÷¾ÿ†ãŠ&ÉËË›>}º !,Ë×Wþx>>>û÷ï‡r)' T%F©»yû½Ø»ÜoŽÈ€ò}B[[çƒ<×0yËn—œœœ‘‘ñöÿŒa•£§§çÈ‘#)))rc¦¤¤>|¸··Ц„€P=òþò·P¾Mƒß#qÞ²Û•••}ñÅØq‚ôøO:üîÝ»îîî:::†††!!!Báþtþ×_]¶l™ŽŽŽA```SS“ôyG{ ²d3‘ò"…Bsssì!ÿeÊ”)ø6°Ùlwww]]]]]]www6›M\·ëׯÏ;wÚ´iÈnWWW\\ÜŒ3(ŠÁW_}õàÁä­­í7ˆ î‰WºDaef* H@‰TZZºzõj:nnn~þüy´AÛµk×Ô©S§M›öõ×_£WH("¶I¨!…Bê܉ˆ% RtvvÒét##£Ý»wËRdÛ”$¨AáDÌ{ iÑ«˜ÈEÊ“Hërþ‘jiºGîóGÐ {{{lˆÅb;;»÷EŠa@YSà 655Óןêâ²äúõëÉ FÓU4OMù1Ì 6 å”Jœú=Pöõõ}üñÇØqžÌ¡žÌðC‡…††¾|ù²°°P]]=(( ?|øpxxxuuuII “Éôóó{û—ÈÍDÊ‹°{÷îõë×KGFÈÈÈð÷÷Ç·! ÀÖÖ¶¢¢¢¼¼|öìÙÄu»uëÖ²eËÐÝ€€€7oÞ\¿~Ïç?zôÈÝÝ=##=êææöË/¿ÈÕJ¡J—(¯Ì A Èf³}||üýýÙlö;w ‘ðS§N>|ø0///;;[®Prm“P" 6õpÜI®% RDGG«©©•––>~ü˜Ëåž;wN9w±A®mJ’T‰ p"æFZÐULä¢À±Gnërþ÷¥¥}÷?~<öBÎÉÉ™0a‡_옘ímmìïâÅl±X,(ó‡Ä|ýúÕðeeeÆÆÆ‡jllhk>xpßÇg݈ä,ñ#“ÉÃŒ€“¤®Žìíí­\VJœz¤~eeÏ/^Ì–®Ü‹³ËÊžKŒ>{{{CCCi4ƒÁÈÌÌ$“ÉHø;wìíí)Š……Å?þˆö÷÷ÇÇÇêëë‹/ÎÍÍE¶sssñ…"b›„€8IROÂuWl)ètz{{;²ÝÖÖæâ⢜;)z“Éä'N̘1ƒJ¥ÚÛÛççç_¸pÁÚÚšJ¥:::–••)'¨Aá8Ò¡‡F#-‘«¤!NžØ˜ 9?¨8ж´ V÷ĉ eþüù¥¥¥2»ƒ]»véèètww£ÉºÆ—––ÖÝÝýâÅ‹¼¼¼‚‚¥ ñA¦Íø$&&ÆÆÆª©©ÉŸ?â·¾T*ÕÆÆ†Åb JGÀ) L}úäÉ.—›žž.aƒ··whh(›Í®®®f0ñññ ™êþ$puumooÏÏÏ'‘HyyyË—/WÔéÞ$ã‡3CÙÝÝ9üŸ±±1›]-óÐŽ1îî«8œÚ—/Ù+V¸ÆÅÅ!ád2ÙÏ﫺:ŽÌÝÔÔ”+\«««ø|^pppDD8 Ù°µµ½}ûVkk3×½õ«¯¾”ˆ€Ÿö‡MÒØXO£ÑÐpŸuÕÕU--ü””o>ûlÜ)¸ÁO?ýS h©¬¬Ø´iÓˆè¯Ð ¥©©imm-²Íf³ÑÛ)ss󬬬††ld33³êêj"7ÁŠÎm”””XYYÕÔÔHD&“ÉÆÆÆ¨… {e4BMM™™Z4‡ƒl×ÖÖ*7C‰cƒL›ñË{ïÞ=GGÇÁÁA™R ÚØØTUUɵÇãYXX áMMMÄuÓÑщDènGGGJJŠ£££ŽŽÎôéÓããã;;;Ñ£}}}4mdg(Ñ»»ÒÒRWW׸¸8飠‚ÔÔÔ  …mmm7n @Â'Mš„DFäÕÔÔ$"¾m’¥PN=™î„o ¨~~~Hg¦¥¥5wÙ N&“…B!: &±+aq)@•ˆ_¹øÒ^ZÐUŒQà穜óTK jueV®D¹,,,P)š››  ™ÉÃoÃMLLØl6:;kjjŠ?ójhh¨ îOºÈ§OŸöðð‹ÅgΜQÂPï©Ü5òf(G`@ÙÕÕ1üŸ––V[›@æ!&“ñüy²]TôÔÄ„‰l“Éd6»&±knn^TôÙ®­­166F£IŸ¢¥…o`` 3(ìMÂáÔzz®AÃëê8è)´´´ä–ˆHÁÍÌLØ_YY>"Ê#?…”šššÈö›7oÐkàéÓ§^^^†††æææ7nÜ@#¿yófÄ”yyy–––EEEÒ‡º»»÷ìÙ³dÉü¦ [ôš ¿c–Ž&Ó›qÊëààpïÞ=W®\ñòò’™„ «V­JKKëèèhooOMMuww'®ÛŒ3°+¶(CCC•••¡¡¡«V­Â.ÚZYYÆ€mÜõõõ¥ÃA ¨§§‡]ä¥ÓéhxWW²ÝÙÙ)}.¡@¶Iˆ¿äMP=|wY*…@ ðõõÕÑÑa0h÷©´;áØ€ ǶഠJ”[¹8f^Zœ«G "ö(êü#ÕÒ‚Z]"áZZZØ{-d[fr"m¸ô°õÑ£GK–,ÑÕÕÅÏd‘> ìëë355=w©i__Ÿ6é=‡ó\ֻ䭡¡!¶É<$õôôí)S¦Bt•–B¡`m±»ÍÍÍ .š2eê”)Sg϶‘~QæÙ³¢Õ«×0™¦S¦Le0LЫK¢D |$Ö‹‘ŽŽ ^¿}ûmŽVö¸qãP/Ä)‘‚=zäþýK—.·³›sëÖí·¿äM¥Rëêêíúúz4|Ö¬Y.\`³Ùû÷ïߺu+¨­­ÍårGvZýòåËÁÁÁ§OŸž9s¦ôщ'†‡‡?{ö ?´uuuT*Ù¦P(èʶhøo–H¯ÝKÛ€o3¥¥¥nnnè;ž¯²X¬Í›7ËL(aC~~~tt´ºº:™LÞ¶m²CP7++«ÇK‡«¨¨0™Ì½{÷bs{òä º7¨¨¨ Éør*¨€ ÍÍÍ%²E6LLLÐ6Š‹‹MLLˆ ² $ 4Õ“ëN K@¥ÐÔÔÌÎÎnjjªªª¢Ó鎎ŽÃt'äÚ6üÖT‰r+ǼÑK‹sã@ÄE4ZZEÑÖÖ.//G›Vt›xކs¹\ …"‘Êßß?((¨¢¢¢­­ YOSÈ P÷'ÍØ±c7mÚ:vìXålñÞóoñ–÷¼ysÿõ/Ù¯ukjjÖ××#Ûuuõèľĩ%v)JaáC.÷%òãpj%¢…„lúòË/=úƒÃ©-+{¡h>§FŽ–––9r½%YH°D h–––?üpüÙ³'ééß&$$¼ý¥§§gBB‚P(ÈÃÁÁÁ555ýýýCCCèƒDÞÞÞ;vìàñx]]]III#ò^grròÕ«W%Ú¸ 6TUU 477§¦¦bŸø‘ù-†øøx@ÐÚÚçéé‰zxx$&&"EKHHPÔ6 ›I¾!ñ)vðúÛo¿©©©ÙÙÙ±Éd²X,ä¡7‹Åd2åê†âââ’““ƒî._¾üÊ•+---ƒƒƒMMM©©©³gÏF^»vmÉ’%#ÛNVTT p¹Üˆˆˆ•+WJ * H@Ÿ¤¤¤ŽŽŽŽŽŽÄÄDgggÔcSSS›››ù|~JJʺuëð…± $ DÔ¹•@¥ kllìíí½yóæîÝ»wìØ¡œ;l ¢Ò¶ J…‘n4Ò‚®b"à䉞WQçǦU¢¥U2™\]]îDEEq8œòòr‰Ï5ÈmÃÝÝÝðØØØÕ«WK¤êííUSS7n\]]öD‚f€º?™DDD‚ððpålñÞóï2 ŒŒŒ<|øè¹sçÚÛÛ{{{‹ŠŠ7m E¹º.OII­­‚]»¾^¹r‘¥¯¯ÏŽq\.÷Í›7•••‘ÑúúúÆ÷ñÇ×××ïØ‹†«««c åƒsjP8º *‘Ä©AÑ"#7³Ùµ}}}CCƒƒƒCo@7iÒ¤éÓ§Ï›7oîܹh¸³³³··7NONNÎÊÊBwìØÁ`0f̘¡««+3Cì ´ô·ë$‘oz! ¯†,]º400F£9::¶µµ–+V ÷ʱ±±—/_¶³³ÓÑÑY´hQggç©S§CÈGF°ŸƒP¨Ò]]]ƒƒƒi4ÚÒ¥K ÷ìÙ#!¨€ |}}uuu­­­ÍÌÌD"ÑÞ½{ÑɃ9sæØÙÙÙÛÛ;88|ùå—øB±MB@àã…Õ¹K@¥°µµurr200øá‡Î;gll¬œ;l b›€¤U"(œˆy£‘t¹Xpò” Èù‰h«hK+—ÈÈÈÏ?ÿ-×æÍ›çÌ™³råJ]]Ýàà`œËÛ†£·@IIIZZZVVV³fÍ¢ÓéÒc¾C‡%&&Nž<ÙÍÍÍÁÁAQ3bccevŠÞ±HïùN¡³=6v6è·ß~Y³Æ·½](71ñ˜D¨­­Ý»÷û‡ÿèíí566Ú¸qR…}}};wîBîï—/_þõ×ÉÈì±ÑË—ÿ÷íb‰Ý¡¡¡¬¬cçÏ_hii100E¾‡Fû÷¿ï¦¦¦þù'F£mØœ”´ ?z4ëàÁC"‘ÙåƒEâÔ ptT"‰Sƒ¢]¿þ¯ÌÌ̆†ÆiÓ¦%$ÄÍŸ?øâ75ýYVVâä´D¢r/]:knniffIzÿ¢P(Èçy544ä¾c^SS³fÍšââb„Db±XÏž=ÃùÖÚQY[[+±~„êA Ä!Ò†CˆpéÒY´ëÿ³¹a¢Ú§cƨªªŽQUUUUU3ÙÜ­ª-£këaóÙ½;c”mm­°JÞwøü¦~@Y\\ìíí|§1Љ‰éïd0ß|ó tJ8 ÄP~4|kÄoýV %pqqÁ~Ò ò÷w"‘hÙ²eèR8@ F`@94”÷ì÷¢qnmƒ‚‚@@ w8=ùa(á %@ PÂ%@ (!@ ïí€rê@ ”JR^^E„@ J%17·,+ƒÊssK(@ ·= ¤Ñè4Š@ òwFJ@ J@ PB @ÞOd?CyéÒY( „ Šþ V^^^HHÈŸþ ÿ>ë}Åb=}úôìY9-ƒµµõ–-[ bJÕƒ@Þ~ê/Ìö:ã°”kÖøB烌*ÉÉÉË–-û0Ú”žžžž#GŽÜ¾}[nÌ””—M›6©©©Aݪ@Þ_d (ÛÛ…PȨRVVöÅ_`Çyÿõ½v‰ñŸtøÝ»wþøcl3ÿI‡:t(44ôåË—………êêêAAAØ£»wï^¿~ý_U®Ã‡‡‡‡WWW—””0™L???"6ËÕAf¹Ú1dddøûûãÛÊ'::ZMM­´´ôñãÇ\.÷ܹsÒg¿uëv:9 àÍ›7ׯ_çóù=rwwÏÈÈ@º¹¹ýòË/rµR¨Ò%Ê+3À€[[ÛŠŠŠòòòÙ³gâœÍfûøøøûû³Ùì;wîÊ­,™gUŽ€©°yQd!‘ª)©¨ëŽ”zÃtºS§N>|ø0///;;?œˆƒFZ"Î’Ç"ŽÜêPBCÊøñã±rNN΄ >übÇÄlokÀüÉü]¼˜]Vö\ü¿ôöö†††Òh4ƒ‘™™I&“‘ð;wîØÛÛS( ‹ü ìïï744Ô××?xð X,&cÀf+±+7\,‹D"mmmt·ªªÊÒÒ²§§GnVd2™ÅbÓh´°°°ÞÞ^$¼§§gãÆÒES‚žž¬m ›‰”¿\ýýý³fÍâñxrm™NoooG¶ÛÚÚ\\\¤ó±´´|ùò%ºK¥R_½z²ŸÃáÌœ9“ J+HEhkkcëQnÁÏŸ?Ÿ§ÌʃSâG!õ$,$R•D”$âº#¥™L>qâÄŒ3¨Tª½½}~~þ… ¬­­©Tª££cYY™rR,^¼877ÙÎÍÍuvvÆÇ‘=4i‰8H:œ<± ¢ \›å–§Õ=qâ„………B™?~ii©Ìî````×®]FFF:::ÝÝÝhòƒš™™ihhà·á‘‘‘zzzzzz[¶lAÃÑü9Ž——N×ÖÖvwwçóù ™êþ$Š|õêÕÅ‹£!NNN×®]SÔéÞ$ãp¸x1íñ_Tq8µõõœÆÆz¯‘Ïçµ¶6 …­íímÝÝ]"ÑëÞÞžþþþE£…˜˜í£5Ci``d``ô.¤ß} ßYÒÓÓAIII^^^^^×ÐГ“STT„Î •——çææ744 +,#2]q÷î]t7)))66–àógùùùÅÅÅ|>÷îÝH`ZZZww÷‹/òòò ”6L$9rdÁ‚rm&~¹.^¼8gÎìº6È™ùˆÅbìnEE…ô)š››±ùÛÙÙEFF¾~ýZ:2Nçóù£pÁP©T‹588(aÑ¢EèêêêììÌÌÌtrrÂ/øƒø|¾‰‰‰®®îúõë;::†YY ŠEÕÃZH¤*å*IÐuGP½{÷îݼy“Ëå®^½ÚÓÓóöíÛW¯^år¹®®®QQQÊIQYYiee…lÏœ9³²²?œˆƒRZ¹ÎBnžmS4‚ç•Éýû÷oݺÅår—-[)³;Ø·oßóçÏssskjjÔÔÔ’““ÑäEEE÷ïßokkÃoÛššž>}úäÉ.—›žž.aƒ··whh(›Í®®®f0ñññ ™êþ$puumooÏÏÏ'‘HyyyË—/WÔéÞ$ã{6CYVö"88ˆÁ`P(ccãÀÀ€/ž£G‘156¾tÈðdÃÉ ÎA*1CijjZ[[‹l³ÙlôvÊÜÜ<++«¡¡ÙÌ̬ººšÈ4‰¢3”%%%VVV555Èî½{÷‰dE&“Ñ"ÔÔÔ˜™™¡Eãp8Èvmm­r3”ˆk£§ÙL¤¼øå´±±©ªª’k(??¿ˆˆ¤ ÓÒÒ’6IGGG$¡»)))ŽŽŽ:::Ó§OïììDöõõÑh´‘¡Dç JKK]]]ãââ¤òx< ¤àMMMø×ÔÔ  …mmm7n  XY2mU„L¥1åÔ“°HUâ+IÜuGJ=2™, Ñ)F‰]œ"àŸnÒ¤IH#U£©©‰NÄÁF/-¾ó€¤ÃÏ“ m8Mœrâ·º2+W TŠææfƒFCfòðÛp6›lWWW›ššâ”T$*d¨û“.òéÓ§=<<Äb±‡‡Ç™3g”°Ô{*w¼+3”MMM+W®úý÷{d–”²nH¤3gÎüþûïŸ}öÙôéÓoÞ¼‰ÎÍ ï"Œ ùùù¾¾¾'OžD_¼HLLLIIQU%:éŽZ>uêÔ––´hzzzÈö”)Säf¢ñ_$NjhhذaCxx8¾ÍDÀ/×õë×  †ôƒS6€òùþûïÛÛÛMMMíìì¦L™2iÒ$é³hkkc§ÍÔÕÕïß¿Ïãñ~þù玎ì³_<OGGg4o̘1æææÇ?þ¼ôѰ°0///.—ËápÖ®]†_ðñãǧ¥¥Mš4ICC#==]âlE+ T2Äy†’¸zÒ©J|%‰»îª‡ÚùüCbw``@9)&L˜€NŸ¿zõjâĉøáDlôÒâ;ü<‰Û¦hþÄÏ‹_× Êmjj²³³CÚU&“ÙÚÚŠ½Žä¶á­­­S§NE¶§M›†MŽðøñã¥K—Òét ]]]t¾“  îO//¯òòòóçÏ———¯]»V @½§×ÈÛPïËbíññq¶¶6ãÆµ³³‹‹„û÷g"kÇ EäŸþ‹91™¦‹;ÿñG!º.sâĉ… ?g2MçÍs¬ãíí½cÇ×ÕÕ•””4ü³:t(99ùêÕ«Ý•Ä|·]âããAkkk\\œ§§'èáᑘ˜ˆ-!!AQÛ6lØPUU500ÐÜÜœššŠ>Ѳ™ÈÀ§\¿ýö›ššš@ù„……566öööÞ¼ys÷îÝ111Ò6¸¸¸ää䠻˗/¿råJKKËàà`SSSjjêìٳѣ׮][²dÉȺ\```EEÅÀÀ—ˈˆX¹r¥´€L&“Åb!ÏPîß¿ŸÉdâÜÇÇ'))©£££££#11ÑÙÙYneY8â@D=…8U‰ªRRQ×AõF£5ðòòJMMmnnæóù)))È'p‰8Øh¤%â<8` <åV7þª‹r* ™L®®®Fw¢¢¢8ÎÀÀ@yy¹Äçä¶áîîî Hxll¬ô¸z{{ÕÔÔÆWWW‡}‘  îO&@b¾Ÿ¸ #Þ{Ž6Éjÿï]w'§%hL&«cŠ­N"ð9±­[7?þóÏ’H¤úúÿLü õÐÐMjjjë×ûgfÈÎ>ôN½eüøñÄËpþü‰´aCиqゃOœ8qþü…M›Bðayýúõرci4‰¤bmmmmm--ÅßÐ'îccc£££§OŸþé§Ÿ†……¡«]ÎÎÎÞÞÞuuuFFFYYYHàŽ;vîÜéàà ‹eV°M²!ý©Elxbb"‰D¶¿J|‘aΜ9sçÎ}õêÕªU«vìØ&$$DEE!E ù÷¿ÿ­PžK—. ¬ªªÒÐÐprr:yò?wJméâûï¿—~kd[[[''§®®.;;»sçÎÉ\w[±bERR—ËEÖ’bcc³²²¶nÝúêÕ+ …òùçŸÿøãÿ½«|YXXxüøqâý‘Jwuu ®ªª¢P(«V­’Ù¶?~|ûöí™™™b±ØÆÆæØ±cøøúú644X[[‹D"ggg‹%·²@6ˬI€Td!‘ª)©¨ëŽ zÃtºõë××ÕÕ!áëׯÿòË/‘£ p"6i‰8H:œ<åV7‘êPTC¥‰ŒŒüüóÏ_¿~ذyóæÌÌÌ•+Wòù|ccãmÛ¶)Ô†'%%mß¾™æwww—ó:t(>>ÞÏÏN§GFF^¹rE!3@ÝŸ¢7BDl Ò{¾S¨ÄÄlÝ!1 DGÈGÎmmíÁãÇ45ÿ³r/¶ÙØØR(”‡¤ÿ®zc§'%B°» †‰Äª¿ªª*›]-3™HDC2d³«UUU™}ôQuu%þ!l&?þxê»ïöôõõ3ÆÜÜlÛ¶h‰¤¡¡ù·M^ºtvÍßK—Κ›[š™Y~%¢P(B¡Dìû±ÕÕÕžžžÅÅÅpNšD"±X¬gÏžÉý术¯ïìÙ³áŸ*' T!üÛ³ìë‘©C‰ôgsÃDµOÇŒQUU£ªªªªª:f ²!¹[U[F×ÖÃæ³{wÆG s`ÿ/ç³ÏüüóÿËËË[¹ròàÁ‰´`Á|%¬§Ñh ý¡¥¥5"rhiiñù|¡PˆÐGwqýï­˜ÿºuÞ•••þñÝw{¶lÙúøñ#üÓ ‹’’ô9âããcbbúûûwîܹbÅ ¨ÁQŽÜ?û†Bõ È ¡g(·lÙ¬¥¥õí·éOž<éïïüøIzúnMÍIQQÿYûGÞóBµññ÷÷#‘Hßuuu‰D¢øû § îî«H$Ò±c?ôõõ?~‚D"yy­•{KhhXee¥©©éœ9ö$iÜ8øGº ...D>Ù…üýݬY³ÔÕÕeþy@  >"‰F£]»v…ÅÚÞÑÑA&“,˜¿ysNG"DD„:tø³Ï’,X¯_ﯦ¦væL¶­­ýرcgÏž8¬ÿÊ‹ŠŠºråê©S§¨Tê–-›Ñ§$qaY½zõ·ß¦?^¢¢¢2kÖ¬øx8ŒøÁ~·g­$((ô׈y€ëÝï Àg(ájïßÄ >¤g(!`Ág(U¡š@ á”@ 8 „@ üu|4œÄMM–••@? ÌÍ-i4:Ô@ È[P–••€þLò~Q^^RVV”@Þö€A¡ÿ›‡@ À¥$b±ê@ ”p@ @ J@ È[g>$ÁßûþŽhhh(š$//oúôéJ$„üU°X,__ùoàùøø°X,(—rBõ Qb”º›·ß‹½Ëý&œ¡„ü$''gdd,[¶ìí_Šð`• §§çÈ‘#·oß–3%%ÅÅÅeÓ¦MjjjP7E„êA ÷—™¡|çÐÑ¡‰!Šð–Ý®¬¬ì‹/¾ÀŽó¤ÇÒáwïÞuww×ÑÑ144  …‘ef…0Ú£É_ýuÙ²e:::MMMø6)/‚P(477Ç’(ï”)SðmåC"‘JKKW¯^M§ÓÍÍÍÏŸ?/}öëׯÏ;wÚ´iÈnWWW\\ÜŒ3(ŠÁW_}õàÁä­­í7ˆ î‰W:‘Êe³Ùîîººººîîîl6[¹‚ãTÛЊÀPC …ÔYØÙÙH§ÓŒŒvïÞ­‹‚Òâ—NZUPþøµ£4 )Äbñ®]»¦N:mÚ´¯¿þmâ@áDì-¤•é< ÇåIðÂÁ7'"n¦ÐŒÚ;2å6‚fhhhØÛÛckD,ÛÙÙ½/R|˜Ê®®®¤¤¶¶vzzSLM̓òòò‘C4š.NBïO…NÔÒÒgkk7eо©©™¯ï—¿ÿþ;z"äÇ`07lØ(äÚ†ßο퀲¯¯ïã?ÆŽódõd†:t(44ôåË—………êêêAAAñA¹½>^]]]RRÂd2ýüüäÚ,·¼»wï^¿~½td„ŒŒ |@ù°Ùl6›}çÎÂÂBé³ßºu ;ðæÍ›ëׯóùüG¹»»gdd GÝÜÜ~ùå¹Z)TéD*7 ÀÖÖ¶¢¢¢¼¼|öìÙÊ\®ƒá†­%Raó$¢ÈÂèèh55µÒÒÒÇs¹ÜsçÎwQPZüÒI« Ê¿v”$Å©S§ >|˜———NÄÁF#-ç9Ž=D.¹í$("n?~<öBÎÉÉ™0a‡_옘ímmìïâÅl±X,(ó‡Ä|ýúÕ(ýÜÜÜ¢¢"+*Ê»º:ëëëþùÏ.Yâ‚"“É#u–ÚZ¶……ŷߦUUUvvv466\»vÕÍÍMâDuuÜàà`ooo¹¶a#hç¨þÊÊž_¼˜-]¹/f—•=—}ööö†††Òh4ƒ‘™™I&“‘ð;wîØÛÛS( ‹ü ìïï744Ô××?xð X,&cÀf+±+7\,‹D"mmm¹Ñ¤³"“É,ËØØ˜F£………õöö"á===7n”.šôôô ¶l&XÞªª*KKËžž™öô÷÷Ïš5‹ÇãɵAf>çÏŸÇ/‹¥¥åË—/Ñ]*•úêÕ+Pd‡3sæL‚*¬t"¡­­­Ç)¸ÌʃSâG!õ$,¤ÓéíííÈv[[›‹‹ q•›Vºtøn)‘?¨vÈdò‰'f̘A¥Ríííóóó/\¸`mmM¥RËÊÊ”“bñâŹ¹¹Èvnn®³³3~8N ‡F#-ç¹ NžØ†NÑ P®ÍŠºÖ˜'NXXXP(”ùóç—––ÊìvíÚedd¤££ÐÝÝ&?xð ™™™††~©§§§§§·eË4ÍŸÃáxyyÑétmmmwww>Ÿ¯ îO¢ÈW¯^]¼x1âäätíÚ5Emî=A2l×ÿ¢¢ˆÃ©­¯ç46Öóx|>¯µµY(lmooëììèîî‰^÷ööô÷÷¿¨(’ÆÄl§g( ôôôTUU'Mš´|ù²K—.‰Åb:}2‰D¢Ó'Óé“ÿë⓳²²¬­m&OÖCvÑðƒ͘1ÓÈÈxëÖ­¨oaÙ³g¯¯odd$N3f ™L^´hÑÙ³ÙèÔ²¡©©¹kW2zlÃ"m'—[çïïolÌ000\·Î§¥¥s DÏœiuøð4þÝ»w.ü|êÔivvöÙÙgß‘Êôôt@PRR’—————‡†‡„„ÄÅÅ544äää¡sååå¹¹¹ÅÅÅ èºóˆÌ#Þ½{ׯÆÝ500 R©666,kpp?mAAA~~~qq1ŸÏGnÒÒÒº»»_¼x‘——WPP ´a"‘èÈ‘# ,k3’’’bccAÏÕ]¼xqΜ94M® 2óyðàŸÏ711ÑÕÕ]¿~}GG‡ô)š››±ùÛÙÙEFF¾~ýZ:2Nçóù#~ë+·r-ZtàÀ®®®ÎÎÎÌÌL''§á\¡ÊU„´€8(ªÖB‰ ¹¢¢‚¸‹ÊM+]:|·”ȧvîÝ»wóæM.—»zõjOOÏÛ·o_½z•Ë庺ºFEE)'Eee¥••²=sæÌÊÊJüp"6Jiå:¹y*Ñ*É_!7“àþýû·nÝâr¹Ë–-‹ŒŒ”ÙìÛ·ïùóç¹¹¹555jjjÉÉÉhò¢¢¢û÷ï·µµá·áMMMOŸ>}òä —ËMOO—°ÁÛÛ;44”ÍfWWW3Œøøx…Ìu¸ºº¶··ççç“H¤¼¼¼ÎÎÎåË—+jƒtï ’ñÙ¡ìîî¥ßÒ¥K¾úêËßÿwss“Ä!2™,±ëç÷U]Gâ(™Lvw_ÅáÔ¾|Évs['}ƒQSS²{¢ÆÆz&×6;mmmoß¾ÕÚÚÌã5FGoýê«/‘ð;b<==ëê8N­‡‡šÊÀÀà§Ÿþ)´TVVlÚ´iô¤Vh†ÒÔÔ´¶¶Ùf³Ùè픹¹yVVVCC6²™™Yuu5‘9Eg(KJJ¬¬¬jjj°¥¥¥®®®qqqø÷ÊhjjjÌÌÌТq8d»¶¶V¹JäÓØØ=¾Íøå½w£ãàà L)mllªªªäÚÊGSS344T(¶µµmܸ1 @ÖCÉ:"‘ÝíèèHIIqttÔÑÑ™>}z|||gg'z´¯¯F£׊x8~åòx< ¤àMMMÃ,8¨²dÚª™’¥¿Ñw:IDATPN= ýüü"""),,LKK‹¸‹â§•.¾[Jçª2™, Ñ)F‰]œ"àK1iÒ$Ä6ÄxMMMüp"6ziñäxøy´ §‰åOÜͤ[]™•+aƒ……*Ess3ƒÁ@£!3yøm¸‰‰ ›ÍF¶«««MMMqJ*‰ 2ÔýIùôéÓb±ØÃÃãÌ™3JØê=•»FÞ å (»º:FéרXŸ””8oÞ\m33³mÛ¢ÿü³9D&“±1Éd2›]ÝE7JJŠ‘íââg¦¦&ÒgÑÒÒjk`Ó"HdÅáÔzz®‘k›„a Òµ´ð mfii ²]RRŒ¦233=p`eeù艌üPjjj ÛoÞ¼A¯§OŸzyyš››ß¸qüæÍ›PæååYZZÉLÂçóõõõñ›6lÐkT4üŽY:Zww÷ž={–,YBÜfPyîÝ»’âÊ•+^^^2ó‘°”žžv‹N§Kg5cÆ ìŠ-ÊÐÐPeeehhèªU«°‹¶VVV£1 Ä¯ÜU«V¥¥¥utt´··§¦¦º»»§à8•%Ó6œŠÉ› zÒ ___ƒ‘‘‘vDÜ?­téðÝR:Píà?Û@ðŽNZ ==½®®.d»³³uP8½´øÎRƒˆ=rmÃQ”¿Bn†³þ ×ÒÒÂÞk! Ü2“iÃ¥‡­=Z²d‰®®.~þ 3ˆôH`__Ÿ©©é¹sçLMMûúú”°Hï9œç²þ^KÞŸ|òɶmÑ997+++Μ9ÝÙÙ*±îR(ì.º1yòdôòhmHŸECƒŒ ¯«ãÔÕq°9L™2uÊ”©ŽŽ ^¿}ûmš\Û$ Ãî>{V´zõ&ÓtÊ”© † ÚŸµ¶ tuuÑ'TÐTG¹ÿÁÒ¥ËíìæÜºuûYò¦R©uuuÈv}}=>kÖ¬ .°Ùìýû÷oݺ ÔÖÖær¹#;­~ùòåàààÓ§OÏœ9Sö»fªªrÿb-B]]•JE¶) º²€-þ›%Òk÷'N öìq›A”––º¹¹¡ïcJ¼Èb±6oÞ,3¡„  |ÌÍͱ©TTT¤³²²²züø±t¸ŠŠ “ÉÜ»w/²²ƒðäÉt¥lT^$Tn~~~tt´ºº:™LŽŽŽFMR¢àJTNEà( AõdZ¨©©™ÝÔÔTUUE§Óqrpü´Ò¥ÃwKéüAµ3J­‰ÉÿoïÜÚ:Ò?áWMQW.r ©ZÁä*¢h·bAeÁ"(þäj•K¼ý ± T-ÐGÖµÞkµÏ£­U[©¶Ý®¸€â•R.% DÀnT.b€ßg÷4›äL&º`ßÏÏsÎäÌä;ï¼™yÏœsæX“wÝ”——[[[£Óqlìò¢‡ =šöŠ8åkäfZ`jjZ]]Mv­änü>œL‰DÆÆÆ ¹"""bbbjjj¤R)q=M#Tß2“'OÞ¼y3‡Ã‰‹‹›xýñÇét:›ÍÆÑ@UκuëÒÒÒº»»»»»ù|¾²__ßÂÂBrwÅŠ_ýu[[ÛÐÐPKK‹@ pvv&?½páÂòåËG·ŸÂi\‹•››KÜ¥———Çb±´«8¢±¨PÙ"À±•ÂøøøæææK—.egg'%%)[‰Ê=yU֎ʪTåSµÎõ¡¡¡ µµU"‘ddd¬]»Žã`c‘Çy¨@”I~/Bj5#\E;ôõõ…B!¹µeË–††™LV]]X@eÄãñˆt.—»zõj…\t:}Ê”)>”¿SÕð§’ÄÄÄŽŽŽ„„í4Œúèù‡(׬ ûæ›oÛÚÚe2™D"Ù³g¯££#ñÑŒ3äoPC”»w :::ÚÛ;>ø ã/ Tþ–mÛ¶?~"//¿±±ñùóç½½½W¯É— ©6yt>{ölÊ”)¯¼òJcccr2—,|åÊ “ðÑŒŒL2ÃÙZ_ÿÙ³gÃÃCCCÃã$ är¹†††vvvK–,qww'Ó}||˜LfzzúÁƒ‰Äääd+++GGGsssª.ŒœçP^jQ!Ïç777kz†ÄÆÆ2 ???KKË={ö káæææîîN,¦˜œœL$òx<===¢j...òkáàççÍ`0<==¥RéÑ£GÑš1í@ž}û”ŸZ Ò@Exx¸¹¹ù¢E‹lllúúúöîÝ«|ÌÊ•+KKKÉse.—{îÜ96›mff¶téÒžžžcÇŽ ¸Èß~Ž·ð§q:TZZÊb±X,VIIɧŸ~ª]ÅE¥YeC H£X ÓzT ]\\¼½½-,,>|êÔ)+++|EäU[;œò5mL¨LéææÆf³]]]=<<Ö¯_OO•Žã`c‘Ó¼*Q¦ü£!TÚÔö6Tå㸙Fp8œ·ß~›Ô°uëV77·U«V™››ÇÆÆ"~*ûð´´´™3g:99-\¸Éd*Ç||>ÿµ×^ ôððÐTÕð§é‰ŽœÑs\1))é}.7ù?϶¿{÷Ýð®®NŒórÜ#µãƲ'NܸQÖÛÛkddäååùþûÿ7sæLö·¿Ü¿¿ ¯¯ïÁƒzfa1Ø w-,æ%''9räéÓÞ+ü‚ …™g±XœŸÿIQÑ5©T:}úô 7lø_///å’q´É£ óïÿI ª¬¬Üµk×õë×õõõy<ÞÚµk•|{l£Éï¿ÿ>??ÿÎ;zzzþóŸƒÁ@kV[_‚ÎÎN///±XL~DL0}úôÆÆF„ªrp´]¼xÑÝÝ}îܹÄîãdz²² ÅbñôéÓ=<>~``€Hïïïï½÷”«¦ýýý¤6*͘õ­­­uppèïïW©gpppáÂ…b±X­t9m< wMLLž>}J¥¿¡¡aÁ‚˜VÂltœ†055•oGµÇq•¡ƒh"ª£‘õ2™Ì®®.b[*•úúújQ;-\ÓJ*[A__ÿÈ‘#ŽŽŽ&&&®®®%%%_|ñÅ¢E‹LLL<==«ªª´3ŲeËŠŠŠˆí¢¢"t:¢EÈÆ"/ŽóP™Q¦|G§éPeÛâôºGޱ··766öòòª¬¬T9Èd²]»vÍ›7ÏÌÌ,**êÉ“'döýû÷ÛØØ ûp‡3kÖ¬Y³fmÛ¶L'Ëohh e2™¦¦¦AAA‰D#TßB•ÏŸ?¿lÙ22ÅÛÛûÂ… šjP=©Ìø"œ9s’ñ©¹×Ðp¿±±¡¹¹Q,n–HÄíí­í]]Òžžî'O÷õõ ôþRsO!ZHJzf(ÉÊÊêè訨¨(.....&Ó7mÚ”’’ÒÔÔTXXxïÞ=rN¢ººº¨¨¨¼¼¼©©‰¼Â¢öüó‚×âÅ‹‰íþóŸ‰ÄÚÚÚÜÜ<22²»»·´´´¤¤¤¼¼\"‘dgg‰™™™Ož<ùå—_Š‹‹KKKµÖ××wàÀ7ß|­“´´4.—Ku_Ý™3gÜÜÜä¯kSi@—ƒÐÖÚÚ*_>›Íæp8eee½½½Ê3™L‰D2ê^gaaabb²xñâÜÜÜ¡¡!å–.]úÉ'Ÿ<~ü¸§§'??ßÛÛ]q‡Ñ¨±¨BÙ€4µž¼Â‘‘ùjjjðó¾ˆëbZ‰Êý®^½zéÒ%‘H´zõê+W®œ?^$lÙ²E;Süúë¯NNNÄö‚ ~ýõWt:ŽƒQ^µÎC…Ú21µ–mq¸víÚåË—E"‘¿¿?‡ÃQ9|üñÇ?ÿüsQQQ]]NOOO'³ß»wïÚµkR©݇·´´Ü¹sçöíÛ"‘(++KACXXX\\\}}½P(´²²JMMÕHÕð§@@@@WWWII F+..îééY±b…¦”GO*3 %üMÔÊùóçß¿ŸØ®¯¯'O§lmm<ØÔÔ$°P(Ä9 Öt†²¢¢ÂÉÉ©®®ŽØ522Š‹‹ëìì”J¥ï½÷^TTú\™¬B]] Yµ††bûþýûÚÍP§˜o¼ñùTšqê{õêUOOÏ¡¡!•¦Z¼xqmm­Z èrÐÚÌÌÌúúúÈÝîî OOO333;;»ÔÔÔžžòÓgÏž1ŒÑ¡$ç *++RRR”?‹ÅöööDÅííí[ZZÐWë0TQ©ª!TP_ í¬§ pÆ ‰‰‰Ä€?sæLü¼Z».¦•¨ZA__¿³³“œSØEW!ÃÐÐø.¢iŒŒŒÐé86vyÑÎCeRt™˜Úpº8LÛâÌPªl\ ööö¤)Z[[­¬¬ÈÈ™ûÇ?þñÖ[oÙÙÙ]ºt‰œ›!ŸEEJJJÂÃÃ=J>x¡§§—™™ihhh``••¥ö‰ZRù믿ÞÖÖFVmÖ¬YÄöìÙ³ÕÊ0ø7 7'555mܸ1!!­>ŸŸ‘‘¡££ú§zñâEKKK+++å§4 ËAk355•Ÿ6›1cŸÏ¿víšX,þòË/»»»åïý‹Åfffcáxººº¶¶¶‡:}ú´ò§ñññ¡¡¡"‘¨¡¡aÍš5ñññ芣FÓÆ¢j•DÜC‰o=e…ûöíëêêš?>›Íž={¶¡¡!~^í\ßJ÷#u¾úê« »2™L;SL:•œ>úôé´iÓÐé86vyÑÎCºL|m£e[p·¥¥…Ífý*‹Åjoo—ÿ©íÃÛÛÛ_ýub{îܹòÙ nݺåççÇd2 ÌÍÍÉùNLTß2¡¡¡ÕÕÕ§OŸ®®®^³f¨FO-~#¿PcbbòðáCb[þ™Ä… ~ñÅõõõyyyÛ·o'»‘H4ºÎ;{âĉ ‰¶¶¶òÇ <¤Ñhd>|hbbBl“WT>n©·©¼v?mÚ´„„„»wï¢5ãPYYH†­ ÁknnîÖ­[UfTЀ(G­6''§[·n)§Oš4‰ÅbíÝ»—¸²CpûömòŠØ˜ôY::ÃÃÃ*G¾;v̘1C__ÇŽ¤$ªŠ#F‹ÆB4€Ê`ZO¥B##£“'O¶´´ÔÖÖ2™Lâ¹{̼Z¸®FVB»ñ¨÷ÖÖÖä]7åååÖÖÖèt»¼hç¡GŽ¶Ñ²íhajjZ]]Mv­TÁ¢'ÓE"‘±±±B®ˆˆˆ˜˜˜šš©TJ\OÓHÕð§ÌäÉ“7oÞÌápâââ&Ož¬†Q=! Æ!!!<¯³³³££ƒ¸ùƒ 66¶®®npppxx˜¼Y',,,99Y,?~ü8--íÅ¿½   ==ýüùó ÃØºuëÒÒÒº»»»»»ù|¾ü<¢r9©©©ííí)))!!!Dbpp0ŸÏ'ªÆãñ4Õ¶qãÆÚÚZ™LÖÚÚ*È»Ž¨4㌬ óXòÁë?þH§ÓÙl6ŽªrÚH|}} ÉÝ+V|ýõ×mmmCCC---ÀÙÙ™üôÂ… Ë—/]—‹ŽŽ®©©‘Éd"‘(11qÕªUÊd±X¹¹¹Ä=”yyy, ]q*‡Á1ˆ*a@8Ö£RßÜÜ<00péÒ¥ìì줤$e+QåÕÔu5µÂÇ¢7 ­­­‰$##ƒ\AŒ*ÇÁÆ"/Žó &À¨Ê$¿¡ µ®‚ø^íÐ×× …änTTÔ–-[d2YuuµÂr jûð   G¤s¹\å¸ètú”)S>|("¦ ªáO%‰‰‰ óýøF}ô„€wp¹\CCC;;»%K–¸»»“é>>>aaaL&3==ýàÁƒDbrr²•••‡‡‡£££¹¹9UFÎ[ÈX*Óù|~ss3y1ÂÀÀ€¸þnnn¾hÑ"›¾¾¾½{÷¢káæææîîîèèhllœœœL$òx<===¢j...òkáàççÍ`0<==¥RéÑ£GÑš1í@ž}û”ŸZ Ò@޶•+W–––’çÊ\.÷ܹsl6ÛÌÌléÒ¥===ÇŽ#>"‘¿ý1ná7z@@@ll,ƒÁðó󳴴ܳgr‡*--e±X,«¤¤äÓO?E  r„A¨4«l„ir7KÈ…i=*…...ÞÞÞ‡>uê”ÊK¨Ty5u]-¬4PɈŒŒtssc³Ù®®®ëׯ'ާJÇq°±È‹ãÿµ×^ ôððÐTÕð§é‰ŽœÑs\1))é}.7‚$€bêå;[[‡— .ÃÃÃÆÆÆ4¼õc…BaHHHyy9¸FËÍͽ{÷®Ú5çÂÃÃáåÚ¬˜ÀkÏF‹³g?÷öþ×U‘G­MÓèÒÕÕÑÑÑÕÑÑÑÑÑÑÕ%6wkïW1MgÉ—“ó?`MàBEEyŸ ‚ÔÔÔ¤¤¤ÁÁÁ;w®\¹ìF€å¨}Ù7¬Àø¤¨ø'üƒ×oPH€ø£àëë+¿¤ÄëïúúúüýýU¾¼^Jâc¶«=æÖÝë7ï^WN‡€ø£ ¿n âZILL Õ«€ñ\ï;Iš†‡‡†cè_ÿÿN¤Êå/””À¸‡øÌŒ¬³g¾”O Z¹mjÍT˜¡~ƒ›šôæ[^䮇ç’mÛÕ¬À%𺺺Y9™vöv4ÍÎÎv·`—Ž®”€ÐéôÜü½nnì=çL¡OQ{<ÜC (b``¿?—X‚RíÁ0C ¼P/\òh·T½¤J`°µu¨ªª¨ªªSÀË7ÊËïÞ„€# &ƒÁ;ÀËÍÚà /’î¡^(€(ŸGPS Correlate Documentation: Concepts

GPS Correlate Documentation: Concepts


Step 1: Taking photos and recording GPS data.

Before you can correlate any photos with GPS data, you first have to have some photos and some GPS data. This generally involves walking/driving/going somewhere, taking photos, and taking a GPS (or GPS like device) with you.

Most cheap GPS devices have a "tracklog" facility that record where you have been. The resolution of these tracks is widely variable - my Garmin eTrex GPS records up to 1600 data points, at around 20 metres between each point. This works out to travelling about 500 kilometres (by car) or 50 kilometres (on foot) before you run out of memory. (These numbers vary very much depending on many factors.)

Other GPS devices will vary in their capacity. If you are using a GPS device made only for a computer, or have attached your GPS device to a computer, you may wish to log the data on the computer. In this case, you will probably end up logging the one second fixes, which results in a fair bit of data (nothing excessive), but very accurate results.

As to taking the photos? Not too many digital cameras add GPS data to the photos themselves - although they do exist. If you have one, then you don't need this program!

Besides that, take photos as you normally would. The correlation program matches photos to GPS data by their timestamp, so it would be a good idea to synchronise the cameras time to the time from the GPS before you start taking photos. (Not the other way around: the GPS gets its time from the satelites, which means that it is probably correct... and most GPSs will not allow you to set their time, anyway).

The photos will have to be tagged with "EXIF tags", which describe metadata about the photo, including the date and time. These tags are embedded in the photos themselves. That is pretty much standard for digital cameras nowadays, however, some very very very old digital cameras (like the ancient floppy-disk Sony Mavica that a family member owns) don't store EXIF tags. These will not work with this program. There are probably ways around this, though - but they are outside what I am talking about here. (Give me half a second, and I'd start talking about writing a script to grab... no, better stop there...)

When you are finished the photo shoot, download the photos as normal and get ready. Mind that any permanent rotations or editing do not remove the EXIF tags.

What you need to know:

  • Syncronise your cameras time from the GPS time. Don't ask, just do it. And do it just before your photo shoot.
  • Use a GPS that has a tracklog capability. Most do - although the quality/resolution varies.
  • Take your GPS with you, and keep it close to the camera. GPS is accurate down to several meters, so use your discretion.

Step 2: Getting the GPS data ready.

The GPS correlate program accepts GPS data in the GPX format: an open XML format. The format is quite arbitrary, and, naturally, everyone has their own GPS file format. So, in that way, this program is no exception.

Exactly how you will get the GPS data from a discrete device is very much device dependant. Many devices have serial cables or USB cables to download the data (although the USB cables are often glorified USB-to-serial convertors). What format it will turn out as is also the big question. I personally recommend GPSBabel, which can convert between many formats (thus translating from whatever strange one your downloading method/program produces) and can also download from Garmin and Magellen GPS devices directly (so you can download straight to GPX and retain lots of useful metadata, like track segments).

If you logged the GPS data with a computer... well, it could be any format, depending on how it is logged. One trick would be to log the NMEA sentences as they came from the GPS directly into a file. Once the logging is done, GPSBabel can convert these NMEA sentences directly into GPX format. Usually these are one second fixes, so this would yeild very good results - however, lugging around a laptop to log the data might not be convenient. A PDA might be convenient, though...

The other thing to know about GPX is that is stores data quite cleverly, seperating tracks into "track segments". This can be explained quite simply with an example. When I drive through a tunnel with my GPS, it has no reception in the tunnel. My GPS records this lack of signal on the map by not showing a track between the points where I was in the tunnel. GPSBabel correctly reads this and enters this in the GPX file by seperating the segments with "track segments". Prior to version 1.1, this program ignored track segments and just hauled out points and considered them to be a single track. So if a photo was taken in between the time where there was no GPS data, you would not get a correct answer (depending on the situation). Now, the program will not match a photo between track segments. You can, however, disable this functionality, and still match between track segments.

What you need to know:

  • The result of downloading the GPS data could be any format. Convert it with GPSBabel to GPX format, or better yet use GPSBabel to download the data in the first place.
  • Absolute best results would be logging NMEA sentences to file, and then GPX'ing it with GPSBabel. This will give you lots of one second fixes. Very accurate.
  • The GPX format is good for archiving tracks. Due to its XML nature, it should be readable for a while to come yet.
  • The GPX file is split up into "track segments". Generally these define when you were or were not logging GPS data. This program does not interpolate between track segments, by default.

Step 3: Getting ready to correlate.

To correlate, you need photos and GPS data. I assume the last two steps would have resulted you with a series of JPEG files with EXIF tags, and a GPX file with a "tracklog" of where you have been when you took the photos.

The last thing you need to know is the timezone that the camera is set to. GPS data is always in UTC, so when we correlate the photos, we need to know how much to add or subtract to the photos time to make the photos time UTC. Just "know" this value, there are places to add this in in the programs.

(This also assumes that the camera is set for local time where you are taking the photos. It is really arbitrary, so long as you know the difference between your cameras time and UTC. If you really wanted, just set the camera to UTC and be done with it... although thats not so friendly when you try to use the EXIF data for other things. Up to you.)

As an example, here in Perth, Western Australia, the timezone is +8:00. So that is the value that I use when correlating the photos.

Since 1.5.4, there is an option to add an offset to the photo time to make it match up with GPS data. An example case is where you take a photo of your GPS showing the current time. You can use this to calculate the difference between the GPSs time and the photos time.

Example calculations:

GPS Time in Photo:  10:10:10
Timestamp on Photo: 10:09:30
GPS - Photo:             +40

GPS Time in Photo:  10:10:10
Timestamp on Photo: 10:11:30
GPS - Photo:             -80

The value determined in the above examples becomes the "Photo offset" time, in seconds, which is added to the photos timestamp before the correlation is performed.

What you need to know:

  • The timezone the camera was set to when taking photos. In hours and minutes, for example: +8:00.
  • As of 1.5.4, you can fine tune the offset between the GPS time and the Photos time. Use the photo offset function to set this.

Step 4: Correlation.

Time to correlate! Exactly how this is performed depends on whether or not you use the GUI version (prettier) or the command line version (scriptable).

Now there are a few options to consider when deciding how to allow the program to determine exactly the location at which the photo was taken. The program works down to the second. If you have one second GPS fixes available, then the photos will be matched exactly with a point having exactly the same timestamp. As this is the accuracy of the camera, and the accuracy of the GPS - you can not get more accurate than this with the given equipment.

When there is less than one second data available, there are a few options available to make the results more accurate. By default, if the program finds photos between points, it will linearly interpolate between the points to determine the correct location of the photo.

You may not wish to allow the interpolation, for whatever reason. If you disable interpolation, then the program will instead "round" to the nearest point. That means that the photo will be set to have the location that is the recorded closest fix in time. (Rounding down if you manage to exactly center it.)

The final major setting has many names, of which I can not decide the best one for. Sometimes I call this "feather time", and other times I call this "max distance". In any event, this setting, specified in seconds, defines the furthest time from any recorded GPS point that a photo will be matched. For example, if two GPS data points are 15 minutes apart, and you took a photo in the middle, you may not wish to match this, as the camera might well have been somewhere else. In this case, set the "feather time" to the maximum time from a recorded point that you want a photo matched. In the above example, you might set it to 60 seconds, if you know how often your GPS is likely to take another data point.

What you need to know:

  • You can use the GUI version if that suits you better.
  • Or there is always the dependable command line version.
  • By default, the program linearly interpolates. If disabled, it rounds to the nearest point.
  • "Feather time" or "max distance", in seconds is the maximum time from a data point that a photo will be matched.

Return to the contents gpscorrelate-1.6.1/doc/gui.html0000644000175000017500000001000710636505725015711 0ustar danieldanielGPS Correlate Documentation: GUI version

GPS Correlate Documentation: GUI version


The Correlation Window

The Correlation Window, 89,363 bytes

How to use the Correlation Window

Step 1: Add Photos

Clicking the "Add Photos..." button will allow you to select photos to add to the list. You can select multiple photos in one go from the open dialog that appears. Once you have chosen the photos that you want, click "Open" to select the photos. They will be scanned for EXIF tags and added to the list. If they already have GPS EXIF tags, the data from those tags will be shown in the window. Otherwise, you will just see the timestamp from the photo, and the status of the photo.

Selecting a photo or multiple photos from the list on the right and clicking "Remove Photos" will remove these from the list.

Step 2: GPS Data

Clicking the "Choose..." button will bring up a dialog asking the GPX file to read GPS data from. Once you have selected the file, it will be loaded into memory. While the loading is proceeding, you will see a dialog asking you to wait while the data is loaded. Once loaded, the name of the file that the data is coming from will be shown above the button.

Step 3: Set options

The "Interpolate" checkbox defines whether or not to interpolate between GPS points. By default, this is checked. Clearing this box makes the algorithm round points.

The "Don't write" checkbox, if checked, will prevent the GPS EXIF tags being written back to the photo. However, the point that is matched will be shown in the list, which allows you to correlate and check the results first.

The "Don't change mtime" checkbox, if checked, will prevent changes to the JPEG files from updating the files mtime.

The "Between Segments" checkbox, if checked, will ignore track segments, and match photos between track segments. Usually a track segment differentiates between multiple GPS data logging sessions, so interpolating between track segments could well be interpolating when there was no GPS data.

The "Write DD MM SS.SS" checkbox, if un-checked, will force the pre 1.5.3 behaviour of writing Longitude and Lattitude as DD MM.MM values. The default is now to write values in DD MM SS.SS.

The "Max gap time" box specifies the maximum distance from a point that a photo will be matched. In seconds.

The "Time Zone" box specifies the time zone that the photos were taken in, so that the times of the photos can be adjusted to match the GPS data.

The "Photo Offset" box specifies the number of seconds to add to the photos time to match the GPS data. See the GPS Correlate Concepts documentation to understand this value.

The "GPS Datum" box specifies the Datum of the source GPS data, which is written into the GPS EXIF tags. By default this is "WGS-84", but really should not be changed, as the GPX format is only supposed to store WGS-84 data. However, you can change this if you wish.

Step 4: Correlate!

The "Correlate Photos" button will start the correlation proceedure on the selected photos with the loaded GPS data. If no photos are selected or no GPS data is loaded, then an error will be generated. Otherwise, the photos will be correlated and you will see their status change in the list as the process goes on. As photos are matched, their locations are also shown in the photo list.

Other Tools

The "Strip GPS tags" button will remove GPS tags from the selected photos.

Saving Settings

As of 1.5.4, when you exit the GUI program, it will save your settings to ~/.gpscorrelaterc. These will be reloaded next time you start gpscorrelate. The command line version does not make use of this file.


Return to the contents gpscorrelate-1.6.1/doc/command.html0000644000175000017500000001115610636505725016551 0ustar danieldanielGPS Correlate Documentation: Command line version

GPS Correlate Documentation: Command line version


Command line options

Basic usage:

gpscorrelate [OPTIONS] -g gpsdata.gpx photo1.jpg photo2.jpg ...

The options are:

--gps or -g gps_data.gpx Specify the file to read the GPS data from
--timeadd or -z +/-XX:[XX] Specifies the timezone of the photos. For example, in Perth, Western Australia, this is +8:00. This can be specified as either "8", "+8", "8:00", or "+8:00".
--no-interpolation or -i Disable interpolation between the points. Instead of interpolating, the program will "round" to the nearest point. If the photo is exactly half way between the two points, it will round down to the earliest point.
--verbose or -v Show the final fixes on the screen.
--no-write or -n Don't write the GPS EXIF tags back to the file. Useful with -v, to do a trial run.
--datum or -d "datum" Specifies the "datum" to write into the GPS EXIF tags. By default, it is WGS-84. However, GPX is not supposed to store anything but WGS-84, so use if you must.
--max-dist or -m time Specifies the maximum distance around a point that a photo will be matched. In seconds.
--show or -s Just show the GPS data embedded into the EXIF tags of the photos specified on the command line, and then quit.
--machine or -o Just show the GPS data embedded into the EXIF tags of the photos specified on the command line, and then quit. This option varies from --show in that it will output a machine readable format: CSV. In this case the fields are:
"filename.jpg","2005:04:23 19:31:00",Latitude,Longitude,Elevation
Where the first value is the filename, as passed, the second is the timestamp, and the last three are floating point values, with a leading plus or minus.
--remove or -r Remove GPS EXIF tags from the specified files, and then quit. Note that this only removes the GPS tags that the program could add, it does not delete all possible GPS EXIF tags. All other tags are left alone.
--ignore-tracksegs or -t Ignores tracksegments in the original GPX file and interpolates between them. Generally track segments show multiple sessions of GPS logging: between them is generally when the GPS was not logging.
--fix-datestamps or -f Prior to 1.5.2 was two bugs that wrote the wrong value for the GPSDateStamp and GPSTimeStamp tag. This option will check each supplied filename for the problem and correct it. Use with --no-write to prevent writing these changes (useful for checking for the issue). This option also implies --no-mtime. You will also need to use --timeadd to specify the difference between localtime and UTC time for the supplied photos.
--degmins or -p Prior to 1.5.3, Longitude and Lattitude values were written as DD MM.MM. After 1.5.3, the default is to write them as DD MM SS.SS, which is more accurate. To force the old behaviour, use this flag.
--photooffset or -O seconds This parameter specifies the Photo offset, that is a number of seconds added to the photo timestamp. It is calculated with GPS - Photo time. See the GPS Correlate concepts for more details on exactly how to use this.

Examples of usage:

In a directory full of photos:

gpscorrelate -g Test.gpx -z +8 *.jpg

Removing or showing GPS tags:

gpscorrelate --show *.jpg
gpscorrelate --remove *.jpg

And thats about it... there is not too much more to say about this program that is not already said in the GPS Correlate concepts documentation.


Return to the contents gpscorrelate-1.6.1/doc/index.html0000644000175000017500000000375610631430573016242 0ustar danieldanielGPS Correlate Documentation

GPS Correlate Documentation

Written by Daniel Foote.

Contents

Important Note for users before 1.5.2

Prior to version 1.5.2, there was a bug in gpscorrelate that caused gpscorrelate to incorrectly parse dates. The result is that the internal date format was one month out (ie, December was considered as the following January, and January was considered as Feburary).

This did not affect the matching of photos, as the GPX data and the EXIF data were passed through the same buggy conversion.

When writing out GPS data, gpscorrelate wrote a GPSTimeStamp and a GPSDateStamp tag, which had the UTC GPS Time and Date of the match. The DateStamp and TimeStamp tags were written incorrectly (from two distinctly seperate bugs), and was around one month ahead of the real time. The original date and time of the photo were not modified.

Version 1.5.2 introduces a new command line option, --fix-datestamps. Running gpscorrelate with this option and then a series of JPEG files that were tagged will detect and correct the problem. This will modify the GPSDateStamp and GPSTimeStamp tag to be correct, but only if this is required. You can run gpscorrelate with the -n (no write) option just to see which files are affected without making any changes. When correcting files, --no-mtime (don't change the mtime of the file) is implied.

When using --fix-datestamps, you will also need to use --timeadd to specify the difference between the photo time and UTC.

More information

For more information, later versions, or comments/questions, please visit http://freefoote.dview.net.

gpscorrelate-1.6.1/main-gui.c0000644000175000017500000000315310476247144015350 0ustar danieldaniel/* main-gui.c * Written by Daniel Foote. * Started Feb 2005. * * GTK GUI program to correlate photos with GPS data. * Uses common parts of a command line version of the same. * Just this time, with a pretty GUI to make it all simple and easy. */ /* Although I did not start out with a clear idea of the contents * of this file, I had thought it would be a little more than a "stub" * to get the GUI up and running. However, this seems to work for me! * All the action really happens in gui.c. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "gui.h" int main(int argc, char* argv[]) { /* Get GTK ready, as appropriate. * (We ignore passed parameters) */ gtk_init(&argc, &argv); /* Create and show the window. */ CreateMatchWindow(); /* Start the main loop. And we're off! */ gtk_main(); return 0; } gpscorrelate-1.6.1/Makefile0000644000175000017500000000314011335414554015126 0ustar danieldaniel# Makefile for gpscorrelate # Written by Daniel Foote. COBJS = main-command.o unixtime.o gpx-read.o correlate.o exif-gps.o GOBJS = main-gui.o gui.o unixtime.o gpx-read.o correlate.o exif-gps.o CFLAGS = -Wall override CFLAGS += $(shell pkg-config --cflags libxml-2.0 gtk+-2.0) -I/usr/include/exiv2 OFLAGS = -Wall override OFLAGS += $(shell pkg-config --libs libxml-2.0 gtk+-2.0) -lm -lexiv2 prefix = /usr/local bindir = $(prefix)/bin datadir = $(prefix)/share mandir = $(datadir)/man docdir = $(datadir)/doc/gpscorrelate applicationsdir = $(datadir)/applications all: gpscorrelate gpscorrelate-gui gpscorrelate.1 gpscorrelate: $(COBJS) g++ $(OFLAGS) -o $@ $(COBJS) gpscorrelate-gui: $(GOBJS) g++ $(OFLAGS) -o $@ $(GOBJS) .c.o: gcc $(CFLAGS) -c -o $*.o $< .cpp.o: g++ $(CFLAGS) -c -o $*.o $< clean: rm -f *.o gpscorrelate{,.exe} gpscorrelate-gui{,.exe} install: all install -d $(DESTDIR)$(bindir) install gpscorrelate gpscorrelate-gui $(DESTDIR)$(bindir) install -d $(DESTDIR)$(mandir)/man1 install -m 0644 gpscorrelate.1 $(DESTDIR)$(mandir)/man1 install -d $(DESTDIR)$(docdir) install -m 0644 doc/*.html doc/*.png $(DESTDIR)$(docdir) install-desktop-file: desktop-file-install --vendor="" --dir="$(DESTDIR)$(applicationsdir)" gpscorrelate.desktop install -p -m0644 -D gpscorrelate-gui.svg $(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/gpscorrelate-gui.svg doc/gpscorrelate-manpage.xml: doc/gpscorrelate-manpage.xml.in sed 's,@DOCDIR@,$(docdir),' $< > $@ gpscorrelate.1: doc/gpscorrelate-manpage.xml xsltproc http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< gpscorrelate-1.6.1/gpscorrelate-gui.svg0000644000175000017500000034112311335414554017471 0ustar danieldaniel image/svg+xml gpscorrelate-gui Icon 2009-04-18 Till Maas <opensource@till.name> The following cliparts from Open Clip Art Library were used: Anonymous: http://openclipart.org/media/files/Anonymous/7187 najsbajs: http://openclipart.org/media/files/najsbajs/3243 Icon for the GUI of gpscorrelate: http://freefoote.dview.net/linux_gpscorr.html S 31° 57'E 115° 51' gpscorrelate-1.6.1/README.mingw320000644000175000017500000000125311065335320015627 0ustar danieldanielTo compile the windows version you need a mingw32 cross compiler toolchain (binutils, gcc, g++, etc). See this page for more pointers http://www.ecn.wfu.edu/~cottrell/cross-gtk/ To compile gpscorrelate for win32 you need cross-compiled versions of: libiconv gettext cairo atk libpng libxml2 pango pixman zlib expat fontconfig freetype gettext glib2 gtk2 exiv2 Once you have all the dependencies cross-compiled, export PKG_CONFIG_PATH to the database of your toolchain environment (somehting like /usr/i486-mingw32/lib/pkgconfig/) Finally run # make -f Makefile.mingw32 (Makefile.mingw32 and README.mingw32 written and contributed by Julio Castilo. Thanks!) gpscorrelate-1.6.1/INSTALL0000644000175000017500000000075611102570417014522 0ustar danieldanielInstallation of GPSCorrelate. - Make sure you have the appropriate development libraries. You will need: libxml2, libgtk2.0, libexiv2. To create the manpage you need: - xsltproc from http://xmlsoft.org/XSLT/ - manpages/docbook.xsl properly installed from http://docbook.sourceforge.net/projects/xsl/ or internet access (xsltproc will download it automatically if it is not installed) - Compile the program: make - Install the program: make install ... and that should be it. gpscorrelate-1.6.1/gui.c0000644000175000017500000013136411065335320014421 0ustar danieldaniel/* gui.c * Written by Daniel Foote. * Started Feb 2005. * * The base of this file was generated with Glade, and then * hand edited by me for some strange reason. * * This file contains the code to create, generate, and look after * a GTK GUI for the photo correlation program. */ /* This is "basically" the GUI version of it. The only other * part of the gui program is main-gui.c, but that ended up * being little more than a "stub" to get the GUI up and running. */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "gpsstructure.h" #include "gui.h" #include "exif-gps.h" #include "gpx-read.h" #include "correlate.h" /* Declare all our widgets. Global to this module. */ GtkWidget *MatchWindow; GtkWidget *WindowHBox; GtkWidget *ControlsVBox; GtkWidget *AddPhotosFrame; GtkWidget *AddPhotosAlignment; GtkWidget *AddPhotosVBox; GtkWidget *PhotoAddButton; GtkWidget *PhotoRemoveButton; GtkWidget *AddPhotosLabel; GtkWidget *GPSDataFrame; GtkWidget *GPSDataAlignment; GtkWidget *GPSDataVBox; GtkWidget *GPSSelectedLabel; GtkWidget *SelectGPSButton; GtkWidget *GPSDataLabel; GtkWidget *OptionsFrame; GtkWidget *OptionsAlignment; GtkWidget *OptionsVBox; GtkWidget *InterpolateCheck; GtkWidget *NoWriteCheck; GtkWidget *NoMtimeCheck; GtkWidget *BetweenSegmentsCheck; GtkWidget *DegMinSecsCheck; GtkWidget *OptionsTable; GtkWidget *MaxGapTimeLabel; GtkWidget *TimeZoneLabel; GtkWidget *PhotoOffsetLabel; GtkWidget *GPSDatumLabel; GtkWidget *GapTimeEntry; GtkWidget *TimeZoneEntry; GtkWidget *PhotoOffsetEntry; GtkWidget *GPSDatumEntry; GtkWidget *OptionsFrameLable; GtkWidget *CorrelateFrame; GtkWidget *CorrelateAlignment; GtkWidget *CorrelateButton; GtkWidget *CorrelateLabel; GtkWidget *OtherOptionsFrame; GtkWidget *OtherOptionsAlignment; GtkWidget *OtherOptionsLabel; GtkWidget *StripGPSButton; GtkWidget *PhotoListVBox; GtkWidget *PhotoListScroll; GtkWidget *PhotoList; GtkTooltips *tooltips; /* Enum and other stuff for the Photo list box. */ enum { LIST_FILENAME, LIST_LAT, LIST_LONG, LIST_ELEV, LIST_TIME, LIST_STATE, LIST_POINTER, LIST_NOCOLUMNS }; GtkListStore *PhotoListStore; GtkCellRenderer *PhotoListRenderer; GtkTreeViewColumn *FileColumn; GtkTreeViewColumn *LatColumn; GtkTreeViewColumn *LongColumn; GtkTreeViewColumn *ElevColumn; GtkTreeViewColumn *TimeColumn; GtkTreeViewColumn *StateColumn; /* Structure and variables for holding the list of * photos in memory. */ struct GUIPhotoList { char* Filename; char* Time; GtkTreeIter ListPointer; struct GUIPhotoList* Next; }; struct GUIPhotoList* FirstPhoto = NULL; struct GUIPhotoList* LastPhoto = NULL; struct GPSPoint* GPSData = NULL; char* ConfigDefaults[] = { "interpolate", "true", "dontwrite", "false", "nochangemtime", "false", "betweensegments", "false", "writeddmmss", "true", "maxgap", "0", "timezone", "+0:00", "photooffset", "0", "gpsdatum", "WGS-84", "gpxopendir", "", "photoopendir", "", NULL, NULL }; GKeyFile* GUISettings; char* SettingsFilename; gchar* GPXOpenDir = NULL; gchar* PhotoOpenDir = NULL; /* Load settings, insert defaults. */ void LoadSettings() { /* Generate the filename. */ const char* UserHomeDir = g_get_user_config_dir(); int FilenameLength = strlen(UserHomeDir) + 30; SettingsFilename = malloc(sizeof(char) * FilenameLength); snprintf(SettingsFilename, FilenameLength, "%s%c.gpscorrelaterc", UserHomeDir, G_DIR_SEPARATOR); /* Create a new key file. */ GUISettings = g_key_file_new(); if (!g_key_file_load_from_file(GUISettings, SettingsFilename, G_KEY_FILE_KEEP_COMMENTS, NULL)) { /* Unable to load the file. Oh well. */ } /* Now create all the default settings. */ int i = 0; while (ConfigDefaults[i]) { /* If the setting doesn't exist, set the default. */ if (NULL == g_key_file_get_value(GUISettings, "default", ConfigDefaults[i], NULL)) { g_key_file_set_value(GUISettings, "default", ConfigDefaults[i], ConfigDefaults[i+1]); } i += 2; } } void SaveSettings() { /* Save the settings to file, and deallocate the settings. */ FILE* OutputFile; OutputFile = fopen(SettingsFilename, "w"); gsize SettingsLength = 0; gchar* SettingsString = g_key_file_to_data(GUISettings, &SettingsLength, NULL); fwrite((void*)SettingsString, sizeof(gchar), (size_t)SettingsLength, OutputFile); fclose(OutputFile); g_free(SettingsString); free(SettingsFilename); } GtkWidget* CreateMatchWindow (void) { /* Load the settings. */ LoadSettings(); /* Get our tooltips ready. */ tooltips = gtk_tooltips_new (); /* Start with the window itself. */ MatchWindow = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (MatchWindow), "GPS Photo Correlate"); gtk_window_set_default_size (GTK_WINDOW (MatchWindow), 700, -1); g_signal_connect (G_OBJECT (MatchWindow), "delete_event", G_CALLBACK (DestroyWindow), NULL); WindowHBox = gtk_hbox_new (FALSE, 0); gtk_widget_show (WindowHBox); gtk_container_add (GTK_CONTAINER (MatchWindow), WindowHBox); /* The controls side of the window. */ ControlsVBox = gtk_vbox_new (FALSE, 0); gtk_widget_show (ControlsVBox); gtk_box_pack_start (GTK_BOX (WindowHBox), ControlsVBox, FALSE, TRUE, 0); /* Add/remove photos area. */ AddPhotosFrame = gtk_frame_new (NULL); gtk_widget_show (AddPhotosFrame); gtk_box_pack_start (GTK_BOX (ControlsVBox), AddPhotosFrame, FALSE, FALSE, 0); AddPhotosAlignment = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_show (AddPhotosAlignment); gtk_container_add (GTK_CONTAINER (AddPhotosFrame), AddPhotosAlignment); gtk_alignment_set_padding (GTK_ALIGNMENT (AddPhotosAlignment), 0, 4, 12, 4); AddPhotosVBox = gtk_vbox_new (FALSE, 0); gtk_widget_show (AddPhotosVBox); gtk_container_add (GTK_CONTAINER (AddPhotosAlignment), AddPhotosVBox); PhotoAddButton = gtk_button_new_with_mnemonic ("Add..."); gtk_widget_show (PhotoAddButton); gtk_box_pack_start (GTK_BOX (AddPhotosVBox), PhotoAddButton, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, PhotoAddButton, "Add photos to be correlated.", NULL); g_signal_connect (G_OBJECT (PhotoAddButton), "clicked", G_CALLBACK (AddPhotosButtonPress), NULL); PhotoRemoveButton = gtk_button_new_with_mnemonic ("Remove"); gtk_widget_show (PhotoRemoveButton); gtk_box_pack_start (GTK_BOX (AddPhotosVBox), PhotoRemoveButton, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, PhotoRemoveButton, "Remove selected photos from the list.", NULL); g_signal_connect (G_OBJECT (PhotoRemoveButton), "clicked", G_CALLBACK (RemovePhotosButtonPress), NULL); AddPhotosLabel = gtk_label_new ("1. Add Photos"); gtk_widget_show (AddPhotosLabel); gtk_frame_set_label_widget (GTK_FRAME (AddPhotosFrame), AddPhotosLabel); gtk_label_set_use_markup (GTK_LABEL (AddPhotosLabel), TRUE); /* GPS data area */ GPSDataFrame = gtk_frame_new (NULL); gtk_widget_show (GPSDataFrame); gtk_box_pack_start (GTK_BOX (ControlsVBox), GPSDataFrame, FALSE, FALSE, 0); GPSDataAlignment = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_show (GPSDataAlignment); gtk_container_add (GTK_CONTAINER (GPSDataFrame), GPSDataAlignment); gtk_alignment_set_padding (GTK_ALIGNMENT (GPSDataAlignment), 0, 4, 12, 4); GPSDataVBox = gtk_vbox_new (FALSE, 0); gtk_widget_show (GPSDataVBox); gtk_container_add (GTK_CONTAINER (GPSDataAlignment), GPSDataVBox); GPSSelectedLabel = gtk_label_new ("Reading From: No file"); /* FIX ME: Label not appropriately sized/placed for data. */ gtk_widget_show (GPSSelectedLabel); gtk_box_pack_start (GTK_BOX (GPSDataVBox), GPSSelectedLabel, FALSE, FALSE, 0); gtk_label_set_ellipsize(GTK_LABEL(GPSSelectedLabel), PANGO_ELLIPSIZE_END); /*gtk_label_set_width_chars(GTK_LABEL(GPSSelectedLabel), 20); gtk_label_set_line_wrap(GTK_LABEL(GPSSelectedLabel), TRUE);*/ SelectGPSButton = gtk_button_new_with_mnemonic ("Choose..."); gtk_widget_show (SelectGPSButton); gtk_box_pack_start (GTK_BOX (GPSDataVBox), SelectGPSButton, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, SelectGPSButton, "Choose GPX file to read GPS data from. If the GPS data is not in the GPX format, use a convertor like GPSBabel to convert it to GPX.", NULL); g_signal_connect (G_OBJECT (SelectGPSButton), "clicked", G_CALLBACK (SelectGPSButtonPress), NULL); GPSDataLabel = gtk_label_new ("2. GPS Data"); gtk_widget_show (GPSDataLabel); gtk_frame_set_label_widget (GTK_FRAME (GPSDataFrame), GPSDataLabel); gtk_label_set_use_markup (GTK_LABEL (GPSDataLabel), TRUE); /* Options area. */ OptionsFrame = gtk_frame_new (NULL); gtk_widget_show (OptionsFrame); gtk_box_pack_start (GTK_BOX (ControlsVBox), OptionsFrame, FALSE, FALSE, 0); OptionsAlignment = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_show (OptionsAlignment); gtk_container_add (GTK_CONTAINER (OptionsFrame), OptionsAlignment); gtk_alignment_set_padding (GTK_ALIGNMENT (OptionsAlignment), 0, 4, 12, 4); OptionsVBox = gtk_vbox_new (FALSE, 0); gtk_widget_show (OptionsVBox); gtk_container_add (GTK_CONTAINER (OptionsAlignment), OptionsVBox); InterpolateCheck = gtk_check_button_new_with_mnemonic ("Interpolate"); gtk_widget_show (InterpolateCheck); gtk_box_pack_start (GTK_BOX (OptionsVBox), InterpolateCheck, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, InterpolateCheck, "Interpolate between points. If disabled, points will be rounded to the nearest recorded point.", NULL); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (InterpolateCheck), g_key_file_get_boolean(GUISettings, "default", "interpolate", NULL)); NoWriteCheck = gtk_check_button_new_with_mnemonic ("Don't write"); gtk_widget_show (NoWriteCheck); gtk_box_pack_start (GTK_BOX (OptionsVBox), NoWriteCheck, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, NoWriteCheck, "Don't write EXIF data back to the photos.", NULL); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (NoWriteCheck), g_key_file_get_boolean(GUISettings, "default", "dontwrite", NULL)); NoMtimeCheck = gtk_check_button_new_with_mnemonic ("Don't change mtime"); gtk_widget_show (NoMtimeCheck); gtk_box_pack_start (GTK_BOX (OptionsVBox), NoMtimeCheck, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, NoMtimeCheck, "Don't change file modification time of the photos.", NULL); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (NoMtimeCheck), g_key_file_get_boolean(GUISettings, "default", "nochangemtime", NULL)); BetweenSegmentsCheck = gtk_check_button_new_with_mnemonic ("Between Segments"); gtk_widget_show (BetweenSegmentsCheck); gtk_box_pack_start (GTK_BOX (OptionsVBox), BetweenSegmentsCheck, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, BetweenSegmentsCheck, "Interpolate between track segments. Generally the data is segmented to show where data was available and not available, but you might still want to interpolate between segments.", NULL); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (BetweenSegmentsCheck), g_key_file_get_boolean(GUISettings, "default", "betweensegments", NULL)); DegMinSecsCheck = gtk_check_button_new_with_mnemonic ("Write DD MM SS.SS"); gtk_widget_show (DegMinSecsCheck); gtk_box_pack_start (GTK_BOX (OptionsVBox), DegMinSecsCheck, FALSE, FALSE, 0); gtk_tooltips_set_tip (tooltips, DegMinSecsCheck, "Write the lattitude and longitude values as DD MM SS.SS. This is the new default as of v1.5.3. The old behaviour is to write it as DD MM.MM, which will occur if you uncheck this box.", NULL); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (DegMinSecsCheck), g_key_file_get_boolean(GUISettings, "default", "writeddmmss", NULL)); OptionsTable = gtk_table_new (4, 2, FALSE); gtk_widget_show (OptionsTable); gtk_box_pack_start (GTK_BOX (OptionsVBox), OptionsTable, TRUE, TRUE, 0); MaxGapTimeLabel = gtk_label_new ("Max gap time:"); gtk_widget_show (MaxGapTimeLabel); gtk_table_attach (GTK_TABLE (OptionsTable), MaxGapTimeLabel, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment (GTK_MISC (MaxGapTimeLabel), 0, 0.5); TimeZoneLabel = gtk_label_new ("Time Zone:"); gtk_widget_show (TimeZoneLabel); gtk_table_attach (GTK_TABLE (OptionsTable), TimeZoneLabel, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment (GTK_MISC (TimeZoneLabel), 0, 0.5); PhotoOffsetLabel = gtk_label_new ("Photo Offset:"); gtk_widget_show (PhotoOffsetLabel); gtk_table_attach (GTK_TABLE (OptionsTable), PhotoOffsetLabel, 0, 1, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment (GTK_MISC (PhotoOffsetLabel), 0, 0.5); GPSDatumLabel = gtk_label_new ("GPS Datum:"); gtk_widget_show (GPSDatumLabel); gtk_table_attach (GTK_TABLE (OptionsTable), GPSDatumLabel, 0, 1, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment (GTK_MISC (GPSDatumLabel), 0, 0.5); GapTimeEntry = gtk_entry_new (); gtk_widget_show (GapTimeEntry); gtk_table_attach (GTK_TABLE (OptionsTable), GapTimeEntry, 1, 2, 0, 1, (GtkAttachOptions) (0), (GtkAttachOptions) (0), 0, 0); gtk_tooltips_set_tip (tooltips, GapTimeEntry, "Maximum time \"away\" from a point that the photo will be matched, in seconds. If a photos time is outside this value from any point, it will not be matched.", NULL); gtk_entry_set_text (GTK_ENTRY (GapTimeEntry), g_key_file_get_value(GUISettings, "default", "maxgap", NULL)); gtk_entry_set_width_chars (GTK_ENTRY (GapTimeEntry), 7); TimeZoneEntry = gtk_entry_new (); gtk_widget_show (TimeZoneEntry); gtk_table_attach (GTK_TABLE (OptionsTable), TimeZoneEntry, 1, 2, 1, 2, (GtkAttachOptions) (0), (GtkAttachOptions) (0), 0, 0); gtk_tooltips_set_tip (tooltips, TimeZoneEntry, "The timezone that the cameras time was set to when the photos were taken. For example, if a camera is set to AWST or +8:00 hours. Enter +8:00 here so that the correct adjustment to the photos time can be made. GPS data is always in UTC.", NULL); gtk_entry_set_text (GTK_ENTRY (TimeZoneEntry), g_key_file_get_value(GUISettings, "default", "timezone", NULL)); gtk_entry_set_width_chars (GTK_ENTRY (TimeZoneEntry), 7); PhotoOffsetEntry = gtk_entry_new (); gtk_widget_show (PhotoOffsetEntry); gtk_table_attach (GTK_TABLE (OptionsTable), PhotoOffsetEntry, 1, 2, 2, 3, (GtkAttachOptions) (0), (GtkAttachOptions) (0), 0, 0); gtk_tooltips_set_tip (tooltips, PhotoOffsetEntry, "The number of seconds to ADD to the photos time to make it match the GPS data. Calculate this with (GPS - Photo). Can be negative or positive.", NULL); gtk_entry_set_text (GTK_ENTRY (PhotoOffsetEntry), g_key_file_get_value(GUISettings, "default", "photooffset", NULL)); gtk_entry_set_width_chars (GTK_ENTRY (PhotoOffsetEntry), 7); GPSDatumEntry = gtk_entry_new (); gtk_widget_show (GPSDatumEntry); gtk_table_attach (GTK_TABLE (OptionsTable), GPSDatumEntry, 1, 2, 4, 5, (GtkAttachOptions) (0), (GtkAttachOptions) (0), 0, 0); gtk_tooltips_set_tip (tooltips, GPSDatumEntry, "The datum used for the GPS data. This text here is recorded in the EXIF tags as the source datum. WGS-84 is very commonly used.", NULL); gtk_entry_set_text (GTK_ENTRY (GPSDatumEntry), g_key_file_get_value(GUISettings, "default", "gpsdatum", NULL)); gtk_entry_set_width_chars (GTK_ENTRY (GPSDatumEntry), 7); OptionsFrameLable = gtk_label_new ("3. Set options"); gtk_widget_show (OptionsFrameLable); gtk_frame_set_label_widget (GTK_FRAME (OptionsFrame), OptionsFrameLable); gtk_label_set_use_markup (GTK_LABEL (OptionsFrameLable), TRUE); /* Correlate button area. */ CorrelateFrame = gtk_frame_new (NULL); gtk_widget_show (CorrelateFrame); gtk_box_pack_start (GTK_BOX (ControlsVBox), CorrelateFrame, FALSE, FALSE, 0); CorrelateAlignment = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_show (CorrelateAlignment); gtk_container_add (GTK_CONTAINER (CorrelateFrame), CorrelateAlignment); gtk_alignment_set_padding (GTK_ALIGNMENT (CorrelateAlignment), 0, 4, 12, 4); CorrelateButton = gtk_button_new_with_mnemonic ("Correlate Photos"); gtk_widget_show (CorrelateButton); gtk_container_add (GTK_CONTAINER (CorrelateAlignment), CorrelateButton); gtk_tooltips_set_tip (tooltips, CorrelateButton, "Begin the correlation process, writing back to the photos if not disabled.", NULL); g_signal_connect (G_OBJECT (CorrelateButton), "clicked", G_CALLBACK (CorrelateButtonPress), NULL); CorrelateLabel = gtk_label_new ("4. Correlate!"); gtk_widget_show (CorrelateLabel); gtk_frame_set_label_widget (GTK_FRAME (CorrelateFrame), CorrelateLabel); gtk_label_set_use_markup (GTK_LABEL (CorrelateLabel), TRUE); /* Other options area. */ OtherOptionsFrame = gtk_frame_new (NULL); gtk_widget_show (OtherOptionsFrame); gtk_box_pack_start (GTK_BOX (ControlsVBox), OtherOptionsFrame, FALSE, FALSE, 0); OtherOptionsAlignment = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_show (OtherOptionsAlignment); gtk_container_add (GTK_CONTAINER (OtherOptionsFrame), OtherOptionsAlignment); gtk_alignment_set_padding (GTK_ALIGNMENT (OtherOptionsAlignment), 0, 4, 12, 4); StripGPSButton = gtk_button_new_with_mnemonic ("Strip GPS tags"); gtk_widget_show (StripGPSButton); gtk_container_add (GTK_CONTAINER (OtherOptionsAlignment), StripGPSButton); gtk_tooltips_set_tip (tooltips, StripGPSButton, "Strip GPS tags from the selected photos.", NULL); g_signal_connect (G_OBJECT (StripGPSButton), "clicked", G_CALLBACK (StripGPSButtonPress), NULL); OtherOptionsLabel = gtk_label_new ("Other Tools"); gtk_widget_show (OtherOptionsLabel); gtk_frame_set_label_widget (GTK_FRAME (OtherOptionsFrame), OtherOptionsLabel); gtk_label_set_use_markup (GTK_LABEL (OtherOptionsLabel), TRUE); /* Photo list box area of the window. */ PhotoListVBox = gtk_vbox_new (FALSE, 0); gtk_widget_show (PhotoListVBox); gtk_box_pack_start (GTK_BOX (WindowHBox), PhotoListVBox, TRUE, TRUE, 0); PhotoListScroll = gtk_scrolled_window_new (NULL, NULL); gtk_widget_show (PhotoListScroll); gtk_box_pack_start (GTK_BOX (PhotoListVBox), PhotoListScroll, TRUE, TRUE, 0); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (PhotoListScroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (PhotoListScroll), GTK_SHADOW_IN); /* Get the photo list store ready. */ PhotoListStore = gtk_list_store_new(LIST_NOCOLUMNS, G_TYPE_STRING, /* The Filename */ G_TYPE_STRING, /* Latitude */ G_TYPE_STRING, /* Longitude */ G_TYPE_STRING, /* Elevation */ G_TYPE_STRING, /* The Time */ G_TYPE_STRING, /* The State */ G_TYPE_POINTER); /* Pointer to the matching list item. */ PhotoList = gtk_tree_view_new_with_model (GTK_TREE_MODEL(PhotoListStore)); gtk_widget_show (PhotoList); gtk_container_add (GTK_CONTAINER (PhotoListScroll), PhotoList); gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(PhotoList)), GTK_SELECTION_MULTIPLE); /* Prepare the columns. We need columns. Columns are good. */ PhotoListRenderer = gtk_cell_renderer_text_new (); /* File Column. */ FileColumn = gtk_tree_view_column_new_with_attributes ("File", PhotoListRenderer, "text", LIST_FILENAME, NULL); gtk_tree_view_column_set_resizable (FileColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), FileColumn); /* Lattitude Column. */ LatColumn = gtk_tree_view_column_new_with_attributes ("Latitude", PhotoListRenderer, "text", LIST_LAT, NULL); gtk_tree_view_column_set_resizable (LatColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), LatColumn); /* Longitude Column. */ LongColumn = gtk_tree_view_column_new_with_attributes ("Longitude", PhotoListRenderer, "text", LIST_LONG, NULL); gtk_tree_view_column_set_resizable (LongColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), LongColumn); /* Elevation Column. */ ElevColumn = gtk_tree_view_column_new_with_attributes ("Elevation", PhotoListRenderer, "text", LIST_ELEV, NULL); gtk_tree_view_column_set_resizable (ElevColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), ElevColumn); /* Time column. */ TimeColumn = gtk_tree_view_column_new_with_attributes ("Time", PhotoListRenderer, "text", LIST_TIME, NULL); gtk_tree_view_column_set_resizable (TimeColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), TimeColumn); /* State column. */ StateColumn = gtk_tree_view_column_new_with_attributes ("State", PhotoListRenderer, "text", LIST_STATE, NULL); gtk_tree_view_column_set_resizable (StateColumn, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (PhotoList), StateColumn); /* Final thing: show the window. */ gtk_widget_show(MatchWindow); /* Done! Return a pointer to the window, although we never use it... */ return MatchWindow; } gboolean DestroyWindow(GtkWidget *Widget, GdkEvent *Event, gpointer Data) { /* Record the settings, and then save them. */ g_key_file_set_boolean(GUISettings, "default", "interpolate", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(InterpolateCheck))); g_key_file_set_boolean(GUISettings, "default", "dontwrite", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(NoWriteCheck))); g_key_file_set_boolean(GUISettings, "default", "nochangemtime", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(NoMtimeCheck))); g_key_file_set_boolean(GUISettings, "default", "betweensegments", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(BetweenSegmentsCheck))); g_key_file_set_boolean(GUISettings, "default", "writeddmmss", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(DegMinSecsCheck))); g_key_file_set_string(GUISettings, "default", "maxgap", gtk_entry_get_text(GTK_ENTRY(GapTimeEntry))); g_key_file_set_string(GUISettings, "default", "timezone", gtk_entry_get_text(GTK_ENTRY(TimeZoneEntry))); g_key_file_set_string(GUISettings, "default", "photooffset", gtk_entry_get_text(GTK_ENTRY(PhotoOffsetEntry))); g_key_file_set_string(GUISettings, "default", "gpsdatum", gtk_entry_get_text(GTK_ENTRY(GPSDatumEntry))); g_key_file_set_string(GUISettings, "default", "gpxopendir", GPXOpenDir); g_key_file_set_string(GUISettings, "default", "photoopendir", PhotoOpenDir); SaveSettings(); /* Someone closed the window. */ /* Free the memory we allocated for the photo list. */ struct GUIPhotoList* Free = NULL; struct GUIPhotoList* Free2 = NULL; if (FirstPhoto) { /* Walk through the singly-linked list * freeing stuff. */ Free = FirstPhoto; while (1) { if (Free->Filename) free(Free->Filename); if (Free->Time) free(Free->Time); Free2 = Free->Next; free(Free); if (Free2 == NULL) break; Free = Free2; } } /* Free the memory for the GPS data, if applicable. */ if (GPSData) FreePointList(GPSData); /* Tell GTK that we're done. */ gtk_exit(0); /* And return FALSE so that GTK knows we have not * vetoed the close. */ return FALSE; } void AddPhotosButtonPress( GtkWidget *Widget, gpointer Data ) { /* Add some photos to this thing. */ GtkWidget *AddPhotosDialog; GSList* FileNames; if (PhotoOpenDir == NULL) { /* First load - fetch the settings from the file. */ PhotoOpenDir = g_key_file_get_value(GUISettings, "default", "photoopendir", NULL); } /* Get the dialog ready. */ AddPhotosDialog = gtk_file_chooser_dialog_new ("Add Photos...", GTK_WINDOW(MatchWindow), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(AddPhotosDialog), TRUE); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(AddPhotosDialog), PhotoOpenDir); /* Run the dialog. */ if (gtk_dialog_run (GTK_DIALOG (AddPhotosDialog)) == GTK_RESPONSE_ACCEPT) { /* Hide the dialog. */ gtk_widget_hide(AddPhotosDialog); /* Haul out the selected files. * We pass them along to another function that will * add them to the internal list and onto the screen. */ /* GTK returns a GSList - a singly-linked list of filenames. */ FileNames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER(AddPhotosDialog)); GSList* Run; for (Run = FileNames; Run; Run = Run->next) { /* Show whats happening on the screen. */ GtkGUIUpdate(); /*printf("Filename: %s.\n", (char*)Run->data);*/ /* Call the other function with the filename - this * function adds it to the internal list, and adds it * to the screen display, too. */ AddPhotoToList((char*)Run->data); /* Free the memory passed to us. */ g_free(Run->data); } /* We're done with the list - free it. */ g_slist_free(FileNames); } /* Copy out the directory that the user ended up at. */ if (PhotoOpenDir) { g_free(PhotoOpenDir); } PhotoOpenDir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(AddPhotosDialog)); /* Now we're done with the dialog. See you! */ gtk_widget_destroy (AddPhotosDialog); } void AddPhotoToList(char* Filename) { /* Add the photo to the list. Query out the exif tags * at the same time, too - so we can add them to the * current list. */ /* Note that this function does more than update the GUI: * it also adds it to the internal list, ready to go. */ /* Get ready to read the relevant data. */ GtkTreeIter AddStuff; char* Time = NULL; double Lat, Long, Elev; Lat = Long = Elev = 0; int IncludesGPS = 0; /* Read the EXIF data. */ Time = ReadExifData(Filename, &Lat, &Long, &Elev, &IncludesGPS); /* Note: we don't check if Time is NULL here. It is done for * us in SetListItem, and we check again before we attempt * to allocate memory to store "Time" in. */ /* Add the data to the list. */ gtk_list_store_append(PhotoListStore, &AddStuff); SetListItem(&AddStuff, Filename, Time, Lat, Long, Elev, NULL, IncludesGPS); /* Save away the filename and the TreeIter information in the internal * singly-linked list. */ /* We also make a copy of Filename - it won't exist once we return. */ if (FirstPhoto) { /* Already at least one element. Add to it. */ LastPhoto->Next = malloc(sizeof(struct GUIPhotoList)); LastPhoto = LastPhoto->Next; LastPhoto->Next = NULL; } else { /* No elements. Righto, add one. */ FirstPhoto = malloc(sizeof(struct GUIPhotoList)); LastPhoto = FirstPhoto; FirstPhoto->Next = NULL; } /* Now that we've allocated memory for the structure, allocate * memory for the strings and then fill them. */ /* Filename first... */ LastPhoto->Filename = malloc((sizeof(char) * strlen(Filename)) + 1); strncpy(LastPhoto->Filename, Filename, strlen(Filename) + 1); /* And then Time, after checking for NULLness. */ if (Time) { LastPhoto->Time = malloc((sizeof(char) * strlen(Time)) + 1); strncpy(LastPhoto->Time, Time, strlen(Time)); } else { LastPhoto->Time = malloc((sizeof(char) * strlen("No EXIF data")) + 1); strncpy(LastPhoto->Time, "No EXIF data", strlen("No EXIF data")); } /* Save the TreeIter as the last step. */ LastPhoto->ListPointer = AddStuff; /* Save the pointer into the data, as well. */ gtk_list_store_set(PhotoListStore, &AddStuff, LIST_POINTER, LastPhoto, -1); /* Free the memory allocated for us. * (ReadExifData allocates and returns memory) */ if (Time) free(Time); } void RemovePhotosButtonPress( GtkWidget *Widget, gpointer Data ) { /* Someone clicked the remove photos button. So make it happen! * First, query out what was selected. */ GtkTreeIter Iter; GtkTreeSelection* Selection; Selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(PhotoList)); GList* Selected = gtk_tree_selection_get_selected_rows(Selection, NULL); /* Sanity check: was anything selected? */ if (Selected == NULL) { /* Nothing is selected. Do nothing. */ return; } /* Count the items on the GList. We need this to be able to * keep a list of items that have been removed from our internal * list, and should be removed from the screen. */ int SelectedCount = 0; GList* Walk; for (Walk = Selected; Walk; Walk = Walk->next) { SelectedCount++; } /* Now get ready to keep a list of Iters that we can * delete. */ GtkTreeIter* RemoveIters = malloc(sizeof(GtkTreeIter) * SelectedCount); /* Walk through and remove the items from our internal list. */ struct GUIPhotoList* PhotoWalk = NULL; struct GUIPhotoList* LastPhotoWalk = NULL; struct GUIPhotoList* FreeHold = NULL; struct GUIPhotoList* FreeMe = NULL; int IterCount = 0; for (Walk = Selected; Walk; Walk = Walk->next) { /* Acquire a new Iter for this selected row. */ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(PhotoListStore), &Iter, Walk->data)) { /* To remove the row from our internal list. * Locate it via the pointer stored. */ gtk_tree_model_get(GTK_TREE_MODEL(PhotoListStore), &Iter, LIST_POINTER, &FreeMe, -1); /* Save the iter for afterwards: we can then remove the * data from the screen. */ /* gtk_list_store_remove(PhotoListStore, &Iter); */ RemoveIters[IterCount] = Iter; IterCount++; /* Now go back through and take it out from our list. */ PhotoWalk = FirstPhoto; LastPhotoWalk = NULL; while (1) { if (PhotoWalk == NULL) break; /*printf("Search: %d / %d.\n", PhotoWalk, FreeMe);*/ /* Check to see if this is the one we want to be rid of. */ if (PhotoWalk == FreeMe) { /* Right. Remove. */ /*printf("Removing: %s.\n", PhotoWalk->Filename);*/ PhotoWalk = PhotoWalk->Next; if (FreeMe == FirstPhoto) { /* Remove the top of the list. */ FreeHold = FreeMe; FirstPhoto = FreeMe->Next; LastPhotoWalk = FreeMe->Next; free(FreeHold->Filename); free(FreeHold->Time); free(FreeHold); break; } else if (FreeMe == LastPhoto) { /* Remove the bottom of the list. */ FreeHold = FreeMe; LastPhoto = LastPhotoWalk; LastPhoto->Next = NULL; free(FreeHold->Filename); free(FreeHold->Time); free(FreeHold); break; } else { /* Remove a middle of the list. */ FreeHold = FreeMe; LastPhotoWalk->Next = FreeHold->Next; free(FreeHold->Filename); free(FreeHold->Time); free(FreeHold); break; } } else { /* Nope, this wasn't what we wanted to delete. * So get ready to look at the next one, keeping * mind of where we were. */ LastPhotoWalk = PhotoWalk; PhotoWalk = PhotoWalk->Next; } } /* End for Walk the photo list. */ } } /* End for Walk the GList. */ /* Now remove the rows from the screen. * By this point, they are no longer in our internal list. */ int i; for (i = 0; i < SelectedCount; i++) { gtk_list_store_remove(PhotoListStore, &RemoveIters[i]); } free(RemoveIters); /* Free the memory used by GList. */ g_list_foreach(Selected, (GFunc)gtk_tree_path_free, NULL); g_list_free(Selected); /* Debug: walk the photo list tree. */ /*struct GUIPhotoList* List; for (List = FirstPhoto; List; List = List->Next) { printf("List Filename: %s\n", List->Filename); } printf("--------------------------------------------------------------\n");*/ } void SetListItem(GtkTreeIter* Iter, char* Filename, char* Time, double Lat, double Long, double Elev, char* PassedState, int IncludesGPS) { /* Scratch areas. */ char LatScratch[100]; char LongScratch[100]; char ElevScratch[100]; char* State = NULL; strncpy(LatScratch, "", 100); strncpy(LongScratch, "", 100); strncpy(ElevScratch, "", 100); /* Format all the data. */ if (!Time) { /* Not good. Failure. */ Time = ""; State = "No EXIF data"; } else { /* All ok. Get ready. */ if (IncludesGPS) { State = "GPS Data Present"; /* In each case below, consider the values * that are invalid for each - if that's the case, * consider the spots as "blank". */ /* Lat can't be greater than 90 degrees. */ if (Lat < 200) { snprintf(LatScratch, 100, "%f (%c)", Lat, (Lat < 0) ? 'S' : 'N'); } else { snprintf(LatScratch, 100, " "); } /* Long can't be greater than 180 degrees. */ if (Long < 200) { snprintf(LongScratch, 100, "%f (%c)", Long, (Long < 0) ? 'W' : 'E'); } else { snprintf(LongScratch, 100, " "); } /* Radius of earth ~6000km */ if (Elev > -7000000) { snprintf(ElevScratch, 100, "%fm", Elev); } else { snprintf(ElevScratch, 100, " "); } } else { /* Placeholders for the lack of data. */ State = "Ready"; } } /* Overwrite state with what we want, if needed. */ if (PassedState) State = PassedState; /* And set all the appropriate data. */ gtk_list_store_set(PhotoListStore, Iter, LIST_FILENAME, strrchr(Filename, G_DIR_SEPARATOR)+1, LIST_LAT, LatScratch, LIST_LONG, LongScratch, LIST_ELEV, ElevScratch, LIST_TIME, Time, LIST_STATE, State, -1); } void SetState(GtkTreeIter* Iter, char* State) { /* Set the state on the item... just the state. */ gtk_list_store_set(PhotoListStore, Iter, LIST_STATE, State, -1); } void SelectGPSButtonPress( GtkWidget *Widget, gpointer Data ) { /* Select and load some GPS data! */ GtkWidget *GPSDataDialog; GtkWidget *ErrorDialog; char* FileName; char* Scratch; if (GPXOpenDir == NULL) { /* First load - fetch the settings from the file. */ GPXOpenDir = g_key_file_get_value(GUISettings, "default", "gpxopendir", NULL); } /* Get the dialog ready... */ GPSDataDialog = gtk_file_chooser_dialog_new ("Select GPS Data...", GTK_WINDOW(MatchWindow), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(GPSDataDialog), FALSE); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(GPSDataDialog), GPXOpenDir); /* Run the dialog... */ if (gtk_dialog_run (GTK_DIALOG (GPSDataDialog)) == GTK_RESPONSE_ACCEPT) { /* Process the result of the dialog... */ FileName = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(GPSDataDialog)); /* Sanity check: do we already have data? * Note: we check this now, because if we cancelled the dialog, * we should run with the old data. */ if (GPSData) { /* We already had GPS data. Free that first. */ FreePointList(GPSData); } /* Prepare our "scratch" for rewriting labels. */ Scratch = malloc(sizeof(char) * (strlen(FileName) + 100)); /* Hide the "open" dialog. */ gtk_widget_hide(GPSDataDialog); /* Display a dialog so the user knows whats going down. */ ErrorDialog = gtk_message_dialog_new (GTK_WINDOW(MatchWindow), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, 0, "Loading GPS data from file... Won't be a moment..."); gtk_widget_show(ErrorDialog); GtkGUIUpdate(); /* Read in the new data... assuming we can. */ GPSData = ReadGPX(FileName); /* Close the dialog now we're done. */ gtk_widget_destroy(ErrorDialog); /* Check if the data was read ok. */ if (GPSData) { /* It's all good! * Adjust the label to say so. */ snprintf(Scratch, strlen(FileName)+100, "Read from: %s", strrchr(FileName, G_DIR_SEPARATOR)+1); gtk_label_set_text(GTK_LABEL(GPSSelectedLabel), Scratch); } else { /* Not good. Say so. */ /* Set the label... */ snprintf(Scratch, strlen(FileName)+100, "Reading from: No file"); gtk_label_set_text(GTK_LABEL(GPSSelectedLabel), Scratch); /* Show an error dialog. */ ErrorDialog = gtk_message_dialog_new (GTK_WINDOW(MatchWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Unable to read file %s for some reason. Please try again", FileName); gtk_dialog_run (GTK_DIALOG (ErrorDialog)); gtk_widget_destroy (ErrorDialog); } /* Clean up... */ free(Scratch); g_free(FileName); } /* Make a note of the directory we stopped at. */ if (GPXOpenDir) { g_free(GPXOpenDir); } GPXOpenDir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(GPSDataDialog)); /* Now we're finished with the dialog... free it. */ gtk_widget_destroy (GPSDataDialog); } void CorrelateButtonPress( GtkWidget *Widget, gpointer Data ) { /* We were asked to correlate some photos... make it happen... */ GtkWidget *ErrorDialog; /* Check to see we have everything we need... */ if (FirstPhoto == NULL) { /* No photos... */ ErrorDialog = gtk_message_dialog_new (GTK_WINDOW(MatchWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "No photos selected to match! Please add photos with first!"); gtk_dialog_run (GTK_DIALOG (ErrorDialog)); gtk_widget_destroy (ErrorDialog); return; } if (GPSData == NULL) { /* No GPS data... */ ErrorDialog = gtk_message_dialog_new (GTK_WINDOW(MatchWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "No GPS data loaded! Please select a file to read GPS data from."); gtk_dialog_run (GTK_DIALOG (ErrorDialog)); gtk_widget_destroy (ErrorDialog); return; } /* Assemble the settings for the correlation run. */ struct CorrelateOptions Options; char* DatumScratch = NULL; /* Interpolation. */ /* This is confusing. I should have thought more about the Interpolate * flags in the CorrelateOptions structure. But, if you think about * it for a bit, it can make sense. Enough sense to use. */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(InterpolateCheck))) { Options.NoInterpolate = 0; } else { Options.NoInterpolate = 1; } /* Write or no write. */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(NoWriteCheck))) { Options.NoWriteExif = 1; } else { Options.NoWriteExif = 0; } /* No change MTime. */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(NoMtimeCheck))) { Options.NoChangeMtime = 1; } else { Options.NoChangeMtime = 0; } /* Between segments? */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(BetweenSegmentsCheck))) { Options.DoBetweenTrkSeg = 1; } else { Options.DoBetweenTrkSeg = 0; } /* DD MM.MM or DD MM SS.SS? */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(DegMinSecsCheck))) { Options.DegMinSecs = 1; } else { Options.DegMinSecs = 0; } /* Feather time. */ Options.FeatherTime = atof(gtk_entry_get_text(GTK_ENTRY(GapTimeEntry))); /* GPS Datum. */ DatumScratch = malloc(sizeof(char) * (strlen(gtk_entry_get_text(GTK_ENTRY(GPSDatumEntry))) + 1)); strcpy(DatumScratch, gtk_entry_get_text(GTK_ENTRY(GPSDatumEntry))); Options.Datum = DatumScratch; /* TimeZone. We may need to extract the timezone from a string. */ Options.TimeZoneHours = 0; Options.TimeZoneMins = 0; char* TZString = (char*) gtk_entry_get_text(GTK_ENTRY(TimeZoneEntry)); /* Check the string. If there is a colon, then it's a time in xx:xx format. * If not, it's probably just a +/-xx format. In all other cases, * it will be interpreted as +/-xx, which, if given a string, returns 0. */ if (strstr(TZString, ":")) { /* Found colon. Split into two. */ sscanf(TZString, "%d:%d", &Options.TimeZoneHours, &Options.TimeZoneMins); if (Options.TimeZoneHours < 0) Options.TimeZoneMins *= -1; } else { /* No colon. Just parse. */ Options.TimeZoneHours = atoi(TZString); } /* Photo Offset time */ Options.PhotoOffset = atoi(gtk_entry_get_text(GTK_ENTRY(PhotoOffsetEntry))); /* Clean up some other pointers in the structure. */ Options.MinTime = 0; Options.MaxTime = 0; Options.Points = GPSData; /* Walk through the list, correlating, and updating the screen. */ struct GUIPhotoList* Walk; struct GPSPoint* Result; char* State; GtkTreePath* ShowPath; for (Walk = FirstPhoto; Walk; Walk = Walk->Next) { /* Say that we're doing it... */ SetState(&Walk->ListPointer, "Correlating..."); /* Point to the cell, too... ie, scroll the tree view * to ensure that the one we're playing with can be seen on screen. */ ShowPath = gtk_tree_model_get_path(GTK_TREE_MODEL(PhotoListStore), &Walk->ListPointer); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(PhotoList), ShowPath, NULL, FALSE, 0, 0); gtk_tree_path_free(ShowPath); GtkGUIUpdate(); /* Do the correlation. */ Result = CorrelatePhoto(Walk->Filename, &Options); /* Figure out if it worked. */ if (Result) { /* Result was not null. That means that we * matched to a point. But that's not the whole * story. Read on... */ switch (Options.Result) { case CORR_OK: /* All cool! Exact match! */ State = "Exact Match"; break; case CORR_INTERPOLATED: /* All cool! Interpolated match. */ State = "Interpolated Match"; break; case CORR_ROUND: /* All cool! Rounded match. */ State = "Rounded Match"; break; case CORR_EXIFWRITEFAIL: /* Not cool - matched, not written. */ State = "Write Failure"; break; } /* Now update the screen with the numbers. */ SetListItem(&Walk->ListPointer, Walk->Filename, Walk->Time, Result->Lat, Result->Long, Result->Elev, State, 1); } else { /* Result was null. This means something * really went wrong. Find out and put that * on the screen. */ if (Options.Result == CORR_GPSDATAEXISTS) { /* Do nothing... */ SetState(&Walk->ListPointer, "Data Already Present"); continue; } switch (Options.Result) { case CORR_NOMATCH: /* No match: outside data. */ State = "No Match"; break; case CORR_TOOFAR: /* Too far from any point. */ State = "Too far"; break; case CORR_NOEXIFINPUT: /* No exif data input. */ State = "No data"; break; } /* Now update the screen with the changed state. */ SetListItem(&Walk->ListPointer, Walk->Filename, Walk->Time, 0, 0, 0, State, 0); } /* End if Result */ } /* End for Walk the list ... */ /* Free the memory used to shuffle around the Datum. */ free(DatumScratch); } void StripGPSButtonPress( GtkWidget *Widget, gpointer Data ) { /* Someone clicked the Strip GPS Data button. So make it happen! * First, query out what was selected. */ GtkTreeIter Iter; GtkTreeSelection* Selection; Selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(PhotoList)); GList* Selected = gtk_tree_selection_get_selected_rows(Selection, NULL); /* Sanity check: was anything selected? */ if (Selected == NULL) { /* Nothing is selected. Do nothing. */ return; } /* No change MTime. */ int NoChangeMtime; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(NoMtimeCheck))) { NoChangeMtime = 1; } else { NoChangeMtime = 0; } /* Walk through and remove the items from our internal list. */ GList* Walk; struct GUIPhotoList* PhotoData = NULL; GtkTreePath* ShowPath; for (Walk = Selected; Walk; Walk = Walk->next) { /* Get an Iter for this selected row. */ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(PhotoListStore), &Iter, Walk->data)) { /* Fetch out the data... */ gtk_tree_model_get(GTK_TREE_MODEL(PhotoListStore), &Iter, LIST_POINTER, &PhotoData, -1); } else { /* Unable to get the iter... * Try again, later. */ continue; } /* Say that we're doing it... */ SetState(&PhotoData->ListPointer, "Stripping..."); /* Point to the cell, too... ie, scroll the tree view * to ensure that the one we're playing with can be seen on screen. */ ShowPath = gtk_tree_model_get_path(GTK_TREE_MODEL(PhotoListStore), &PhotoData->ListPointer); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(PhotoList), ShowPath, NULL, FALSE, 0, 0); gtk_tree_path_free(ShowPath); GtkGUIUpdate(); /* Strip the tags. */ if (RemoveGPSExif(PhotoData->Filename, NoChangeMtime)) { SetListItem(&PhotoData->ListPointer, PhotoData->Filename, PhotoData->Time, 200, 200, -7000000, "", 1); } else { SetListItem(&PhotoData->ListPointer, PhotoData->Filename, PhotoData->Time, 200, 200, -7000000, "Error Stripping", 1); } } /* End for Walk the GList. */ /* Debug: walk the photo list tree. */ /*struct GUIPhotoList* List; for (List = FirstPhoto; List; List = List->Next) { printf("List Filename: %s\n", List->Filename); } printf("--------------------------------------------------------------\n");*/ } void GtkGUIUpdate(void) { /* Process all GUI events that need to happen. */ /* This lets us "update" the screen while things are * in motion. Might generate slowdowns with heaps of data, * but generally, it's a good thing. */ while (gtk_events_pending ()) gtk_main_iteration (); }; gpscorrelate-1.6.1/exif-gps.cpp0000644000175000017500000005172411166002235015716 0ustar danieldaniel/* exif-gps.cpp * Written by Daniel Foote. * Started Feb 2005. * * This file contains routines for reading dates * from exif data, and writing GPS data into the * appropriate photos. * * Uses the libexiv2 library. * From http://home.arcor.de/ahuggel/exiv2/ */ /* Copyright 2005 Daniel Foote. * * This file is part of gpscorrelate. * * gpscorrelate is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gpscorrelate 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gpscorrelate; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include "exiv2/image.hpp" #include "exiv2/exif.hpp" #include "gpsstructure.h" #include "exif-gps.h" /* Debug int main(int argc, char* argv[]) { printf("Starting with write...\n"); struct GPSPoint Foo; Foo.Lat = -41.1234567; Foo.Long = 115.12345; Foo.Elev = 25.12345; Foo.Time = 123456; WriteGPSData(argv[1], &Foo, "WGS-84", 0); printf("Done write, now reading...\n"); int GPS = 0; char* Ret = ReadExifDate(argv[1], &GPS); if (Ret) { printf("Date: %s.\n", Ret); } else { printf("Failed!\n"); } if (GPS) { printf("Includes GPS data!\n"); } else { printf("No GPS data!\n"); } }; */ char* ReadExifDate(char* File, int* IncludesGPS) { // Open and read the file. Exiv2::ExifData ExifRead; Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, // File).c_str()); fprintf(stderr, "Failed to read file %s.\n", File); return 0; } ExifRead = Image->exifData(); // Read the tag out. Exiv2::Exifdatum& Tag = ExifRead["Exif.Photo.DateTimeOriginal"]; // Check that the tag is not blank. std::string Value = Tag.toString(); if (Value.length() == 0) { // No date/time stamp. // Not good. // Just return - above us will handle it. return 0; } // Copy the tag and return that. char* Copy = (char*)malloc((sizeof(char) * Value.length()) + 1); strcpy(Copy, Value.c_str()); // Check if we have GPS tags. Exiv2::Exifdatum& GPSData = ExifRead["Exif.GPSInfo.GPSVersionID"]; Value = GPSData.toString(); if (Value.length() == 0) { // No GPS data. *IncludesGPS = 0; } else { // Seems to include GPS data... *IncludesGPS = 1; } // Now return, passing a pointer to the date string. return Copy; // Its up to the caller to free this. }; char* ReadExifData(char* File, double* Lat, double* Long, double* Elev, int* IncludesGPS) { // This function varies in that it reads // much more data than the last, specifically // for display purposes. For the GUI version. // Open and read the file. Exiv2::ExifData ExifRead; Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, // File).c_str()); fprintf(stderr, "Unable to open file %s.\n", File); return 0; } ExifRead = Image->exifData(); // Read the tag out. Exiv2::Exifdatum& Tag = ExifRead["Exif.Photo.DateTimeOriginal"]; // Check that the tag is not blank. std::string Value = Tag.toString(); if (Value.length() == 0) { // No date/time stamp. // Not good. // Just return - above us will handle it. return 0; } // Copy the tag and return that. char* Copy = (char*)malloc((sizeof(char) * Value.length()) + 1); strcpy(Copy, Value.c_str()); // Check if we have GPS tags. Exiv2::Exifdatum GPSData = ExifRead["Exif.GPSInfo.GPSVersionID"]; Value = GPSData.toString(); if (Value.length() == 0) { // No GPS data. // Just return. *IncludesGPS = 0; } else { // Seems to include GPS data... *IncludesGPS = 1; // Read it out and send it up! // What we are trying to do here is convert the // three rationals: // dd/v mm/v ss/v // To a decimal // dd.dddddd... // dd/v is easy: result = dd/v. // mm/v is harder: // mm // -- / 60 = result. // v // ss/v is sorta easy. // ss // -- / 3600 = result // v // Each part is added to the final number. Exiv2::Rational RatNum; GPSData = ExifRead["Exif.GPSInfo.GPSLatitude"]; if (GPSData.count() < 3) *Lat = nan("invalid"); else { RatNum = GPSData.toRational(0); *Lat = (double)RatNum.first / (double)RatNum.second; RatNum = GPSData.toRational(1); *Lat = *Lat + (((double)RatNum.first / (double)RatNum.second) / 60); RatNum = GPSData.toRational(2); *Lat = *Lat + (((double)RatNum.first / (double)RatNum.second) / 3600); GPSData = ExifRead["Exif.GPSInfo.GPSLatitudeRef"]; if (strcmp(GPSData.toString().c_str(), "S") == 0) { // Negate the value - Western Hemisphere. *Lat = -*Lat; } } GPSData = ExifRead["Exif.GPSInfo.GPSLongitude"]; if (GPSData.count() < 3) *Long = nan("invalid"); else { RatNum = GPSData.toRational(0); *Long = (double)RatNum.first / (double)RatNum.second; RatNum = GPSData.toRational(1); *Long = *Long + (((double)RatNum.first / (double)RatNum.second) / 60); RatNum = GPSData.toRational(2); *Long = *Long + (((double)RatNum.first / (double)RatNum.second) / 3600); GPSData = ExifRead["Exif.GPSInfo.GPSLongitudeRef"]; if (strcmp(GPSData.toString().c_str(), "W") == 0) { // Negate the value - Western Hemisphere. *Long = -*Long; } } // Finally, read elevation out. This one is simple. GPSData = ExifRead["Exif.GPSInfo.GPSAltitude"]; if (GPSData.count() < 1) *Elev = nan("invalid"); else { RatNum = GPSData.toRational(0); *Elev = (double)RatNum.first / (double)RatNum.second; } // Is the altitude below sea level? If so, negate the value. GPSData = ExifRead["Exif.GPSInfo.GPSAltitudeRef"]; if (GPSData.count() >= 1 && GPSData.toLong() == 1) { // Negate the elevation. *Elev = -*Elev; } } // Now return, passing a pointer to the date string. return Copy; // Its up to the caller to free this. }; // This function is for the --fix-datestamp option. // DateStamp and TimeStamp should be 12-char strings. char* ReadGPSTimestamp(char* File, char* DateStamp, char* TimeStamp, int* IncludesGPS) { // This function varies in that it reads // much more data than the last, specifically // for display purposes. For the GUI version. // Open and read the file. Exiv2::ExifData ExifRead; Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, // File).c_str()); fprintf(stderr, "Unable to open file %s.\n", File); return 0; } ExifRead = Image->exifData(); // Read the tag out. Exiv2::Exifdatum& Tag = ExifRead["Exif.Photo.DateTimeOriginal"]; // Check that the tag is not blank. std::string Value = Tag.toString(); if (Value.length() == 0) { // No date/time stamp. // Not good. // Just return - above us will handle it. return 0; } // Copy the tag and return that. char* Copy = (char*)malloc((sizeof(char) * Value.length()) + 1); strcpy(Copy, Value.c_str()); // Check if we have GPS tags. Exiv2::Exifdatum& GPSData = ExifRead["Exif.GPSInfo.GPSVersionID"]; Value = GPSData.toString(); if (Value.length() == 0) { // No GPS data. // Just return. *IncludesGPS = 0; } else { // Seems to include GPS data... *IncludesGPS = 1; Exiv2::Rational RatNum1; Exiv2::Rational RatNum2; Exiv2::Rational RatNum3; // Read out the Time and Date stamp, for correction. GPSData = ExifRead["Exif.GPSInfo.GPSTimeStamp"]; if (GPSData.count() < 3) { *IncludesGPS = 0; return Copy; } RatNum1 = GPSData.toRational(0); RatNum2 = GPSData.toRational(1); RatNum3 = GPSData.toRational(2); snprintf(TimeStamp, 12, "%02d:%02d:%02d", RatNum1.first, RatNum2.first, RatNum3.first); GPSData = ExifRead["Exif.GPSInfo.GPSDateStamp"]; if (GPSData.count() < 3) { *IncludesGPS = 0; return Copy; } RatNum1 = GPSData.toRational(0); RatNum2 = GPSData.toRational(1); RatNum3 = GPSData.toRational(2); snprintf(DateStamp, 12, "%04d:%02d:%02d", RatNum1.first, RatNum2.first, RatNum3.first); } return Copy; }; void ConvertToRational(double Number, long int* Numerator, long int* Denominator, int Rounding) { // This function converts the given decimal number // to a rational (fractional) number. // // Examples in comments use Number as 25.12345, Rounding as 4. // Split up the number. double Whole = trunc(Number); double Fractional = Number - Whole; // Calculate the "number" used for rounding. // This is 10^Digits - ie, 4 places gives us 10000. double Rounder = pow(10, Rounding); // Round the fractional part, and leave the number // as greater than 1. // To do this we: (for example) // 0.12345 * 10000 = 1234.5 // floor(1234.5) = 1234 - now bigger than 1 - ready... Fractional = trunc(Fractional * Rounder); // Convert the whole thing to a fraction. // Fraction is: // (25 * 10000) + 1234 251234 // ------------------- = ------ = 25.1234 // 10000 10000 double NumTemp = (Whole * Rounder) + Fractional; double DenTemp = Rounder; // Now we should reduce until we can reduce no more. // Try simple reduction... // if Num // ----- = integer out then.... // Den if (trunc(NumTemp / DenTemp) == (NumTemp / DenTemp)) { // Divide both by Denominator. NumTemp /= DenTemp; DenTemp /= DenTemp; } // And, if that fails, brute force it. while (1) { // Jump out if we can't integer divide one. if ((NumTemp / 2) != trunc(NumTemp / 2)) break; if ((DenTemp / 2) != trunc(DenTemp / 2)) break; // Otherwise, divide away. NumTemp /= 2; DenTemp /= 2; } // Copy out the numbers. *Numerator = (int)NumTemp; *Denominator = (int)DenTemp; // And finished... } int WriteGPSData(char* File, struct GPSPoint* Point, char* Datum, int NoChangeMtime, int DegMinSecs) { // Write the GPS data to the file... struct stat statbuf; struct stat statbuf2; struct utimbuf utb; if (NoChangeMtime) stat(File, &statbuf); Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { // It failed if we got here. //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, File).c_str()); fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Exiv2::ExifData &ExifToWrite = Image->exifData(); char ScratchBuf[100]; long int Nom, Denom; long int Deg, Min, Sec; double FracPart; // Do all the easy constant ones first. // GPSVersionID tag: standard says is should be four bytes: 02 00 00 00 // (and, must be present). Exiv2::Value::AutoPtr Value = Exiv2::Value::create(Exiv2::unsignedByte); Value->read("2 0 0 0"); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), Value.get()); // Datum: the datum of the measured data. If not given, we insert WGS-84. ExifToWrite["Exif.GPSInfo.GPSMapDatum"] = Datum; // Now start adding data. // ALTITUDE. // Altitude reference: byte "00" meaning "sea level". // Or "01" if the altitude value is negative. Value = Exiv2::Value::create(Exiv2::unsignedByte); if (Point->Elev > 0) { Value->read("0"); } else { Value->read("1"); } ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), Value.get()); // And the actual altitude. Value = Exiv2::Value::create(Exiv2::unsignedRational); ConvertToRational(fabs(Point->Elev), &Nom, &Denom, 4); snprintf(ScratchBuf, 100, "%ld/%ld", Nom, Denom); /* printf("Altitude: %f -> %s\n", Point->Elev, ScratchBuf); */ Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"), Value.get()); // LATTITUDE // Latitude reference: "N" or "S". if (Point->Lat < 0) { // Less than Zero: ie, minus: means // Southern hemisphere. Where I live. ExifToWrite["Exif.GPSInfo.GPSLatitudeRef"] = "S"; } else { // More than Zero: ie, plus: means // Northern hemisphere. ExifToWrite["Exif.GPSInfo.GPSLatitudeRef"] = "N"; } // Now the actual lattitude itself. // The original comment read: // This is done as three rationals. // I choose to do it as: // dd/1 - degrees. // mmmm/100 - minutes // 0/1 - seconds // Exif standard says you can do it with minutes // as mm/1 and then seconds as ss/1, but its // (slightly) more accurate to do it as // mmmm/100 than to split it. // We also absolute the value (with fabs()) // as the sign is encoded in LatRef. // Further note: original code did not translate between // dd.dddddd to dd mm.mm - that's why we now multiply // by 6000 - x60 to get minutes, x100 to get to mmmm/100. // // Rereading the EXIF standard, it's quite ok to do DD MM SS.SS // Which is much more accurate. This is the new default, unless otherwise // set. Value = Exiv2::Value::create(Exiv2::unsignedRational); if (DegMinSecs) { Deg = (int)floor(fabs(Point->Lat)); // Slice off after decimal. Min = (int)floor((fabs(Point->Lat) - floor(fabs(Point->Lat))) * 60); // Now grab just the minutes. FracPart = ((fabs(Point->Lat) - floor(fabs(Point->Lat))) * 60) - (double)Min; // Grab the fractional minute. Sec = (int)floor(FracPart * 6000); // Convert to seconds. /* printf("New style lattitude: %f -> %ld/%ld/ %ld/100\n", Point->Lat, Deg, Min, Sec); */ snprintf(ScratchBuf, 100, "%ld/1 %ld/1 %ld/100", Deg, Min, Sec); } else { Deg = (int)floor(fabs(Point->Lat)); // Slice off after decimal. Min = (int)floor((fabs(Point->Lat) - floor(fabs(Point->Lat))) * 6000); snprintf(ScratchBuf, 100, "%ld/1 %ld/100 0/1", Deg, Min); } Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLatitude"), Value.get()); // LONGITUDE // Longitude reference: "E" or "W". if (Point->Long < 0) { // Less than Zero: ie, minus: means // Western hemisphere. ExifToWrite["Exif.GPSInfo.GPSLongitudeRef"] = "W"; } else { // More than Zero: ie, plus: means // Eastern hemisphere. Where I live. ExifToWrite["Exif.GPSInfo.GPSLongitudeRef"] = "E"; } // Now the actual longitude itself. // This is done as three rationals. // I choose to do it as: // dd/1 - degrees. // mmmm/100 - minutes // 0/1 - seconds // Exif standard says you can do it with minutes // as mm/1 and then seconds as ss/1, but its // (slightly) more accurate to do it as // mmmm/100 than to split it. // We also absolute the value (with fabs()) // as the sign is encoded in LongRef. // Further note: original code did not translate between // dd.dddddd to dd mm.mm - that's why we now multiply // by 6000 - x60 to get minutes, x100 to get to mmmm/100. Value = Exiv2::Value::create(Exiv2::unsignedRational); if (DegMinSecs) { Deg = (int)floor(fabs(Point->Long)); // Slice off after decimal. Min = (int)floor((fabs(Point->Long) - floor(fabs(Point->Long))) * 60); // Now grab just the minutes. FracPart = ((fabs(Point->Long) - floor(fabs(Point->Long))) * 60) - (double)Min; // Grab the fractional minute. Sec = (int)floor(FracPart * 6000); // Convert to seconds. /* printf("New style longitude: %f -> %ld/%ld/ %ld/100\n", Point->Long, Deg, Min, Sec); */ snprintf(ScratchBuf, 100, "%ld/1 %ld/1 %ld/100", Deg, Min, Sec); } else { Deg = (int)floor(fabs(Point->Long)); // Slice off after decimal. Min = (int)floor((fabs(Point->Long) - floor(fabs(Point->Long))) * 6000); snprintf(ScratchBuf, 100, "%ld/1 %ld/100 0/1", Deg, Min); } Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLongitude"), Value.get()); // The timestamp. // Make up the timestamp... // The timestamp is taken as the UTC time of the photo. // If interpolation occured, then this time is the time of the photo. struct tm TimeStamp; TimeStamp.tm_isdst = -1; struct tm *tmp = gmtime(&(Point->Time)); memcpy(&TimeStamp, tmp, sizeof(struct tm)); TimeStamp.tm_isdst = -1; if (Point->Time != mktime(&TimeStamp)) { // What happened is gmtime subtracted the current time zone. // I thought it was called "gmtime" for a reason. // Oh well. Add the difference and try again. // This is a hack. time_t CorrectedTime = Point->Time + (Point->Time - mktime(&TimeStamp)); struct tm *tmp2 = gmtime(&CorrectedTime); memcpy(&TimeStamp, tmp2, sizeof(struct tm)); } Value = Exiv2::Value::create(Exiv2::signedRational); snprintf(ScratchBuf, 100, "%d/1 %d/1 %d/1", TimeStamp.tm_hour, TimeStamp.tm_min, TimeStamp.tm_sec); Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"), Value.get()); // And we should also do a datestamp. Value = Exiv2::Value::create(Exiv2::signedRational); snprintf(ScratchBuf, 100, "%d/1 %d/1 %d/1", TimeStamp.tm_year + 1900, TimeStamp.tm_mon + 1, TimeStamp.tm_mday); Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSDateStamp"), Value.get()); // Write the data to file. Image->writeMetadata(); if (NoChangeMtime) { stat(File, &statbuf2); utb.actime = statbuf2.st_atime; utb.modtime = statbuf.st_mtime; utime(File, &utb); } return 1; }; int WriteFixedDatestamp(char* File, time_t Time) { // Write the GPS data to the file... struct stat statbuf; struct stat statbuf2; struct utimbuf utb; stat(File, &statbuf); Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { // It failed if we got here. //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, File).c_str()); fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Exiv2::ExifData &ExifToWrite = Image->exifData(); struct tm TimeStamp; TimeStamp.tm_isdst = -1; struct tm *tmp = gmtime(&Time); memcpy(&TimeStamp, tmp, sizeof(struct tm)); TimeStamp.tm_isdst = -1; if (Time != mktime(&TimeStamp)) { // What happened is gmtime subtracted the current time zone. // I thought it was called "gmtime" for a reason. // Oh well. Add the difference and try again. // This is a hack. time_t CorrectedTime = Time + (Time - mktime(&TimeStamp)); struct tm *tmp2 = gmtime(&CorrectedTime); memcpy(&TimeStamp, tmp2, sizeof(struct tm)); } char ScratchBuf[100]; Exiv2::Value::AutoPtr Value; Value = Exiv2::Value::create(Exiv2::signedRational); snprintf(ScratchBuf, 100, "%d/1 %d/1 %d/1", TimeStamp.tm_year + 1900, TimeStamp.tm_mon + 1, TimeStamp.tm_mday); Value->read(ScratchBuf); ExifToWrite.erase(ExifToWrite.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSDateStamp"))); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSDateStamp"), Value.get()); Value = Exiv2::Value::create(Exiv2::signedRational); snprintf(ScratchBuf, 100, "%d/1 %d/1 %d/1", TimeStamp.tm_hour, TimeStamp.tm_min, TimeStamp.tm_sec); Value->read(ScratchBuf); ExifToWrite.erase(ExifToWrite.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"))); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"), Value.get()); Image->writeMetadata(); // Reset the mtime. stat(File, &statbuf2); utb.actime = statbuf2.st_atime; utb.modtime = statbuf.st_mtime; utime(File, &utb); return 1; } int RemoveGPSExif(char* File, int NoChangeMtime) { struct stat statbuf; struct stat statbuf2; struct utimbuf utb; if (NoChangeMtime) stat(File, &statbuf); // Open the file and start reading. Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { // It failed if we got here. //fprintf(stderr, "%s\n", // Exiv2::ExifData::strError(Result, File).c_str()); fprintf(stderr, "Failed to open file %s.\n", File); return 0; } Exiv2::ExifData &ExifInfo = Image->exifData(); // Search through, find the keys that we want, and wipe them // Code below submitted by Marc Horowitz Exiv2::ExifData::iterator Iter; for (Exiv2::ExifData::iterator Iter = ExifInfo.begin(); Iter != ExifInfo.end(); ) { if (Iter->key().find("Exif.GPSInfo") == 0) Iter = ExifInfo.erase(Iter); else Iter++; } Image->writeMetadata(); if (NoChangeMtime) { stat(File, &statbuf2); utb.actime = statbuf2.st_atime; utb.modtime = statbuf.st_mtime; utime(File, &utb); } return 1; }