Refactor Editor storage to use diff based approach
Signed-off-by: map-per <map-per@gmx.de>
This commit is contained in:
parent
3ce6a9a29d
commit
efcadd6f25
17 changed files with 1046 additions and 99 deletions
|
@ -149,7 +149,8 @@ Java_app_organicmaps_editor_Editor_nativeHasWifi(JNIEnv *, jclass)
|
|||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_editor_Editor_nativeSetHasWifi(JNIEnv *, jclass, jboolean hasWifi)
|
||||
{
|
||||
g_editableMapObject.SetInternet(hasWifi ? feature::Internet::Wlan : feature::Internet::Unknown);
|
||||
if (hasWifi != (g_editableMapObject.GetInternet() == feature::Internet::Wlan))
|
||||
g_editableMapObject.SetInternet(hasWifi ? feature::Internet::Wlan : feature::Internet::Unknown);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
|
@ -362,7 +363,11 @@ Java_app_organicmaps_editor_Editor_nativeStartEdit(JNIEnv *, jclass)
|
|||
{
|
||||
::Framework * frm = g_framework->NativeFramework();
|
||||
if (!frm->HasPlacePageInfo())
|
||||
{
|
||||
ASSERT(g_editableMapObject.GetEditingLifecycle() == osm::EditingLifecycle::CREATED,
|
||||
("PlacePageInfo should only be empty for new features."));
|
||||
return;
|
||||
}
|
||||
|
||||
place_page::Info const & info = g_framework->GetPlacePageInfo();
|
||||
CHECK(frm->GetEditableMapObject(info.GetID(), g_editableMapObject), ("Invalid feature in the place page."));
|
||||
|
|
|
@ -167,6 +167,35 @@ std::vector<std::string_view> const * StringUtf8Multilang::GetTransliteratorsIds
|
|||
return &kLanguages[langCode].m_transliteratorsIds;
|
||||
}
|
||||
|
||||
std::string StringUtf8Multilang::GetOSMTagByCode(uint8_t const langCode)
|
||||
{
|
||||
std::string_view lang = StringUtf8Multilang::GetLangByCode(langCode);
|
||||
if (lang == "int_name" || lang == "alt_name" || lang == "old_name")
|
||||
return std::string{lang};
|
||||
else if (lang == "default")
|
||||
return "name";
|
||||
else if (!lang.empty())
|
||||
return std::string{"name:"}.append(lang);
|
||||
else
|
||||
{
|
||||
ASSERT_FAIL(("Language can not be an empty string"));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t StringUtf8Multilang::GetCodeByOSMTag(std::string const & name)
|
||||
{
|
||||
std::string lang;
|
||||
if (name.starts_with("name:"))
|
||||
lang = name.substr(5);
|
||||
else if (name == "name")
|
||||
lang = "default";
|
||||
else
|
||||
lang = name;
|
||||
|
||||
return StringUtf8Multilang::GetLangIndex(lang);
|
||||
}
|
||||
|
||||
size_t StringUtf8Multilang::GetNextIndex(size_t i) const
|
||||
{
|
||||
++i;
|
||||
|
|
|
@ -109,6 +109,9 @@ public:
|
|||
/// @returns nullptr if langCode is invalid.
|
||||
static std::vector<std::string_view> const * GetTransliteratorsIdsByCode(int8_t langCode);
|
||||
|
||||
static std::string GetOSMTagByCode(uint8_t const langCode);
|
||||
static uint8_t GetCodeByOSMTag(std::string const & name);
|
||||
|
||||
inline bool operator==(StringUtf8Multilang const & rhs) const { return m_s == rhs.m_s; }
|
||||
inline bool operator!=(StringUtf8Multilang const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ bool NeedsUpload(string const & uploadStatus)
|
|||
uploadStatus != kMatchedFeatureIsEmpty;
|
||||
}
|
||||
|
||||
XMLFeature GetMatchingFeatureFromOSM(osm::ChangesetWrapper & cw, osm::EditableMapObject & o)
|
||||
XMLFeature GetMatchingFeatureFromOSM(osm::ChangesetWrapper & cw, const osm::EditableMapObject & o)
|
||||
{
|
||||
ASSERT_NOT_EQUAL(o.GetGeomType(), feature::GeomType::Line,
|
||||
("Line features are not supported yet."));
|
||||
|
@ -227,6 +227,7 @@ bool Editor::Save(FeaturesContainer const & features) const
|
|||
// and meta fields are enough.
|
||||
XMLFeature xf =
|
||||
editor::ToXML(fti.m_object, true /* type serializing helps during migration */);
|
||||
xf.SetEditJournal(fti.m_object.GetJournal());
|
||||
xf.SetMWMFeatureIndex(index.first);
|
||||
if (!fti.m_street.empty())
|
||||
xf.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
|
@ -473,6 +474,16 @@ std::optional<osm::EditableMapObject> Editor::GetEditedFeature(FeatureID const &
|
|||
return featureInfo->m_object;
|
||||
}
|
||||
|
||||
std::optional<osm::EditJournal> Editor::GetEditedFeatureJournal(FeatureID const & fid) const
|
||||
{
|
||||
auto const features = m_features.Get();
|
||||
auto const * featureInfo = GetFeatureTypeInfo(*features, fid.m_mwmId, fid.m_index);
|
||||
if (featureInfo == nullptr)
|
||||
return {};
|
||||
|
||||
return featureInfo->m_object.GetJournal();
|
||||
}
|
||||
|
||||
bool Editor::GetEditedFeatureStreet(FeatureID const & fid, string & outFeatureStreet) const
|
||||
{
|
||||
auto const features = m_features.Get();
|
||||
|
@ -613,110 +624,219 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags,
|
|||
// TODO(a): Use UploadInfo as part of FeatureTypeInfo.
|
||||
UploadInfo uploadInfo = {fti.m_uploadAttemptTimestamp, fti.m_uploadStatus, fti.m_uploadError};
|
||||
|
||||
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
|
||||
|
||||
// Don't use new editor for Legacy Objects
|
||||
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
|
||||
bool useNewEditor = journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
||||
|
||||
try
|
||||
{
|
||||
switch (fti.m_status)
|
||||
if (useNewEditor)
|
||||
{
|
||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||
case FeatureStatus::Created:
|
||||
{
|
||||
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
||||
if (!fti.m_street.empty())
|
||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
LOG(LDEBUG, ("New Editor used\n"));
|
||||
|
||||
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
||||
("Linear and area features creation is not supported yet."));
|
||||
try
|
||||
switch (fti.m_status)
|
||||
{
|
||||
auto const center = fti.m_object.GetMercator();
|
||||
// Throws, see catch below.
|
||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
||||
case FeatureStatus::Untouched:
|
||||
CHECK(false, ("It's impossible."));
|
||||
continue;
|
||||
case FeatureStatus::Obsolete:
|
||||
continue; // Obsolete features will be deleted by OSMers.
|
||||
case FeatureStatus::Created: // fallthrough
|
||||
case FeatureStatus::Modified:
|
||||
{
|
||||
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
||||
|
||||
// If we are here, it means that object already exists at the given point.
|
||||
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
||||
XMLFeature const osmFeatureCopy = osmFeature;
|
||||
osmFeature.ApplyPatch(feature);
|
||||
// Check to avoid uploading duplicates into OSM.
|
||||
if (osmFeature == osmFeatureCopy)
|
||||
{
|
||||
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||
// Don't delete this local change right now for user to see it in profile.
|
||||
// It will be automatically deleted by migration code on the next maps update.
|
||||
switch (fti.m_object.GetEditingLifecycle())
|
||||
{
|
||||
case EditingLifecycle::CREATED:
|
||||
{
|
||||
// Generate XMLFeature for new object
|
||||
JournalEntry const & createEntry = journal.front();
|
||||
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,("First item should have type ObjectCreated"));
|
||||
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
|
||||
XMLFeature feature = editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
|
||||
|
||||
// Check if place already exists
|
||||
bool mergeSameLocation = false;
|
||||
try
|
||||
{
|
||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
||||
if (objCreateData.mercator == osmFeature.GetMercatorCenter()) {
|
||||
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
||||
feature = osmFeature;
|
||||
mergeSameLocation = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
||||
}
|
||||
}
|
||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &) {}
|
||||
catch (ChangesetWrapper::EmptyFeatureException const &) {}
|
||||
|
||||
// Add tags to XMLFeature
|
||||
UpdateXMLFeatureTags(feature, journal);
|
||||
|
||||
// Upload XMLFeature to OSM
|
||||
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
|
||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||
if (!mergeSameLocation)
|
||||
changeset.Create(feature);
|
||||
else
|
||||
changeset.Modify(feature);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditingLifecycle::MODIFIED:
|
||||
{
|
||||
// Load existing OSM object (Throws, see catch below)
|
||||
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
|
||||
|
||||
// Update tags of XMLFeature
|
||||
UpdateXMLFeatureTags(feature, journal);
|
||||
|
||||
// Upload XMLFeature to OSM
|
||||
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
|
||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||
changeset.Modify(feature);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditingLifecycle::IN_SYNC:
|
||||
{
|
||||
CHECK(false, ("Object already IN_SYNC should not be here"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
||||
changeset.AddChangesetTag("info:features_merged", "yes");
|
||||
changeset.Modify(osmFeature);
|
||||
}
|
||||
}
|
||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||
{
|
||||
// Object was never created by anyone else - it's safe to create it.
|
||||
changeset.Create(feature);
|
||||
}
|
||||
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||
{
|
||||
// There is another node nearby, but it should be safe to create a new one.
|
||||
changeset.Create(feature);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Pass network or other errors to outside exception handler.
|
||||
throw;
|
||||
case FeatureStatus::Deleted:
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||
if (!originalObjectPtr)
|
||||
{
|
||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||
GetPlatform().RunTask(Platform::Thread::Gui,[this, fid = fti.m_object.GetID()]() {
|
||||
RemoveFeatureIfExists(fid);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FeatureStatus::Modified:
|
||||
else // Use old editor
|
||||
{
|
||||
// Do not serialize feature's type to avoid breaking OSM data.
|
||||
// TODO: Implement correct types matching when we support modifying existing feature types.
|
||||
XMLFeature feature = editor::ToXML(fti.m_object, false);
|
||||
if (!fti.m_street.empty())
|
||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
// Todo: Remove old editor after transition period
|
||||
switch (fti.m_status)
|
||||
{
|
||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||
case FeatureStatus::Created:
|
||||
{
|
||||
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
||||
if (!fti.m_street.empty())
|
||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||
if (!originalObjectPtr)
|
||||
{
|
||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, fid = fti.m_object.GetID()]() {
|
||||
RemoveFeatureIfExists(fid);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
||||
("Linear and area features creation is not supported yet."));
|
||||
try
|
||||
{
|
||||
auto const center = fti.m_object.GetMercator();
|
||||
// Throws, see catch below.
|
||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
||||
|
||||
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
|
||||
XMLFeature const osmFeatureCopy = osmFeature;
|
||||
osmFeature.ApplyPatch(feature);
|
||||
// Check to avoid uploading duplicates into OSM.
|
||||
if (osmFeature == osmFeatureCopy)
|
||||
{
|
||||
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||
// Don't delete this local change right now for user to see it in profile.
|
||||
// It will be automatically deleted by migration code on the next maps update.
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
||||
changeset.Modify(osmFeature);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// If we are here, it means that object already exists at the given point.
|
||||
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
||||
XMLFeature const osmFeatureCopy = osmFeature;
|
||||
osmFeature.ApplyPatch(feature);
|
||||
// Check to avoid uploading duplicates into OSM.
|
||||
if (osmFeature == osmFeatureCopy)
|
||||
{
|
||||
LOG(LWARNING,("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||
// Don't delete this local change right now for user to see it in profile.
|
||||
// It will be automatically deleted by migration code on the next maps update.
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.AddChangesetTag("info:features_merged", "yes");
|
||||
changeset.Modify(osmFeature);
|
||||
}
|
||||
}
|
||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||
{
|
||||
// Object was never created by anyone else - it's safe to create it.
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Create(feature);
|
||||
}
|
||||
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||
{
|
||||
// There is another node nearby, but it should be safe to create a new one.
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Create(feature);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Pass network or other errors to outside exception handler.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FeatureStatus::Deleted:
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||
if (!originalObjectPtr)
|
||||
{
|
||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, fid = fti.m_object.GetID()]() {
|
||||
RemoveFeatureIfExists(fid);
|
||||
});
|
||||
continue;
|
||||
case FeatureStatus::Modified:
|
||||
{
|
||||
// Do not serialize feature's type to avoid breaking OSM data.
|
||||
// TODO: Implement correct types matching when we support modifying existing feature types.
|
||||
XMLFeature feature = editor::ToXML(fti.m_object, false);
|
||||
if (!fti.m_street.empty())
|
||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||
if (!originalObjectPtr)
|
||||
{
|
||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||
GetPlatform().RunTask(Platform::Thread::Gui,[this, fid = fti.m_object.GetID()]() {
|
||||
RemoveFeatureIfExists(fid);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
|
||||
XMLFeature const osmFeatureCopy = osmFeature;
|
||||
osmFeature.ApplyPatch(feature);
|
||||
// Check to avoid uploading duplicates into OSM.
|
||||
if (osmFeature == osmFeatureCopy)
|
||||
{
|
||||
LOG(LWARNING,("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||
// Don't delete this local change right now for user to see it in profile.
|
||||
// It will be automatically deleted by migration code on the next maps update.
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Modify(osmFeature);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FeatureStatus::Deleted:
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||
if (!originalObjectPtr)
|
||||
{
|
||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||
GetPlatform().RunTask(Platform::Thread::Gui,[this, fid = fti.m_object.GetID()]() {
|
||||
RemoveFeatureIfExists(fid);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||
break;
|
||||
}
|
||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||
break;
|
||||
}
|
||||
uploadInfo.m_uploadStatus = kUploaded;
|
||||
uploadInfo.m_uploadError.clear();
|
||||
|
@ -804,15 +924,29 @@ void Editor::SaveUploadedInformation(FeatureID const & fid, UploadInfo const & u
|
|||
fti.m_uploadStatus = uploadInfo.m_uploadStatus;
|
||||
fti.m_uploadError = uploadInfo.m_uploadError;
|
||||
|
||||
if (!NeedsUpload(uploadInfo.m_uploadStatus))
|
||||
fti.m_object.ClearJournal();
|
||||
|
||||
SaveTransaction(editableFeatures);
|
||||
}
|
||||
|
||||
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
|
||||
FeatureTypeInfo & fti) const
|
||||
{
|
||||
EditJournal journal = xml.GetEditJournal();
|
||||
|
||||
// Do not load Legacy Objects form Journal
|
||||
auto const & journalHistory = journal.GetJournalHistory();
|
||||
bool loadFromJournal = journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
||||
|
||||
LOG(LDEBUG, ("loadFromJournal: ", loadFromJournal));
|
||||
|
||||
if (status == FeatureStatus::Created)
|
||||
{
|
||||
editor::FromXML(xml, fti.m_object);
|
||||
if (loadFromJournal)
|
||||
fti.m_object.ApplyEditsFromJournal(journal);
|
||||
else
|
||||
editor::FromXML(xml, fti.m_object);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -824,9 +958,14 @@ bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, Featu
|
|||
}
|
||||
|
||||
fti.m_object = *originalObjectPtr;
|
||||
editor::ApplyPatch(xml, fti.m_object);
|
||||
|
||||
if (loadFromJournal)
|
||||
fti.m_object.ApplyEditsFromJournal(journal);
|
||||
else
|
||||
editor::ApplyPatch(xml, fti.m_object);
|
||||
}
|
||||
|
||||
fti.m_object.SetJournal(std::move(journal));
|
||||
fti.m_object.SetID(fid);
|
||||
fti.m_street = xml.GetTagValue(kAddrStreetTag);
|
||||
|
||||
|
@ -988,6 +1127,7 @@ bool Editor::CreatePoint(uint32_t type, m2::PointD const & mercator, MwmId const
|
|||
outFeature.SetEditableProperties(GetEditablePropertiesForTypes(outFeature.GetTypes()));
|
||||
// Only point type features can be created at the moment.
|
||||
outFeature.SetPointType();
|
||||
outFeature.MarkAsCreated(type, feature::GeomType::Point, mercator);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1212,6 +1352,27 @@ bool Editor::IsFeatureUploadedImpl(FeaturesContainer const & features, MwmId con
|
|||
return info && info->m_uploadStatus == kUploaded;
|
||||
}
|
||||
|
||||
void Editor::UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal)
|
||||
{
|
||||
for (JournalEntry const & entry: journal)
|
||||
{
|
||||
switch (entry.journalEntryType)
|
||||
{
|
||||
case JournalEntryType::TagModification:
|
||||
{
|
||||
TagModData const & tagModData = std::get<TagModData>(entry.data);
|
||||
feature.UpdateOSMTag(tagModData.key, tagModData.new_value);
|
||||
break;
|
||||
}
|
||||
case JournalEntryType::ObjectCreated:
|
||||
break;
|
||||
case JournalEntryType::LegacyObject:
|
||||
ASSERT_FAIL(("Legacy Objects can not be edited with the new editor"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string DebugPrint(Editor::SaveResult saveResult)
|
||||
{
|
||||
switch (saveResult)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "editor/xml_feature.hpp"
|
||||
|
||||
#include "indexer/editable_map_object.hpp"
|
||||
#include "indexer/edit_journal.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_source.hpp"
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
@ -131,6 +132,9 @@ public:
|
|||
/// @returns empty object if feature wasn't edited.
|
||||
std::optional<osm::EditableMapObject> GetEditedFeature(FeatureID const & fid) const;
|
||||
|
||||
/// @returns empty object if feature wasn't edited.
|
||||
std::optional<osm::EditJournal> GetEditedFeatureJournal(FeatureID const & fid) const;
|
||||
|
||||
/// @returns false if feature wasn't edited.
|
||||
/// @param outFeatureStreet is valid only if true was returned.
|
||||
bool GetEditedFeatureStreet(FeatureID const & fid, std::string & outFeatureStreet) const;
|
||||
|
@ -235,6 +239,8 @@ private:
|
|||
|
||||
static bool IsFeatureUploadedImpl(FeaturesContainer const & features, MwmId const & mwmId, uint32_t index);
|
||||
|
||||
void UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal);
|
||||
|
||||
/// Deleted, edited and created features.
|
||||
base::AtomicSharedPtr<FeaturesContainer> m_features;
|
||||
|
||||
|
|
|
@ -409,6 +409,178 @@ string XMLFeature::GetUploadError() const { return GetRootNode().attribute(kUplo
|
|||
|
||||
void XMLFeature::SetUploadError(string const & error) { SetAttribute(kUploadError, error); }
|
||||
|
||||
osm::EditJournal XMLFeature::GetEditJournal() const
|
||||
{
|
||||
// debug print
|
||||
std::ostringstream ost;
|
||||
Save(ost);
|
||||
LOG(LDEBUG, ("Read in XMLFeature:\n", ost.str()));
|
||||
|
||||
auto readEditJournalList = [] (pugi::xml_node & xmlNode, osm::EditJournal & journal, bool isHistory)
|
||||
{
|
||||
auto getAttribute = [] (pugi::xml_node & xmlNode, std::string_view attribute)
|
||||
{
|
||||
pugi::xml_attribute xmlValue = xmlNode.attribute(attribute.data());
|
||||
if (xmlValue.empty())
|
||||
MYTHROW(editor::InvalidJournalEntry, ("JournalEntry does not contain attribute: ", attribute));
|
||||
|
||||
return xmlValue.value();
|
||||
};
|
||||
|
||||
for (auto xmlEntry = xmlNode.child("entry"); xmlEntry; xmlEntry = xmlEntry.next_sibling("entry"))
|
||||
{
|
||||
osm::JournalEntry entry;
|
||||
|
||||
// JournalEntryType
|
||||
std::string strEntryType = getAttribute(xmlEntry, "type");
|
||||
std::optional<osm::JournalEntryType> entryType = osm::EditJournal::TypeFromString(strEntryType);
|
||||
if (!entryType)
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Invalid JournalEntryType:", strEntryType));
|
||||
entry.journalEntryType = *entryType;
|
||||
|
||||
// Timestamp
|
||||
std::string strTimestamp = getAttribute(xmlEntry, "timestamp");
|
||||
entry.timestamp = base::StringToTimestamp(strTimestamp);
|
||||
if (entry.timestamp == base::INVALID_TIME_STAMP)
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Invalid Timestamp:", strTimestamp));
|
||||
|
||||
// Data
|
||||
auto xmlData = xmlEntry.child("data");
|
||||
if (!xmlData)
|
||||
MYTHROW(editor::InvalidJournalEntry, ("No Data item"));
|
||||
|
||||
switch (entry.journalEntryType)
|
||||
{
|
||||
case osm::JournalEntryType::TagModification:
|
||||
{
|
||||
osm::TagModData tagModData;
|
||||
|
||||
tagModData.key = getAttribute(xmlData, "key");
|
||||
if (tagModData.key.empty())
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Empty key in TagModData"));
|
||||
tagModData.old_value = getAttribute(xmlData, "old_value");
|
||||
tagModData.new_value = getAttribute(xmlData, "new_value");
|
||||
|
||||
entry.data = tagModData;
|
||||
break;
|
||||
}
|
||||
case osm::JournalEntryType::ObjectCreated:
|
||||
{
|
||||
osm::ObjCreateData objCreateData;
|
||||
|
||||
// Feature Type
|
||||
std::string strType = getAttribute(xmlData, "type");
|
||||
if (strType.empty())
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Feature type is empty"));
|
||||
objCreateData.type = classif().GetTypeByReadableObjectName(strType);
|
||||
if (objCreateData.type == IndexAndTypeMapping::INVALID_TYPE)
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Invalid Feature Type:", strType));
|
||||
|
||||
// GeomType
|
||||
std::string strGeomType = getAttribute(xmlData, "geomType");
|
||||
objCreateData.geomType = feature::TypeFromString(strGeomType);
|
||||
if (objCreateData.geomType != feature::GeomType::Point)
|
||||
MYTHROW(editor::InvalidJournalEntry, ("Invalid geomType (only point features are supported):", strGeomType));
|
||||
|
||||
// Mercator
|
||||
objCreateData.mercator = mercator::FromLatLon(GetLatLonFromNode(xmlData));
|
||||
|
||||
entry.data = objCreateData;
|
||||
break;
|
||||
}
|
||||
case osm::JournalEntryType::LegacyObject:
|
||||
{
|
||||
osm::LegacyObjData legacyObjData;
|
||||
legacyObjData.version = getAttribute(xmlData, "version");
|
||||
entry.data = legacyObjData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isHistory)
|
||||
journal.AddJournalHistoryEntry(entry);
|
||||
else
|
||||
journal.AddJournalEntry(entry);
|
||||
}
|
||||
};
|
||||
|
||||
osm::EditJournal journal = osm::EditJournal();
|
||||
|
||||
auto xmlJournal = GetRootNode().child("journal");
|
||||
if (xmlJournal)
|
||||
readEditJournalList(xmlJournal, journal, false);
|
||||
else
|
||||
{
|
||||
// Mark as Legacy Object
|
||||
osm::LegacyObjData legacyObjData = {"1.0"};
|
||||
time_t timestamp = time(nullptr);
|
||||
journal.AddJournalEntry({osm::JournalEntryType::LegacyObject, timestamp, legacyObjData});
|
||||
journal.AddJournalHistoryEntry({osm::JournalEntryType::LegacyObject, timestamp, std::move(legacyObjData)});
|
||||
}
|
||||
|
||||
auto xmlJournalHistory = GetRootNode().child("journalHistory");
|
||||
readEditJournalList(xmlJournalHistory, journal, true);
|
||||
|
||||
LOG(LDEBUG, ("Read in Journal:\n", journal.JournalToString()));
|
||||
|
||||
return journal;
|
||||
}
|
||||
|
||||
void XMLFeature::SetEditJournal(osm::EditJournal const & journal)
|
||||
{
|
||||
LOG(LDEBUG, ("Saving Journal:\n", journal.JournalToString()));
|
||||
|
||||
auto const insertEditJournalList = [] (pugi::xml_node & xmlNode, std::list<osm::JournalEntry> const & journalList)
|
||||
{
|
||||
xmlNode.append_attribute("version") = "1.0";
|
||||
for (osm::JournalEntry const & entry : journalList)
|
||||
{
|
||||
auto xmlEntry = xmlNode.append_child("entry");
|
||||
xmlEntry.append_attribute("type") = osm::EditJournal::ToString(entry.journalEntryType).data();
|
||||
xmlEntry.append_attribute("timestamp") = base::TimestampToString(entry.timestamp).data();
|
||||
|
||||
auto xmlData = xmlEntry.append_child("data");
|
||||
switch (entry.journalEntryType)
|
||||
{
|
||||
case osm::JournalEntryType::TagModification:
|
||||
{
|
||||
osm::TagModData const & tagModData = std::get<osm::TagModData>(entry.data);
|
||||
xmlData.append_attribute("key") = tagModData.key.data();
|
||||
xmlData.append_attribute("old_value") = tagModData.old_value.data();
|
||||
xmlData.append_attribute("new_value") = tagModData.new_value.data();
|
||||
break;
|
||||
}
|
||||
case osm::JournalEntryType::ObjectCreated:
|
||||
{
|
||||
osm::ObjCreateData const & objCreateData = std::get<osm::ObjCreateData>(entry.data);
|
||||
xmlData.append_attribute("type") = classif().GetReadableObjectName(objCreateData.type).data();
|
||||
xmlData.append_attribute("geomType") = ToString(objCreateData.geomType).data();
|
||||
ms::LatLon ll = mercator::ToLatLon(objCreateData.mercator);
|
||||
xmlData.append_attribute("lat") = strings::to_string_dac(ll.m_lat, kLatLonTolerance).data();
|
||||
xmlData.append_attribute("lon") = strings::to_string_dac(ll.m_lon, kLatLonTolerance).data();
|
||||
break;
|
||||
}
|
||||
case osm::JournalEntryType::LegacyObject:
|
||||
{
|
||||
osm::LegacyObjData const & legacyObjData = std::get<osm::LegacyObjData>(entry.data);
|
||||
xmlData.append_attribute("version") = legacyObjData.version.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto xmlJournal = GetRootNode().append_child("journal");
|
||||
insertEditJournalList(xmlJournal, journal.GetJournal());
|
||||
|
||||
auto xmlJournalHistory = GetRootNode().append_child("journalHistory");
|
||||
insertEditJournalList(xmlJournalHistory, journal.GetJournalHistory());
|
||||
|
||||
// debug print
|
||||
std::ostringstream ost;
|
||||
Save(ost);
|
||||
LOG(LDEBUG, ("Saving XMLFeature:\n", ost.str()));
|
||||
}
|
||||
|
||||
bool XMLFeature::HasAnyTags() const { return GetRootNode().child("tag"); }
|
||||
|
||||
bool XMLFeature::HasTag(string_view key) const { return FindTag(m_document, key); }
|
||||
|
@ -449,6 +621,43 @@ void XMLFeature::RemoveTag(string_view key)
|
|||
GetRootNode().remove_child(tag);
|
||||
}
|
||||
|
||||
void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
|
||||
{
|
||||
if (value.empty())
|
||||
RemoveTag(key);
|
||||
|
||||
else
|
||||
{
|
||||
// TODO(mgsergio): Get these alt tags from the config.
|
||||
base::StringIL const alternativeTags[] = {
|
||||
{"phone", "contact:phone", "contact:mobile", "mobile"},
|
||||
{"website", "contact:website", "url"},
|
||||
{"fax", "contact:fax"},
|
||||
{"email", "contact:email"}
|
||||
};
|
||||
|
||||
// Avoid duplication for similar alternative osm tags.
|
||||
for (auto const & alt : alternativeTags)
|
||||
{
|
||||
auto it = alt.begin();
|
||||
ASSERT(it != alt.end(), ());
|
||||
if (key == *it)
|
||||
{
|
||||
for (auto const & tag : alt)
|
||||
{
|
||||
// Reuse already existing tag if it's present.
|
||||
if (HasTag(tag))
|
||||
{
|
||||
SetTagValue(tag, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetTagValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
string XMLFeature::GetAttribute(string const & key) const
|
||||
{
|
||||
return GetRootNode().attribute(key.data()).value();
|
||||
|
@ -611,6 +820,38 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
|||
return toFeature;
|
||||
}
|
||||
|
||||
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator)
|
||||
{
|
||||
ASSERT(geomType == feature::GeomType::Point, ("Only point features can be added"));
|
||||
XMLFeature toFeature(XMLFeature::Type::Node);
|
||||
toFeature.SetCenter(mercator);
|
||||
|
||||
// Set Type
|
||||
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
|
||||
{
|
||||
toFeature.SetTagValue("amenity", "recycling");
|
||||
toFeature.SetTagValue("recycling_type", "centre");
|
||||
}
|
||||
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
|
||||
{
|
||||
toFeature.SetTagValue("amenity", "recycling");
|
||||
toFeature.SetTagValue("recycling_type", "container");
|
||||
}
|
||||
else
|
||||
{
|
||||
string const strType = classif().GetReadableObjectName(type);
|
||||
strings::SimpleTokenizer iter(strType, "-");
|
||||
string_view const k = *iter;
|
||||
|
||||
CHECK(++iter, ("Processing Type failed: ", strType));
|
||||
// Main type is always stored as "k=amenity v=restaurant".
|
||||
toFeature.SetTagValue(k, *iter);
|
||||
|
||||
ASSERT(!(++iter), ("Can not process 3-arity/complex types: ", strType));
|
||||
}
|
||||
return toFeature;
|
||||
}
|
||||
|
||||
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object)
|
||||
{
|
||||
ASSERT_EQUAL(XMLFeature::Type::Node, xml.GetType(),
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "indexer/feature_decl.hpp"
|
||||
#include "indexer/edit_journal.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
|
@ -27,6 +29,7 @@ DECLARE_EXCEPTION(NoLatLon, XMLFeatureError);
|
|||
DECLARE_EXCEPTION(NoXY, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(NoTimestamp, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(NoHeader, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(InvalidJournalEntry, XMLFeatureError);
|
||||
|
||||
class XMLFeature
|
||||
{
|
||||
|
@ -161,6 +164,9 @@ public:
|
|||
|
||||
std::string GetUploadError() const;
|
||||
void SetUploadError(std::string const & error);
|
||||
|
||||
osm::EditJournal GetEditJournal() const;
|
||||
void SetEditJournal(osm::EditJournal const & journal);
|
||||
//@}
|
||||
|
||||
bool HasAnyTags() const;
|
||||
|
@ -179,6 +185,9 @@ public:
|
|||
void SetTagValue(std::string_view key, std::string_view value);
|
||||
void RemoveTag(std::string_view key);
|
||||
|
||||
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
|
||||
void UpdateOSMTag(std::string_view key, std::string_view value);
|
||||
|
||||
std::string GetAttribute(std::string const & key) const;
|
||||
void SetAttribute(std::string const & key, std::string const & value);
|
||||
|
||||
|
@ -203,6 +212,9 @@ void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object);
|
|||
/// has changed a type in OSM, but our users uploaded invalid outdated type after modifying feature.
|
||||
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
|
||||
|
||||
/// Used to generate XML for created objects in the new editor
|
||||
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
|
||||
|
||||
/// Creates new feature, including geometry and types.
|
||||
/// @Note: only nodes (points) are supported at the moment.
|
||||
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object);
|
||||
|
|
|
@ -51,6 +51,8 @@ set(SRC
|
|||
drules_selector_parser.hpp
|
||||
drules_struct.pb.cc
|
||||
drules_struct.pb.h
|
||||
edit_journal.cpp
|
||||
edit_journal.hpp
|
||||
editable_map_object.cpp
|
||||
editable_map_object.hpp
|
||||
fake_feature_ids.cpp
|
||||
|
|
125
indexer/edit_journal.cpp
Normal file
125
indexer/edit_journal.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#include "indexer/edit_journal.hpp"
|
||||
|
||||
#include "base/control_flow.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
namespace osm
|
||||
{
|
||||
std::list<JournalEntry> const & EditJournal::GetJournal() const
|
||||
{
|
||||
return m_journal;
|
||||
}
|
||||
|
||||
osm::EditingLifecycle EditJournal::GetEditingLifecycle() const
|
||||
{
|
||||
if (m_journal.empty())
|
||||
return EditingLifecycle::IN_SYNC;
|
||||
|
||||
else if (m_journal.front().journalEntryType == JournalEntryType::ObjectCreated)
|
||||
return EditingLifecycle::CREATED;
|
||||
|
||||
return EditingLifecycle::MODIFIED;
|
||||
}
|
||||
|
||||
void EditJournal::AddTagChange(std::string key, std::string old_value, std::string new_value)
|
||||
{
|
||||
LOG(LDEBUG, ("Key ", key, "changed from \"", old_value, "\" to \"", new_value, "\""));
|
||||
AddJournalEntry({JournalEntryType::TagModification, time(nullptr), TagModData{std::move(key), std::move(old_value), std::move(new_value)}});
|
||||
}
|
||||
|
||||
void EditJournal::MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator)
|
||||
{
|
||||
ASSERT(m_journal.empty(), ("Only empty journals can be marked as created"));
|
||||
LOG(LDEBUG, ("Object of type ", classif().GetReadableObjectName(type), " created"));
|
||||
AddJournalEntry({JournalEntryType::ObjectCreated, time(nullptr), osm::ObjCreateData{type, geomType, mercator}});
|
||||
}
|
||||
|
||||
void EditJournal::AddJournalEntry(JournalEntry entry)
|
||||
{
|
||||
m_journal.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
void EditJournal::Clear()
|
||||
{
|
||||
for (JournalEntry & entry : m_journal)
|
||||
m_journalHistory.push_back(std::move(entry));
|
||||
|
||||
m_journal = {};
|
||||
}
|
||||
|
||||
std::list<JournalEntry> const & EditJournal::GetJournalHistory() const
|
||||
{
|
||||
return m_journalHistory;
|
||||
}
|
||||
|
||||
void EditJournal::AddJournalHistoryEntry(JournalEntry entry)
|
||||
{
|
||||
m_journalHistory.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
std::string EditJournal::JournalToString() const
|
||||
{
|
||||
std::string string;
|
||||
std::for_each(m_journal.begin(), m_journal.end(), [&](auto const & journalEntry) {
|
||||
string += ToString(journalEntry) + "\n";
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
std::string EditJournal::ToString(osm::JournalEntry const & journalEntry)
|
||||
{
|
||||
switch (journalEntry.journalEntryType)
|
||||
{
|
||||
case osm::JournalEntryType::TagModification:
|
||||
{
|
||||
TagModData const & tagModData = std::get<TagModData>(journalEntry.data);
|
||||
return ToString(journalEntry.journalEntryType)
|
||||
.append(": Key ").append(tagModData.key)
|
||||
.append(" changed from \"").append(tagModData.old_value)
|
||||
.append("\" to \"").append(tagModData.new_value).append("\"");
|
||||
}
|
||||
case osm::JournalEntryType::ObjectCreated:
|
||||
{
|
||||
ObjCreateData const & objCreatedData = std::get<ObjCreateData>(journalEntry.data);
|
||||
return ToString(journalEntry.journalEntryType)
|
||||
.append(": ").append(classif().GetReadableObjectName(objCreatedData.type))
|
||||
.append(" (").append(std::to_string(objCreatedData.type)).append(")");
|
||||
}
|
||||
case osm::JournalEntryType::LegacyObject:
|
||||
{
|
||||
LegacyObjData const & legacyObjData = std::get<LegacyObjData>(journalEntry.data);
|
||||
return ToString(journalEntry.journalEntryType)
|
||||
.append(": version=\"").append(legacyObjData.version).append("\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string EditJournal::ToString(osm::JournalEntryType journalEntryType)
|
||||
{
|
||||
switch (journalEntryType)
|
||||
{
|
||||
case osm::JournalEntryType::TagModification:
|
||||
return "TagModification";
|
||||
case osm::JournalEntryType::ObjectCreated:
|
||||
return "ObjectCreated";
|
||||
case osm::JournalEntryType::LegacyObject:
|
||||
return "LegacyObject";
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<JournalEntryType> EditJournal::TypeFromString(std::string const & entryType) {
|
||||
if (entryType == "TagModification")
|
||||
return JournalEntryType::TagModification;
|
||||
else if (entryType == "ObjectCreated")
|
||||
return JournalEntryType::ObjectCreated;
|
||||
else if (entryType == "LegacyObject")
|
||||
return JournalEntryType::LegacyObject;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
}
|
88
indexer/edit_journal.hpp
Normal file
88
indexer/edit_journal.hpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/feature_decl.hpp"
|
||||
#include "indexer/feature_meta.hpp"
|
||||
#include "indexer/feature_utils.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace osm
|
||||
{
|
||||
enum class JournalEntryType
|
||||
{
|
||||
TagModification,
|
||||
ObjectCreated,
|
||||
LegacyObject, //object without full journal history, used for transition to new editor
|
||||
//Possible future values: ObjectDeleted, ObjectDisused, ObjectNotDisused, LocationChanged, FeatureTypeChanged
|
||||
};
|
||||
|
||||
struct TagModData
|
||||
{
|
||||
std::string key;
|
||||
std::string old_value;
|
||||
std::string new_value;
|
||||
};
|
||||
|
||||
struct ObjCreateData
|
||||
{
|
||||
uint32_t type;
|
||||
feature::GeomType geomType;
|
||||
m2::PointD mercator;
|
||||
};
|
||||
|
||||
struct LegacyObjData
|
||||
{
|
||||
std::string version;
|
||||
};
|
||||
|
||||
struct JournalEntry
|
||||
{
|
||||
JournalEntryType journalEntryType = JournalEntryType::TagModification;
|
||||
time_t timestamp;
|
||||
std::variant<TagModData, ObjCreateData, LegacyObjData> data;
|
||||
};
|
||||
|
||||
/// Used to determine whether existing OSM object should be updated or new one created
|
||||
enum class EditingLifecycle
|
||||
{
|
||||
CREATED, //newly created and not synced with OSM
|
||||
MODIFIED, //modified and not synced with OSM
|
||||
IN_SYNC //synced with OSM (including never edited)
|
||||
};
|
||||
|
||||
class EditJournal
|
||||
{
|
||||
std::list<JournalEntry> m_journal{};
|
||||
std::list<JournalEntry> m_journalHistory{};
|
||||
|
||||
public:
|
||||
std::list<JournalEntry> const & GetJournal() const;
|
||||
|
||||
osm::EditingLifecycle GetEditingLifecycle() const;
|
||||
|
||||
/// Log object edits in the journal
|
||||
void AddTagChange(std::string key, std::string old_value, std::string new_value);
|
||||
|
||||
/// Log object creation in the journal
|
||||
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
|
||||
|
||||
void AddJournalEntry(JournalEntry entry);
|
||||
|
||||
/// Clear Journal and move content to journalHistory, used after upload to OSM
|
||||
void Clear();
|
||||
|
||||
std::list<JournalEntry> const & GetJournalHistory() const;
|
||||
|
||||
void AddJournalHistoryEntry(JournalEntry entry);
|
||||
|
||||
std::string JournalToString() const;
|
||||
|
||||
static std::string ToString(osm::JournalEntry const & journalEntry);
|
||||
|
||||
static std::string ToString(osm::JournalEntryType journalEntryType);
|
||||
|
||||
static std::optional<JournalEntryType> TypeFromString(std::string const & entryType);
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/postcodes_matcher.hpp"
|
||||
#include "indexer/validate_and_format_contacts.hpp"
|
||||
#include "indexer/edit_journal.hpp"
|
||||
|
||||
#include "platform/preferred_languages.hpp"
|
||||
|
||||
|
@ -314,7 +315,7 @@ void EditableMapObject::SetInternet(feature::Internet internet)
|
|||
if (hasWiFi && internet != feature::Internet::Wlan)
|
||||
m_types.Remove(wifiType);
|
||||
else if (!hasWiFi && internet == feature::Internet::Wlan)
|
||||
m_types.Add(wifiType);
|
||||
m_types.SafeAdd(wifiType);
|
||||
}
|
||||
|
||||
LocalizedStreet const & EditableMapObject::GetStreet() const { return m_street; }
|
||||
|
@ -578,6 +579,215 @@ bool EditableMapObject::ValidateName(string const & name)
|
|||
return true;
|
||||
}
|
||||
|
||||
EditJournal const & EditableMapObject::GetJournal() const
|
||||
{
|
||||
return m_journal;
|
||||
}
|
||||
|
||||
void EditableMapObject::SetJournal(EditJournal && editJournal)
|
||||
{
|
||||
m_journal = std::move(editJournal);
|
||||
}
|
||||
|
||||
EditingLifecycle EditableMapObject::GetEditingLifecycle() const
|
||||
{
|
||||
return m_journal.GetEditingLifecycle();
|
||||
}
|
||||
|
||||
void EditableMapObject::MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator)
|
||||
{
|
||||
m_journal.MarkAsCreated(type, geomType, std::move(mercator));
|
||||
}
|
||||
|
||||
void EditableMapObject::ClearJournal()
|
||||
{
|
||||
m_journal.Clear();
|
||||
}
|
||||
|
||||
void EditableMapObject::ApplyEditsFromJournal(EditJournal const & editJournal)
|
||||
{
|
||||
for (JournalEntry const & entry : editJournal.GetJournalHistory())
|
||||
ApplyJournalEntry(entry);
|
||||
|
||||
for (JournalEntry const & entry : editJournal.GetJournal())
|
||||
ApplyJournalEntry(entry);
|
||||
}
|
||||
|
||||
void EditableMapObject::ApplyJournalEntry(JournalEntry const & entry)
|
||||
{
|
||||
LOG(LDEBUG, ("Applying Journal Entry: ", osm::EditJournal::ToString(entry)));
|
||||
//Todo
|
||||
switch (entry.journalEntryType)
|
||||
{
|
||||
case JournalEntryType::TagModification:
|
||||
{
|
||||
TagModData const & tagModData = std::get<TagModData>(entry.data);
|
||||
|
||||
//Metadata
|
||||
MetadataID type;
|
||||
if (feature::Metadata::TypeFromString(tagModData.key, type))
|
||||
{
|
||||
m_metadata.Set(type, tagModData.new_value);
|
||||
if (type == MetadataID::FMD_INTERNET)
|
||||
{
|
||||
uint32_t const wifiType = ftypes::IsWifiChecker::Instance().GetType();
|
||||
if (tagModData.new_value == "wifi")
|
||||
m_types.SafeAdd(wifiType);
|
||||
else
|
||||
m_types.Remove(wifiType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Names
|
||||
int8_t langCode = StringUtf8Multilang::GetCodeByOSMTag(tagModData.key);
|
||||
if (langCode != StringUtf8Multilang::kUnsupportedLanguageCode)
|
||||
{
|
||||
m_name.AddString(langCode, tagModData.new_value);
|
||||
break;
|
||||
}
|
||||
|
||||
if (tagModData.key == "addr:street")
|
||||
m_street.m_defaultName = tagModData.new_value;
|
||||
|
||||
else if (tagModData.key == "addr:housenumber")
|
||||
m_houseNumber = tagModData.new_value;
|
||||
|
||||
else if (tagModData.key == "cuisine")
|
||||
{
|
||||
Classificator const & cl = classif();
|
||||
// Remove old cuisine values
|
||||
vector<std::string_view> oldCuisines = strings::Tokenize(tagModData.old_value, ";");
|
||||
for (std::string_view const & cuisine : oldCuisines)
|
||||
m_types.Remove(cl.GetTypeByPath({string_view("cuisine"), cuisine}));
|
||||
// Add new cuisine values
|
||||
vector<std::string_view> newCuisines = strings::Tokenize(tagModData.new_value, ";");
|
||||
for (std::string_view const & cuisine : newCuisines)
|
||||
m_types.SafeAdd(cl.GetTypeByPath({string_view("cuisine"), cuisine}));
|
||||
}
|
||||
else if (tagModData.key == "diet:vegetarian")
|
||||
{
|
||||
Classificator const & cl = classif();
|
||||
uint32_t const vegetarianType = cl.GetTypeByPath({string_view("cuisine"), "vegetarian"});
|
||||
if (tagModData.new_value == "yes")
|
||||
m_types.SafeAdd(vegetarianType);
|
||||
else
|
||||
m_types.Remove(vegetarianType);
|
||||
}
|
||||
else if (tagModData.key == "diet:vegan")
|
||||
{
|
||||
Classificator const & cl = classif();
|
||||
uint32_t const veganType = cl.GetTypeByPath({string_view("cuisine"), "vegan"});
|
||||
if (tagModData.new_value == "yes")
|
||||
m_types.SafeAdd(veganType);
|
||||
else
|
||||
m_types.Remove(veganType);
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("OSM key \"" , tagModData.key, "\" is unknown, skipped"));
|
||||
|
||||
break;
|
||||
}
|
||||
case JournalEntryType::ObjectCreated:
|
||||
{
|
||||
ObjCreateData const & objCreatedData = std::get<ObjCreateData>(entry.data);
|
||||
ASSERT_EQUAL(feature::GeomType::Point, objCreatedData.geomType, ("At the moment only new nodes (points) can be created."));
|
||||
SetPointType();
|
||||
SetMercator(objCreatedData.mercator);
|
||||
m_types.Add(objCreatedData.type);
|
||||
break;
|
||||
}
|
||||
case JournalEntryType::LegacyObject:
|
||||
{
|
||||
ASSERT_FAIL(("Legacy Objects can not be loaded from Journal"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditableMapObject::LogDiffInJournal(EditableMapObject const & unedited_emo)
|
||||
{
|
||||
LOG(LDEBUG, ("Executing LogDiffInJournal"));
|
||||
|
||||
// Name
|
||||
for (StringUtf8Multilang::Lang language : StringUtf8Multilang::GetSupportedLanguages())
|
||||
{
|
||||
int8_t langCode = StringUtf8Multilang::GetLangIndex(language.m_code);
|
||||
std::string_view new_name;
|
||||
std::string_view old_name;
|
||||
m_name.GetString(langCode, new_name);
|
||||
unedited_emo.GetNameMultilang().GetString(langCode, old_name);
|
||||
|
||||
if (new_name != old_name)
|
||||
{
|
||||
std::string osmLangName = StringUtf8Multilang::GetOSMTagByCode(langCode);
|
||||
m_journal.AddTagChange(std::move(osmLangName), std::string(old_name), std::string(new_name));
|
||||
}
|
||||
}
|
||||
|
||||
// Address
|
||||
if (m_street.m_defaultName != unedited_emo.GetStreet().m_defaultName)
|
||||
m_journal.AddTagChange("addr:street", unedited_emo.GetStreet().m_defaultName, m_street.m_defaultName);
|
||||
|
||||
if (m_houseNumber != unedited_emo.GetHouseNumber())
|
||||
m_journal.AddTagChange("addr:housenumber", unedited_emo.GetHouseNumber(), m_houseNumber);
|
||||
|
||||
// Metadata
|
||||
for (uint8_t i = 0; i < static_cast<uint8_t>(feature::Metadata::FMD_COUNT); ++i)
|
||||
{
|
||||
auto const type = static_cast<feature::Metadata::EType>(i);
|
||||
std::string_view const & value = GetMetadata(type);
|
||||
std::string_view const & old_value = unedited_emo.GetMetadata(type);
|
||||
|
||||
if (value != old_value)
|
||||
m_journal.AddTagChange(ToString(type), std::string(old_value), std::string(value));
|
||||
}
|
||||
|
||||
// cuisine and diet
|
||||
std::vector<std::string> new_cuisines = GetCuisines();
|
||||
std::vector<std::string> old_cuisines = unedited_emo.GetCuisines();
|
||||
|
||||
auto const findAndErase = [] (std::vector<std::string> & cuisinesPtr, std::string_view s)
|
||||
{
|
||||
auto it = std::find(cuisinesPtr.begin(), cuisinesPtr.end(), s);
|
||||
if (it != cuisinesPtr.end())
|
||||
{
|
||||
cuisinesPtr.erase(it);
|
||||
return "yes";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
std::string new_vegetarian = findAndErase(new_cuisines, "vegetarian");
|
||||
std::string old_vegetarian = findAndErase(old_cuisines, "vegetarian");
|
||||
if (new_vegetarian != old_vegetarian)
|
||||
m_journal.AddTagChange("diet:vegetarian", old_vegetarian, new_vegetarian);
|
||||
|
||||
std::string new_vegan = findAndErase(new_cuisines, "vegan");
|
||||
std::string old_vegan = findAndErase(old_cuisines, "vegan");
|
||||
if (new_vegan != old_vegan)
|
||||
m_journal.AddTagChange("diet:vegan", old_vegan, new_vegan);
|
||||
|
||||
bool cuisinesModified = false;
|
||||
|
||||
if (new_cuisines.size() != old_cuisines.size())
|
||||
cuisinesModified = true;
|
||||
else
|
||||
{
|
||||
for (auto const & new_cuisine : new_cuisines)
|
||||
{
|
||||
if (!base::IsExist(old_cuisines, new_cuisine))
|
||||
{
|
||||
cuisinesModified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cuisinesModified)
|
||||
m_journal.AddTagChange("cuisine", strings::JoinStrings(old_cuisines, ";"), strings::JoinStrings(new_cuisines, ";"));
|
||||
}
|
||||
|
||||
bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs)
|
||||
{
|
||||
feature::TypesHolder const & lhsTypes = lhs.GetTypes();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "indexer/feature_meta.hpp"
|
||||
#include "indexer/feature_utils.hpp"
|
||||
#include "indexer/map_object.hpp"
|
||||
#include "indexer/edit_journal.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
|
@ -129,6 +130,16 @@ public:
|
|||
static bool ValidateLevel(std::string const & level);
|
||||
static bool ValidateName(std::string const & name);
|
||||
|
||||
/// Journal that stores changes to map object
|
||||
EditJournal const & GetJournal() const;
|
||||
void SetJournal(EditJournal && editJournal);
|
||||
EditingLifecycle GetEditingLifecycle() const;
|
||||
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
|
||||
void ClearJournal();
|
||||
void ApplyEditsFromJournal(EditJournal const & journal);
|
||||
void ApplyJournalEntry(JournalEntry const & entry);
|
||||
void LogDiffInJournal(EditableMapObject const & unedited_emo);
|
||||
|
||||
/// Check whether langCode can be used as default name.
|
||||
static bool CanUseAsDefaultName(int8_t const langCode, std::vector<int8_t> const & nativeMwmLanguages);
|
||||
|
||||
|
@ -144,5 +155,6 @@ private:
|
|||
LocalizedStreet m_street;
|
||||
std::vector<LocalizedStreet> m_nearbyStreets;
|
||||
EditableProperties m_editableProperties;
|
||||
osm::EditJournal m_journal;
|
||||
};
|
||||
} // namespace osm
|
||||
|
|
|
@ -73,6 +73,17 @@ namespace feature
|
|||
m_types[m_size++] = type;
|
||||
}
|
||||
|
||||
void SafeAdd(uint32_t type)
|
||||
{
|
||||
if (!Has(type))
|
||||
{
|
||||
if (m_size < kMaxTypesCount)
|
||||
Add(type);
|
||||
else
|
||||
LOG(LWARNING, ("Type could not be added, MaxTypesCount exceeded"));
|
||||
}
|
||||
}
|
||||
|
||||
GeomType GetGeomType() const { return m_geomType; }
|
||||
|
||||
size_t Size() const { return m_size; }
|
||||
|
|
|
@ -7,16 +7,33 @@
|
|||
namespace feature
|
||||
{
|
||||
std::string DebugPrint(GeomType type)
|
||||
{
|
||||
return ToString(type);
|
||||
}
|
||||
|
||||
std::string ToString(GeomType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GeomType::Undefined: return "Undefined";
|
||||
case GeomType::Point: return "Point";
|
||||
case GeomType::Line: return "Line";
|
||||
case GeomType::Area: return "Area";
|
||||
case GeomType::Undefined: return "Undefined";
|
||||
case GeomType::Point: return "Point";
|
||||
case GeomType::Line: return "Line";
|
||||
case GeomType::Area: return "Area";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
GeomType TypeFromString(std::string type)
|
||||
{
|
||||
if (type == "Point")
|
||||
return GeomType::Point;
|
||||
else if (type == "Line")
|
||||
return GeomType::Line;
|
||||
else if (type == "Area")
|
||||
return GeomType::Area;
|
||||
else
|
||||
return GeomType::Undefined;
|
||||
}
|
||||
} // namespace feature
|
||||
|
||||
std::string DebugPrint(FeatureID const & id)
|
||||
|
|
|
@ -17,6 +17,10 @@ enum class GeomType : int8_t
|
|||
};
|
||||
|
||||
std::string DebugPrint(GeomType type);
|
||||
|
||||
std::string ToString(GeomType type);
|
||||
|
||||
GeomType TypeFromString(std::string type);
|
||||
} // namespace feature
|
||||
|
||||
uint32_t constexpr kInvalidFeatureId = std::numeric_limits<uint32_t>::max();
|
||||
|
|
|
@ -3013,6 +3013,10 @@ bool Framework::GetEditableMapObject(FeatureID const & fid, osm::EditableMapObje
|
|||
SetHostingBuildingAddress(FindBuildingAtPoint(feature::GetCenter(*ft)), dataSource, coder, emo);
|
||||
}
|
||||
|
||||
auto optJournal = editor.GetEditedFeatureJournal(fid);
|
||||
if (optJournal)
|
||||
emo.SetJournal(std::move(*optJournal));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3020,6 +3024,15 @@ osm::Editor::SaveResult Framework::SaveEditedMapObject(osm::EditableMapObject em
|
|||
{
|
||||
auto & editor = osm::Editor::Instance();
|
||||
|
||||
// Update EditJournal
|
||||
osm::EditableMapObject unedited_emo;
|
||||
if (emo.GetEditingLifecycle() == osm::EditingLifecycle::CREATED && emo.GetJournal().GetJournal().size() == 1)
|
||||
unedited_emo = {};
|
||||
else
|
||||
CHECK(GetEditableMapObject(emo.GetID(), unedited_emo), ("Loading unedited EditableMapObject failed."));
|
||||
|
||||
emo.LogDiffInJournal(unedited_emo);
|
||||
|
||||
ms::LatLon issueLatLon;
|
||||
|
||||
auto shouldNotify = false;
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
456E1B181F90E5B7009C32E1 /* cities_boundaries_serdes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 456E1B141F90E5B6009C32E1 /* cities_boundaries_serdes.hpp */; };
|
||||
456E1B1B1F90E5B7009C32E1 /* city_boundary.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 456E1B171F90E5B7009C32E1 /* city_boundary.hpp */; };
|
||||
467F34202BCAB6DE00CDC7DE /* yes_no_unknown.h in Headers */ = {isa = PBXBuildFile; fileRef = 467F341F2BCAB6DE00CDC7DE /* yes_no_unknown.h */; };
|
||||
46CB685D2CBC17F2003E40AB /* edit_journal.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 46CB685B2CBC17F2003E40AB /* edit_journal.hpp */; };
|
||||
46CB685E2CBC17F2003E40AB /* edit_journal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46CB685C2CBC17F2003E40AB /* edit_journal.cpp */; };
|
||||
56C74C1C1C749E4700B71B9F /* categories_holder_loader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 56C74C121C749E4700B71B9F /* categories_holder_loader.cpp */; };
|
||||
56C74C1D1C749E4700B71B9F /* categories_holder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 56C74C131C749E4700B71B9F /* categories_holder.cpp */; };
|
||||
56C74C1E1C749E4700B71B9F /* categories_holder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 56C74C141C749E4700B71B9F /* categories_holder.hpp */; };
|
||||
|
@ -303,6 +305,8 @@
|
|||
456E1B141F90E5B6009C32E1 /* cities_boundaries_serdes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = cities_boundaries_serdes.hpp; sourceTree = "<group>"; };
|
||||
456E1B171F90E5B7009C32E1 /* city_boundary.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = city_boundary.hpp; sourceTree = "<group>"; };
|
||||
467F341F2BCAB6DE00CDC7DE /* yes_no_unknown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = yes_no_unknown.h; sourceTree = "<group>"; };
|
||||
46CB685B2CBC17F2003E40AB /* edit_journal.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = edit_journal.hpp; sourceTree = "<group>"; };
|
||||
46CB685C2CBC17F2003E40AB /* edit_journal.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_journal.cpp; sourceTree = "<group>"; };
|
||||
56C74C121C749E4700B71B9F /* categories_holder_loader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = categories_holder_loader.cpp; sourceTree = "<group>"; };
|
||||
56C74C131C749E4700B71B9F /* categories_holder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = categories_holder.cpp; sourceTree = "<group>"; };
|
||||
56C74C141C749E4700B71B9F /* categories_holder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = categories_holder.hpp; sourceTree = "<group>"; };
|
||||
|
@ -603,6 +607,8 @@
|
|||
6753409C1A3F53CB00A0A8C3 /* indexer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
46CB685B2CBC17F2003E40AB /* edit_journal.hpp */,
|
||||
46CB685C2CBC17F2003E40AB /* edit_journal.cpp */,
|
||||
30FE368A2AF994E600DAC107 /* kayak.cpp */,
|
||||
30FE368B2AF994E600DAC107 /* kayak.hpp */,
|
||||
34664CEE1D49FEC1003D7096 /* altitude_loader.cpp */,
|
||||
|
@ -796,6 +802,7 @@
|
|||
675341001A3F540F00A0A8C3 /* cell_id.hpp in Headers */,
|
||||
3D74ABBC1EA67C1E0063A898 /* ftypes_mapping.hpp in Headers */,
|
||||
67BC92F51D21476500A4A378 /* string_slice.hpp in Headers */,
|
||||
46CB685D2CBC17F2003E40AB /* edit_journal.hpp in Headers */,
|
||||
F6F1DABE1F13D8B4006A69B7 /* ftraits.hpp in Headers */,
|
||||
675341271A3F540F00A0A8C3 /* features_vector.hpp in Headers */,
|
||||
408FE47624FEB95600F5D06D /* metadata_serdes.hpp in Headers */,
|
||||
|
@ -1010,6 +1017,7 @@
|
|||
675341211A3F540F00A0A8C3 /* feature_utils.cpp in Sources */,
|
||||
4099F6491FC7142A002A7B05 /* fake_feature_ids.cpp in Sources */,
|
||||
FA7F9B85273F32680093EA08 /* validate_and_format_contacts.cpp in Sources */,
|
||||
46CB685E2CBC17F2003E40AB /* edit_journal.cpp in Sources */,
|
||||
675341231A3F540F00A0A8C3 /* feature_visibility.cpp in Sources */,
|
||||
56C74C221C749E4700B71B9F /* search_delimiters.cpp in Sources */,
|
||||
347F337B1C454242009758CC /* rank_table.cpp in Sources */,
|
||||
|
|
Reference in a new issue