/* ============================================================ * * This file is a part of kipi-plugins project * http://www.kipi-plugins.org * * Date : 2006-05-16 * Description : a tool to export GPS data to KML file. * * Copyright (C) 2006-2007 by Stephane Pontier * * 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, 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. * * ============================================================ */ extern "C" { #include } // TQt includes. #include #include // KDE includes. #include #include #include #include #include #include // Libkexiv2 includes. #include // LibKipi includes #include #include #include // Local includes. #include "kmlexport.h" namespace KIPIGPSSyncPlugin { kmlExport::kmlExport(KIPI::Interface* interface) { m_interface = interface; TQWidget* parent = TDEApplication::kApplication()->mainWidget(); m_progressDialog = new KIPI::BatchProgressDialog(parent, i18n("Generating KML file...")); } kmlExport::~kmlExport() { delete m_progressDialog; } /*! \fn kmlExport::createDir(TQDir dir) */ bool kmlExport::createDir(TQDir dir) { if (dir.exists()) return true; TQDir parent = dir; parent.cdUp(); bool ok = createDir(parent); if (!ok) { logError(i18n("Could not create '%1").arg(parent.path())); return false; } return parent.mkdir(dir.dirName()); } /*! \fn kmlExport::webifyFileName(const TQString &fileName) */ TQString kmlExport::webifyFileName(const TQString &fileName) { TQString webFileName=fileName.lower(); // Remove potentially troublesome chars webFileName=webFileName.replace(TQRegExp("[^-0-9a-z]+"), "_"); return webFileName; } /*! \fn kmlExport::generateSquareThumbnail(const TQImage& fullImage, int size) */ TQImage kmlExport::generateSquareThumbnail(const TQImage& fullImage, int size) { TQImage image = fullImage.smoothScale(size, size, TQ_ScaleMax); if (image.width() == size && image.height() == size) { return image; } TQPixmap croppedPix(size, size); TQPainter painter(&croppedPix); int sx=0, sy=0; if (image.width()>size) { sx=(image.width() - size)/2; } else { sy=(image.height() - size)/2; } painter.drawImage(0, 0, image, sx, sy, size, size); painter.end(); return croppedPix.convertToImage(); } /*! \fn kmlExport::generateBorderedThumbnail(const TQImage& fullImage, int size) */ TQImage kmlExport::generateBorderedThumbnail(const TQImage& fullImage, int size) { int image_border = 3; // getting an image minus the border TQImage image = fullImage.smoothScale(size -(2*image_border), size - (2*image_border), TQ_ScaleMax); TQPixmap croppedPix(image.width() + (2*image_border), image.height() + (2*image_border)); TQPainter painter(&croppedPix); TQColor BrushColor(255,255,255); painter.fillRect(0,0,image.width() + (2*image_border),image.height() + (2*image_border),BrushColor); /*! @todo add a corner to the thumbnail and a hotspot to the kml element */ painter.drawImage(image_border, image_border, image ); painter.end(); return croppedPix.convertToImage(); } /*! \fn kmlExport::generateImagesthumb(KIPI::Interface* interface, const KURL& imageURL, TQDomElement &kmlAlbum ) */ void kmlExport::generateImagesthumb(KIPI::Interface* interface, const KURL& imageURL, TQDomElement &kmlAlbum ) { KIPI::Interface* mInterface = interface; KIPI::ImageInfo info = mInterface->info(imageURL); // Load image TQString path = imageURL.path(); TQFile imageFile(path); if (!imageFile.open(IO_ReadOnly)) { logWarning(i18n("Could not read image '%1'").arg(path)); return; } TQString imageFormat = TQImageIO::imageFormat(TQT_TQIODEVICE(&imageFile)); if (imageFormat.isEmpty()) { logWarning(i18n("Format of image '%1' is unknown").arg(path)); return; } imageFile.close(); imageFile.open(IO_ReadOnly); TQByteArray imageData = imageFile.readAll(); TQImage image; if (!image.loadFromData(imageData) ) { logWarning(i18n("Error loading image '%1'").arg(path)); return; } // Process images /** FIXME depending the soft used, angle could return a good value (digikam) or a value of 0 (gwenview) * and, in some case the picture is not rotated as it should be. */ if ( info.angle() != 0 ) { TQWMatrix matrix; matrix.rotate( info.angle() ); image = image.xForm( matrix ); } image = image.smoothScale(m_size, m_size, TQ_ScaleMax); TQImage icon; if (m_optimize_googlemap) { icon = generateSquareThumbnail(image,m_googlemapSize); } else { // icon = image.smoothScale(m_iconSize, m_iconSize, TQ_ScaleMax); icon = generateBorderedThumbnail(image, m_iconSize); } // Save images /** @todo remove the extension of the file * it's appear with digikam but not with gwenview * which already seems to strip the extension */ TQString baseFileName = webifyFileName(info.title()); // baseFileName = mUniqueNameHelper.makeNameUnique(baseFileName); TQString fullFileName; fullFileName = baseFileName + '.' + imageFormat.lower(); TQString destPath = m_tempDestDir + m_imageDir + fullFileName; if (!image.save(destPath, imageFormat.ascii(), 85)) { // if not able to save the image, it's pointless to create a placemark logWarning(i18n("Could not save image '%1' to '%2'").arg(path).arg(destPath)); } else { //logInfo(i18n("Creation of picture '%1'").arg(fullFileName)); KExiv2Iface::KExiv2 exiv2Iface; exiv2Iface.load(imageURL.path()); double alt, lat, lng; exiv2Iface.getGPSInfo(alt, lat, lng); TQDomElement kmlPlacemark = addKmlElement(kmlAlbum, "Placemark"); addKmlTextElement(kmlPlacemark,"name",fullFileName); // location and altitude TQDomElement kmlGeometry = addKmlElement(kmlPlacemark, "Point"); if (alt) { addKmlTextElement(kmlGeometry, "coordinates", TQString("%1,%2,%3").arg(lng).arg(lat).arg(alt)); } else { addKmlTextElement(kmlGeometry, "coordinates", TQString("%1,%2").arg(lng).arg(lat)); } if (m_altitudeMode == 2 ) { addKmlTextElement(kmlGeometry, "altitudeMode", "absolute"); } else if (m_altitudeMode == 1 ) { addKmlTextElement(kmlGeometry, "altitudeMode", "relativeToGround"); } else { addKmlTextElement(kmlGeometry, "altitudeMode", "clampToGround"); } addKmlTextElement(kmlGeometry, "extrude", "1"); // we try to load exif value if any otherwise, try the application db /** we need to take the DateTimeOriginal * if we refer to http://www.exif.org/Exif2-2.PDF * (standard)DateTime: is The date and time of image creation. In this standard it is the date and time the file was changed * DateTimeOriginal: The date and time when the original image data was generated. * For a DSC the date and time the picture was taken are recorded. * DateTimeDigitized: The date and time when the image was stored as digital data. * So for: * - a DSC: the right time is the DateTimeDigitized which is also DateTimeOriginal * if the picture has been modified the (standard)DateTime should change. * - a scanned picture, the right time is the DateTimeOriginal which should also be the the DateTime * the (standard)DateTime should be the same except if the picture is modified * - a panorama created from several pictures, the right time is the DateTimeOriginal (average of DateTimeOriginal actually) * The (standard)DateTime is the creation date of the panorama. * it's seems the time to take into acccount is the DateTimeOriginal. * but the exiv2Iface.getImageDateTime() return the (standard)DateTime first * libkexiv2 seems to take Original dateTime first so it shoul be alright now. */ TQDateTime datetime = exiv2Iface.getImageDateTime(); if (datetime.isValid()) { TQDomElement kmlTimeStamp = addKmlElement(kmlPlacemark, "TimeStamp"); addKmlTextElement(kmlTimeStamp, "when", datetime.toString("yyyy-MM-ddThh:mm:ssZ")); } else if ( mInterface->hasFeature(KIPI::ImagesHasTime)) { TQDomElement kmlTimeStamp = addKmlElement(kmlGeometry, "TimeStamp"); addKmlTextElement(kmlTimeStamp, "when", (info.time()).toString("yyyy-MM-ddThh:mm:ssZ")); } TQString my_description; if (m_optimize_googlemap) { my_description = ""; } else { my_description = ""; } if ( m_interface->hasFeature( KIPI::ImagesHasComments ) ) { my_description += "
" + info.description() ; } addKmlTextElement(kmlPlacemark, "description", my_description); logInfo(i18n("Creation of placemark '%1'").arg(fullFileName)); // Save icon TQString iconFileName = "thumb_" + baseFileName + '.' + imageFormat.lower(); TQString destPath = m_tempDestDir + m_imageDir + iconFileName; if (!icon.save(destPath, imageFormat.ascii(), 85)) { logWarning(i18n("Could not save icon for image '%1' to '%2'").arg(path).arg(destPath)); } else { //logInfo(i18n("Creation of icon '%1'").arg(iconFileName)); // style et icon TQDomElement kmlStyle = addKmlElement(kmlPlacemark, "Style"); TQDomElement kmlIconStyle = addKmlElement(kmlStyle, "IconStyle"); TQDomElement kmlIcon = addKmlElement(kmlIconStyle, "Icon"); if (m_optimize_googlemap) { addKmlTextElement(kmlIcon, "href", m_UrlDestDir + m_imageDir + iconFileName); } else { addKmlTextElement(kmlIcon, "href", m_imageDir + iconFileName); } TQDomElement kmlBallonStyle = addKmlElement(kmlStyle, "BalloonStyle"); addKmlTextElement(kmlBallonStyle, "text", "$[description]"); } } } /*! \fn kmlExport::addTrack(TQDomElement &kmlAlbum) */ void kmlExport::addTrack(TQDomElement &kmlAlbum) { if( m_GPXFile.isEmpty() ) { logWarning(i18n("No GPX file Chosen!")); return; } m_gpxParser.clear(); bool ret = m_gpxParser.loadGPXFile(m_GPXFile); if (!ret) { logError(i18n("Cannot parse %1 GPX file!").arg(m_GPXFile)); return; } if (m_gpxParser.numPoints() <= 0) { logError(i18n("The %1 GPX file do not have a date-time track to use!") .arg(m_GPXFile)); return; } // create a folder that will contain tracks and points TQDomElement kmlFolder = addKmlElement(kmlAlbum, "Folder"); addKmlTextElement(kmlFolder, "name", i18n("Tracks")); if (!m_optimize_googlemap) { // style of points and track TQDomElement kmlTrackStyle = addKmlElement(kmlAlbum, "Style"); kmlTrackStyle.setAttribute("id","track"); TQDomElement kmlIconStyle = addKmlElement(kmlTrackStyle, "IconStyle"); TQDomElement kmlIcon = addKmlElement(kmlIconStyle, "Icon"); //! FIXME is there a way to be sure of the location of the icon? addKmlTextElement(kmlIcon, "href", "http://maps.google.com/mapfiles/kml/pal4/icon60.png"); m_gpxParser.CreateTrackPoints(kmlFolder, *kmlDocument, m_TimeZone - 12, m_GPXAltitudeMode); } // linetrack style TQDomElement kmlLineTrackStyle = addKmlElement(kmlAlbum, "Style"); kmlLineTrackStyle.setAttribute("id","linetrack"); TQDomElement kmlLineStyle = addKmlElement(kmlLineTrackStyle, "LineStyle"); // the KML color is not #RRGGBB but AABBGGRR TQString KMLColorValue = TQString("%1%2%3%4") .arg((int)m_GPXOpacity*256/100, 2, 16) .arg((&m_GPXColor)->blue(), 2, 16) .arg((&m_GPXColor)->green(), 2, 16) .arg((&m_GPXColor)->red(), 2, 16); addKmlTextElement(kmlLineStyle, "color", KMLColorValue); addKmlTextElement(kmlLineStyle, "width", TQString("%1").arg(m_LineWidth) ); m_gpxParser.CreateTrackLine(kmlAlbum, *kmlDocument, m_GPXAltitudeMode); } /*! \fn kmlExport::generate() */ void kmlExport::generate() { //! @todo perform a test here before to continue. createDir(m_tempDestDir + m_imageDir); m_progressDialog->show(); KIPI::ImageCollection selection = m_interface->currentSelection(); KIPI::ImageCollection album = m_interface->currentAlbum(); // create the document, and it's root kmlDocument = new TQDomDocument(""); TQDomImplementation impl; TQDomProcessingInstruction instr = kmlDocument->createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\""); kmlDocument->appendChild(instr); TQDomElement kmlRoot = kmlDocument->createElementNS( "http://earth.google.com/kml/2.1","kml"); kmlDocument->appendChild( kmlRoot ); TQDomElement kmlAlbum = addKmlElement( kmlRoot, "Document"); TQDomElement kmlName= addKmlTextElement( kmlAlbum, "name", album.name()); TQDomElement kmlDescription = addKmlHtmlElement( kmlAlbum, "description", "Created with kmlexport kipi-plugin"); if (m_GPXtracks) { addTrack(kmlAlbum); } KURL::List images = selection.images(); int defectImage = 0; int pos = 1; int count = images.count(); KURL::List::ConstIterator imagesEnd (images.constEnd()); for( KURL::List::ConstIterator selIt = images.constBegin(); selIt != imagesEnd; ++selIt, ++pos) { KExiv2Iface::KExiv2 exiv2Iface; KIPI::ImageInfo info = m_interface->info( *selIt ); // exiv2 load from url not image KURL url = *selIt; exiv2Iface.load(url.path()); double alt, lat, lng; bool hasGPSInfo = exiv2Iface.getGPSInfo(alt, lat, lng); if ( hasGPSInfo ) { // generation de l'image et de l'icone generateImagesthumb(m_interface,url,kmlAlbum); } else { logWarning(i18n("No position data for '%1'").arg(info.title())); defectImage++; } m_progressDialog->setProgress(pos, count); tqApp->processEvents(); } if (defectImage) { /** @todo if defectImage==count there are no pictures exported, does it worst to continue? */ TQWidget* parent = TDEApplication::kApplication()->mainWidget(); KMessageBox::information(parent, i18n("No position data for 1 picture", "No position data for %n pictures", defectImage)); } /** @todo change to kml or kmz if compressed */ TQFile file( m_tempDestDir + m_KMLFileName + ".kml"); /** @todo handle file opening problems */ file.open( IO_WriteOnly ); TQTextStream stream( &file ); // we will serialize the data into the file stream << kmlDocument->toString(); file.close(); delete kmlDocument; TDEIO::moveAs(m_tempDestDir,m_baseDestDir,false); logInfo(i18n("Move to final directory")); m_progressDialog->close(); } /*! \fn kmlExport::getConfig() */ int kmlExport::getConfig() { TDEConfig config("kipirc"); config.setGroup("KMLExport Settings"); m_localTarget = config.readBoolEntry("localTarget"); m_optimize_googlemap = config.readBoolEntry("optimize_googlemap"); m_iconSize = config.readNumEntry("iconSize"); // googlemapSize = config.readNumEntry("googlemapSize"); m_size = config.readNumEntry("size"); // UrlDestDir have to have the trailing m_baseDestDir = config.readEntry("baseDestDir"); m_UrlDestDir = config.readEntry("UrlDestDir"); m_altitudeMode = config.readNumEntry("Altitude Mode", 0); m_KMLFileName = config.readEntry("KMLFileName"); m_GPXtracks = config.readBoolEntry("UseGPXTracks"); m_GPXFile = config.readEntry("GPXFile"); m_TimeZone = config.readNumEntry("Time Zone", 12); m_LineWidth = config.readNumEntry("Line Width", 4); m_GPXColor = config.readEntry("Track Color", "#17eeee" ); m_GPXOpacity = config.readNumEntry("Track Opacity", 64 ); m_GPXAltitudeMode = config.readNumEntry("GPX Altitude Mode", 0); TDEStandardDirs dir; m_tempDestDir = dir.saveLocation("tmp", "kipi-kmlrexportplugin-" + TQString::number(getpid()) + '/'); m_imageDir = "images/"; m_googlemapSize = 32; return 1; } void kmlExport::logInfo(const TQString& msg) { m_progressDialog->addedAction(msg, KIPI::ProgressMessage); } void kmlExport::logError(const TQString& msg) { m_progressDialog->addedAction(msg, KIPI::ErrorMessage); } void kmlExport::logWarning(const TQString& msg) { m_progressDialog->addedAction(msg, KIPI::WarningMessage); // mWarnings=true; } } //namespace KIPIGPSSyncPlugin