You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
524 lines
18 KiB
524 lines
18 KiB
/* ============================================================
|
|
*
|
|
* 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 <shadow dot walker at free dot fr>
|
|
*
|
|
* 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 <unistd.h>
|
|
}
|
|
|
|
// TQt includes.
|
|
|
|
#include <tqpainter.h>
|
|
#include <tqregexp.h>
|
|
|
|
// KDE includes.
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdeconfig.h>
|
|
#include <tdeio/job.h>
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
// Libkexiv2 includes.
|
|
|
|
#include <libkexiv2/kexiv2.h>
|
|
|
|
// LibKipi includes
|
|
|
|
#include <libkipi/plugin.h>
|
|
#include <libkipi/batchprogressdialog.h>
|
|
#include <libkipi/imageinfo.h>
|
|
|
|
// 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, TQImage::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), TQImage::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(&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, TQImage::ScaleMax);
|
|
|
|
TQImage icon;
|
|
if (m_optimize_googlemap)
|
|
{
|
|
icon = generateSquareThumbnail(image,m_googlemapSize);
|
|
}
|
|
else
|
|
{
|
|
// icon = image.smoothScale(m_iconSize, m_iconSize, TQImage::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 = "<img src=\"" + m_UrlDestDir + m_imageDir + fullFileName + "\">";
|
|
}
|
|
else
|
|
{
|
|
my_description = "<img src=\"" + m_imageDir + fullFileName + "\">";
|
|
}
|
|
if ( m_interface->hasFeature( KIPI::ImagesHasComments ) )
|
|
{
|
|
my_description += "<br/>" + 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 <a href=\"http://www.kipi-plugins.org/\">kipi-plugin</a>");
|
|
|
|
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
|