File indexing completed on 2025-01-05 03:58:31
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-03-21 0007 * Description : A container to hold GPS information about an item. 0008 * 0009 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2010-2014 by Michael G. Hansen <mike at mghansen dot de> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "gpsitemcontainer.h" 0017 0018 // Qt includes 0019 0020 #include <QBrush> 0021 #include <QFileInfo> 0022 #include <QScopedPointer> 0023 #include <QLocale> 0024 0025 // KDE includes 0026 0027 #include <klocalizedstring.h> 0028 0029 // Local includes 0030 0031 #include "digikam_debug.h" 0032 #include "gpsitemmodel.h" 0033 #include "dmetadata.h" 0034 #include "metaenginesettings.h" 0035 0036 namespace Digikam 0037 { 0038 0039 bool setExifXmpTagDataVariant(DMetadata* const meta, const char* const exifTagName, 0040 const char* const xmpTagName, const QVariant& value) 0041 { 0042 bool success = meta->setExifTagVariant(exifTagName, value); 0043 0044 if (success) 0045 { 0046 /** 0047 * @todo Here we save all data types as XMP Strings. 0048 * Is that okay or do we have to store them as some other type? 0049 */ 0050 0051 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0052 0053 switch (static_cast<QMetaType::Type>(value.typeId())) 0054 0055 #else 0056 0057 switch (value.type()) 0058 0059 #endif 0060 0061 { 0062 case QVariant::Int: 0063 case QVariant::UInt: 0064 case QVariant::Bool: 0065 case QVariant::LongLong: 0066 case QVariant::ULongLong: 0067 success = meta->setXmpTagString(xmpTagName, QString::number(value.toInt())); 0068 break; 0069 0070 case QVariant::Double: 0071 { 0072 long num, den; 0073 meta->convertToRationalSmallDenominator(value.toDouble(), &num, &den); 0074 success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); 0075 break; 0076 } 0077 0078 case QVariant::List: 0079 { 0080 long num = 0; 0081 long den = 1; 0082 QList<QVariant> list = value.toList(); 0083 0084 if (list.size() >= 1) 0085 { 0086 num = list[0].toInt(); 0087 } 0088 0089 if (list.size() >= 2) 0090 { 0091 den = list[1].toInt(); 0092 } 0093 0094 success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); 0095 break; 0096 } 0097 0098 case QVariant::Date: 0099 case QVariant::DateTime: 0100 { 0101 QDateTime dateTime = value.toDateTime(); 0102 0103 if (!dateTime.isValid()) 0104 { 0105 success = false; 0106 break; 0107 } 0108 0109 success = meta->setXmpTagString(xmpTagName, dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss"))); 0110 break; 0111 } 0112 0113 case QVariant::String: 0114 case QVariant::Char: 0115 success = meta->setXmpTagString(xmpTagName, value.toString()); 0116 break; 0117 0118 case QVariant::ByteArray: 0119 0120 /// @todo I don't know a straightforward way to convert a byte array to XMP 0121 0122 success = false; 0123 break; 0124 0125 default: 0126 success = false; 0127 break; 0128 } 0129 } 0130 0131 return success; 0132 } 0133 0134 GPSItemContainer::GPSItemContainer(const QUrl& url) 0135 : m_model (nullptr), 0136 m_url (url), 0137 m_dateTime (), 0138 m_dirty (false), 0139 m_gpsData (), 0140 m_savedState (), 0141 m_tagListDirty(false), 0142 m_tagList (), 0143 m_savedTagList(), 0144 m_writeXmpTags(true), 0145 m_writeMetaLoc(true) 0146 { 0147 } 0148 0149 GPSItemContainer::~GPSItemContainer() 0150 { 0151 } 0152 0153 DMetadata* GPSItemContainer::getMetadataForFile() const 0154 { 0155 QScopedPointer<DMetadata> meta(new DMetadata); 0156 0157 if (!meta->load(m_url.toLocalFile())) 0158 { 0159 // It is possible that no sidecar file has yet been created. 0160 // If writing to sidecar file is activated, we ignore the loading error of the metadata. 0161 0162 if (MetaEngineSettings::instance()->settings().metadataWritingMode == DMetadata::WRITE_TO_FILE_ONLY) 0163 { 0164 return nullptr; 0165 } 0166 } 0167 0168 return meta.take(); 0169 } 0170 0171 int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data) 0172 { 0173 if (data.hasDop()) 0174 { 0175 const int dopValue = data.getDop(); 0176 0177 if (dopValue < 2) 0178 { 0179 return 1; 0180 } 0181 0182 if (dopValue < 4) 0183 { 0184 return 2; 0185 } 0186 0187 if (dopValue < 10) 0188 { 0189 return 3; 0190 } 0191 0192 return 4; 0193 } 0194 else if (data.hasFixType()) 0195 { 0196 if (data.getFixType() < 3) 0197 { 0198 return 4; 0199 } 0200 } 0201 else if (data.hasNSatellites()) 0202 { 0203 if (data.getNSatellites() < 4) 0204 { 0205 return 4; 0206 } 0207 } 0208 0209 // no warning level 0210 0211 return -1; 0212 } 0213 0214 bool GPSItemContainer::loadImageData() 0215 { 0216 QScopedPointer<DMetadata> meta(getMetadataForFile()); 0217 0218 if (meta && !m_dateTime.isValid()) 0219 { 0220 m_dateTime = meta->getItemDateTime(); 0221 } 0222 0223 if (!m_dateTime.isValid()) 0224 { 0225 // Get date from filesystem. 0226 0227 QFileInfo info(m_url.toLocalFile()); 0228 0229 QDateTime ctime = info.birthTime(); 0230 QDateTime mtime = info.lastModified(); 0231 0232 if (ctime.isNull() || mtime.isNull()) 0233 { 0234 m_dateTime = qMax(ctime, mtime); 0235 } 0236 else 0237 { 0238 m_dateTime = qMin(ctime, mtime); 0239 } 0240 0241 m_dateTime.setTimeSpec(Qt::UTC); 0242 } 0243 0244 if (!meta) 0245 { 0246 return false; 0247 } 0248 0249 // The way we read the coordinates here is problematic 0250 // if the coordinates were in the file initially, but 0251 // the user deleted them in the database. Then we still load 0252 // them from the file. On the other hand, we can not clear 0253 // the coordinates, because then we would loose them if 0254 // they are only stored in the database. 0255 /* 0256 m_gpsData.clear(); 0257 */ 0258 0259 if (!m_gpsData.hasCoordinates()) 0260 { 0261 // could not load the coordinates from the interface, 0262 // read them directly from the file 0263 0264 double lat, lng; 0265 bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng); 0266 0267 if (haveCoordinates) 0268 { 0269 GeoCoordinates coordinates(lat, lng); 0270 double alt; 0271 0272 if (meta->getGPSAltitude(&alt)) 0273 { 0274 coordinates.setAlt(alt); 0275 } 0276 0277 m_gpsData.setCoordinates(coordinates); 0278 } 0279 } 0280 0281 /** 0282 * @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist, 0283 * therefore no need to read XMP as well? 0284 */ 0285 0286 // read the remaining GPS information from the file: 0287 0288 const QByteArray speedRef = meta->getExifTagData("Exif.GPSInfo.GPSSpeedRef"); 0289 bool success = !speedRef.isEmpty(); 0290 long num, den; 0291 success &= meta->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den); 0292 0293 if (success) 0294 { 0295 // be relaxed about 0/0 0296 0297 if ((num == 0.0) && (den == 0.0)) 0298 { 0299 den = 1.0; 0300 } 0301 0302 const qreal speedInRef = qreal(num)/qreal(den); 0303 qreal FactorToMetersPerSecond; 0304 0305 if (speedRef.startsWith('K')) 0306 { 0307 // km/h = 1000 * 3600 0308 0309 FactorToMetersPerSecond = 1.0 / 3.6; 0310 } 0311 else if (speedRef.startsWith('M')) 0312 { 0313 // TODO: someone please check that this is the 'right' mile 0314 // miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds 0315 0316 FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0); 0317 } 0318 else if (speedRef.startsWith('N')) 0319 { 0320 // speed is in knots. 0321 // knot = one nautical mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds 0322 0323 FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0); 0324 } 0325 else 0326 { 0327 success = false; 0328 } 0329 0330 if (success) 0331 { 0332 const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond; 0333 m_gpsData.setSpeed(speedInMetersPerSecond); 0334 } 0335 } 0336 0337 // number of satellites 0338 0339 const QString gpsSatellitesString = meta->getExifTagString("Exif.GPSInfo.GPSSatellites"); 0340 bool satellitesOkay = !gpsSatellitesString.isEmpty(); 0341 0342 if (satellitesOkay) 0343 { 0344 /** 0345 * @todo Here we only accept a single integer denoting the number of satellites used 0346 * but not detailed information about all satellites. 0347 */ 0348 0349 const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay); 0350 0351 if (satellitesOkay) 0352 { 0353 m_gpsData.setNSatellites(nSatellites); 0354 } 0355 } 0356 0357 // fix type / measure mode 0358 0359 const QByteArray gpsMeasureModeByteArray = meta->getExifTagData("Exif.GPSInfo.GPSMeasureMode"); 0360 bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty(); 0361 0362 if (measureModeOkay) 0363 { 0364 const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay); 0365 0366 if (measureModeOkay) 0367 { 0368 if ((measureMode == 2) || (measureMode == 3)) 0369 { 0370 m_gpsData.setFixType(measureMode); 0371 } 0372 } 0373 } 0374 0375 // read the DOP value: 0376 0377 success = meta->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den); 0378 0379 if (success) 0380 { 0381 // be relaxed about 0/0 0382 0383 if ((num == 0.0) && (den == 0.0)) 0384 { 0385 den = 1.0; 0386 } 0387 0388 const qreal dop = qreal(num)/qreal(den); 0389 0390 m_gpsData.setDop(dop); 0391 } 0392 0393 // mark us as not-dirty, because the data was just loaded: 0394 0395 m_dirty = false; 0396 m_savedState = m_gpsData; 0397 0398 emitDataChanged(); 0399 0400 return true; 0401 } 0402 0403 QVariant GPSItemContainer::data(const int column, const int role) const 0404 { 0405 if ((column == ColumnFilename) && (role == Qt::DisplayRole)) 0406 { 0407 return m_url.fileName(); 0408 } 0409 else if ((column == ColumnDateTime) && (role == Qt::DisplayRole)) 0410 { 0411 if (m_dateTime.isValid()) 0412 { 0413 return QLocale().toString(m_dateTime, QLocale::ShortFormat); 0414 } 0415 0416 return i18n("Not available"); 0417 } 0418 else if (role == RoleCoordinates) 0419 { 0420 return QVariant::fromValue(m_gpsData.getCoordinates()); 0421 } 0422 else if ((column == ColumnLatitude) && (role == Qt::DisplayRole)) 0423 { 0424 if (!m_gpsData.getCoordinates().hasLatitude()) 0425 { 0426 return QString(); 0427 } 0428 0429 return QLocale().toString(m_gpsData.getCoordinates().lat(), 'g', 7); 0430 } 0431 else if ((column == ColumnLongitude) && (role == Qt::DisplayRole)) 0432 { 0433 if (!m_gpsData.getCoordinates().hasLongitude()) 0434 { 0435 return QString(); 0436 } 0437 0438 return QLocale().toString(m_gpsData.getCoordinates().lon(), 'g', 7); 0439 } 0440 else if ((column == ColumnAltitude) && (role == Qt::DisplayRole)) 0441 { 0442 if (!m_gpsData.getCoordinates().hasAltitude()) 0443 { 0444 return QString(); 0445 } 0446 0447 return QLocale().toString(m_gpsData.getCoordinates().alt(), 'g', 7); 0448 } 0449 else if (column == ColumnAccuracy) 0450 { 0451 if (role == Qt::DisplayRole) 0452 { 0453 if (m_gpsData.hasDop()) 0454 { 0455 return i18n("DOP: %1", m_gpsData.getDop()); 0456 } 0457 0458 if (m_gpsData.hasFixType()) 0459 { 0460 return i18n("Fix: %1d", m_gpsData.getFixType()); 0461 } 0462 0463 if (m_gpsData.hasNSatellites()) 0464 { 0465 return i18n("#Sat: %1", m_gpsData.getNSatellites()); 0466 } 0467 } 0468 else if (role == Qt::BackgroundRole) 0469 { 0470 const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData); 0471 0472 switch (warningLevel) 0473 { 0474 case 1: 0475 return QBrush(Qt::green); 0476 0477 case 2: 0478 return QBrush(Qt::yellow); 0479 0480 case 3: 0481 // orange 0482 return QBrush(QColor(0xff, 0x80, 0x00)); 0483 0484 case 4: 0485 return QBrush(Qt::red); 0486 0487 default: 0488 break; 0489 } 0490 } 0491 } 0492 else if ((column == ColumnDOP) && (role == Qt::DisplayRole)) 0493 { 0494 if (!m_gpsData.hasDop()) 0495 { 0496 return QString(); 0497 } 0498 0499 return QString::number(m_gpsData.getDop()); 0500 } 0501 else if ((column == ColumnFixType) && (role == Qt::DisplayRole)) 0502 { 0503 if (!m_gpsData.hasFixType()) 0504 { 0505 return QString(); 0506 } 0507 0508 return i18n("%1d", m_gpsData.getFixType()); 0509 } 0510 else if ((column == ColumnNSatellites) && (role == Qt::DisplayRole)) 0511 { 0512 if (!m_gpsData.hasNSatellites()) 0513 { 0514 return QString(); 0515 } 0516 0517 return QString::number(m_gpsData.getNSatellites()); 0518 } 0519 else if ((column == ColumnSpeed) && (role == Qt::DisplayRole)) 0520 { 0521 if (!m_gpsData.hasSpeed()) 0522 { 0523 return QString(); 0524 } 0525 0526 return QLocale().toString(m_gpsData.getSpeed()); 0527 } 0528 else if ((column == ColumnStatus) && (role == Qt::DisplayRole)) 0529 { 0530 if (m_dirty || m_tagListDirty) 0531 { 0532 return i18n("Modified"); 0533 } 0534 0535 return QString(); 0536 } 0537 else if ((column == ColumnTags) && (role == Qt::DisplayRole)) 0538 { 0539 if (!m_tagList.isEmpty()) 0540 { 0541 0542 QString myTagsList; 0543 0544 for (int i = 0 ; i < m_tagList.count() ; ++i) 0545 { 0546 QString myTag; 0547 0548 for (int j = 0 ; j < m_tagList[i].count() ; ++j) 0549 { 0550 myTag.append(QLatin1Char('/') + m_tagList[i].at(j).tagName); 0551 0552 if (j == 0) 0553 { 0554 myTag.remove(0, 1); 0555 } 0556 } 0557 0558 if (!myTagsList.isEmpty()) 0559 { 0560 myTagsList.append(QLatin1String(", ")); 0561 } 0562 0563 myTagsList.append(myTag); 0564 } 0565 0566 return myTagsList; 0567 } 0568 0569 return QString(); 0570 } 0571 0572 return QVariant(); 0573 } 0574 0575 void GPSItemContainer::setCoordinates(const GeoCoordinates& newCoordinates) 0576 { 0577 m_gpsData.setCoordinates(newCoordinates); 0578 m_dirty = true; 0579 0580 emitDataChanged(); 0581 } 0582 0583 void GPSItemContainer::setModel(GPSItemModel* const model) 0584 { 0585 m_model = model; 0586 } 0587 0588 void GPSItemContainer::emitDataChanged() 0589 { 0590 if (m_model) 0591 { 0592 m_model->itemChanged(this); 0593 } 0594 } 0595 0596 void GPSItemContainer::setHeaderData(GPSItemModel* const model) 0597 { 0598 model->setColumnCount(ColumnGPSItemContainerCount); 0599 model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole); 0600 model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole); 0601 model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole); 0602 model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole); 0603 model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole); 0604 model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole); 0605 model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole); 0606 model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole); 0607 model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole); 0608 model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole); 0609 model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole); 0610 model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole); 0611 model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole); 0612 } 0613 0614 bool GPSItemContainer::lessThan(const GPSItemContainer* const otherItem, const int column) const 0615 { 0616 switch (column) 0617 { 0618 case ColumnThumbnail: 0619 return false; 0620 0621 case ColumnFilename: 0622 return (m_url < otherItem->m_url); 0623 0624 case ColumnDateTime: 0625 return (m_dateTime < otherItem->m_dateTime); 0626 0627 case ColumnAltitude: 0628 { 0629 if (!m_gpsData.hasAltitude()) 0630 { 0631 return false; 0632 } 0633 0634 if (!otherItem->m_gpsData.hasAltitude()) 0635 { 0636 return true; 0637 } 0638 0639 return (m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt()); 0640 } 0641 0642 case ColumnNSatellites: 0643 { 0644 if (!m_gpsData.hasNSatellites()) 0645 { 0646 return false; 0647 } 0648 0649 if (!otherItem->m_gpsData.hasNSatellites()) 0650 { 0651 return true; 0652 } 0653 0654 return (m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites()); 0655 } 0656 0657 case ColumnAccuracy: 0658 { 0659 const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData); 0660 const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData); 0661 0662 if (myWarning < 0) 0663 { 0664 return false; 0665 } 0666 0667 if (otherWarning < 0) 0668 { 0669 return true; 0670 } 0671 0672 if (myWarning != otherWarning) 0673 { 0674 return (myWarning < otherWarning); 0675 } 0676 0677 // TODO: this may not be the best way to sort images with equal warning levels 0678 // but it works for now 0679 0680 if (m_gpsData.hasDop() != otherItem->m_gpsData.hasDop()) 0681 { 0682 return !m_gpsData.hasDop(); 0683 } 0684 0685 if (m_gpsData.hasDop() && otherItem->m_gpsData.hasDop()) 0686 { 0687 return (m_gpsData.getDop() < otherItem->m_gpsData.getDop()); 0688 } 0689 0690 if (m_gpsData.hasFixType() != otherItem->m_gpsData.hasFixType()) 0691 { 0692 return m_gpsData.hasFixType(); 0693 } 0694 0695 if (m_gpsData.hasFixType() && otherItem->m_gpsData.hasFixType()) 0696 { 0697 return (m_gpsData.getFixType() > otherItem->m_gpsData.getFixType()); 0698 } 0699 0700 if (m_gpsData.hasNSatellites() != otherItem->m_gpsData.hasNSatellites()) 0701 { 0702 return m_gpsData.hasNSatellites(); 0703 } 0704 0705 if (m_gpsData.hasNSatellites() && otherItem->m_gpsData.hasNSatellites()) 0706 { 0707 return (m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites()); 0708 } 0709 0710 return false; 0711 } 0712 0713 case ColumnDOP: 0714 { 0715 if (!m_gpsData.hasDop()) 0716 { 0717 return false; 0718 } 0719 0720 if (!otherItem->m_gpsData.hasDop()) 0721 { 0722 return true; 0723 } 0724 0725 return (m_gpsData.getDop() < otherItem->m_gpsData.getDop()); 0726 } 0727 0728 case ColumnFixType: 0729 { 0730 if (!m_gpsData.hasFixType()) 0731 { 0732 return false; 0733 } 0734 0735 if (!otherItem->m_gpsData.hasFixType()) 0736 { 0737 return true; 0738 } 0739 0740 return (m_gpsData.getFixType() < otherItem->m_gpsData.getFixType()); 0741 } 0742 0743 case ColumnSpeed: 0744 { 0745 if (!m_gpsData.hasSpeed()) 0746 { 0747 return false; 0748 } 0749 0750 if (!otherItem->m_gpsData.hasSpeed()) 0751 { 0752 return true; 0753 } 0754 0755 return (m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed()); 0756 } 0757 0758 case ColumnLatitude: 0759 { 0760 if (!m_gpsData.hasCoordinates()) 0761 { 0762 return false; 0763 } 0764 0765 if (!otherItem->m_gpsData.hasCoordinates()) 0766 { 0767 return true; 0768 } 0769 0770 return (m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat()); 0771 } 0772 0773 case ColumnLongitude: 0774 { 0775 if (!m_gpsData.hasCoordinates()) 0776 { 0777 return false; 0778 } 0779 0780 if (!otherItem->m_gpsData.hasCoordinates()) 0781 { 0782 return true; 0783 } 0784 0785 return (m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon()); 0786 } 0787 0788 case ColumnStatus: 0789 { 0790 return (m_dirty && !otherItem->m_dirty); 0791 } 0792 0793 default: 0794 { 0795 return false; 0796 } 0797 } 0798 } 0799 0800 SaveProperties GPSItemContainer::saveProperties() const 0801 { 0802 SaveProperties p; 0803 0804 // do we have gps information? 0805 0806 if (m_gpsData.hasCoordinates()) 0807 { 0808 p.shouldWriteCoordinates = true; 0809 p.latitude = m_gpsData.getCoordinates().lat(); 0810 p.longitude = m_gpsData.getCoordinates().lon(); 0811 0812 if (m_gpsData.hasAltitude()) 0813 { 0814 p.shouldWriteAltitude = true; 0815 p.altitude = m_gpsData.getCoordinates().alt(); 0816 } 0817 else 0818 { 0819 p.shouldRemoveAltitude = true; 0820 } 0821 } 0822 else 0823 { 0824 p.shouldRemoveCoordinates = true; 0825 } 0826 0827 return p; 0828 } 0829 0830 QString GPSItemContainer::saveChanges() 0831 { 0832 SaveProperties p = saveProperties(); 0833 0834 QString returnString; 0835 0836 // first try to write the information to the image file 0837 0838 bool success = true; 0839 QScopedPointer<DMetadata> meta(getMetadataForFile()); 0840 0841 if (!meta) 0842 { 0843 // TODO: more verbosity! 0844 0845 returnString = i18n("Failed to open file."); 0846 success = false; 0847 } 0848 else 0849 { 0850 if (p.shouldWriteCoordinates) 0851 { 0852 if (p.shouldWriteAltitude) 0853 { 0854 success = meta->setGPSInfo(p.altitude, p.latitude, p.longitude); 0855 } 0856 else 0857 { 0858 success = meta->setGPSInfo(nullptr, 0859 p.latitude, p.longitude); 0860 } 0861 0862 // write all other GPS information here too 0863 0864 if (success && m_gpsData.hasSpeed()) 0865 { 0866 success = setExifXmpTagDataVariant(meta.data(), 0867 "Exif.GPSInfo.GPSSpeedRef", 0868 "Xmp.exif.GPSSpeedRef", 0869 QVariant(QLatin1String("K"))); 0870 0871 if (success) 0872 { 0873 const qreal speedInMetersPerSecond = m_gpsData.getSpeed(); 0874 0875 // km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s 0876 0877 const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond; 0878 success = setExifXmpTagDataVariant(meta.data(), 0879 "Exif.GPSInfo.GPSSpeed", 0880 "Xmp.exif.GPSSpeed", 0881 QVariant(speedInKilometersPerHour)); 0882 } 0883 } 0884 0885 if (success && m_gpsData.hasNSatellites()) 0886 { 0887 /** 0888 * @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the 0889 * number of satellites or more details about each satellite used. For now, we just write 0890 * the number of satellites. Are we using the correct format for the number of satellites here? 0891 */ 0892 success = setExifXmpTagDataVariant(meta.data(), 0893 "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites", 0894 QVariant(QString::number(m_gpsData.getNSatellites()))); 0895 } 0896 0897 if (success && m_gpsData.hasFixType()) 0898 { 0899 success = setExifXmpTagDataVariant(meta.data(), 0900 "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode", 0901 QVariant(QString::number(m_gpsData.getFixType()))); 0902 } 0903 0904 // write DOP 0905 0906 if (success && m_gpsData.hasDop()) 0907 { 0908 success = setExifXmpTagDataVariant(meta.data(), 0909 "Exif.GPSInfo.GPSDOP", 0910 "Xmp.exif.GPSDOP", 0911 QVariant(m_gpsData.getDop())); 0912 } 0913 0914 0915 if (!success) 0916 { 0917 returnString = i18n("Failed to add GPS info to image."); 0918 } 0919 } 0920 0921 if (success && p.shouldRemoveCoordinates) 0922 { 0923 // TODO: remove only the altitude if requested 0924 0925 success = meta->removeGPSInfo(); 0926 0927 if (!success) 0928 { 0929 returnString = i18n("Failed to remove GPS info from image"); 0930 } 0931 } 0932 0933 if (success && !m_tagList.isEmpty() && (m_writeXmpTags || m_writeMetaLoc)) 0934 { 0935 if (m_writeXmpTags) 0936 { 0937 QStringList tagSeq = meta->getXmpTagStringSeq("Xmp.digiKam.TagsList", false); 0938 0939 for (int i = 0 ; i < m_tagList.count() ; ++i) 0940 { 0941 QList<TagData> currentTagList = m_tagList[i]; 0942 QString tag; 0943 0944 for (int j = 0 ; j < currentTagList.count() ; ++j) 0945 { 0946 if (currentTagList[j].tipName != QLatin1String("{Country code}")) 0947 { 0948 tag.append(QLatin1Char('/') + currentTagList[j].tagName); 0949 } 0950 } 0951 0952 tag.remove(0, 1); 0953 0954 if (!tag.isEmpty() && !tagSeq.contains(tag)) 0955 { 0956 tagSeq.append(tag); 0957 } 0958 } 0959 0960 success = meta->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq); 0961 0962 if (!success) 0963 { 0964 returnString = i18n("Failed to save tags to file."); 0965 } 0966 else 0967 { 0968 success = meta->setXmpTagStringSeq("Xmp.dc.subject", tagSeq); 0969 0970 if (!success) 0971 { 0972 returnString = i18n("Failed to save tags to file."); 0973 } 0974 } 0975 } 0976 0977 if (success && m_writeMetaLoc) 0978 { 0979 IptcCoreLocationInfo locationInfo; 0980 0981 for (int i = 0 ; i < m_tagList.count() ; ++i) 0982 { 0983 QList<TagData> currentTagList = m_tagList[i]; 0984 QString tag; 0985 0986 for (int j = 0 ; j < currentTagList.count() ; ++j) 0987 { 0988 setLocationInfo(currentTagList[j], locationInfo); 0989 } 0990 } 0991 0992 success = meta->setIptcCoreLocation(locationInfo); 0993 0994 if (!success) 0995 { 0996 returnString = i18n("Failed to save tags to file."); 0997 } 0998 } 0999 } 1000 } 1001 1002 if (success) 1003 { 1004 success = meta->save(m_url.toLocalFile()); 1005 1006 if (!success) 1007 { 1008 returnString = i18n("Unable to save changes to file"); 1009 } 1010 else 1011 { 1012 m_dirty = false; 1013 m_savedState = m_gpsData; 1014 m_tagListDirty = false; 1015 m_savedTagList = m_tagList; 1016 } 1017 } 1018 1019 if (returnString.isEmpty()) 1020 { 1021 // mark all changes as not dirty and tell the model: 1022 1023 emitDataChanged(); 1024 } 1025 1026 return returnString; 1027 } 1028 1029 /** 1030 * @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState. 1031 */ 1032 void GPSItemContainer::restoreGPSData(const GPSDataContainer& container) 1033 { 1034 m_dirty = !(container == m_savedState); 1035 m_gpsData = container; 1036 1037 emitDataChanged(); 1038 } 1039 1040 void GPSItemContainer::restoreRGTagList(const QList<QList<TagData> >& tagList) 1041 { 1042 // TODO: override == operator 1043 1044 if (tagList.count() != m_savedTagList.count()) 1045 { 1046 m_tagListDirty = true; 1047 } 1048 else 1049 { 1050 for (int i = 0 ; i < tagList.count() ; ++i) 1051 { 1052 bool foundNotEqual = false; 1053 1054 if (tagList[i].count() != m_savedTagList[i].count()) 1055 { 1056 m_tagListDirty = true; 1057 break; 1058 } 1059 1060 for (int j = 0 ; j < tagList[i].count() ; ++j) 1061 { 1062 if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName) 1063 { 1064 foundNotEqual = true; 1065 break; 1066 } 1067 } 1068 1069 if (foundNotEqual) 1070 { 1071 m_tagListDirty = true; 1072 break; 1073 } 1074 } 1075 } 1076 1077 m_tagList = tagList; 1078 1079 emitDataChanged(); 1080 } 1081 1082 bool GPSItemContainer::isDirty() const 1083 { 1084 return m_dirty; 1085 } 1086 1087 QUrl GPSItemContainer::url() const 1088 { 1089 return m_url; 1090 } 1091 1092 QDateTime GPSItemContainer::dateTime() const 1093 { 1094 return m_dateTime; 1095 } 1096 1097 GeoCoordinates GPSItemContainer::coordinates() const 1098 { 1099 return m_gpsData.getCoordinates(); 1100 } 1101 1102 GPSDataContainer GPSItemContainer::gpsData() const 1103 { 1104 return m_gpsData; 1105 } 1106 1107 void GPSItemContainer::setGPSData(const GPSDataContainer& container) 1108 { 1109 m_gpsData = container; 1110 m_dirty = true; 1111 1112 emitDataChanged(); 1113 } 1114 1115 void GPSItemContainer::setTagList(const QList<QList<TagData> >& externalTagList) 1116 { 1117 m_tagList = externalTagList; 1118 m_tagListDirty = true; 1119 1120 emitDataChanged(); 1121 } 1122 1123 bool GPSItemContainer::isTagListDirty() const 1124 { 1125 return m_tagListDirty; 1126 } 1127 1128 QList<QList<TagData> > GPSItemContainer::getTagList() const 1129 { 1130 return m_tagList; 1131 } 1132 1133 void GPSItemContainer::setLocationInfo(const TagData& tagData, IptcCoreLocationInfo& locationInfo) 1134 { 1135 if (tagData.tipName == QLatin1String("{Country}")) 1136 { 1137 locationInfo.country = tagData.tagName; 1138 } 1139 else if (tagData.tipName == QLatin1String("{Country code}")) 1140 { 1141 locationInfo.countryCode = tagData.tagName; 1142 } 1143 else if (tagData.tipName == QLatin1String("{State}")) 1144 { 1145 locationInfo.provinceState = tagData.tagName; 1146 } 1147 else if (locationInfo.provinceState.isEmpty() && 1148 (tagData.tipName == QLatin1String("{State district}"))) 1149 { 1150 locationInfo.provinceState = tagData.tagName; 1151 } 1152 else if (locationInfo.provinceState.isEmpty() && 1153 (tagData.tipName == QLatin1String("{County}"))) 1154 { 1155 locationInfo.provinceState = tagData.tagName; 1156 } 1157 else if (tagData.tipName == QLatin1String("{City}")) 1158 { 1159 locationInfo.city = tagData.tagName; 1160 } 1161 else if (locationInfo.city.isEmpty() && 1162 (tagData.tipName == QLatin1String("{City district}"))) 1163 { 1164 locationInfo.city = tagData.tagName; 1165 } 1166 else if (locationInfo.city.isEmpty() && 1167 (tagData.tipName == QLatin1String("{Suburb}"))) 1168 { 1169 locationInfo.city = tagData.tagName; 1170 } 1171 else if (locationInfo.city.isEmpty() && 1172 (tagData.tipName == QLatin1String("{Town}"))) 1173 { 1174 locationInfo.city = tagData.tagName; 1175 } 1176 else if (locationInfo.city.isEmpty() && 1177 (tagData.tipName == QLatin1String("{Village}"))) 1178 { 1179 locationInfo.city = tagData.tagName; 1180 } 1181 else if (locationInfo.city.isEmpty() && 1182 (tagData.tipName == QLatin1String("{Hamlet}"))) 1183 { 1184 locationInfo.city = tagData.tagName; 1185 } 1186 else if (tagData.tipName == QLatin1String("{Street}")) 1187 { 1188 locationInfo.location = tagData.tagName; 1189 } 1190 else if (!locationInfo.location.isEmpty() && 1191 (tagData.tipName == QLatin1String("{House number}"))) 1192 { 1193 locationInfo.location.append(QLatin1Char(' ') + tagData.tagName); 1194 } 1195 } 1196 1197 } // namespace Digikam