[editor] Mark businesse as disused/vacant

Signed-off-by: map-per <map-per@gmx.de>
This commit is contained in:
map-per
2025-10-27 09:04:40 +01:00
parent 1de35bb5f8
commit 4ae64791ff
19 changed files with 406 additions and 8 deletions

View File

@@ -153,6 +153,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
private final Map<Metadata.MetadataType, View> mDetailsBlocks = new HashMap<>(); private final Map<Metadata.MetadataType, View> mDetailsBlocks = new HashMap<>();
private final Map<Metadata.MetadataType, View> mSocialMediaBlocks = new HashMap<>(); private final Map<Metadata.MetadataType, View> mSocialMediaBlocks = new HashMap<>();
private MaterialButton mReset; private MaterialButton mReset;
private MaterialButton mDisused;
private EditorHostFragment mParent; private EditorHostFragment mParent;
@@ -827,6 +828,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
osmInfo.setMovementMethod(LinkMovementMethod.getInstance()); osmInfo.setMovementMethod(LinkMovementMethod.getInstance());
mReset = view.findViewById(R.id.reset); mReset = view.findViewById(R.id.reset);
mReset.setOnClickListener(this); mReset.setOnClickListener(this);
mDisused = view.findViewById(R.id.disused);
mDisused.setOnClickListener(this);
mDetailsBlocks.put(Metadata.MetadataType.FMD_OPEN_HOURS, blockOpeningHours); mDetailsBlocks.put(Metadata.MetadataType.FMD_OPEN_HOURS, blockOpeningHours);
mDetailsBlocks.put(Metadata.MetadataType.FMD_PHONE_NUMBER, blockPhone); mDetailsBlocks.put(Metadata.MetadataType.FMD_PHONE_NUMBER, blockPhone);
@@ -894,6 +897,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
mParent.addLanguage(); mParent.addLanguage();
else if (id == R.id.reset) else if (id == R.id.reset)
reset(); reset();
else if (id == R.id.disused)
placeDisused();
else if (id == R.id.block_outdoor_seating) else if (id == R.id.block_outdoor_seating)
mOutdoorSeating.toggle(); mOutdoorSeating.toggle();
} }
@@ -939,9 +944,12 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
if (mParent.addingNewObject()) if (mParent.addingNewObject())
{ {
UiUtils.hide(mReset); UiUtils.hide(mReset);
UiUtils.hide(mDisused);
return; return;
} }
mDisused.setVisibility(Editor.nativeCanMarkPlaceAsDisused() ? View.VISIBLE : View.GONE);
if (Editor.nativeIsMapObjectUploaded()) if (Editor.nativeIsMapObjectUploaded())
{ {
mReset.setText(R.string.editor_place_doesnt_exist); mReset.setText(R.string.editor_place_doesnt_exist);
@@ -1014,6 +1022,19 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
dialogFragment.setTextSaveListener(this::commitPlaceDoesntExists); dialogFragment.setTextSaveListener(this::commitPlaceDoesntExists);
} }
private void placeDisused()
{
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
.setTitle(R.string.editor_mark_business_vacant_title)
.setMessage(R.string.editor_mark_business_vacant_description)
.setPositiveButton(R.string.editor_submit, (dlg, which) -> {
Editor.nativeMarkPlaceAsDisused();
mParent.processEditedFeatures();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void commitPlaceDoesntExists(@NonNull String text) private void commitPlaceDoesntExists(@NonNull String text)
{ {
Editor.nativePlaceDoesNotExist(text); Editor.nativePlaceDoesNotExist(text);

View File

@@ -358,7 +358,7 @@ public class EditorHostFragment
.show(); .show();
} }
private void processEditedFeatures() public void processEditedFeatures()
{ {
if (OsmOAuth.isAuthorized()) if (OsmOAuth.isAuthorized())
{ {

View File

@@ -394,7 +394,8 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/cv__more" android:id="@+id/cv__more"
style="@style/MwmWidget.Editor.CardView"> style="@style/MwmWidget.Editor.CardView"
android:layout_marginBottom="@dimen/margin_base">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -421,6 +422,17 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/disused"
style="@style/MwmWidget.M3.Button.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginBottom="@dimen/margin_quarter"
app:backgroundTint="?cardBackground"
android:textColor="@color/base_red"
app:strokeColor="@color/base_red"
android:text="@string/editor_business_vacant_button"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/reset" android:id="@+id/reset"
style="@style/MwmWidget.M3.Button.Secondary" style="@style/MwmWidget.M3.Button.Secondary"

View File

@@ -551,6 +551,14 @@
<string name="editor_place_doesnt_exist_description">Describe what the place looks like now to send an error note to the OpenStreetMap community</string> <string name="editor_place_doesnt_exist_description">Describe what the place looks like now to send an error note to the OpenStreetMap community</string>
<!-- Error message for "Place doesn't exist" dialog when comment is empty --> <!-- Error message for "Place doesn't exist" dialog when comment is empty -->
<string name="delete_place_empty_comment_error">Please indicate the reason for deleting the place</string> <string name="delete_place_empty_comment_error">Please indicate the reason for deleting the place</string>
<!-- Button in the editor to mark business as vacant -->
<string name="editor_business_vacant_button">Business is vacant</string>
<!-- Title of confirmation dialog before marking business as vacant -->
<string name="editor_mark_business_vacant_title">Mark business as vacant</string>
<!-- Description in confirmation dialog before marking business as vacant -->
<string name="editor_mark_business_vacant_description">Use this if the business has moved out and the space is empty and ready for a new tenant.</string>
<!-- Submit change to OSM in the editor -->
<string name="editor_submit">Submit</string>
<!-- Phone number error message --> <!-- Phone number error message -->
<string name="error_enter_correct_phone">Enter a valid phone number</string> <string name="error_enter_correct_phone">Enter a valid phone number</string>
<string name="error_enter_correct_web">Enter a valid web address</string> <string name="error_enter_correct_web">Enter a valid web address</string>

View File

@@ -277,6 +277,11 @@ JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeIsNameEd
return g_editableMapObject.IsNameEditable(); return g_editableMapObject.IsNameEditable();
} }
JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeCanMarkPlaceAsDisused(JNIEnv * env, jclass clazz)
{
return g_editableMapObject.CanMarkPlaceAsDisused();
}
JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeIsPointType(JNIEnv * env, jclass clazz) JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeIsPointType(JNIEnv * env, jclass clazz)
{ {
return g_editableMapObject.IsPointType(); return g_editableMapObject.IsPointType();
@@ -434,6 +439,11 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeRollbackMapO
g_framework->NativeFramework()->RollBackChanges(g_editableMapObject.GetID()); g_framework->NativeFramework()->RollBackChanges(g_editableMapObject.GetID());
} }
JNIEXPORT void JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeMarkPlaceAsDisused(JNIEnv * env, jclass clazz)
{
g_framework->NativeFramework()->MarkPlaceAsDisused(g_editableMapObject);
}
JNIEXPORT jobjectArray JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeGetAllCreatableFeatureTypes(JNIEnv * env, JNIEXPORT jobjectArray JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeGetAllCreatableFeatureTypes(JNIEnv * env,
jclass clazz, jclass clazz,
jstring jLang) jstring jLang)

View File

@@ -99,6 +99,7 @@ public final class Editor
public static native boolean nativeIsAddressEditable(); public static native boolean nativeIsAddressEditable();
public static native boolean nativeIsNameEditable(); public static native boolean nativeIsNameEditable();
public static native boolean nativeCanMarkPlaceAsDisused();
public static native boolean nativeIsPointType(); public static native boolean nativeIsPointType();
public static native boolean nativeIsBuilding(); public static native boolean nativeIsBuilding();
@@ -164,6 +165,7 @@ public final class Editor
public static native void nativeCreateNote(String text); public static native void nativeCreateNote(String text);
public static native void nativePlaceDoesNotExist(@NonNull String comment); public static native void nativePlaceDoesNotExist(@NonNull String comment);
public static native void nativeRollbackMapObject(); public static native void nativeRollbackMapObject();
public static native void nativeMarkPlaceAsDisused();
public static native void nativeCreateStandaloneNote(double lat, double lon, String text); public static native void nativeCreateStandaloneNote(double lat, double lon, String text);
/** /**

View File

@@ -33,6 +33,7 @@ set(SRC
xml_feature.cpp xml_feature.cpp
xml_feature.hpp xml_feature.hpp
yes_no_unknown.hpp yes_no_unknown.hpp
keys_to_remove.hpp
) )
omim_add_library(${PROJECT_NAME} ${SRC}) omim_add_library(${PROJECT_NAME} ${SRC})

View File

@@ -57,6 +57,9 @@ std::string GetTypeForFeature(editor::XMLFeature const & node)
} }
} }
if (node.HasTag("disused:shop") || node.HasTag("disused:amenity"))
return "vacant business";
if (node.HasTag("addr:housenumber") || node.HasTag("addr:street") || node.HasTag("addr:postcode")) if (node.HasTag("addr:housenumber") || node.HasTag("addr:street") || node.HasTag("addr:postcode"))
return "address"; return "address";

View File

@@ -0,0 +1,133 @@
#pragma once
#include <string_view>
// Keys that should be removed when a place in OSM is replaced
// Copied from https://github.com/mnalis/StreetComplete-taginfo-categorize/blob/master/sc_to_remove.txt
// TODO(map-per) Check licence compatibility
inline constexpr std::string_view kKeysToRemove[] = {
"shop_?[1-9]?(:.*)?", "craft_?[1-9]?", "amenity_?[1-9]?", "club_?[1-9]?", "old_amenity",
"old_shop", "information", "leisure", "office_?[1-9]?", "tourism",
// popular shop=* / craft=* subkeys
"marketplace", "household", "swimming_pool", "laundry", "golf", "sports", "ice_cream",
"scooter", "music", "retail", "yes", "ticket", "newsagent", "lighting", "truck", "car_repair",
"car_parts", "video", "fuel", "farm", "car", "tractor", "hgv", "ski", "sculptor",
"hearing_aids", "surf", "photo", "boat", "gas", "kitchen", "anime", "builder", "hairdresser",
"security", "bakery", "bakehouse", "fishing", "doors", "kiosk", "market", "bathroom", "lamps",
"vacant", "insurance(:.*)?", "caravan", "gift", "bicycle", "bicycle_rental", "insulation",
"communication", "mall", "model", "empty", "wood", "hunting", "motorcycle", "trailer",
"camera", "water", "fireplace", "outdoor", "blacksmith", "electronics", "fan", "piercing",
"stationery", "sensory_friendly(:.*)?", "street_vendor",
// obsoleted information
"(demolished|abandoned|disused)(:(?!bui).+)?", "was:.*", "not:.*", "damage", "created_by",
"check_date", "opening_date", "last_checked", "checked_exists:date", "pharmacy_survey",
"old_ref", "update", "import_uuid", "review", "fixme:atp",
// classifications / links to external databases
"fhrs:.*", "old_fhrs:.*", "fvst:.*", "ncat", "nat_ref", "gnis:.*", "winkelnummer",
"type:FR:FINESS", "type:FR:APE", "kvl_hro:amenity", "ref:DK:cvr(:.*)?", "certifications?",
"transiscope", "opendata:type", "local_ref", "official_ref",
// names and identifications
"name_?[1-9]?(:.*)?", ".*_name_?[1-9]?(:.*)?", "noname", "branch(:.*)?", "brand(:.*)?",
"not:brand(:.*)?", "network(:.*)?", "operator(:.*)?", "operator_type", "ref", "ref:vatin",
"designation", "SEP:CLAVEESC", "identifier", "ref:FR:SIRET", "ref:FR:SIREN", "ref:FR:NAF",
"(old_)?ref:FR:prix-carburants",
// contacts
"contact_person", "contact(:.*)?", "phone(:.*)?", "phone_?[1-9]?", "emergency:phone",
"emergency_telephone_code",
"mobile", "fax", "facebook", "instagram", "twitter", "youtube", "telegram", "email",
"website_?[1-9]?(:.*)?", "app:.*", "ownership",
"url", "url:official", "source_ref:url", "owner",
// payments
"payment(:.*)?", "payment_multi_fee", "currency(:.*)?", "cash_withdrawal(:.*)?", "fee",
"charge", "charge_fee", "money_transfer", "donation:compensation", "paypoint",
// generic shop/craft attributes
"seasonal", "time", "opening_hours(:.*)?", "check_(in|out)", "wifi", "internet",
"internet_access(:.*)?", "second_hand", "self_service", "automated", "license:.*",
"bulk_purchase", ".*:covid19", "language:.*", "baby_feeding", "description(:.*)?",
"description[0-9]", "min_age", "max_age", "supermarket(:.*)?", "social_facility(:.*)?",
"functional", "trade", "wholesale", "sale", "smoking(:outside)?", "zero_waste", "origin",
"attraction", "strapline", "dog", "showroom", "toilets?(:.*)?", "sanitary_dump_station",
"changing_table(:.*)?", "wheelchair(.*)?", "blind", "company(:.*)?", "stroller", "walk-in",
"webshop", "operational_status.*", "status", "drive_through", "surveillance(:.*)?",
"outdoor_seating", "indoor_seating", "colour", "access_simple", "floor", "product_category",
"guide", "source_url", "category", "kids_area", "kids_area:indoor", "resort", "since", "state",
"temporary", "self_checkout", "audio_loop", "related_law(:.*)?", "official_status(:.*)?",
// food and drink details
"bar", "cafe", "coffee", "microroasting", "microbrewery", "brewery", "real_ale", "taproom",
"training", "distillery", "drink(:.*)?", "cocktails", "alcohol", "wine([:_].*)?",
"happy_hours", "diet:.*", "cuisine", "ethnic", "tasting", "breakfast", "lunch", "organic",
"produced_on_site", "restaurant", "food", "pastry", "pastry_shop", "product", "produce",
"chocolate", "fair_trade", "butcher", "reservation(:.*)?", "takeaway(:.*)?", "delivery(:.*)?",
"caterer", "real_fire", "flour_fortified", "highchair", "fast_food", "pub", "snack",
"confectionery", "drinking_water:refill",
// related to repair shops/crafts
"service(:.*)?", "motorcycle:.*", "repair", ".*:repair", "electronics_repair(:.*)?",
"workshop",
// shop=hairdresser, shop=clothes
"unisex", "male", "female", "gender", "gender_simple", "lgbtq(:.*)?", "gay", "female:signed",
"male:signed",
// healthcare
"healthcare(:.*)?", "healthcare_.*", "health", "health_.*", "medical_.*", "emergency_ward",
"facility(:.*)?", "activities", "healthcare_facility(:.*)?", "laboratory(:.*)?", "blood(:.*)?",
"blood_components", "infection(:.*)?", "disease(:.*)?", "covid19(:.*)?", "COVID_.*",
"CovidVaccineCenterId", "coronaquarantine", "hospital(:.*)?", "hospital_type_id",
"emergency_room", "sample_collection(:.*)?", "bed_count", "capacity:beds", "part_time_beds",
"personnel:count", "staff_count(:.*)?", "admin_staff", "doctors", "doctors_num", "nurses_num",
"counselling_type", "testing_centres", "toilets_number", "urgent_care", "vaccination",
"clinic", "hospital", "pharmacy", "alternative", "laboratory", "sample_collection",
"provided_for(:.*)?", "social_facility_for", "ambulance", "ward",
"HSE_(code|hgid|hgroup|region)", "collection_centre", "design", "AUTORIZATIE", "reg_id",
"post_addr", "scope", "ESTADO", "NIVSOCIO", "NO", "EMP_EST", "COD_HAB", "CLA_PERS", "CLA_PRES",
"snis_code:.*", "hfac_bed", "hfac_type", "nature", "moph_code", "IJSN:.*", "massgis:id",
"OGD-Stmk:.*", "paho:.*", "panchayath", "pbf_contract", "pcode", "pe:minsa:.*", "who:.*",
"pharmacy:category", "tactile_paving", "HF_(ID|TYPE|N_EN)", "RoadConn", "bin", "hiv(:.*)?",
// accommodation & layout
"rooms", "stars", "accommodation", "beds", "capacity(:persons)?", "laundry_service",
"guest_house",
// amenity=place_of_worship
"deanery", "subject:(wikidata|wikipedia|wikimedia_commons)", "church", "church:type",
// schools
"capacity:(pupils|teachers)", "grades", "population:pupils(:.*)?",
"school:(FR|gender|trust|type|type_idn)", "primary",
// clubs
"animal(_breeding|_training)?", "billiards(:.*)?", "board_game", "sport_1", "sport:boating",
"boat:type", "canoe(_rental|:service)?", "kayak(_rental|:service)?",
"sailboat(_rental|:service)?", "horse_riding", "rugby", "boules", "callsign", "card_games",
"car_service", "catastro:ref", "chess(:.*)?", "children", "climbing(:.*)?", "club(:.*)?",
"communication(:amateur_radio.*)", "community_centre:for", "dffr:network", "dormitory",
"education_for:ages", "electrified", "esperanto", "events_venue", "family", "federation",
"free_flying(:.*)?", "freemasonry(:.*)?", "free_refill", "gaelic_games(:.*)?", "membership",
"military_service", "model_aerodrome(:.*)?", "mode_of_organisation(:.*)?", "snowmobile",
"social_centre(:for)?", "source_dat", "tennis", "old_website", "organisation", "school_type",
"scout(:type)?", "fraternity", "live_music", "lockable", "playground(:theme)?", "nudism",
"music_genre", "length", "fire_station:type:FR", "cadet", "observatory:type", "tower:type",
"zoo", "shooting", "commons", "groomer", "group_only", "hazard", "identity", "interaction",
"logo", "maxheight", "provides", "regional", "scale", "site", "plots", "allotments",
"local_food", "monitoring:pedestrian", "recording:automated", "yacht", "background_music",
"url:spaceapi", "openfire",
// misc specific attributes
"clothes", "shoes", "tailor", "beauty", "tobacco", "carpenter", "furniture", "lottery",
"sport", "dispensing", "tailor:.*", "gambling", "material", "raw_material", "stonemason",
"studio", "scuba_diving(:.*)?", "polling_station", "collector", "books", "agrarian",
"musical_instrument", "massage", "parts", "post_office(:.*)?", "religion", "denomination",
"rental", ".*:rental", "tickets:.*", "public_transport", "goods_supply", "pet", "appliance",
"artwork_type", "charity", "company", "crop", "dry_cleaning", "factory", "feature",
"air_conditioning", "atm", "vending", "vending_machine", "recycling_type", "museum",
"license_classes", "dance:.*", "isced:level", "school", "preschool", "university",
"research_institution", "research", "member_of", "topic", "townhall:type", "parish", "police",
"government", "thw:(lv|rb|ltg)", "office", "administration", "administrative", "association",
"transport", "utility", "consulting", "Commercial", "commercial", "private", "taxi",
"admin_level", "official_status", "target", "liaison", "diplomatic(:.*)?", "embassy",
"consulate", "aeroway", "department", "faculty", "aerospace:product", "boundary", "population",
"diocese", "depot", "cargo", "function", "game", "party", "political_party.*",
"telecom(munication)?", "service_times", "kitchen:facilities", "it:(type|sales)",
"cannabis:cbd", "bath:type", "bath:(open_air|sand_bath)", "animal_boarding", "animal_shelter",
"mattress", "screen", "monitoring:weather", "public", "theatre", "culture", "library",
"cooperative(:.*)?", "winery", "curtain", "lawyer(:.*)?", "local_authority(:.*)?", "equipment",
"hackerspace",
"camp_site", "camping", "bbq", "static_caravans", "emergency(:.*)?", "evacuation_cent(er|re)",
"education", "engineering", "forestry", "foundation", "lawyer", "logistics", "military",
"community_centre", "bank", "operational", "users_(PLWD|boy|elderly|female|girl|men)",
"Comments?", "comments?", "entrance:(width|step_count|kerb:height)", "fenced", "motor_vehicle",
"shelter",
};

View File

@@ -668,7 +668,7 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
{} {}
// Add tags to XMLFeature // Add tags to XMLFeature
UpdateXMLFeatureTags(feature, journal); UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM // Upload XMLFeature to OSM
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature)); LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
@@ -686,7 +686,7 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object); XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
// Update tags of XMLFeature // Update tags of XMLFeature
UpdateXMLFeatureTags(feature, journal); UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM // Upload XMLFeature to OSM
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature)); LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
@@ -1321,7 +1321,7 @@ bool Editor::IsFeatureUploadedImpl(FeaturesContainer const & features, MwmId con
return info && info->m_uploadStatus == kUploaded; return info && info->m_uploadStatus == kUploaded;
} }
void Editor::UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal) void Editor::UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal, ChangesetWrapper & changeset)
{ {
for (JournalEntry const & entry : journal) for (JournalEntry const & entry : journal)
{ {
@@ -1335,6 +1335,13 @@ void Editor::UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<Journa
} }
case JournalEntryType::ObjectCreated: break; case JournalEntryType::ObjectCreated: break;
case JournalEntryType::LegacyObject: ASSERT_FAIL(("Legacy Objects can not be edited with the new editor")); break; case JournalEntryType::LegacyObject: ASSERT_FAIL(("Legacy Objects can not be edited with the new editor")); break;
case JournalEntryType::BusinessReplacement:
{
BusinessReplacementData const & businessReplacementData = std::get<BusinessReplacementData>(entry.data);
feature.OSMBusinessReplacement(businessReplacementData.old_type, businessReplacementData.new_type);
changeset.AddChangesetTag("info:place_marked_as_disused", "yes");
break;
}
} }
} }
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "editor/changeset_wrapper.hpp"
#include "editor/config_loader.hpp" #include "editor/config_loader.hpp"
#include "editor/editor_config.hpp" #include "editor/editor_config.hpp"
#include "editor/editor_notes.hpp" #include "editor/editor_notes.hpp"
@@ -241,7 +242,7 @@ private:
static bool IsFeatureUploadedImpl(FeaturesContainer const & features, MwmId const & mwmId, uint32_t index); static bool IsFeatureUploadedImpl(FeaturesContainer const & features, MwmId const & mwmId, uint32_t index);
void UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal); static void UpdateXMLFeatureTags(editor::XMLFeature & feature, std::list<JournalEntry> const & journal, ChangesetWrapper & changeset);
/// Deleted, edited and created features. /// Deleted, edited and created features.
base::AtomicSharedPtr<FeaturesContainer> m_features; base::AtomicSharedPtr<FeaturesContainer> m_features;

View File

@@ -1,4 +1,5 @@
#include "editor/xml_feature.hpp" #include "editor/xml_feature.hpp"
#include "editor/keys_to_remove.hpp"
#include "indexer/classificator.hpp" #include "indexer/classificator.hpp"
#include "indexer/editable_map_object.hpp" #include "indexer/editable_map_object.hpp"
@@ -15,6 +16,7 @@
#include "base/timer.hpp" #include "base/timer.hpp"
#include <array> #include <array>
#include <regex>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -502,6 +504,29 @@ osm::EditJournal XMLFeature::GetEditJournal() const
entry.data = legacyObjData; entry.data = legacyObjData;
break; break;
} }
case osm::JournalEntryType::BusinessReplacement:
{
osm::BusinessReplacementData businessReplacementData;
// Old Feature Type
std::string old_strType = getAttribute(xmlData, "old_type");
if (old_strType.empty())
MYTHROW(editor::InvalidJournalEntry, ("Old Feature type is empty"));
businessReplacementData.old_type = classif().GetTypeByReadableObjectName(old_strType);
if (businessReplacementData.old_type == IndexAndTypeMapping::INVALID_TYPE)
MYTHROW(editor::InvalidJournalEntry, ("Invalid old Feature Type:", old_strType));
// New Feature Type
std::string new_strType = getAttribute(xmlData, "new_type");
if (new_strType.empty())
MYTHROW(editor::InvalidJournalEntry, ("New Feature type is empty"));
businessReplacementData.new_type = classif().GetTypeByReadableObjectName(new_strType);
if (businessReplacementData.new_type == IndexAndTypeMapping::INVALID_TYPE)
MYTHROW(editor::InvalidJournalEntry, ("Invalid new Feature Type:", new_strType));
entry.data = businessReplacementData;
break;
}
} }
if (isHistory) if (isHistory)
journal.AddJournalHistoryEntry(entry); journal.AddJournalHistoryEntry(entry);
@@ -572,6 +597,13 @@ void XMLFeature::SetEditJournal(osm::EditJournal const & journal)
xmlData.append_attribute("version") = legacyObjData.version.data(); xmlData.append_attribute("version") = legacyObjData.version.data();
break; break;
} }
case osm::JournalEntryType::BusinessReplacement:
{
osm::BusinessReplacementData const & businessReplacementData = std::get<osm::BusinessReplacementData>(entry.data);
xmlData.append_attribute("old_type") = classif().GetReadableObjectName(businessReplacementData.old_type).data();
xmlData.append_attribute("new_type") = classif().GetReadableObjectName(businessReplacementData.new_type).data();
break;
}
} }
} }
}; };
@@ -675,6 +707,49 @@ void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
} }
} }
void XMLFeature::OSMBusinessReplacement(uint32_t old_type, uint32_t new_type)
{
std::string name = GetTagValue("name");
// Remove OSM tags using the list from keys_to_remove.hpp
std::string regexPattern;
for (auto const & key : kKeysToRemove)
{
if (!regexPattern.empty())
regexPattern.append("|");
regexPattern.append(key);
}
std::regex regex(regexPattern);
ForEachTag([& regex, this](std::string_view key, std::string_view /*value*/)
{
if (std::regex_search(key.begin(), key.end(), regex))
RemoveTag(key);
});
if (classif().GetReadableObjectName(new_type) == "disusedbusiness")
{
// Mark as 'disused'
string const strOldType = classif().GetReadableObjectName(old_type);
strings::SimpleTokenizer iter(strOldType, "-");
string_view const key = *iter;
if (++iter)
SetTagValue("disused:" + std::string(key), *iter);
else
SetTagValue("disused:" + std::string(key), "yes");
SetTagValue("old_name", name);
}
else
{
// Add new category tag
ASSERT_FAIL("Only marking places as 'disused' is implemented yet. "
"Wrong new_type: " + classif().GetReadableObjectName(new_type));
}
}
string XMLFeature::GetAttribute(string const & key) const string XMLFeature::GetAttribute(string const & key) const
{ {
return GetRootNode().attribute(key.data()).value(); return GetRootNode().attribute(key.data()).value();

View File

@@ -187,6 +187,8 @@ public:
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags /// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
void UpdateOSMTag(std::string_view key, std::string_view value); void UpdateOSMTag(std::string_view key, std::string_view value);
/// Replace an old business with a new business
void OSMBusinessReplacement(uint32_t old_type, uint32_t new_type);
std::string GetAttribute(std::string const & key) const; std::string GetAttribute(std::string const & key) const;
void SetAttribute(std::string const & key, std::string const & value); void SetAttribute(std::string const & key, std::string const & value);

View File

@@ -41,6 +41,13 @@ void EditJournal::MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::P
AddJournalEntry({JournalEntryType::ObjectCreated, time(nullptr), osm::ObjCreateData{type, geomType, mercator}}); AddJournalEntry({JournalEntryType::ObjectCreated, time(nullptr), osm::ObjCreateData{type, geomType, mercator}});
} }
void EditJournal::AddBusinessReplacement(uint32_t old_type, uint32_t new_type)
{
LOG(LDEBUG, ("Business of type ", classif().GetReadableObjectName(old_type),
" was replaced by a ", classif().GetReadableObjectName(new_type)));
AddJournalEntry({JournalEntryType::BusinessReplacement, time(nullptr), osm::BusinessReplacementData{old_type, new_type}});
}
void EditJournal::AddJournalEntry(JournalEntry entry) void EditJournal::AddJournalEntry(JournalEntry entry)
{ {
m_journal.push_back(std::move(entry)); m_journal.push_back(std::move(entry));
@@ -103,6 +110,15 @@ std::string EditJournal::ToString(osm::JournalEntry const & journalEntry)
LegacyObjData const & legacyObjData = std::get<LegacyObjData>(journalEntry.data); LegacyObjData const & legacyObjData = std::get<LegacyObjData>(journalEntry.data);
return ToString(journalEntry.journalEntryType).append(": version=\"").append(legacyObjData.version).append("\""); return ToString(journalEntry.journalEntryType).append(": version=\"").append(legacyObjData.version).append("\"");
} }
case osm::JournalEntryType::BusinessReplacement:
{
BusinessReplacementData const & businessReplacementData = std::get<BusinessReplacementData>(journalEntry.data);
return ToString(journalEntry.journalEntryType)
.append(": Category changed from ")
.append(classif().GetReadableObjectName(businessReplacementData.old_type))
.append(" to ")
.append(classif().GetReadableObjectName(businessReplacementData.new_type));
}
default: UNREACHABLE(); default: UNREACHABLE();
} }
} }
@@ -114,6 +130,7 @@ std::string EditJournal::ToString(osm::JournalEntryType journalEntryType)
case osm::JournalEntryType::TagModification: return "TagModification"; case osm::JournalEntryType::TagModification: return "TagModification";
case osm::JournalEntryType::ObjectCreated: return "ObjectCreated"; case osm::JournalEntryType::ObjectCreated: return "ObjectCreated";
case osm::JournalEntryType::LegacyObject: return "LegacyObject"; case osm::JournalEntryType::LegacyObject: return "LegacyObject";
case osm::JournalEntryType::BusinessReplacement: return "BusinessReplacement";
default: UNREACHABLE(); default: UNREACHABLE();
} }
} }
@@ -126,6 +143,8 @@ std::optional<JournalEntryType> EditJournal::TypeFromString(std::string const &
return JournalEntryType::ObjectCreated; return JournalEntryType::ObjectCreated;
else if (entryType == "LegacyObject") else if (entryType == "LegacyObject")
return JournalEntryType::LegacyObject; return JournalEntryType::LegacyObject;
else if (entryType == "BusinessReplacement")
return JournalEntryType::BusinessReplacement;
else else
return {}; return {};
} }

