/* ============================================================
*
* 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