View File

@@ -16,6 +16,7 @@ enum class JournalEntryType
TagModification, TagModification,
ObjectCreated, ObjectCreated,
LegacyObject, // object without full journal history, used for transition to new editor LegacyObject, // object without full journal history, used for transition to new editor
BusinessReplacement,
// Possible future values: ObjectDeleted, ObjectDisused, ObjectNotDisused, LocationChanged, FeatureTypeChanged // Possible future values: ObjectDeleted, ObjectDisused, ObjectNotDisused, LocationChanged, FeatureTypeChanged
}; };
@@ -38,11 +39,17 @@ struct LegacyObjData
std::string version; std::string version;
}; };
struct BusinessReplacementData
{
uint32_t old_type;
uint32_t new_type;
};
struct JournalEntry struct JournalEntry
{ {
JournalEntryType journalEntryType = JournalEntryType::TagModification; JournalEntryType journalEntryType = JournalEntryType::TagModification;
time_t timestamp; time_t timestamp;
std::variant<TagModData, ObjCreateData, LegacyObjData> data; std::variant<TagModData, ObjCreateData, LegacyObjData, BusinessReplacementData> data;
}; };
/// Used to determine whether existing OSM object should be updated or new one created /// Used to determine whether existing OSM object should be updated or new one created
@@ -69,6 +76,9 @@ public:
/// Log object creation in the journal /// Log object creation in the journal
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator); void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
/// Log business replacement in the journal
void AddBusinessReplacement(uint32_t old_type, uint32_t new_type);
void AddJournalEntry(JournalEntry entry); void AddJournalEntry(JournalEntry entry);
/// Clear Journal and move content to journalHistory, used after upload to OSM /// Clear Journal and move content to journalHistory, used after upload to OSM

View File

@@ -87,6 +87,29 @@ vector<MapObject::MetadataID> EditableMapObject::GetEditableProperties() const
return props; return props;
} }
bool EditableMapObject::CanMarkPlaceAsDisused() const
{
auto types = GetTypes();
types.SortBySpec();
uint32_t mainType = *types.begin();
std::string mainTypeStr = classif().GetReadableObjectName(mainType);
std::vector<string_view> typePrefixes = {
"shop",
"amenity-restaurant",
"amenity-fast_food",
"amenity-cafe",
"amenity-pub",
"amenity-bar",
};
for (auto const & typePrefix : typePrefixes)
if (mainTypeStr.starts_with(typePrefix))
return true;
return false;
}
NamesDataSource EditableMapObject::GetNamesDataSource() NamesDataSource EditableMapObject::GetNamesDataSource()
{ {
auto const mwmInfo = GetID().m_mwmId.GetInfo(); auto const mwmInfo = GetID().m_mwmId.GetInfo();
@@ -656,6 +679,16 @@ void EditableMapObject::MarkAsCreated(uint32_t type, feature::GeomType geomType,
m_journal.MarkAsCreated(type, geomType, std::move(mercator)); m_journal.MarkAsCreated(type, geomType, std::move(mercator));
} }
void EditableMapObject::MarkAsDisused()
{
auto types = GetTypes();
types.SortBySpec();
uint32_t old_type = *types.begin();
uint32_t new_type = classif().GetTypeByReadableObjectName("disusedbusiness");
ApplyBusinessReplacement(new_type);
m_journal.AddBusinessReplacement(old_type, new_type);
}
void EditableMapObject::ClearJournal() void EditableMapObject::ClearJournal()
{ {
m_journal.Clear(); m_journal.Clear();
@@ -673,7 +706,7 @@ void EditableMapObject::ApplyEditsFromJournal(EditJournal const & editJournal)
void EditableMapObject::ApplyJournalEntry(JournalEntry const & entry) void EditableMapObject::ApplyJournalEntry(JournalEntry const & entry)
{ {
LOG(LDEBUG, ("Applying Journal Entry: ", osm::EditJournal::ToString(entry))); LOG(LDEBUG, ("Applying Journal Entry: ", osm::EditJournal::ToString(entry)));
// Todo
switch (entry.journalEntryType) switch (entry.journalEntryType)
{ {
case JournalEntryType::TagModification: case JournalEntryType::TagModification:
@@ -760,6 +793,12 @@ void EditableMapObject::ApplyJournalEntry(JournalEntry const & entry)
ASSERT_FAIL(("Legacy Objects can not be loaded from Journal")); ASSERT_FAIL(("Legacy Objects can not be loaded from Journal"));
break; break;
} }
case JournalEntryType::BusinessReplacement:
{
BusinessReplacementData const & businessReplacementData = std::get<BusinessReplacementData>(entry.data);
ApplyBusinessReplacement(businessReplacementData.new_type);
break;
}
} }
} }
@@ -859,6 +898,47 @@ void EditableMapObject::LogDiffInJournal(EditableMapObject const & unedited_emo)
} }
} }
void EditableMapObject::ApplyBusinessReplacement(uint32_t new_type)
{
// Types
feature::TypesHolder new_feature_types;
new_feature_types.Add(new_type); // Update feature type
std::string wheelchairType = feature::GetReadableWheelchairType(m_types);
if (!wheelchairType.empty())
new_feature_types.SafeAdd(classif().GetTypeByReadableObjectName(wheelchairType));
std::vector<uint32_t> const buildingTypes = ftypes::IsBuildingChecker::Instance().GetTypes();
for(uint32_t const & type : buildingTypes)
if (m_types.Has(type))
new_feature_types.SafeAdd(type);
m_types = new_feature_types;
// Names
m_name.Clear();
// Metadata
feature::Metadata new_metadata;
constexpr MetadataID metadataToKeep[] = {
MetadataID::FMD_WHEELCHAIR,
MetadataID::FMD_POSTCODE,
MetadataID::FMD_LEVEL,
MetadataID::FMD_ELE,
MetadataID::FMD_HEIGHT,
MetadataID::FMD_MIN_HEIGHT,
MetadataID::FMD_BUILDING_LEVELS,
MetadataID::FMD_BUILDING_MIN_LEVEL
};
for(MetadataID const & metadataID : metadataToKeep)
new_metadata.Set(metadataID, std::string(m_metadata.Get(metadataID)));
m_metadata = new_metadata;
}
bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs) bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs)
{ {
feature::TypesHolder const & lhsTypes = lhs.GetTypes(); feature::TypesHolder const & lhsTypes = lhs.GetTypes();

View File

@@ -78,6 +78,8 @@ public:
/// All store/load/valid operations will be via MetadataEntryIFace interface instead of switch-case. /// All store/load/valid operations will be via MetadataEntryIFace interface instead of switch-case.
std::vector<MetadataID> GetEditableProperties() const; std::vector<MetadataID> GetEditableProperties() const;
bool CanMarkPlaceAsDisused() const;
/// See comment for NamesDataSource class. /// See comment for NamesDataSource class.
NamesDataSource GetNamesDataSource(); NamesDataSource GetNamesDataSource();
LocalizedStreet const & GetStreet() const; LocalizedStreet const & GetStreet() const;
@@ -141,11 +143,15 @@ public:
void SetJournal(EditJournal && editJournal); void SetJournal(EditJournal && editJournal);
EditingLifecycle GetEditingLifecycle() const; EditingLifecycle GetEditingLifecycle() const;
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator); void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
void MarkAsDisused();
void ClearJournal(); void ClearJournal();
void ApplyEditsFromJournal(EditJournal const & journal); void ApplyEditsFromJournal(EditJournal const & journal);
void ApplyJournalEntry(JournalEntry const & entry); void ApplyJournalEntry(JournalEntry const & entry);
void LogDiffInJournal(EditableMapObject const & unedited_emo); void LogDiffInJournal(EditableMapObject const & unedited_emo);
private:
void ApplyBusinessReplacement(uint32_t new_type);
public:
/// Check whether langCode can be used as default name. /// Check whether langCode can be used as default name.
static bool CanUseAsDefaultName(int8_t const langCode, std::vector<int8_t> const & nativeMwmLanguages); static bool CanUseAsDefaultName(int8_t const langCode, std::vector<int8_t> const & nativeMwmLanguages);

View File

@@ -3091,6 +3091,13 @@ void Framework::DeleteFeature(FeatureID const & fid)
UpdatePlacePageInfoForCurrentSelection(); UpdatePlacePageInfoForCurrentSelection();
} }
void Framework::MarkPlaceAsDisused(osm::EditableMapObject emo)
{
emo.MarkAsDisused();
osm::Editor::Instance().SaveEditedFeature(emo);
UpdatePlacePageInfoForCurrentSelection();
}
osm::NewFeatureCategories Framework::GetEditorCategories() const osm::NewFeatureCategories Framework::GetEditorCategories() const
{ {
return osm::Editor::Instance().GetNewFeatureCategories(); return osm::Editor::Instance().GetNewFeatureCategories();

View File

@@ -755,6 +755,7 @@ public:
bool GetEditableMapObject(FeatureID const & fid, osm::EditableMapObject & emo) const; bool GetEditableMapObject(FeatureID const & fid, osm::EditableMapObject & emo) const;
osm::Editor::SaveResult SaveEditedMapObject(osm::EditableMapObject emo); osm::Editor::SaveResult SaveEditedMapObject(osm::EditableMapObject emo);
void DeleteFeature(FeatureID const & fid); void DeleteFeature(FeatureID const & fid);
void MarkPlaceAsDisused(osm::EditableMapObject emo);
osm::NewFeatureCategories GetEditorCategories() const; osm::NewFeatureCategories GetEditorCategories() const;
bool RollBackChanges(FeatureID const & fid); bool RollBackChanges(FeatureID const & fid);
void CreateNote(osm::MapObject const & mapObject, osm::Editor::NoteProblemType const type, std::string const & note); void CreateNote(osm::MapObject const & mapObject, osm::Editor::NoteProblemType const type, std::string const & note);