Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViktor Govako <viktor.govako@gmail.com>2016-01-19 12:12:41 +0300
committerViktor Govako <viktor.govako@gmail.com>2016-01-19 12:12:41 +0300
commit39518a55cfc2be7cbc5e8336b61ce306f5c539db (patch)
tree8097e8833a74e450766b79f60cd47acaae54be8f
parent7091951a9f272e9a15d4e871a1a108178393ee6c (diff)
parentc96f703988215efe52bc726a729d05b194e8ecee (diff)
Merge pull request #1395 from deathbaba/osm-editor-changesosm-editor
OSM editor changes & iOS integration.
-rw-r--r--data/categories.txt462
-rw-r--r--editor/changeset_wrapper.cpp120
-rw-r--r--editor/changeset_wrapper.hpp48
-rw-r--r--editor/editor.pro6
-rw-r--r--editor/editor_tests/server_api_test.cpp2
-rw-r--r--editor/editor_tests/xml_feature_test.cpp42
-rw-r--r--editor/osm_auth.cpp6
-rw-r--r--editor/osm_auth.hpp2
-rw-r--r--editor/server_api.cpp2
-rw-r--r--editor/server_api.hpp2
-rw-r--r--editor/xml_feature.cpp25
-rw-r--r--editor/xml_feature.hpp7
-rw-r--r--indexer/feature.cpp13
-rw-r--r--indexer/feature.hpp3
-rw-r--r--indexer/ftypes_matcher.cpp5
-rw-r--r--indexer/ftypes_matcher.hpp2
-rw-r--r--indexer/index.cpp11
-rw-r--r--indexer/index.hpp3
-rw-r--r--indexer/osm_editor.cpp457
-rw-r--r--indexer/osm_editor.hpp22
-rw-r--r--iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm3
-rw-r--r--iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationWebViewLoginViewController.mm4
-rw-r--r--iphone/Maps/Classes/MWMPlacePageEntity.mm53
-rw-r--r--iphone/Maps/Classes/MWMPlacePageViewManager.mm8
-rw-r--r--map/address_finder.cpp48
-rw-r--r--map/framework.cpp30
-rw-r--r--map/framework.hpp12
-rw-r--r--map/map_tests/bookmarks_test.cpp34
-rw-r--r--qt/draw_widget.cpp21
-rw-r--r--qt/editor_dialog.cpp122
-rw-r--r--qt/editor_dialog.hpp5
-rw-r--r--qt/mainwindow.cpp16
-rw-r--r--qt/mainwindow.hpp1
-rw-r--r--qt/osm_auth_dialog.cpp3
34 files changed, 1245 insertions, 355 deletions
diff --git a/data/categories.txt b/data/categories.txt
index 34e710fab4..44d69acd6c 100644
--- a/data/categories.txt
+++ b/data/categories.txt
@@ -259,7 +259,7 @@ ro:3brutărie|magazin
nb:3bakeri|butikk
fi:3leipomo|kauppa
-shop|shop-convenience|shop-mall|shop-doityourself|shop-florist|shop-butcher|shop-furniture|shop-alcohol|shop-books|shop-shoes|shop-electronics|shop-hardware|shop-jewelry|shop-optician|shop-gift|shop-mobile_phone|shop-beauty|shop-greengrocer|shop-chemist|shop-garden_centre|shop-sports
+shop|shop-convenience|shop-mall|shop-doityourself|shop-mobile_phone|shop-chemist|shop-garden_centre
en:2shop|U+1F3EA|U+1F3EC
ru:2магазин
uk:2магазин|крамниця
@@ -288,18 +288,424 @@ ro:3magazin
nb:3butikk
fi:3kauppa
+shop-florist
+en:florist’s|U+1F337|U+1F338|U+1F339|U+1F33A|U+1F33B|U+1F33C|U+1F490|U+1F33E|2shop
+ru:цветочный магазин|цветы|2магазин
+fr:fleuriste|2magasin
+da:blomsterbutik|2butik
+id:tukang bunga|toko bunga|2toko
+ko:플로리스트의꽃가게|1쇼핑|가게
+sv:blomsteraffär|2butik
+tr:çiçekçi|2mağaza
+uk:магазин квітів|2магазин|крамниця
+vi:cửa hàng hoa|tiệm hoa|cửa hàng
+hu:virágos|virágbolt|2bolt
+de:Florist|Blumengeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:floristi|kukkakauppa|3kauppa
+cs:květinářství|2obchod
+it:fiorista|fioraio|2negozio
+nb:blomsterhandler|3butikk
+zh-Hant:花店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:花店|商店
+th:ร้านดอกไม้|2ร้านค้า
+ja:フローリスト/花屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:florărie|3magazin
+ar:محل زهور|متجر زهور |متجر|المتجر
+sk:kvety|kvetinárstvo|2obchod
+es:floristería|tienda de flores|2tienda
+pl:kwiaciarnia|3sklep|towary
+nl:bloemist|bloemenwinkel|2winkel
+pt:floricultura|2loja|compras
+
+shop-butcher
+en:butcher’s|U+1F356|U+1F357|2shop
+ru:мясная лавка|мясо|2магазин
+fr:boucherie|2magasin
+da:slagter|2butik
+id:tukang daging|2toko
+ko:정육점의|1쇼핑|가게
+sv:slaktare|2butik
+tr:kasap|2mağaza
+uk:м'ясний магазин|2магазин|крамниця
+vi:cửa hàng thịt|cửa hàng
+hu:hentes|2bolt
+de:Metzgerei|3Einkaufenladen|Geschäft|2Laden
+fi:lihakauppias|3kauppa
+cs:řeznictví|2obchod
+it:macellaio|2negozio
+nb:slakter|3butikk
+zh-Hant:肉商|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:肉商|商店
+th:ร้านขายเนื้อ|2ร้านค้า
+ja:肉屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:măcelărie|3magazin
+ar:ججزارة|متجر|المتجر
+sk:mäsiar|2obchod
+es:carnicería|2tienda
+pl:rzeźnik|3sklep|towary
+nl:slager|2winkel
+pt:açougueiro|2loja|compras
+
+shop-furniture
+en:furniture store|2shop
+ru:магазин мебели|2магазин
+fr:magasin de meubles|2magasin
+da:møbelbutik|2butik
+id:toko mebel|2toko
+ko:가구 상점|1쇼핑|가게
+sv:möbelaffär|2butik
+tr:mobilya mağazası|2mağaza
+uk:магазин меблів|2магазин|крамниця
+vi:cửa hàng nội thất|cửa hàng
+hu:bútoráruház|2bolt
+de:Möbelgeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:huonekalukauppa|3kauppa
+cs:nábytek|2obchod
+it:negozio di mobili|2negozio
+nb:møbelbutikk|3butikk
+zh-Hant:家具店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:家具店|商店
+th:ร้านเฟอร์นิเจอร์|2ร้านค้า
+ja:家具店|1買い物|お買い物|ショップ|商店|雑貨
+ro:magazin de mobilă|3magazin
+ar:معرض أثاث|متجر|المتجر
+sk:nábytok|2obchod
+es:tienda de muebles|2tienda
+pl:sklep meblowy|3sklep|towary
+nl:meubelzaak|2winkel
+pt:loja de móveis|2loja|compras
+
+shop-alcohol
+en:liquor store|liquor|U+1F377|2shop
+ru:магазин алкоголя|4алкоголь|3винный|3вино-водочный|2магазин
+fr:magasin de vins et spiritueux|vins et spiritueux|2magasin
+da:vinhandel|2butik
+id:toko minuman keras|minuman keras|2toko
+ko:주류 판매점|1쇼핑|가게
+sv:spritaffär|alkohol|2butik
+tr:içki dükkanı|2mağaza
+uk:винний магазин|крамниця спиртних напоїв|2магазин|крамниця
+vi:cửa hàng rượu|rượu|cửa hàng
+hu:italbolt|ital|2bolt
+de:Spirituosengeschäft|Spirituosen|3Einkaufenladen|Geschäft|2Laden
+fi:alkoholikauppa|alkoholi|3kauppa
+cs:obchod s alkoholem|2obchod
+it:enoteca|2negozio
+nb:vinmonopol|brennevin|3butikk
+zh-Hant:烈酒|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:烈酒|商店
+th:ร้านขายเหล้า|2ร้านค้า
+ja:酒屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:băuturi alcoolice|3magazin
+ar:متجر مشروبات كحولية|خمور|متجر|المتجر
+sk:liehoviny|alkohol|2obchod
+es:licorería|licor|2tienda
+pl:monopolowy|3sklep|towary
+nl:slijterij|drankhandel|2winkel
+pt:loja de bebidas|bebidas alcoólicas|2loja|compras
+
+shop-books
+en:bookstore|U+1F4D6|U+1F4DA|U+1F4D9|U+1F4D8|U+1F4D7|U+1F4D5|2shop
+ru:книжный магазин|книги|2магазин
+fr:librairie|2magasin
+da:boghandel|2butik
+id:toko buku|2toko
+ko:서점|1쇼핑|가게
+sv:bokaffär|2butik
+tr:kitapçı|2mağaza
+uk:книгарня|2магазин|крамниця
+vi:hiệu sách|cửa hàng
+hu:könyvesbolt|2bolt
+de:Büchergeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:kirjakauppa|3kauppa
+cs:knihkupectví|2obchod
+it:libreria|2negozio
+nb:bokhandel|3butikk
+zh-Hant:書店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:书店|商店
+th:ร้านหนังสือ|2ร้านค้า
+ja:本屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:librărie|3magazin
+ar:مكتبة لبيع الكتب|متجر|المتجر
+sk:kníhkupectvo|2obchod
+es:librería|2tienda
+pl:księgarnia|3sklep|towary
+nl:boekwinkel|2winkel
+pt:livraria|2loja|compras
+
+shop-shoes
+en:shoe store|U+1F461|U+1F460|U+1F462|U+1F45E|U+1F45F|2shop
+ru:магазин обуви|2обувь|2обувной|2магазин
+fr:magasin de chaussures|2magasin
+da:skobutik|2butik
+id:toko sepatu|2toko
+ko:신발 가게|1쇼핑|가게
+sv:skobutik|2butik
+tr:ayakkabı mağazası|2mağaza
+uk:магазин взуття|2магазин|крамниця
+vi:cửa hàng giày|cửa hàng
+hu:cipőbolt|2bolt
+de:Schuhgeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:kenkäkauppa|3kauppa
+cs:obuv|2obchod
+it:calzolaio|2negozio
+nb:skobutikk|3butikk
+zh-Hant:鞋店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:鞋店|商店
+th:ร้านขายรองเท้า|2ร้านค้า
+ja:靴屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:magazin de încălțăminte|3magazin
+ar:متجر أحذية|متجر|المتجر
+sk:obuvníctvo|2obchod
+es:zapatería|2tienda
+pl:sklep obuwniczy|3sklep|towary
+nl:schoenenwinkel|2winkel
+pt:sapataria|loja de calçados|2loja|compras
+
+shop-electronics
+en:electronics|U+1F4F1|U+1F4BB|U+23F0|U+1F4F7|U+1F4F9|U+1F3A5|U+1F4FA|U+1F4FB|U+1F4DF|U+1F4DE|U+260E|U+1F4E0|U+1F4BD|U+1F4BE|U+1F4BF|U+1F4C0|U+1F4FC|U+1F50B|U+1F4E1|2shop
+ru:электротехника|2магазин
+fr:magasin d'électroménager|2magasin
+da:elektronikbutik|2butik
+id:elektronik|2toko
+ko:전자|1쇼핑|가게
+sv:elektronik|2butik
+tr:elektronik mağazası|2mağaza
+uk:електротехнічний магазин|2магазин|крамниця
+vi:cửa hàng điện|cửa hàng
+hu:elektronika|2bolt
+de:Elektronik|3Einkaufenladen|Geschäft|2Laden
+fi:elektroniikka|3kauppa
+cs:elektronika|2obchod
+it:negozio di elettronica|2negozio
+nb:elektronikk forhandler|3butikk
+zh-Hant:電子產品|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:电子产品|商店
+th:ร้านขายอุปกรณ์อิเล็กทรอนิกส์|2ร้านค้า
+ja:電気店|1買い物|お買い物|ショップ|商店|雑貨
+ro:electronice|3magazin
+ar:متجر إلكترونيات|متجر|المتجر
+sk:elektronika|2obchod
+es:electrónica|2tienda
+pl:sklep ze sprzętem elektronicznym|3sklep|towary
+nl:elektronicazaak|2winkel
+pt:loja de eletrônicos|2loja|compras
+
+shop-hardware
+en:hardware store|U+1F50B|U+1F50C|U+1F4A1|U+1F526|U+1F529|U+1F528|U+2614|2shop
+ru:хозяйственный магазин|2магазин
+fr:quincaillerie|2magasin
+da:isenkræmmer|2butik
+id:toko perangkat keras|2toko
+ko:철물점|1쇼핑|가게
+sv:järnhandel|2butik
+tr:hırdavatçı|2mağaza
+uk:магазин побутової техніки|2магазин|крамниця
+vi:cửa hàng phần cứng|cửa hàng
+hu:vaskereskedés|2bolt
+de:Eisenwarengeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:työkalukauppa|3kauppa
+cs:železářství|2obchod
+it:ferramenta|2negozio
+nb:jernvareforretning|3butikk
+zh-Hant:硬件店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:硬件店|商店
+th:ร้านขายฮาร์ดแวร์|2ร้านค้า
+ja:工具店|1買い物|お買い物|ショップ|商店|雑貨
+ro:magazin hardware|3magazin
+ar:متجر حواسيب|متجر|المتجر
+sk:železiarstvo|2obchod
+es:ferretería|2tienda
+pl:sklep narzędziowy|3sklep|towary
+nl:ijzerhandel|2winkel
+pt:loja de ferramentas|2loja|compras
+
+shop-jewelry
+en:jewelry|U+1F48D|2shop
+ru:ювелирный магазин|2магазин
+fr:bijouterie|2magasin
+da:smykkebutik|2butik
+id:perhiasan|2toko
+ko:보석류|1쇼핑|가게
+sv:smycken|2butik
+tr:kuyumcu|2mağaza
+uk:ювелірний магазин|2магазин|крамниця
+vi:đồ trang sức|cửa hàng
+hu:ékszer|2bolt
+de:Juweliergeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:korukauppa|3kauppa
+cs:klenotnictví|2obchod
+it:gioielleria|2negozio
+nb:gullsmed|3butikk
+zh-Hant:珠寶店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:珠宝店|商店
+th:ร้านขายเครื่องประดับ|2ร้านค้า
+ja:宝石店|1買い物|お買い物|ショップ|商店|雑貨
+ro:bijutier|3magazin
+ar:مجوهرات|المتجر
+sk:klenotníctvo|2obchod
+es:joyería|2tienda
+pl:jubiler|3sklep|towary
+nl:juwelier|2winkel
+pt:joias|2loja|compras
+
+shop-optician
+en:optician’s|U+1F453|2shop
+ru:оптика|2магазин
+fr:opticien|2magasin
+da:optiker|2butik
+id:toko kacamata|2toko
+ko:안경점 의|1쇼핑|가게
+sv:optiker|2butik
+tr:gözlükçü|2mağaza
+uk:оптика|2магазин|крамниця
+vi:cửa hàng mắt kính|cửa hàng
+hu:optika|2bolt
+de:Optiker|3Einkaufenladen|Geschäft|2Laden
+fi:optikko|3kauppa
+cs:optika|2obchod
+it:ottico|2negozio
+nb:optiker|3butikk
+zh-Hant:眼鏡店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:眼镜店|商店
+th:ร้านแว่น|2ร้านค้า
+ja:眼鏡店|1買い物|お買い物|ショップ|商店|雑貨
+ro:optică|3magazin
+ar:مركز بصريات|متجر|المتجر
+sk:optika|2obchod
+es:óptica|2tienda
+pl:sklep optyczny|3sklep|towary
+nl:opticien|2winkel
+pt:oculista|2loja|compras
+
+shop-gift
+en:gift shop|U+1F381|2shop
+ru:сувениры|2магазин
+fr:boutique de souvenirs|2magasin
+da:gavebutik|2butik
+id:toko hadiah|2toko
+ko:선물 가게|1쇼핑|가게
+sv:presentaffär|2butik
+tr:hediyelik eşya mağazası|2mağaza
+uk:магазин сувенірів|2магазин|крамниця
+vi:cửa hàng quà tặng|cửa hàng
+hu:ajándékbolt|2bolt
+de:Geschenkladen|3Einkaufenladen|Geschäft|2Laden
+fi:lahjakauppa|3kauppa
+cs:obchod s dárkovým zbožím|2obchod
+it:negozio di regali|2negozio
+nb:gavebutikk|3butikk
+zh-Hant:禮品店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:礼品店|商店
+th:ร้านของขวัญ|2ร้านค้า
+ja:ギフトショップ|1買い物|お買い物|ショップ|商店|雑貨
+ro:magazin de suveniruri|3magazin
+ar:متجر هدايا|متجر|المتجر
+sk:darčeky|2obchod
+es:tienda de regalos|2tienda
+pl:sklep pamiątkarski|3sklep|towary
+nl:cadeauwinkel|2winkel
+pt:loja de presentes|2loja|compras
+
+shop-beauty
+en:beauty shop|U+1F484|2shop
+ru:магазин косметики|косметика|2магазин
+fr:institut de beauté|2magasin
+da:skønhedsbutik|2butik
+id:toko barang kecantikan|2toko
+ko:미용 용품 가게|1쇼핑|가게
+sv:skönhetsbutik|2butik
+tr:kozmetik ürünler mağazası|2mağaza
+uk:магазин товарів для краси|2магазин|крамниця
+vi:cửa hàng làm đẹp|cửa hàng
+hu:kozmetikai termékek|2bolt
+de:Kosmetikgeschäft|3Einkaufenladen|Geschäft|2Laden
+fi:kauneusliike|3kauppa
+cs:kosmetický salón|2obchod
+it:estetista|2negozio
+nb:skjønnhetssalong|3butikk
+zh-Hant:美容產品店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:美容产品店|商店
+th:ร้านขายผลิตภัณฑ์ความงาม|2ร้านค้า
+ja:美容品店|1買い物|お買い物|ショップ|商店|雑貨
+ro:magazin de cosmetice|3magazin
+ar:مركز أدوات تجميل|متجر|المتجر
+sk:drogéria|2obchod
+es:tienda de productos de belleza|2tienda
+pl:sklep kosmetyczny|3sklep|towary
+nl:winkel voor schoonheidsproducten|2winkel
+pt:loja de cosméticos|2loja|compras
+
+shop-greengrocer
+en:4greengrocer's|greengrocers|greengrocers'|3grocery|U+1F345|U+1F346|U+1F33D|U+1F360|U+1F348|U+1F347|U+1F349|U+1F34A|U+1F34C|U+1F34D|U+1F34E|U+1F34F|U+1F350|U+1F351|U+1F353|2shop
+ru:овощи и фрукты|овощи|фрукты|2магазин
+fr:primeur|2magasin
+da:grønthandler|2butik
+id:penjual sayuran|2toko
+ko:청과물 상인의|1쇼핑|가게
+sv:grönsakshandlare|2butik
+tr:manav|2mağaza
+uk:магазин овочів|овочі|фрукти|2магазин|крамниця
+vi:cửa hàng rau củ|cửa hàng
+hu:zöldséges|2bolt
+de:Gemüseladen|3Einkaufenladen|Geschäft|2Laden
+fi:vihanneskauppias|3kauppa
+cs:ovoce a zelenina|2obchod
+it:fruttivendolo|2negozio
+nb:frukt- og grønnsakshandler|3butikk
+zh-Hant:蔬果零售店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:蔬果零售店|商店
+th:ร้านขายผัด|2ร้านค้า
+ja:八百屋|1買い物|お買い物|ショップ|商店|雑貨
+ro:băcănie|3magazin
+ar:محل خضراوات|متجر|المتجر
+sk:zelovoc|2obchod
+es:frutería|2tienda
+pl:warzywniak|3sklep|towary
+nl:groenteboer|2winkel
+pt:quitanda|2loja|compras
+
+shop-sports
+en:sports goods|U+1F3BF|U+1F3A3|U+1F3C2|U+1F6B4|U+26BD|U+1F3C0|U+1F3C8|U+26BE|U+1F3BE|U+1F3C9|U+26F3|2shop
+ru:спорттовары|товары для спорта|2магазин
+fr:articles de sport|2magasin
+da:sportsudstyr|2butik
+id:barang olahraga|2toko
+ko:스포츠 용품|1쇼핑|가게
+sv:sportaffär|2butik
+tr:spor ürünleri|2mağaza
+uk:спортивні товари|2магазин|крамниця
+vi:đồ dùng thể thao|cửa hàng
+hu:sporteszközök|2bolt
+de:Sportartikel|3Einkaufenladen|Geschäft|2Laden
+fi:urheilukauppa|3kauppa
+cs:sportovní zboží|2obchod
+it:negozio sportivo|negozio articoli sportivi|2negozio
+nb:sportsutstyr|3butikk
+zh-Hant:運動商品店|1購物|店鋪|商店|雜貨店|便利商店
+zh-Hans:运动商品店|商店
+th:สินค้ากีฬา|2ร้านค้า
+ja:スポーツ用品店|1買い物|お買い物|ショップ|商店|雑貨
+ro:articole sportive|3magazin
+ar:أدوات رياضية|متجر|المتجر
+sk:športové potreby|2obchod
+es:productos de deporte|2tienda
+pl:sklep sportowy|3sklep|towary
+nl:sportartikelen|2winkel
+pt:artigos esportivos|2loja|compras
+
shop-supermarket|shop-department_store
en:3supermarket|shop|U+1F3EA|U+1F3EC
ru:3универсам|3супермаркет|магазин
uk:3супермаркет|3універсам|2магазин|крамниця
de:3Supermarkt|Laden
-fr:3supermarché|magasin
+fr:3supermarché|2magasin
it:3supermercato|negozio
es:3supermercado|tienda
ko:1슈퍼마켓|쇼핑|가게
ja:1スーパーマーケット|ショップ|お買い物|買い物|商店
-cs:3supermarket|obchod
-sk:3supermarket|obchod
+cs:3supermarket|2obchod
+sk:3supermarket|2obchod
nl:3supermarkt|winkel
zh-Hant:1超級市場|市場|購物
pl:4supermarket|zakupy|sklep
@@ -1725,7 +2131,7 @@ zh-Hant:1垃圾桶|垃圾|回收|回收場
pl:3recykling|ponowne odtworzenie|odpady
pt:reciclagem|lixo
hu:szemetes
-th: การรีไซเคิล|ถังขยะ|ขยะ
+th:การรีไซเคิล|ถังขยะ|ขยะ
zh-Hans:1回收
ar:تدوير
da:genbrug|skrald
@@ -2073,7 +2479,7 @@ zh-Hant:省|州
pl:3stan|4region|5prowincja
pt:estado|província
hu:állam
-th: รัฐ
+th:รัฐ
zh-Hans:州|省
ar:محافظة
da:stat|provins
@@ -2102,7 +2508,7 @@ zh-Hant:1地區
pl:4region
pt:região
hu:régió
-th: ภูมิภาค
+th:ภูมิภาค
zh-Hans:区域
ar:منطقة
da:region
@@ -2706,7 +3112,7 @@ sk:penzión|hotel|hostel|ubytovňa
nl:gasthuis|hotel|hostel
zh-Hant:1賓館|旅館|飯店|酒店|旅舍|住宿|招待所
pl:4pensjonat|hotel|hostel|gościnne pokoje
-pt: casa de hóspedes|pousada|motel
+pt:casa de hóspedes|pousada|motel
hu:vendégház|hotel|szálloda
th:3เกสท์เฮ้าส์|โรงแรม์|์โรงแรม
zh-Hans:1招待所์|์旅馆์|์旅社|旅店
@@ -2953,33 +3359,33 @@ nb:golfbane
fi:golf-rata
leisure-pitch
-en:pitch|sport|U+26BD|U+26BE|U+1F3BE|U+1F3C0|U+1F3C8|U+1F3C9|U+1F3C3
+en:sports ground|sport|U+26BD|U+26BE|U+1F3BE|U+1F3C0|U+1F3C8|U+1F3C9|U+1F3C3
ru:спортплощадка|спорт
uk:спортмайданчик|спорт
-de:Feld|Sport
+de:Sportplatz|Feld|Sport
fr:terrain de sport|sport
-it:campo|sport
-es:terreno|deporte
-ko:스포츠 경내|스포츠
+it:campo sportivo|campo|sport
+es:complejo deportivo|terreno|deporte
+ko:경기장|스포츠 경내|스포츠
ja:1運動場|スポーツ|トラック|球場
-cs:sport
-sk:šport
-nl:veld|sport
+cs:sportovní hřiště|sport
+sk:športovisko|šport
+nl:sportveld|veld|sport
zh-Hant:2運動場|體育館|球場|2足球場|體育|運動|健身
-pl:pole|boisko|sport
-pt:campo|desporto
-hu:oszlop|sport
-th:ขว้าง|กีฬา
+pl:boisko sportowe|pole|boisko|sport
+pt:campo de esportes|campo|desporto
+hu:sportpálya|oszlop|sport
+th:พื้นสนามกีฬา|ขว้าง|กีฬา
zh-Hans:2球场|运动
-ar:ميدان اللعب|رياضة
-da:sportsplads|fiskested|fodboldbane|bane
-tr:alan|spor
-sv:bana|sport
-vi:đường pích
-id:lapangan
-ro:gazon
+ar:ميدان اللعب|رياضة |ملاعب رياضية
+da:teltplads|fiskested|fodboldbane|bane|sportsplads
+tr:spor sahası|alan|spor
+sv:idrottsplats|bana|sport
+vi:sân vận động|đường pích
+id:lapangan olahraga|lapangan
+ro:teren de sport|gazon
nb:sportssenter
-fi:nimikkopaikka
+fi:urheilukenttä|nimikkopaikka
leisure-swimming_pool
en:4swimming pool|sport|U+1F3CA
diff --git a/editor/changeset_wrapper.cpp b/editor/changeset_wrapper.cpp
new file mode 100644
index 0000000000..fe2fff6df4
--- /dev/null
+++ b/editor/changeset_wrapper.cpp
@@ -0,0 +1,120 @@
+#include "indexer/feature.hpp"
+
+#include "editor/changeset_wrapper.hpp"
+
+#include "std/algorithm.hpp"
+#include "std/sstream.hpp"
+
+#include "private.h"
+
+using editor::XMLFeature;
+
+string DebugPrint(pugi::xml_document const & doc)
+{
+ ostringstream stream;
+ doc.print(stream, " ");
+ return stream.str();
+}
+
+namespace osm
+{
+
+ChangesetWrapper::ChangesetWrapper(TKeySecret const & keySecret,
+ ServerApi06::TKeyValueTags const & comments)
+ : m_changesetComments(comments),
+ m_api(OsmOAuth::ServerAuth().SetToken(keySecret))
+{
+}
+
+ChangesetWrapper::~ChangesetWrapper()
+{
+ if (m_changesetId)
+ m_api.CloseChangeSet(m_changesetId);
+}
+
+void ChangesetWrapper::LoadXmlFromOSM(ms::LatLon const & ll, pugi::xml_document & doc)
+{
+ auto const response = m_api.GetXmlFeaturesAtLatLon(ll.lat, ll.lon);
+ if (response.first == OsmOAuth::ResponseCode::NetworkError)
+ MYTHROW(NetworkErrorException, ("NetworkError with GetXmlFeaturesAtLatLon request."));
+ if (response.first != OsmOAuth::ResponseCode::OK)
+ MYTHROW(HttpErrorException, ("HTTP error", response.first, "with GetXmlFeaturesAtLatLon", ll));
+
+ if (pugi::status_ok != doc.load(response.second.c_str()).status)
+ MYTHROW(OsmXmlParseException, ("Can't parse OSM server response for GetXmlFeaturesAtLatLon request", response.second));
+}
+
+XMLFeature ChangesetWrapper::GetMatchingFeatureFromOSM(XMLFeature const & ourPatch, FeatureType const & feature)
+{
+ if (feature.GetFeatureType() == feature::EGeomType::GEOM_POINT)
+ {
+ // Match with OSM node.
+ ms::LatLon const ll = ourPatch.GetCenter();
+ pugi::xml_document doc;
+ // Throws!
+ LoadXmlFromOSM(ll, doc);
+
+ // TODO(AlexZ): Select best matching OSM node, not just the first one.
+ pugi::xml_node const firstNode = doc.child("osm").child("node");
+ if (firstNode.empty())
+ MYTHROW(OsmObjectWasDeletedException, ("OSM does not have any nodes at the coordinates", ll, ", server has returned:", doc));
+
+ return XMLFeature(firstNode);
+ }
+ else if (feature.GetFeatureType() == feature::EGeomType::GEOM_AREA)
+ {
+ using m2::PointD;
+ // Set filters out duplicate points for closed ways or triangles' vertices.
+ set<PointD> geometry;
+ feature.ForEachTriangle([&geometry](PointD const & p1, PointD const & p2, PointD const & p3)
+ {
+ geometry.insert(p1);
+ geometry.insert(p2);
+ geometry.insert(p3);
+ }, FeatureType::BEST_GEOMETRY);
+
+ ASSERT_GREATER_OR_EQUAL(geometry.size(), 3, ("Is it an area feature?"));
+
+ for (auto const & pt : geometry)
+ {
+ ms::LatLon const ll = MercatorBounds::ToLatLon(pt);
+ pugi::xml_document doc;
+ // Throws!
+ LoadXmlFromOSM(ll, doc);
+
+ // TODO(AlexZ): Select best matching OSM way from possible many ways.
+ pugi::xml_node const firstWay = doc.child("osm").child("way");
+ if (firstWay.empty())
+ continue;
+
+ XMLFeature const way(firstWay);
+ if (!way.IsArea())
+ continue;
+
+ // TODO: Check that this way is really match our feature.
+
+ return way;
+ }
+ MYTHROW(OsmObjectWasDeletedException, ("OSM does not have any matching way for feature", feature));
+ }
+ MYTHROW(LinearFeaturesAreNotSupportedException, ("We don't edit linear features yet."));
+}
+
+void ChangesetWrapper::ModifyNode(XMLFeature node)
+{
+ // TODO(AlexZ): ServerApi can be much better with exceptions.
+ if (m_changesetId == kInvalidChangesetId && !m_api.CreateChangeSet(m_changesetComments, m_changesetId))
+ MYTHROW(CreateChangeSetFailedException, ("CreateChangeSetFailedException"));
+
+ uint64_t nodeId;
+ if (!strings::to_uint64(node.GetAttribute("id"), nodeId))
+ MYTHROW(CreateChangeSetFailedException, ("CreateChangeSetFailedException"));
+
+ // Changeset id should be updated for every OSM server commit.
+ node.SetAttribute("changeset", strings::to_string(m_changesetId));
+
+ if (!m_api.ModifyNode(node.ToOSMString(), nodeId))
+ MYTHROW(ModifyNodeFailedException, ("ModifyNodeFailedException"));
+}
+
+} // namespace osm
diff --git a/editor/changeset_wrapper.hpp b/editor/changeset_wrapper.hpp
new file mode 100644
index 0000000000..07d4696bd0
--- /dev/null
+++ b/editor/changeset_wrapper.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "editor/server_api.hpp"
+#include "editor/xml_feature.hpp"
+
+#include "base/exception.hpp"
+
+class FeatureType;
+
+namespace osm
+{
+
+struct ClientToken;
+
+class ChangesetWrapper
+{
+public:
+ DECLARE_EXCEPTION(ChangesetWrapperException, RootException);
+ DECLARE_EXCEPTION(NetworkErrorException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(HttpErrorException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(OsmXmlParseException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(OsmObjectWasDeletedException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(CreateChangeSetFailedException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(ModifyNodeFailedException, ChangesetWrapperException);
+ DECLARE_EXCEPTION(LinearFeaturesAreNotSupportedException, ChangesetWrapperException);
+
+ ChangesetWrapper(TKeySecret const & keySecret, ServerApi06::TKeyValueTags const & comments);
+ ~ChangesetWrapper();
+
+ /// Throws many exceptions from above list, plus including XMLNode's parsing ones.
+ /// OsmObjectWasDeletedException means that node was deleted from OSM server by someone else.
+ editor::XMLFeature GetMatchingFeatureFromOSM(editor::XMLFeature const & ourPatch, FeatureType const & feature);
+
+ /// Throws exceptions from above list.
+ void ModifyNode(editor::XMLFeature node);
+
+private:
+ /// Unfortunately, pugi can't return xml_documents from methods.
+ /// Throws exceptions from above list.
+ void LoadXmlFromOSM(ms::LatLon const & ll, pugi::xml_document & doc);
+
+ ServerApi06::TKeyValueTags m_changesetComments;
+ ServerApi06 m_api;
+ static constexpr uint64_t kInvalidChangesetId = 0;
+ uint64_t m_changesetId = kInvalidChangesetId;
+};
+
+} // namespace osm
diff --git a/editor/editor.pro b/editor/editor.pro
index ce3506fdbe..03db84f275 100644
--- a/editor/editor.pro
+++ b/editor/editor.pro
@@ -9,15 +9,17 @@ ROOT_DIR = ..
include($$ROOT_DIR/common.pri)
SOURCES += \
+ changeset_wrapper.cpp \
opening_hours_ui.cpp \
+ osm_auth.cpp \
server_api.cpp \
ui2oh.cpp \
xml_feature.cpp \
- osm_auth.cpp \
HEADERS += \
+ changeset_wrapper.hpp \
opening_hours_ui.hpp \
+ osm_auth.hpp \
server_api.hpp \
ui2oh.hpp \
xml_feature.hpp \
- osm_auth.hpp \
diff --git a/editor/editor_tests/server_api_test.cpp b/editor/editor_tests/server_api_test.cpp
index 42e7ec4126..43d4bb6bbe 100644
--- a/editor/editor_tests/server_api_test.cpp
+++ b/editor/editor_tests/server_api_test.cpp
@@ -116,7 +116,7 @@ UNIT_TEST(OSM_ServerAPI_ChangesetActions)
// New changeset has new id.
TEST(SetAttributeForOsmNode(node, "changeset", changeSetId), ());
- auto const response = api.GetXmlNodeByLatLon(node.child("osm").child("node").attribute("lat").as_double(),
+ auto const response = api.GetXmlFeaturesAtLatLon(node.child("osm").child("node").attribute("lat").as_double(),
node.child("osm").child("node").attribute("lon").as_double());
TEST_EQUAL(response.first, OsmOAuth::ResponseCode::OK, ());
xml_document reply;
diff --git a/editor/editor_tests/xml_feature_test.cpp b/editor/editor_tests/xml_feature_test.cpp
index c18d2b8b53..450ea99117 100644
--- a/editor/editor_tests/xml_feature_test.cpp
+++ b/editor/editor_tests/xml_feature_test.cpp
@@ -93,6 +93,48 @@ UNIT_TEST(XMLFeature_ToOSMString)
TEST_EQUAL(expectedString, feature.ToOSMString(), ());
}
+UNIT_TEST(XMLFeature_IsArea)
+{
+ constexpr char const * validAreaXml = R"(
+<way timestamp="2015-11-27T21:13:32Z">
+ <nd ref="822403"/>
+ <nd ref="21533912"/>
+ <nd ref="821601"/>
+ <nd ref="822403"/>
+</way>
+)";
+ TEST(XMLFeature(validAreaXml).IsArea(), ());
+
+ constexpr char const * notClosedWayXml = R"(
+<way timestamp="2015-11-27T21:13:32Z">
+ <nd ref="822403"/>
+ <nd ref="21533912"/>
+ <nd ref="821601"/>
+ <nd ref="123321"/>
+</way>
+)";
+ TEST(!XMLFeature(notClosedWayXml).IsArea(), ());
+
+ constexpr char const * invalidWayXml = R"(
+<way timestamp="2015-11-27T21:13:32Z">
+ <nd ref="822403"/>
+ <nd ref="21533912"/>
+ <nd ref="822403"/>
+</way>
+)";
+ TEST(!XMLFeature(invalidWayXml).IsArea(), ());
+
+ constexpr char const * emptyWay = R"(
+<way timestamp="2015-11-27T21:13:32Z"/>
+)";
+ TEST(!XMLFeature(emptyWay).IsArea(), ());
+
+ constexpr char const * node = R"(
+<node lat="0.0" lon="0.0" timestamp="2015-11-27T21:13:32Z"/>
+)";
+ TEST(!XMLFeature(node).IsArea(), ());
+}
+
// UNIT_TEST(XMLFeature_FromXml)
// {
// auto const srcString = R"(<?xml version="1.0"?>
diff --git a/editor/osm_auth.cpp b/editor/osm_auth.cpp
index d381884311..7ce5af73a4 100644
--- a/editor/osm_auth.cpp
+++ b/editor/osm_auth.cpp
@@ -91,6 +91,12 @@ OsmOAuth OsmOAuth::ProductionServerAuth()
return OsmOAuth(OSM_CONSUMER_KEY, OSM_CONSUMER_SECRET, kOsmMainSiteURL, kOsmApiURL);
}
+OsmOAuth OsmOAuth::ServerAuth()
+{
+ // TODO(AlexZ): Replace with ProductionServerAuth before release.
+ return IZServerAuth();
+}
+
// Opens a login page and extract a cookie and a secret token.
OsmOAuth::AuthResult OsmOAuth::FetchSessionId(OsmOAuth::SessionID & sid) const
{
diff --git a/editor/osm_auth.hpp b/editor/osm_auth.hpp
index aef959a009..4665c19b25 100644
--- a/editor/osm_auth.hpp
+++ b/editor/osm_auth.hpp
@@ -56,6 +56,8 @@ public:
static OsmOAuth DevServerAuth();
/// api.openstreetmap.org
static OsmOAuth ProductionServerAuth();
+ /// Should be used everywhere in production code.
+ static OsmOAuth ServerAuth();
/// @name Stateless methods.
//@{
diff --git a/editor/server_api.cpp b/editor/server_api.cpp
index 10d04192df..162c0a36a7 100644
--- a/editor/server_api.cpp
+++ b/editor/server_api.cpp
@@ -114,7 +114,7 @@ OsmOAuth::Response ServerApi06::GetXmlFeaturesInRect(m2::RectD const & latLonRec
return m_auth.DirectRequest(url);
}
-OsmOAuth::Response ServerApi06::GetXmlNodeByLatLon(double lat, double lon) const
+OsmOAuth::Response ServerApi06::GetXmlFeaturesAtLatLon(double lat, double lon) const
{
double const kInflateEpsilon = MercatorBounds::GetCellID2PointAbsEpsilon();
m2::RectD rect(lon, lat, lon, lat);
diff --git a/editor/server_api.hpp b/editor/server_api.hpp
index db6b0d74dd..b0de50fdfa 100644
--- a/editor/server_api.hpp
+++ b/editor/server_api.hpp
@@ -42,7 +42,7 @@ public:
/// @returns OSM xml string with features in the bounding box or empty string on error.
OsmOAuth::Response GetXmlFeaturesInRect(m2::RectD const & latLonRect) const;
- OsmOAuth::Response GetXmlNodeByLatLon(double lat, double lon) const;
+ OsmOAuth::Response GetXmlFeaturesAtLatLon(double lat, double lon) const;
private:
OsmOAuth m_auth;
diff --git a/editor/xml_feature.cpp b/editor/xml_feature.cpp
index 27bb8f2966..57ad1dd873 100644
--- a/editor/xml_feature.cpp
+++ b/editor/xml_feature.cpp
@@ -16,7 +16,7 @@ namespace
{
constexpr int const kLatLonTolerance = 7;
constexpr char const * kTimestamp = "timestamp";
-constexpr char const * kOffset = "offset";
+constexpr char const * kIndex = "mwm_file_index";
constexpr char const * kUploadTimestamp = "upload_timestamp";
constexpr char const * kUploadStatus = "upload_status";
constexpr char const * kUploadError = "upload_error";
@@ -112,6 +112,21 @@ XMLFeature::Type XMLFeature::GetType() const
return strcmp(GetRootNode().name(), "node") == 0 ? Type::Node : Type::Way;
}
+bool XMLFeature::IsArea() const
+{
+ if (strcmp(GetRootNode().name(), kWayType) != 0)
+ return false;
+
+ vector<string> ndIds;
+ for (auto const & nd : GetRootNode().select_nodes("nd"))
+ ndIds.push_back(nd.node().attribute("ref").value());
+
+ if (ndIds.size() < 4)
+ return false;
+
+ return ndIds.front() == ndIds.back();
+}
+
void XMLFeature::Save(ostream & ost) const
{
m_document.save(ost, " ");
@@ -197,15 +212,15 @@ void XMLFeature::SetModificationTime(time_t const time)
SetAttribute(kTimestamp, my::TimestampToString(time));
}
-uint32_t XMLFeature::GetOffset() const
+uint32_t XMLFeature::GetMWMFeatureIndex() const
{
// Always cast to uint32_t to avoid warnings on different platforms.
- return static_cast<uint32_t>(GetRootNode().attribute(kOffset).as_uint(0));
+ return static_cast<uint32_t>(GetRootNode().attribute(kIndex).as_uint(0));
}
-void XMLFeature::SetOffset(uint32_t featureOffset)
+void XMLFeature::SetMWMFeatureIndex(uint32_t index)
{
- SetAttribute(kOffset, strings::to_string(featureOffset));
+ SetAttribute(kIndex, strings::to_string(index));
}
time_t XMLFeature::GetUploadTime() const
diff --git a/editor/xml_feature.hpp b/editor/xml_feature.hpp
index 3821be7b5e..f2d59784e6 100644
--- a/editor/xml_feature.hpp
+++ b/editor/xml_feature.hpp
@@ -49,6 +49,9 @@ public:
Type GetType() const;
+ /// @returns true only if it is a way and it is closed (area).
+ bool IsArea() const;
+
ms::LatLon GetCenter() const;
void SetCenter(m2::PointD const & mercatorCenter);
@@ -84,8 +87,8 @@ public:
/// @name XML storage format helpers.
//@{
- uint32_t GetOffset() const;
- void SetOffset(uint32_t featureOffset);
+ uint32_t GetMWMFeatureIndex() const;
+ void SetMWMFeatureIndex(uint32_t index);
/// @returns my::INVALID_TIME_STAMP if there were no any upload attempt.
time_t GetUploadTime() const;
diff --git a/indexer/feature.cpp b/indexer/feature.cpp
index 326c54fc3e..f712d1215b 100644
--- a/indexer/feature.cpp
+++ b/indexer/feature.cpp
@@ -266,6 +266,11 @@ void FeatureType::ParseMetadata() const
m_bMetadataParsed = true;
}
+StringUtf8Multilang const & FeatureType::GetNames() const
+{
+ return m_params.name;
+}
+
void FeatureType::SetNames(StringUtf8Multilang const & newNames)
{
m_params.name.Clear();
@@ -466,6 +471,14 @@ string FeatureType::GetHouseNumber() const
return m_params.house.Get();
}
+void FeatureType::SetHouseNumber(string const & number)
+{
+ if (number.empty())
+ m_params.house.Clear();
+ else
+ m_params.house.Set(number);
+}
+
bool FeatureType::GetName(int8_t lang, string & name) const
{
if (!HasName())
diff --git a/indexer/feature.hpp b/indexer/feature.hpp
index 3598ea1701..9c1ef8bf83 100644
--- a/indexer/feature.hpp
+++ b/indexer/feature.hpp
@@ -167,6 +167,7 @@ public:
/// @name Editor functions.
//@{
+ StringUtf8Multilang const & GetNames() const;
void SetNames(StringUtf8Multilang const & newNames);
void SetMetadata(feature::Metadata const & newMetadata);
//@}
@@ -261,6 +262,8 @@ public:
friend string DebugPrint(FeatureType const & ft);
string GetHouseNumber() const;
+ /// Needed for Editor, to change house numbers in runtime.
+ void SetHouseNumber(string const & number);
/// @name Get names for feature.
/// @param[out] defaultName corresponds to osm tag "name"
diff --git a/indexer/ftypes_matcher.cpp b/indexer/ftypes_matcher.cpp
index 11e53c4083..a9b9ee2ad3 100644
--- a/indexer/ftypes_matcher.cpp
+++ b/indexer/ftypes_matcher.cpp
@@ -45,6 +45,11 @@ bool BaseChecker::operator() (vector<uint32_t> const & types) const
return false;
}
+bool BaseChecker::HasTypeValue(uint32_t const type) const
+{
+ return find(m_types.begin(), m_types.end(), type) != m_types.end();
+}
+
IsPeakChecker::IsPeakChecker()
{
Classificator const & c = classif();
diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp
index f6a745017c..a8e563e7ad 100644
--- a/indexer/ftypes_matcher.hpp
+++ b/indexer/ftypes_matcher.hpp
@@ -28,6 +28,8 @@ public:
bool operator() (feature::TypesHolder const & types) const;
bool operator() (FeatureType const & ft) const;
bool operator() (vector<uint32_t> const & types) const;
+ // Simple type equality comparison. No magic like in IsMatched.
+ bool HasTypeValue(uint32_t const type) const;
static uint32_t PrepareToMatch(uint32_t type, uint8_t level);
};
diff --git a/indexer/index.cpp b/indexer/index.cpp
index 79f7f63398..3110b8a08a 100644
--- a/indexer/index.cpp
+++ b/indexer/index.cpp
@@ -123,8 +123,11 @@ void Index::FeaturesLoaderGuard::GetFeatureByIndex(uint32_t index, FeatureType &
ASSERT_NOT_EQUAL(osm::Editor::FeatureStatus::Deleted, m_editor.GetFeatureStatus(id, index),
("Deleted feature was cached. Please review your code."));
if (!m_editor.Instance().GetEditedFeature(id, index, ft))
- {
- m_vector.GetByIndex(index, ft);
- ft.SetID(FeatureID(id, index));
- }
+ GetOriginalFeatureByIndex(index, ft);
+}
+
+void Index::FeaturesLoaderGuard::GetOriginalFeatureByIndex(uint32_t index, FeatureType & ft) const
+{
+ m_vector.GetByIndex(index, ft);
+ ft.SetID(FeatureID(m_handle.GetId(), index));
}
diff --git a/indexer/index.hpp b/indexer/index.hpp
index 08b1c9474f..8694055151 100644
--- a/indexer/index.hpp
+++ b/indexer/index.hpp
@@ -284,7 +284,10 @@ public:
inline MwmSet::MwmId const & GetId() const { return m_handle.GetId(); }
string GetCountryFileName() const;
bool IsWorld() const;
+ /// Everyone, except Editor core, should use this method.
void GetFeatureByIndex(uint32_t index, FeatureType & ft) const;
+ /// Editor core only method, to get 'untouched', original version of feature.
+ void GetOriginalFeatureByIndex(uint32_t index, FeatureType & ft) const;
inline FeaturesVector const & GetFeaturesVector() const { return m_vector; }
private:
diff --git a/indexer/osm_editor.cpp b/indexer/osm_editor.cpp
index 3fedbf7844..b24ceb0e28 100644
--- a/indexer/osm_editor.cpp
+++ b/indexer/osm_editor.cpp
@@ -1,18 +1,25 @@
#include "indexer/classificator.hpp"
#include "indexer/feature_decl.hpp"
+#include "indexer/feature_impl.hpp"
#include "indexer/feature_meta.hpp"
+#include "indexer/ftypes_matcher.hpp"
#include "indexer/index.hpp"
#include "indexer/osm_editor.hpp"
#include "platform/platform.hpp"
+#include "editor/changeset_wrapper.hpp"
+#include "editor/osm_auth.hpp"
+#include "editor/server_api.hpp"
#include "editor/xml_feature.hpp"
+#include "coding/internal/file_data.hpp"
+
#include "base/logging.hpp"
#include "base/string_utils.hpp"
-#include "coding/internal/file_data.hpp"
-
+#include "std/chrono.hpp"
+#include "std/future.hpp"
#include "std/tuple.hpp"
#include "std/unordered_map.hpp"
#include "std/unordered_set.hpp"
@@ -30,6 +37,12 @@ constexpr char const * kXmlMwmNode = "mwm";
constexpr char const * kDeleteSection = "delete";
constexpr char const * kModifySection = "modify";
constexpr char const * kCreateSection = "create";
+/// We store edited streets in OSM-compatible way.
+constexpr char const * kAddrStreetTag = "addr:street";
+
+constexpr char const * kUploaded = "Uploaded";
+constexpr char const * kDeletedFromOSMServer = "Deleted from OSM by someone";
+constexpr char const * kNeedsRetry = "Needs Retry";
namespace osm
{
@@ -43,7 +56,7 @@ string GetEditorFilePath() { return GetPlatform().WritablePathForFile(kEditorXML
/// type:string -> description:pair<fields:vector<???>, editName:bool, editAddr:bool>
using EType = feature::Metadata::EType;
-using TEditableFields = set<EType>;
+using TEditableFields = vector<EType>;
struct TypeDescription
{
@@ -56,76 +69,77 @@ struct TypeDescription
TEditableFields const fields;
bool const name;
+ // Address == true implies Street, House Number, Phone, Fax, Opening Hours, Website, EMail, Postcode.
bool const address;
};
static unordered_map<string, TypeDescription> const gEditableTypes = {
- {"aeroway-aerodrome", {{EType::FMD_ELE, EType::FMD_PHONE_NUMBER, EType::FMD_OPERATOR}, false, true}},
- {"aeroway-airport", {{EType::FMD_ELE, EType::FMD_PHONE_NUMBER, EType::FMD_OPERATOR}, false, true}},
- {"amenity-atm", {{}, true, false}},
- {"amenity-bank", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS, EType::FMD_WEBSITE, EType::FMD_OPERATOR}, true, true}},
- {"amenity-bar", {{EType::FMD_OPEN_HOURS}, true, true}},
- {"amenity-bicycle_rental", {{EType::FMD_OPERATOR}, false, true}},
- {"amenity-bureau_de_change", {{EType::FMD_OPEN_HOURS}, true, true}},
- {"amenity-bus_station", {{EType::FMD_OPERATOR}, true, false}},
- {"amenity-cafe", {{EType::FMD_OPEN_HOURS, EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE, EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
- {"amenity-car_rental", {{EType::FMD_OPERATOR}, true, false}},
+ {"aeroway-aerodrome", {{EType::FMD_ELE, EType::FMD_OPERATOR}, false, true}},
+ {"aeroway-airport", {{EType::FMD_ELE, EType::FMD_OPERATOR}, false, true}},
+ {"amenity-atm", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, false}},
+ {"amenity-bank", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-bar", {{EType::FMD_CUISINE, EType::FMD_INTERNET}, true, true}},
+ {"amenity-bicycle_rental", {{EType::FMD_OPERATOR}, true, false}},
+ {"amenity-bureau_de_change", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-bus_station", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-cafe", {{EType::FMD_CUISINE, EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"amenity-car_rental", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
{"amenity-car_sharing", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, false}},
- {"amenity-casino", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_OPEN_HOURS}, true, false}},
- {"amenity-cinema", {{EType::FMD_OPERATOR, EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE}, true, true}},
- {"amenity-college", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_WEBSITE}, true, true}},
- {"amenity-doctors", {{}, true, true}},
- {"amenity-drinking_water", {{EType::FMD_OPERATOR}, true, false}},
- {"amenity-embassy", {{EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE}, true, false}},
- {"amenity-fast_food", {{EType::FMD_OPERATOR, EType::FMD_CUISINE}, true, false}},
- {"amenity-ferry_terminal", {{EType::FMD_OPERATOR}, true, false}},
- {"amenity-fire_station", {{}, true, false}},
+ {"amenity-casino", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"amenity-cinema", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-college", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-doctors", {{EType::FMD_INTERNET}, true, true}},
+ {"amenity-drinking_water", {{}, true, false}},
+ {"amenity-embassy", {{}, true, true}},
+ {"amenity-fast_food", {{EType::FMD_OPERATOR, EType::FMD_CUISINE}, true, true}},
+ {"amenity-ferry_terminal", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-fire_station", {{}, true, true}},
{"amenity-fountain", {{}, true, false}},
- {"amenity-fuel", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS, EType::FMD_PHONE_NUMBER, EType::FMD_HEIGHT /*maxheight?*/, EType::FMD_WEBSITE}, true, true }},
+ {"amenity-fuel", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
{"amenity-grave_yard", {{}, true, false}},
- {"amenity-hospital", {{EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER}, true, true}},
- {"amenity-hunting_stand", {{EType::FMD_HEIGHT}, false, false}},
- {"amenity-kindergarten", {{EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER, EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, true}},
- {"amenity-library", {{EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE, EType::FMD_FAX_NUMBER, EType::FMD_FAX_NUMBER, EType::FMD_EMAIL}, true, true}},
- {"amenity-marketplace", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"amenity-nightclub", {{EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER, EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR, EType::FMD_POSTCODE}, true, true}},
- {"amenity-parking", {{EType::FMD_OPERATOR}, true, false}},
- {"amenity-pharmacy", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, true}},
- {"amenity-place_of_worship", {{EType::FMD_OPEN_HOURS, EType::FMD_WEBSITE}, true, false}},
- {"amenity-police", {{EType::FMD_OPERATOR, EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE, EType::FMD_OPEN_HOURS, EType::FMD_POSTCODE}, true, true}},
- {"amenity-post_box", {{EType::FMD_OPERATOR}, true, false}},
- {"amenity-post_office", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER}, true, true}},
- {"amenity-pub", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS, EType::FMD_CUISINE, EType::FMD_PHONE_NUMBER, EType::FMD_EMAIL, EType::FMD_WEBSITE, EType::FMD_FAX_NUMBER}, true, true}},
- {"amenity-recycling", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, false}},
- {"amenity-restaurant", {{EType::FMD_OPERATOR, EType::FMD_CUISINE, EType::FMD_OPEN_HOURS, EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE}, true, true}},
- {"amenity-school", {{EType::FMD_OPERATOR, EType::FMD_WIKIPEDIA}, true, true}},
- {"amenity-taxi", {{EType::FMD_OPERATOR, EType::FMD_PHONE_NUMBER}, true, false}},
+ {"amenity-hospital", {{}, true, true}},
+ {"amenity-hunting_stand", {{EType::FMD_HEIGHT}, true, false}},
+ {"amenity-kindergarten", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-library", {{EType::FMD_INTERNET}, true, true}},
+ {"amenity-marketplace", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-nightclub", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"amenity-parking", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-pharmacy", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-place_of_worship", {{}, true, true}},
+ {"amenity-police", {{}, true, true}},
+ {"amenity-post_box", {{EType::FMD_OPERATOR, EType::FMD_POSTCODE}, true, false}},
+ {"amenity-post_office", {{EType::FMD_OPERATOR, EType::FMD_POSTCODE, EType::FMD_INTERNET}, true, true}},
+ {"amenity-pub", {{EType::FMD_OPERATOR, EType::FMD_CUISINE, EType::FMD_INTERNET}, true, true}},
+ {"amenity-recycling", {{EType::FMD_OPERATOR}, true, false}},
+ {"amenity-restaurant", {{EType::FMD_OPERATOR, EType::FMD_CUISINE, EType::FMD_INTERNET}, true, true}},
+ {"amenity-school", {{EType::FMD_OPERATOR}, true, true}},
+ {"amenity-taxi", {{EType::FMD_OPERATOR}, true, false}},
{"amenity-telephone", {{EType::FMD_OPERATOR, EType::FMD_PHONE_NUMBER}, false, false}},
- {"amenity-theatre", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER, EType::FMD_POSTCODE}, true, true}},
- {"amenity-toilets", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
- {"amenity-townhall", {{EType::FMD_OPERATOR}, true, true}},
- {"amenity-university", {{EType::FMD_OPERATOR, EType::FMD_PHONE_NUMBER, EType::FMD_WEBSITE, EType::FMD_FAX_NUMBER, EType::FMD_EMAIL, }, true, true}},
+ {"amenity-theatre", {{}, true, true}},
+ {"amenity-toilets", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
+ {"amenity-townhall", {{}, true, true}},
+ {"amenity-university", {{}, true, true}},
{"amenity-waste_disposal", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, false, false}},
{"highway-bus_stop", {{EType::FMD_OPERATOR}, true, false}},
- {"historic-archaeological_site", {{}, true, false}},
+ {"historic-archaeological_site", {{EType::FMD_WIKIPEDIA}, true, false}},
{"historic-castle", {{EType::FMD_WIKIPEDIA}, true, false}},
- {"historic-memorial", {{}, true, false}},
- {"historic-monument", {{}, true, false}},
- {"historic-ruins", {{}, true, false}},
- {"internet-access", {{EType::FMD_INTERNET /*??*/}, false, false}},
- {"internet-access|wlan", {{EType::FMD_INTERNET /*??*/}, false, false}},
- {"landuse-cemetery", {{}, true, false}},
- {"leisure-garden", {{}, true, false}},
- {"leisure-sports_centre", {{}, true, true}},
- {"leisure-stadium", {{EType::FMD_WIKIPEDIA, EType::FMD_WEBSITE, EType::FMD_OPERATOR}, true, true}},
- {"leisure-swimming_pool", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
+ {"historic-memorial", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"historic-monument", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"historic-ruins", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"internet-access", {{EType::FMD_INTERNET}, false, false}},
+ {"internet-access|wlan", {{EType::FMD_INTERNET}, false, false}},
+ {"landuse-cemetery", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"leisure-garden", {{EType::FMD_OPEN_HOURS, EType::FMD_INTERNET}, true, false}},
+ {"leisure-sports_centre", {{EType::FMD_INTERNET}, true, true}},
+ {"leisure-stadium", {{EType::FMD_WIKIPEDIA, EType::FMD_OPERATOR}, true, true}},
+ {"leisure-swimming_pool", {{EType::FMD_OPERATOR}, true, true}},
{"natural-peak", {{EType::FMD_WIKIPEDIA, EType::FMD_ELE}, true, false}},
- {"natural-spring", {{}, true, false}},
- {"natural-waterfall", {{}, true, false}},
- {"office-company", {{}, true, false}},
- {"office-government", {{}, true, false}},
- {"office-lawyer", {{EType::FMD_OPEN_HOURS, EType::FMD_PHONE_NUMBER, EType::FMD_FAX_NUMBER, EType::FMD_WEBSITE, EType::FMD_EMAIL}, true, false}},
- {"office-telecommunication", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
+ {"natural-spring", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"natural-waterfall", {{EType::FMD_WIKIPEDIA}, true, false}},
+ {"office-company", {{}, true, true}},
+ {"office-government", {{}, true, true}},
+ {"office-lawyer", {{}, true, true}},
+ {"office-telecommunication", {{EType::FMD_INTERNET, EType::FMD_OPERATOR}, true, true}},
{"place-farm", {{EType::FMD_WIKIPEDIA}, true, false}},
{"place-hamlet", {{EType::FMD_WIKIPEDIA}, true, false}},
{"place-village", {{EType::FMD_WIKIPEDIA}, true, false}},
@@ -133,50 +147,50 @@ static unordered_map<string, TypeDescription> const gEditableTypes = {
{"railway-station", {{EType::FMD_OPERATOR}, true, false}},
{"railway-subway_entrance", {{}, true, false}},
{"railway-tram_stop", {{EType::FMD_OPERATOR}, true, false}},
- {"shop-alcohol", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-bakery", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-beauty", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-beverages", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-bicycle", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_OPEN_HOURS, EType::FMD_POSTCODE}, true, true}},
- {"shop-books", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
- {"shop-butcher", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-car", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-car_repair", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER, EType::FMD_POSTCODE}, true, true}},
- {"shop-chemist", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-clothes", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-computer", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-confectionery", {{EType::FMD_OPEN_HOURS}, true, false }},
- {"shop-convenience", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-department_store", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, false, false}},
- {"shop-doityourself", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-electronics", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
- {"shop-florist", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-furniture", {{EType::FMD_OPEN_HOURS}, false, false}},
- {"shop-garden_centre", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-gift", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-greengrocer", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-hairdresser", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-hardware", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-jewelry", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-kiosk", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-laundry", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-mall", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, true}},
- {"shop-mobile_phone", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-optician", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-shoes", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-sports", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"shop-supermarket", {{EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
- {"shop-toys", {{EType::FMD_OPEN_HOURS}, true, false}},
- {"tourism-alpine_hut", {{EType::FMD_ELE, EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR}, true, false}},
+ {"shop-alcohol", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-bakery", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-beauty", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-beverages", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-bicycle", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-books", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-butcher", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-car", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-car_repair", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-chemist", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-clothes", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-computer", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-confectionery", {{EType::FMD_INTERNET}, true, true }},
+ {"shop-convenience", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-department_store", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, false, true}},
+ {"shop-doityourself", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-electronics", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-florist", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-furniture", {{EType::FMD_INTERNET}, false, true}},
+ {"shop-garden_centre", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-gift", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-greengrocer", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-hairdresser", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-hardware", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-jewelry", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-kiosk", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-laundry", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-mall", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-mobile_phone", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-optician", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-shoes", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-sports", {{EType::FMD_INTERNET}, true, true}},
+ {"shop-supermarket", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"shop-toys", {{EType::FMD_INTERNET}, true, true}},
+ {"tourism-alpine_hut", {{EType::FMD_ELE, EType::FMD_OPEN_HOURS, EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_INTERNET}, true, false}},
{"tourism-artwork", {{EType::FMD_WEBSITE, EType::FMD_WIKIPEDIA}, true, false}},
- {"tourism-camp_site", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_OPEN_HOURS}, true, false}},
- {"tourism-caravan_site", {{EType::FMD_WEBSITE, EType::FMD_OPERATOR}, true, false}},
- {"tourism-guest_house", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, false}},
- {"tourism-hostel", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE}, true, true}},
- {"tourism-hotel", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_PHONE_NUMBER}, true, true}},
+ {"tourism-camp_site", {{EType::FMD_OPERATOR, EType::FMD_WEBSITE, EType::FMD_OPEN_HOURS, EType::FMD_INTERNET}, true, false}},
+ {"tourism-caravan_site", {{EType::FMD_WEBSITE, EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, false}},
+ {"tourism-guest_house", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"tourism-hostel", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"tourism-hotel", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
{"tourism-information", {{}, true, false}},
- {"tourism-motel", {{EType::FMD_OPERATOR}, true, true}},
- {"tourism-museum", {{EType::FMD_OPERATOR, EType::FMD_OPEN_HOURS}, true, false}},
+ {"tourism-motel", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
+ {"tourism-museum", {{EType::FMD_OPERATOR, EType::FMD_INTERNET}, true, true}},
{"tourism-viewpoint", {{}, true, false}},
{"waterway-waterfall", {{EType::FMD_HEIGHT}, true, false}}};
@@ -189,29 +203,14 @@ TypeDescription const * GetTypeDescription(uint32_t const type)
return nullptr;
}
-template <typename TIterator>
-TEditableFields GetEditableFields(TIterator from, TIterator const to)
+uint32_t MigrateFeatureIndex(XMLFeature const & /*xml*/)
{
- TEditableFields fields;
- while (from != to)
- {
- auto const * desc = GetTypeDescription(*from++);
- if (desc)
- {
- for (auto field : desc->fields)
- fields.insert(field);
- }
- }
-
- return fields;
+ // @TODO(mgsergio): Update feature's index when user has downloaded fresh MWM file and old indices point to other features.
+ // Possible implementation: use function to load features in rect (center feature's point) and somehow compare/choose from them.
+ // Probably we need to store more data about features in xml, e.g. types, may be other data, to match them correctly.
+ return 0;
}
-Editor::TTypes GetAllTypes(FeatureType const & feature)
-{
- Editor::TTypes types;
- feature.ForEachType([&types](uint32_t type) { types.push_back(type); });
- return types;
-}
} // namespace
Editor & Editor::Instance()
@@ -255,7 +254,7 @@ void Editor::LoadMapEdits()
MwmSet::MwmId const id = m_mwmIdByMapNameFn(mapName);
if (!id.IsAlive())
{
- // TODO(AlexZ): Handle case when map was upgraded and edits should migrate to fresh map data.
+ // TODO(AlexZ): MWM file was deleted, but changes remain. What should we do in this case?
LOG(LWARNING, (mapName, "version", mapVersion, "references not existing MWM file."));
continue;
}
@@ -267,23 +266,23 @@ void Editor::LoadMapEdits()
try
{
XMLFeature const xml(nodeOrWay.node());
- FeatureID const fid(id, xml.GetOffset());
- FeatureTypeInfo fti;
-
- /// TODO(mgsergio): uncomment when feature creating will
- /// be required
- // if (xml.GetType() != XMLFeature::Type::Way)
- // {
- // TODO(mgsergio): Check if feature can be read.
- fti.m_feature = *m_featureLoaderFn(fid);
- fti.m_feature.ApplyPatch(xml);
- // }
- // else
- // {
- // fti.m_feature = FeatureType::FromXML(xml);
- // }
+ uint32_t const featureIndex = mapVersion == id.GetInfo()->GetVersion() ? xml.GetMWMFeatureIndex() : MigrateFeatureIndex(xml);
+ FeatureID const fid(id, featureIndex);
+
+ FeatureTypeInfo & fti = m_features[id][fid.m_index];
+
+ if (section.first == FeatureStatus::Created)
+ {
+ // TODO(mgsergio): Create features which are not present in mwm.
+ }
+ else
+ {
+ fti.m_feature = *m_featureLoaderFn(fid);
+ fti.m_feature.ApplyPatch(xml);
+ }
fti.m_feature.SetID(fid);
+ fti.m_street = xml.GetTagValue(kAddrStreetTag);
fti.m_modificationTimestamp = xml.GetModificationTime();
ASSERT_NOT_EQUAL(my::INVALID_TIME_STAMP, fti.m_modificationTimestamp, ());
@@ -291,11 +290,13 @@ void Editor::LoadMapEdits()
fti.m_uploadStatus = xml.GetUploadStatus();
fti.m_uploadError = xml.GetUploadError();
fti.m_status = section.first;
-
- /// Call to m_featureLoaderFn indirectly tries to load feature by
- /// it's ID from the editor's m_features.
- /// That's why insertion into m_features should go AFTER call to m_featureLoaderFn.
- m_features[id][fid.m_index] = fti;
+ switch (section.first)
+ {
+ case FeatureStatus::Deleted: ++deleted; break;
+ case FeatureStatus::Modified: ++modified; break;
+ case FeatureStatus::Created: ++created; break;
+ case FeatureStatus::Untouched: ASSERT(false, ()); break;
+ }
}
catch (editor::XMLFeatureError const & ex)
{
@@ -328,11 +329,13 @@ void Editor::Save(string const & fullFilePath) const
xml_node deleted = mwmNode.append_child(kDeleteSection);
xml_node modified = mwmNode.append_child(kModifySection);
xml_node created = mwmNode.append_child(kCreateSection);
- for (auto const & offset : mwm.second)
+ for (auto const & index : mwm.second)
{
- FeatureTypeInfo const & fti = offset.second;
+ FeatureTypeInfo const & fti = index.second;
XMLFeature xf = fti.m_feature.ToXML();
- xf.SetOffset(offset.first);
+ xf.SetMWMFeatureIndex(index.first);
+ if (!fti.m_street.empty())
+ xf.SetTagValue(kAddrStreetTag, fti.m_street);
ASSERT_NOT_EQUAL(0, fti.m_modificationTimestamp, ());
xf.SetModificationTime(fti.m_modificationTimestamp);
if (fti.m_uploadAttemptTimestamp != my::INVALID_TIME_STAMP)
@@ -363,7 +366,7 @@ void Editor::Save(string const & fullFilePath) const
}
}
-Editor::FeatureStatus Editor::GetFeatureStatus(MwmSet::MwmId const & mwmId, uint32_t offset) const
+Editor::FeatureStatus Editor::GetFeatureStatus(MwmSet::MwmId const & mwmId, uint32_t index) const
{
// Most popular case optimization.
if (m_features.empty())
@@ -373,11 +376,11 @@ Editor::FeatureStatus Editor::GetFeatureStatus(MwmSet::MwmId const & mwmId, uint
if (mwmMatched == m_features.end())
return FeatureStatus::Untouched;
- auto const offsetMatched = mwmMatched->second.find(offset);
- if (offsetMatched == mwmMatched->second.end())
+ auto const matchedIndex = mwmMatched->second.find(index);
+ if (matchedIndex == mwmMatched->second.end())
return FeatureStatus::Untouched;
- return offsetMatched->second.m_status;
+ return matchedIndex->second.m_status;
}
void Editor::DeleteFeature(FeatureType const & feature)
@@ -401,20 +404,26 @@ void Editor::DeleteFeature(FeatureType const & feature)
//FeatureID GenerateNewFeatureId(FeatureID const & oldFeatureId)
//{
// // TODO(AlexZ): Stable & unique features ID generation.
-// static uint32_t newOffset = 0x0effffff;
-// return FeatureID(oldFeatureId.m_mwmId, newOffset++);
+// static uint32_t newIndex = 0x0effffff;
+// return FeatureID(oldFeatureId.m_mwmId, newIndex++);
//}
//} // namespace
-void Editor::EditFeature(FeatureType & editedFeature)
+void Editor::EditFeature(FeatureType const & editedFeature, string const & editedStreet,
+ string const & editedHouseNumber)
{
// TODO(AlexZ): Check if feature has not changed and reset status.
FeatureID const fid = editedFeature.GetID();
- FeatureTypeInfo & ftInfo = m_features[fid.m_mwmId][fid.m_index];
- ftInfo.m_status = FeatureStatus::Modified;
- ftInfo.m_feature = editedFeature;
+ FeatureTypeInfo & fti = m_features[fid.m_mwmId][fid.m_index];
+ fti.m_status = FeatureStatus::Modified;
+ fti.m_feature = editedFeature;
// TODO: What if local client time is absolutely wrong?
- ftInfo.m_modificationTimestamp = time(nullptr);
+ fti.m_modificationTimestamp = time(nullptr);
+
+ fti.m_street = editedStreet;
+ if (editedHouseNumber.empty() || feature::IsHouseNumber(editedHouseNumber))
+ fti.m_feature.SetHouseNumber(editedHouseNumber);
+ // TODO(AlexZ): Store edited house number as house name if feature::IsHouseNumber() returned false.
// TODO(AlexZ): Synchronize Save call/make it on a separate thread.
Save(GetEditorFilePath());
@@ -434,12 +443,12 @@ void Editor::ForEachFeatureInMwmRectAndScale(MwmSet::MwmId const & id,
// TODO(AlexZ): Check that features are visible at this scale.
// Process only new (created) features.
- for (auto const & offset : mwmFound->second)
+ for (auto const & index : mwmFound->second)
{
- FeatureTypeInfo const & ftInfo = offset.second;
+ FeatureTypeInfo const & ftInfo = index.second;
if (ftInfo.m_status == FeatureStatus::Created &&
rect.IsPointInside(ftInfo.m_feature.GetCenter()))
- f(FeatureID(id, offset.first));
+ f(FeatureID(id, index.first));
}
}
@@ -454,60 +463,160 @@ void Editor::ForEachFeatureInMwmRectAndScale(MwmSet::MwmId const & id,
// TODO(AlexZ): Check that features are visible at this scale.
// Process only new (created) features.
- for (auto & offset : mwmFound->second)
+ for (auto & index : mwmFound->second)
{
- FeatureTypeInfo & ftInfo = offset.second;
+ FeatureTypeInfo & ftInfo = index.second;
if (ftInfo.m_status == FeatureStatus::Created &&
rect.IsPointInside(ftInfo.m_feature.GetCenter()))
f(ftInfo.m_feature);
}
}
-bool Editor::GetEditedFeature(MwmSet::MwmId const & mwmId, uint32_t offset, FeatureType & outFeature) const
+bool Editor::GetEditedFeature(MwmSet::MwmId const & mwmId, uint32_t index, FeatureType & outFeature) const
{
auto const mwmMatched = m_features.find(mwmId);
if (mwmMatched == m_features.end())
return false;
- auto const offsetMatched = mwmMatched->second.find(offset);
- if (offsetMatched == mwmMatched->second.end())
+ auto const matchedIndex = mwmMatched->second.find(index);
+ if (matchedIndex == mwmMatched->second.end())
return false;
// TODO(AlexZ): Should we process deleted/created features as well?
- outFeature = offsetMatched->second.m_feature;
+ outFeature = matchedIndex->second.m_feature;
return true;
}
vector<Metadata::EType> Editor::EditableMetadataForType(FeatureType const & feature) const
{
// TODO(mgsergio): Load editable fields into memory from XML and query them here.
- auto const types = GetAllTypes(feature);
-
- auto const fields = GetEditableFields(begin(types), end(types));
+ feature::TypesHolder const types(feature);
+ set<Metadata::EType> fields;
+ auto const & isBuilding = ftypes::IsBuildingChecker::Instance();
+ for (auto type : types)
+ {
+ auto const * desc = GetTypeDescription(type);
+ if (desc)
+ {
+ for (auto field : desc->fields)
+ fields.insert(field);
+ // If address is editable, many metadata fields are editable too.
+ if (desc->address)
+ {
+ fields.insert(EType::FMD_EMAIL);
+ fields.insert(EType::FMD_OPEN_HOURS);
+ fields.insert(EType::FMD_PHONE_NUMBER);
+ fields.insert(EType::FMD_WEBSITE);
+ }
+ }
+ else if (isBuilding.HasTypeValue(type))
+ {
+ // Post boxes and post offices have editable postcode field defined separately.
+ fields.insert(EType::FMD_POSTCODE);
+ }
+ }
return {begin(fields), end(fields)};
}
bool Editor::IsNameEditable(FeatureType const & feature) const
{
- for (auto type : GetAllTypes(feature))
+ feature::TypesHolder const types(feature);
+ for (auto type : types)
{
auto const * typeDesc = GetTypeDescription(type);
if (typeDesc && typeDesc->name)
return true;
}
-
return false;
}
bool Editor::IsAddressEditable(FeatureType const & feature) const
{
- for (auto type : GetAllTypes(feature))
+ feature::TypesHolder const types(feature);
+ auto & isBuilding = ftypes::IsBuildingChecker::Instance();
+ for (auto type : types)
{
+ // Building addresses are always editable.
+ if (isBuilding.HasTypeValue(type))
+ return true;
auto const * typeDesc = GetTypeDescription(type);
if (typeDesc && typeDesc->address)
return true;
}
-
return false;
}
+
+void Editor::UploadChanges(string const & key, string const & secret, TChangesetTags const & tags)
+{
+ // TODO(AlexZ): features access should be synchronized.
+ auto const lambda = [this](string key, string secret, TChangesetTags tags)
+ {
+ int uploadedFeaturesCount = 0;
+ // TODO(AlexZ): insert usefull changeset comments.
+ ChangesetWrapper changeset({key, secret}, tags);
+ for (auto & id : m_features)
+ {
+ for (auto & index : id.second)
+ {
+ FeatureTypeInfo & fti = index.second;
+ // Do not process already uploaded features or those failed permanently.
+ if (!(fti.m_uploadStatus.empty() || fti.m_uploadStatus == kNeedsRetry))
+ continue;
+
+ // TODO(AlexZ): Create/delete nodes support.
+ if (fti.m_status != FeatureStatus::Modified)
+ continue;
+
+ XMLFeature feature = fti.m_feature.ToXML();
+ // TODO(AlexZ): Add areas(ways) upload support.
+ if (feature.GetType() != XMLFeature::Type::Node)
+ continue;
+
+ try
+ {
+ XMLFeature osmFeature = changeset.GetMatchingFeatureFromOSM(feature, fti.m_feature);
+ XMLFeature const osmFeatureCopy = osmFeature;
+ osmFeature.ApplyPatch(feature);
+ // Check to avoid duplicates.
+ if (osmFeature == osmFeatureCopy)
+ {
+ LOG(LWARNING, ("Local changes are equal to OSM, feature was not uploaded, local changes were deleted.", feature));
+ // TODO(AlexZ): Delete local change.
+ continue;
+ }
+ LOG(LDEBUG, ("Uploading patched feature", osmFeature));
+ changeset.ModifyNode(osmFeature);
+ fti.m_uploadStatus = kUploaded;
+ fti.m_uploadAttemptTimestamp = time(nullptr);
+ ++uploadedFeaturesCount;
+ }
+ catch (ChangesetWrapper::OsmObjectWasDeletedException const & ex)
+ {
+ fti.m_uploadStatus = kDeletedFromOSMServer;
+ fti.m_uploadAttemptTimestamp = time(nullptr);
+ fti.m_uploadError = "Node was deleted from the server.";
+ LOG(LWARNING, (fti.m_uploadError, ex.what()));
+ }
+ catch (RootException const & ex)
+ {
+ LOG(LWARNING, (ex.what()));
+ fti.m_uploadStatus = kNeedsRetry;
+ fti.m_uploadAttemptTimestamp = time(nullptr);
+ fti.m_uploadError = ex.what();
+ }
+ // TODO(AlexZ): Synchronize save after edits.
+ // Call Save every time we modify each feature's information.
+ Save(GetEditorFilePath());
+ }
+ }
+ // TODO(AlexZ): Should we call any callback at the end?
+ };
+
+ // Do not run more than one upload thread at a time.
+ static auto future = async(launch::async, lambda, key, secret, tags);
+ auto const status = future.wait_for(milliseconds(0));
+ if (status == future_status::ready)
+ future = async(launch::async, lambda, key, secret, tags);
+}
+
} // namespace osm
diff --git a/indexer/osm_editor.hpp b/indexer/osm_editor.hpp
index c522900b7e..671a7ce987 100644
--- a/indexer/osm_editor.hpp
+++ b/indexer/osm_editor.hpp
@@ -38,8 +38,6 @@ public:
Created
};
- using TTypes = vector<uint32_t>;
-
static Editor & Instance();
void SetMwmIdByNameAndVersionFn(TMwmIdByMapNameFn const & fn) { m_mwmIdByMapNameFn = fn; }
@@ -60,22 +58,32 @@ public:
uint32_t scale);
/// Easy way to check if feature was deleted, modified, created or not changed at all.
- FeatureStatus GetFeatureStatus(MwmSet::MwmId const & mwmId, uint32_t offset) const;
+ FeatureStatus GetFeatureStatus(MwmSet::MwmId const & mwmId, uint32_t index) const;
/// Marks feature as "deleted" from MwM file.
void DeleteFeature(FeatureType const & feature);
/// @returns false if feature wasn't edited.
/// @param outFeature is valid only if true was returned.
- bool GetEditedFeature(MwmSet::MwmId const & mwmId, uint32_t offset, FeatureType & outFeature) const;
+ bool GetEditedFeature(MwmSet::MwmId const & mwmId, uint32_t index, FeatureType & outFeature) const;
/// Original feature with same FeatureID as newFeature is replaced by newFeature.
- void EditFeature(FeatureType & editedFeature);
+ /// Please pass editedStreet only if it was changed by user.
+ void EditFeature(FeatureType const & editedFeature,
+ string const & editedStreet = "",
+ string const & editedHouseNumber = "");
vector<feature::Metadata::EType> EditableMetadataForType(FeatureType const & feature) const;
+ /// @returns true if feature's name is editable.
bool IsNameEditable(FeatureType const & feature) const;
+ /// @returns true if street and house number are editable.
bool IsAddressEditable(FeatureType const & feature) const;
+ using TChangesetTags = map<string, string>;
+ /// Tries to upload all local changes to OSM server in a separate thread.
+ /// @param[in] tags should provide additional information about client to use in changeset.
+ void UploadChanges(string const & key, string const & secret, TChangesetTags const & tags);
+
private:
// TODO(AlexZ): Synchronize Save call/make it on a separate thread.
void Save(string const & fullFilePath) const;
@@ -84,9 +92,11 @@ private:
{
FeatureStatus m_status;
FeatureType m_feature;
+ /// If not empty contains Feature's addr:street, edited by user.
+ string m_street;
time_t m_modificationTimestamp = my::INVALID_TIME_STAMP;
time_t m_uploadAttemptTimestamp = my::INVALID_TIME_STAMP;
- /// "" | "ok" | "repeat" | "failed"
+ /// Is empty if upload has never occured or one of k* constants above otherwise.
string m_uploadStatus;
string m_uploadError;
};
diff --git a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm
index 531ea2b19b..e17c589323 100644
--- a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm
+++ b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm
@@ -136,8 +136,7 @@ using namespace osm;
{
string const username = self.loginTextField.text.UTF8String;
string const password = self.passwordTextField.text.UTF8String;
- // TODO(AlexZ): Change to production.
- OsmOAuth auth = OsmOAuth::IZServerAuth();
+ OsmOAuth auth = OsmOAuth::ServerAuth();
OsmOAuth::AuthResult const result = auth.AuthorizePassword(username, password);
dispatch_async(dispatch_get_main_queue(), ^
{
diff --git a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationWebViewLoginViewController.mm b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationWebViewLoginViewController.mm
index 8b2d1f6713..55096320cb 100644
--- a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationWebViewLoginViewController.mm
+++ b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationWebViewLoginViewController.mm
@@ -71,7 +71,7 @@ NSString * getVerifier(NSString * urlString)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
{
// TODO(AlexZ): Change to production.
- OsmOAuth auth = OsmOAuth::IZServerAuth();
+ OsmOAuth const auth = OsmOAuth::ServerAuth();
OsmOAuth::TUrlKeySecret urlKey;
switch (self.authType)
{
@@ -119,7 +119,7 @@ NSString * getVerifier(NSString * urlString)
{
TKeySecret outKeySecret;
// TODO(AlexZ): Change to production.
- OsmOAuth auth = OsmOAuth::IZServerAuth();
+ OsmOAuth const auth = OsmOAuth::ServerAuth();
OsmOAuth::AuthResult const result = auth.FinishAuthorization(self->m_keySecret, verifier.UTF8String, outKeySecret);
dispatch_async(dispatch_get_main_queue(), ^
{
diff --git a/iphone/Maps/Classes/MWMPlacePageEntity.mm b/iphone/Maps/Classes/MWMPlacePageEntity.mm
index c13b556c63..4b7b5ef579 100644
--- a/iphone/Maps/Classes/MWMPlacePageEntity.mm
+++ b/iphone/Maps/Classes/MWMPlacePageEntity.mm
@@ -185,9 +185,10 @@ void initFieldsMap()
self.title = name.length > 0 ? name : L(@"dropped_pin");
self.category = @(info.GetPinType().c_str());
- auto const presentTypes = metadata.GetPresentTypes();
+ if (!info.m_house.empty())
+ [self addMetaField:MWMPlacePageCellTypeBuilding value:info.m_house];
- for (auto const & type : presentTypes)
+ for (auto const type : metadata.GetPresentTypes())
{
switch (type)
{
@@ -281,11 +282,19 @@ void initFieldsMap()
- (void)processStreets
{
- // TODO Replace with real Getters
- // FeatureType * feature = self.delegate.userMark->GetFeature();
- string featureString = "street#2";
- self.nearbyStreets = @[@"street#1", @(featureString.c_str()), @"street#3"];
- [self addMetaField:MWMPlacePageCellTypeStreet value:featureString];
+ FeatureType const * feature = self.delegate.userMark->GetFeature();
+ if (!feature)
+ return;
+
+ Framework & frm = GetFramework();
+ auto const streets = frm.GetNearbyFeatureStreets(*feature);
+ NSMutableArray * arr = [[NSMutableArray alloc] initWithCapacity:streets.size()];
+ for (auto const & street : streets)
+ [arr addObject:@(street.c_str())];
+ self.nearbyStreets = arr;
+
+ auto const info = frm.GetFeatureAddressInfo(*feature);
+ [self addMetaField:MWMPlacePageCellTypeStreet value:info.m_street];
}
#pragma mark - Editing
@@ -295,9 +304,20 @@ void initFieldsMap()
FeatureType * feature = self.delegate.userMark->GetFeature();
if (!feature)
return;
- vector<Metadata::EType> const editableTypes = osm::Editor::Instance().EditableMetadataForType(*feature);
- if (!editableTypes.empty())
+
+ auto & editor = osm::Editor::Instance();
+ vector<Metadata::EType> const editableTypes = editor.EditableMetadataForType(*feature);
+ bool const isNameEditable = editor.IsNameEditable(*feature);
+ bool const isAddressEditable = editor.IsAddressEditable(*feature);
+ if (!editableTypes.empty() || isAddressEditable || isNameEditable)
[self addEditField];
+ if (isNameEditable)
+ m_editableFields.insert(MWMPlacePageCellTypeName);
+ if (isAddressEditable)
+ {
+ m_editableFields.insert(MWMPlacePageCellTypeStreet);
+ m_editableFields.insert(MWMPlacePageCellTypeBuilding);
+ }
for (auto const & type : editableTypes)
{
NSAssert(kMetaFieldsMap[type] >= Metadata::FMD_COUNT || kMetaFieldsMap[type] == 0, @"Incorrect enum value");
@@ -317,7 +337,9 @@ void initFieldsMap()
NSAssert(feature != nullptr, @"Feature is null");
if (!feature)
return;
+
auto & metadata = feature->GetMetadata();
+ string streetName, houseNumber;
for (auto const & cell : cells)
{
switch (cell.first)
@@ -343,18 +365,20 @@ void initFieldsMap()
}
case MWMPlacePageCellTypeName:
{
- // TODO Add implementation
+ // TODO(AlexZ): Make sure that we display and save name in the same language (default?).
+ auto names = feature->GetNames();
+ names.AddString(StringUtf8Multilang::DEFAULT_CODE, cell.second);
+ feature->SetNames(names);
break;
}
case MWMPlacePageCellTypeStreet:
{
- // TODO Add implementation
- // Save cell.second
+ streetName = cell.second;
break;
}
case MWMPlacePageCellTypeBuilding:
{
- // TODO Add implementation
+ houseNumber = cell.second;
break;
}
default:
@@ -362,8 +386,7 @@ void initFieldsMap()
break;
}
}
- feature->SetMetadata(metadata);
- osm::Editor::Instance().EditFeature(*feature);
+ osm::Editor::Instance().EditFeature(*feature, streetName, houseNumber);
}
#pragma mark - Getters
diff --git a/iphone/Maps/Classes/MWMPlacePageViewManager.mm b/iphone/Maps/Classes/MWMPlacePageViewManager.mm
index e3bdbf6591..df5ee26ea6 100644
--- a/iphone/Maps/Classes/MWMPlacePageViewManager.mm
+++ b/iphone/Maps/Classes/MWMPlacePageViewManager.mm
@@ -295,13 +295,17 @@ typedef NS_ENUM(NSUInteger, MWMPlacePageManagerState)
Framework & f = GetFramework();
BookmarkData data = BookmarkData(self.entity.title.UTF8String, f.LastEditedBMType());
size_t const categoryIndex = f.LastEditedBMCategory();
- size_t const bookmarkIndex = f.GetBookmarkManager().AddBookmark(categoryIndex, m_userMark->GetUserMark()->GetPivot(), data);
+ m2::PointD const mercator = m_userMark->GetUserMark()->GetPivot();
+ size_t const bookmarkIndex = f.GetBookmarkManager().AddBookmark(categoryIndex, mercator, data);
self.entity.bac = make_pair(categoryIndex, bookmarkIndex);
self.entity.type = MWMPlacePageEntityTypeBookmark;
BookmarkCategory::Guard guard(*f.GetBmCategory(categoryIndex));
UserMark const * bookmark = guard.m_controller.GetUserMark(bookmarkIndex);
+ // TODO(AlexZ): Refactor bookmarks code together to hide this code in the Framework/Drape.
+ // UI code should never know about any guards, pointers to UserMark etc.
+ const_cast<UserMark *>(bookmark)->SetFeature(f.GetFeatureAtMercatorPoint(mercator));
m_userMark.reset(new UserMarkCopy(bookmark, false));
[NSNotificationCenter.defaultCenter postNotificationName:kBookmarksChangedNotification
object:nil
@@ -324,6 +328,8 @@ typedef NS_ENUM(NSUInteger, MWMPlacePageManagerState)
self.entity.type = MWMPlacePageEntityTypeRegular;
+ // TODO(AlexZ): SetFeature is called in GetAddressMark here.
+ // UI code should never know about any guards, pointers to UserMark etc.
PoiMarkPoint const * poi = f.GetAddressMark(bookmark->GetPivot());
m_userMark.reset(new UserMarkCopy(poi, false));
if (bookmarkCategory)
diff --git a/map/address_finder.cpp b/map/address_finder.cpp
index a27f5fc088..353385e788 100644
--- a/map/address_finder.cpp
+++ b/map/address_finder.cpp
@@ -12,7 +12,7 @@
#include "platform/preferred_languages.hpp"
-
+/*
namespace
{
class FeatureInfoT
@@ -180,7 +180,6 @@ void Framework::GetFeatureTypes(m2::PointD const & pxPoint, vector<string> & typ
getTypes.GetFeatureTypes(5, types);
}
-/*
namespace
{
class DoGetAddressBase : public DoGetFeatureInfoBase
@@ -479,14 +478,51 @@ search::AddressInfo Framework::GetFeatureAddressInfo(FeatureType const & ft) con
//GetLocality(pt, info);
info.m_house = ft.GetHouseNumber();
+ // TODO(vng): Now geocoder assumes that buildings without house numbers also do not have a specified street.
+ if (!info.m_house.empty())
+ {
+ // TODO(vng): Return feature's street only if it was specified in OSM data.
+ search::ReverseGeocoder const coder(m_model.GetIndex());
+ vector<search::ReverseGeocoder::Street> const streets = coder.GetNearbyFeatureStreets(ft);
+ if (!streets.empty())
+ info.m_street = streets.front().m_name;
+ }
+
+ // TODO(vng): Why AddressInfo is responsible for types and names?
+ string defaultName, intName;
+ ft.GetPreferredNames(defaultName, intName);
+ info.m_name = defaultName.empty() ? intName : defaultName;
+ info.m_types = GetPrintableFeatureTypes(ft);
- search::ReverseGeocoder const coder(m_model.GetIndex());
- vector<search::ReverseGeocoder::Street> const streets = coder.GetNearbyFeatureStreets(ft);
- if (!streets.empty())
- info.m_street = streets.front().m_name;
return info;
}
+vector<string> Framework::GetPrintableFeatureTypes(FeatureType const & ft) const
+{
+ ASSERT(m_searchEngine, ());
+
+ vector<string> results;
+ int8_t const locale = CategoriesHolder::MapLocaleToInteger(languages::GetCurrentOrig());
+
+ feature::TypesHolder types(ft);
+ types.SortBySpec();
+ // Try to add types from categories.
+ for (uint32_t type : types)
+ {
+ string s;
+ if (m_searchEngine->GetNameByType(type, locale, s))
+ results.push_back(s);
+ }
+ // If nothing added - return raw classificator types.
+ if (results.empty())
+ {
+ Classificator const & c = classif();
+ for (uint32_t type : types)
+ results.push_back(c.GetReadableObjectName(type));
+ }
+ return results;
+}
+
vector<string> Framework::GetNearbyFeatureStreets(FeatureType const & ft) const
{
search::ReverseGeocoder const coder(m_model.GetIndex());
diff --git a/map/framework.cpp b/map/framework.cpp
index 910161e17b..4f7d6cb49a 100644
--- a/map/framework.cpp
+++ b/map/framework.cpp
@@ -322,7 +322,11 @@ Framework::Framework()
editor.SetInvalidateFn([this](){ InvalidateRect(GetCurrentViewport()); });
editor.SetFeatureLoaderFn([this](FeatureID const & fid) -> unique_ptr<FeatureType>
{
- return GetPOIByID(fid);
+ unique_ptr<FeatureType> feature(new FeatureType());
+ Index::FeaturesLoaderGuard const guard(m_model.GetIndex(), fid.m_mwmId);
+ guard.GetOriginalFeatureByIndex(fid.m_index, *feature);
+ feature->ParseEverything();
+ return feature;
});
editor.LoadMapEdits();
}
@@ -1119,12 +1123,7 @@ void Framework::ShowSearchResult(search::Result const & res)
{
case Result::RESULT_FEATURE:
{
- FeatureID const id = res.GetFeatureID();
- Index::FeaturesLoaderGuard guard(m_model.GetIndex(), id.m_mwmId);
-
- ft.reset(new FeatureType);
- guard.GetFeatureByIndex(id.m_index, *ft);
-
+ ft = GetFeatureByID(res.GetFeatureID());
scale = GetFeatureViewportScale(TypesHolder(*ft));
center = GetCenter(*ft, scale);
break;
@@ -1137,6 +1136,7 @@ void Framework::ShowSearchResult(search::Result const & res)
break;
default:
+ ASSERT(false, ("Suggests should not be here."));
return;
}
@@ -1632,7 +1632,7 @@ unique_ptr<FeatureType> Framework::GetFeatureAtMercatorPoint(m2::PointD const &
return pointFt ? move(pointFt) : move(areaFt);
}
-unique_ptr<FeatureType> Framework::GetPOIByID(FeatureID const & fid) const
+unique_ptr<FeatureType> Framework::GetFeatureByID(FeatureID const & fid) const
{
ASSERT(fid.IsValid(), ());
@@ -1690,18 +1690,6 @@ public:
}
-void Framework::FindClosestPOIMetadata(m2::PointD const & pt, feature::Metadata & metadata) const
-{
- m2::RectD rect(pt, pt);
- double const inf = MercatorBounds::GetCellID2PointAbsEpsilon();
- rect.Inflate(inf, inf);
-
- DoFindClosestPOI doFind(pt, 1.1 /* search radius in meters */);
- m_model.ForEachFeature(rect, doFind, scales::GetUpperScale() /* scale level for POI */);
-
- doFind.LoadMetadata(m_model, metadata);
-}
-
BookmarkAndCategory Framework::FindBookmark(UserMark const * mark) const
{
BookmarkAndCategory empty = MakeEmptyBookmarkAndCategory();
@@ -1854,7 +1842,7 @@ UserMark const * Framework::OnTapEventImpl(m2::PointD pxPoint, bool isLong, bool
if (fid.IsValid())
{
- feature = GetPOIByID(fid);
+ feature = GetFeatureByID(fid);
mercatorPivot = feature::GetCenter(*feature);
needMark = true;
}
diff --git a/map/framework.hpp b/map/framework.hpp
index 4172daad10..4cdb132bed 100644
--- a/map/framework.hpp
+++ b/map/framework.hpp
@@ -461,19 +461,16 @@ public:
/// Set correct viewport, parse API, show balloon.
bool ShowMapForURL(string const & url);
- /// Get classificator types for nearest features.
- /// @param[in] pxPoint Current touch point in device pixel coordinates.
- void GetFeatureTypes(m2::PointD const & pxPoint, vector<string> & types) const;
- //@}
-
private:
// TODO(vng): Uncomment when needed.
//void GetLocality(m2::PointD const & pt, search::AddressInfo & info) const;
public:
- /// Please use this method for debug purposes only. It always tries to find closest street.
+ /// @returns address of nearby building with house number in approx 1km distance.
search::AddressInfo GetMercatorAddressInfo(m2::PointD const & mercator) const;
+ /// @returns address only if it was specified for given feature; used in the editor.
search::AddressInfo GetFeatureAddressInfo(FeatureType const & ft) const;
+ vector<string> GetPrintableFeatureTypes(FeatureType const & ft) const;
/// If feature does not have explicit street in OSM data, first value can be a closest named street.
/// If it does have explicit street name in OSM, it goes first in the returned vector.
/// @returns empty vector if no named streets were found around feature.
@@ -483,8 +480,7 @@ public:
/// @returns nullptr if no feature was found at the given mercator point.
unique_ptr<FeatureType> GetFeatureAtMercatorPoint(m2::PointD const & mercator) const;
// TODO(AlexZ): Do we really need to avoid linear features?
- unique_ptr<FeatureType> GetPOIByID(FeatureID const & fid) const;
- void FindClosestPOIMetadata(m2::PointD const & pt, feature::Metadata & metadata) const;
+ unique_ptr<FeatureType> GetFeatureByID(FeatureID const & fid) const;
void MemoryWarning();
void EnterBackground();
diff --git a/map/map_tests/bookmarks_test.cpp b/map/map_tests/bookmarks_test.cpp
index 79b813c8fa..c52c5db7f3 100644
--- a/map/map_tests/bookmarks_test.cpp
+++ b/map/map_tests/bookmarks_test.cpp
@@ -396,11 +396,12 @@ namespace
{
search::AddressInfo const info = fm.GetMercatorAddressInfo(MercatorBounds::FromLatLon(lat, lon));
- TEST_EQUAL(info.m_name, poi.m_name, ());
TEST_EQUAL(info.m_street, poi.m_street, ());
TEST_EQUAL(info.m_house, poi.m_house, ());
- TEST_EQUAL(info.m_types.size(), 1, ());
- TEST_EQUAL(info.GetBestType(), poi.m_type, ());
+ // TODO(AlexZ): AddressInfo should contain addresses only. Refactor.
+ //TEST_EQUAL(info.m_name, poi.m_name, ());
+ //TEST_EQUAL(info.m_types.size(), 1, ());
+ //TEST_EQUAL(info.GetBestType(), poi.m_type, ());
}
}
@@ -412,30 +413,9 @@ UNIT_TEST(Bookmarks_AddressInfo)
fm.RegisterMap(platform::LocalCountryFile::MakeForTesting("minsk-pass"));
fm.OnSize(800, 600);
- // assume that developers have English or Russian system language :)
- string const lang = languages::GetCurrentNorm();
- LOG(LINFO, ("Current language =", lang));
-
- // default name (street in russian, category in english).
- size_t index = 0;
- if (lang == "ru")
- index = 1;
- if (lang == "en")
- index = 2;
-
- POIInfo poi1[] = {
- { "Планета Pizza", "улица Карла Маркса", "10", "cafe" },
- { "Планета Pizza", "улица Карла Маркса", "10", "кафе" },
- { "Планета Pizza", "vulica Karla Marksa", "10", "cafe" }
- };
- CheckPlace(fm, 53.8964918, 27.555559, poi1[index]);
-
- POIInfo poi2[] = {
- { "Нц Шашек И Шахмат", "улица Карла Маркса", "10", "hotel" },
- { "Нц Шашек И Шахмат", "улица Карла Маркса", "10", "гостиница" },
- { "Нц Шашек И Шахмат", "vulica Karla Marksa", "10", "hotel" }
- };
- CheckPlace(fm, 53.8964365, 27.5554007, poi2[index]);
+ // Our code always uses "default" street name for addresses.
+ CheckPlace(fm, 53.8964918, 27.555559, { "Планета Pizza", "улица Карла Маркса", "10", "cafe" });
+ CheckPlace(fm, 53.8964365, 27.5554007, { "Нц Шашек И Шахмат", "улица Карла Маркса", "10", "hotel" });
}
UNIT_TEST(Bookmarks_IllegalFileName)
diff --git a/qt/draw_widget.cpp b/qt/draw_widget.cpp
index c75f112699..f17c913519 100644
--- a/qt/draw_widget.cpp
+++ b/qt/draw_widget.cpp
@@ -470,15 +470,14 @@ void DrawWidget::ShowPOIEditor(FeatureType & feature)
{
// Show Edit POI dialog.
auto & editor = osm::Editor::Instance();
- EditorDialog dlg(this, feature);
+ EditorDialog dlg(this, feature, *m_framework);
int const result = dlg.exec();
if (result == QDialog::Accepted)
{
- // Save edited data.
feature.SetNames(dlg.GetEditedNames());
- // Save edited metadata.
feature.SetMetadata(dlg.GetEditedMetadata());
- editor.EditFeature(feature);
+ // TODO(AlexZ): Check that street was actually changed/edited.
+ editor.EditFeature(feature, dlg.GetEditedStreet(), dlg.GetEditedHouseNumber());
}
else if (result == QDialogButtonBox::DestructiveRole)
{
@@ -496,20 +495,18 @@ void DrawWidget::ShowInfoPopup(QMouseEvent * e, m2::PointD const & pt)
};
search::AddressInfo const info = m_framework->GetMercatorAddressInfo(m_framework->PtoG(pt));
-
- // Get feature types under cursor.
- vector<string> types;
- m_framework->GetFeatureTypes(pt, types);
- for (size_t i = 0; i < types.size(); ++i)
- addStringFn(types[i]);
+ for (auto const & type : info.m_types)
+ addStringFn(type);
menu.addSeparator();
- // Format address and types.
if (!info.m_name.empty())
+ {
addStringFn(info.m_name);
+ menu.addSeparator();
+ }
+
addStringFn(info.FormatAddress());
- addStringFn(info.FormatTypes());
menu.exec(e->pos());
}
diff --git a/qt/editor_dialog.cpp b/qt/editor_dialog.cpp
index 359418be95..d0b460bdb6 100644
--- a/qt/editor_dialog.cpp
+++ b/qt/editor_dialog.cpp
@@ -1,9 +1,12 @@
#include "qt/editor_dialog.hpp"
+#include "map/framework.hpp"
+
#include "search/result.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature.hpp"
+#include "indexer/feature_algo.hpp"
#include "indexer/osm_editor.hpp"
#include "base/collection_cast.hpp"
@@ -11,6 +14,7 @@
#include "std/set.hpp"
#include "std/vector.hpp"
+#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
@@ -22,10 +26,25 @@
using feature::Metadata;
-EditorDialog::EditorDialog(QWidget * parent, FeatureType const & feature) : QDialog(parent)
+constexpr char const * kStreetObjectName = "addr:street";
+constexpr char const * kHouseNumberObjectName = "addr:housenumber";
+
+EditorDialog::EditorDialog(QWidget * parent, FeatureType const & feature, Framework & frm) : QDialog(parent)
{
+ osm::Editor & editor = osm::Editor::Instance();
+
QVBoxLayout * vLayout = new QVBoxLayout();
+ // Zero uneditable row: coordinates.
+ ms::LatLon const ll = MercatorBounds::ToLatLon(feature::GetCenter(feature));
+ QHBoxLayout * coordinatesRow = new QHBoxLayout();
+ coordinatesRow->addWidget(new QLabel("Latitude, Longitude:"));
+ QLabel * coords = new QLabel(QString::fromStdString(strings::to_string_dac(ll.lat, 6) +
+ "," + strings::to_string_dac(ll.lon, 6)));
+ coords->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ coordinatesRow->addWidget(coords);
+ vLayout->addLayout(coordinatesRow);
+
// First uneditable row: feature types.
string strTypes;
feature.ForEachType([&strTypes](uint32_t type)
@@ -37,48 +56,70 @@ EditorDialog::EditorDialog(QWidget * parent, FeatureType const & feature) : QDia
typesRow->addWidget(new QLabel(QString::fromStdString(strTypes)));
vLayout->addLayout(typesRow);
- if (osm::Editor::Instance().IsNameEditable(feature))
+ bool const readOnlyName = !editor.IsNameEditable(feature);
+ // Rows block: Name(s) label(s) and text input.
+ char const * defaultLangStr = StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::DEFAULT_CODE);
+ // Default name editor is always displayed, even if feature name is empty.
+ QHBoxLayout * defaultNameRow = new QHBoxLayout();
+ defaultNameRow->addWidget(new QLabel(QString("name")));
+ QLineEdit * defaultNamelineEdit = new QLineEdit();
+ defaultNamelineEdit->setReadOnly(readOnlyName);
+ defaultNamelineEdit->setObjectName(defaultLangStr);
+ defaultNameRow->addWidget(defaultNamelineEdit);
+ vLayout->addLayout(defaultNameRow);
+
+ feature.ForEachNameRef([&](int8_t langCode, string const & name) -> bool
{
- // Rows block: Name(s) label(s) and text input.
- char const * defaultLangStr = StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::DEFAULT_CODE);
- // Default name editor is always displayed, even if feature name is empty.
- QHBoxLayout * defaultNameRow = new QHBoxLayout();
- defaultNameRow->addWidget(new QLabel(QString("Name:")));
- QLineEdit * defaultNamelineEdit = new QLineEdit();
- defaultNamelineEdit->setObjectName(defaultLangStr);
- defaultNameRow->addWidget(defaultNamelineEdit);
- vLayout->addLayout(defaultNameRow);
-
- feature.ForEachNameRef([&vLayout, &defaultNamelineEdit](int8_t langCode, string const & name) -> bool
+ if (langCode == StringUtf8Multilang::DEFAULT_CODE)
+ defaultNamelineEdit->setText(QString::fromStdString(name));
+ else
{
- if (langCode == StringUtf8Multilang::DEFAULT_CODE)
- defaultNamelineEdit->setText(QString::fromStdString(name));
- else
- {
- QHBoxLayout * nameRow = new QHBoxLayout();
- char const * langStr = StringUtf8Multilang::GetLangByCode(langCode);
- nameRow->addWidget(new QLabel(QString("Name:") + langStr));
- QLineEdit * lineEditName = new QLineEdit(QString::fromStdString(name));
- lineEditName->setObjectName(langStr);
- nameRow->addWidget(lineEditName);
- vLayout->addLayout(nameRow);
- }
- return true; // true is needed to enumerate all languages.
- });
- }
+ QHBoxLayout * nameRow = new QHBoxLayout();
+ char const * langStr = StringUtf8Multilang::GetLangByCode(langCode);
+ nameRow->addWidget(new QLabel(QString("name:") + langStr));
+ QLineEdit * lineEditName = new QLineEdit(QString::fromStdString(name));
+ lineEditName->setReadOnly(readOnlyName);
+ lineEditName->setObjectName(langStr);
+ nameRow->addWidget(lineEditName);
+ vLayout->addLayout(nameRow);
+ }
+ return true; // true is needed to enumerate all languages.
+ });
+
+ // Address rows.
+ bool const readOnlyAddress = !editor.IsAddressEditable(feature);
+ vector<string> nearbyStreets = frm.GetNearbyFeatureStreets(feature);
+ // If feature does not have a specified street, display empty combo box.
+ search::AddressInfo const info = frm.GetFeatureAddressInfo(feature);
+ if (info.m_street.empty())
+ nearbyStreets.insert(nearbyStreets.begin(), "");
+ QHBoxLayout * streetRow = new QHBoxLayout();
+ streetRow->addWidget(new QLabel(QString(kStreetObjectName)));
+ QComboBox * cmb = new QComboBox();
+ for (auto const & street : nearbyStreets)
+ cmb->addItem(street.c_str());
+ cmb->setEditable(!readOnlyAddress);
+ cmb->setEnabled(!readOnlyAddress);
+ cmb->setObjectName(kStreetObjectName);
+ streetRow->addWidget(cmb) ;
+ vLayout->addLayout(streetRow);
+ QHBoxLayout * houseRow = new QHBoxLayout();
+ houseRow->addWidget(new QLabel(QString(kHouseNumberObjectName)));
+ QLineEdit * houseLineEdit = new QLineEdit();
+ houseLineEdit->setText(info.m_house.c_str());
+ houseLineEdit->setReadOnly(readOnlyAddress);
+ houseLineEdit->setObjectName(kHouseNumberObjectName);
+ houseRow->addWidget(houseLineEdit);
+ vLayout->addLayout(houseRow);
// All metadata rows.
QVBoxLayout * metaRows = new QVBoxLayout();
- // Features can have several types, so we merge all editable fields here.
- set<Metadata::EType> const editableMetadataFields =
- // TODO(mgsergio, Alex): Maybe just return set in EditableMetadataForType?
- my::collection_cast<set>(osm::Editor::Instance().EditableMetadataForType(feature));
-
+ vector<Metadata::EType> const editableMetadataFields = editor.EditableMetadataForType(feature);
for (Metadata::EType const field : editableMetadataFields)
{
QString const fieldName = QString::fromStdString(DebugPrint(field));
QHBoxLayout * fieldRow = new QHBoxLayout();
- fieldRow->addWidget(new QLabel(fieldName + ":"));
+ fieldRow->addWidget(new QLabel(fieldName));
QLineEdit * lineEdit = new QLineEdit(QString::fromStdString(feature.GetMetadata().Get(field)));
// Mark line editor to query it's text value when editing is finished.
lineEdit->setObjectName(fieldName);
@@ -133,3 +174,16 @@ Metadata EditorDialog::GetEditedMetadata() const
}
return metadata;
}
+
+string EditorDialog::GetEditedStreet() const
+{
+ QComboBox const * cmb = findChild<QComboBox *>();
+ if (cmb->count())
+ return cmb->itemText(0).toStdString();
+ return string();
+}
+
+string EditorDialog::GetEditedHouseNumber() const
+{
+ return findChild<QLineEdit *>(kHouseNumberObjectName)->text().toStdString();
+}
diff --git a/qt/editor_dialog.hpp b/qt/editor_dialog.hpp
index a383851068..964467065f 100644
--- a/qt/editor_dialog.hpp
+++ b/qt/editor_dialog.hpp
@@ -9,13 +9,16 @@
#include <QtWidgets/QDialog>
class FeatureType;
+class Framework;
class QLineEdit;
class EditorDialog : public QDialog
{
Q_OBJECT
public:
- EditorDialog(QWidget * parent, FeatureType const & feature);
+ EditorDialog(QWidget * parent, FeatureType const & feature, Framework & frm);
StringUtf8Multilang GetEditedNames() const;
feature::Metadata GetEditedMetadata() const;
+ string GetEditedStreet() const;
+ string GetEditedHouseNumber() const;
};
diff --git a/qt/mainwindow.cpp b/qt/mainwindow.cpp
index fb1c4b8f24..3a70cd2285 100644
--- a/qt/mainwindow.cpp
+++ b/qt/mainwindow.cpp
@@ -49,6 +49,10 @@
namespace qt
{
+// Defined in osm_auth_dialog.cpp.
+extern char const * kTokenKeySetting;
+extern char const * kTokenSecretSetting;
+
MainWindow::MainWindow() : m_locationService(CreateDesktopLocationService(*this))
{
// Always runs on the first desktop
@@ -91,6 +95,7 @@ MainWindow::MainWindow() : m_locationService(CreateDesktopLocationService(*this)
helpMenu->addAction(tr("About"), this, SLOT(OnAbout()));
helpMenu->addAction(tr("Preferences"), this, SLOT(OnPreferences()));
helpMenu->addAction(tr("OpenStreetMap Login"), this, SLOT(OnLoginMenuItem()));
+ helpMenu->addAction(tr("Upload Edits"), this, SLOT(OnUploadEditsMenuItem()));
#else
{
// create items in the system menu
@@ -371,6 +376,17 @@ void MainWindow::OnLoginMenuItem()
dlg.exec();
}
+void MainWindow::OnUploadEditsMenuItem()
+{
+ string key, secret;
+ Settings::Get(kTokenKeySetting, key);
+ Settings::Get(kTokenSecretSetting, secret);
+ if (key.empty() || secret.empty())
+ OnLoginMenuItem();
+ else
+ osm::Editor::Instance().UploadChanges(key, secret, {{"created_by", "MAPS.ME " OMIM_OS_NAME}});
+}
+
void MainWindow::OnBeforeEngineCreation()
{
m_pDrawWidget->GetFramework().SetMyPositionModeListener([this](location::EMyPositionMode mode)
diff --git a/qt/mainwindow.hpp b/qt/mainwindow.hpp
index ab496bacf2..73d01ff74a 100644
--- a/qt/mainwindow.hpp
+++ b/qt/mainwindow.hpp
@@ -69,6 +69,7 @@ namespace qt
void OnMyPosition();
void OnSearchButtonClicked();
void OnLoginMenuItem();
+ void OnUploadEditsMenuItem();
void OnBeforeEngineCreation();
};
diff --git a/qt/osm_auth_dialog.cpp b/qt/osm_auth_dialog.cpp
index a6bcda9e25..28ae1c08ba 100644
--- a/qt/osm_auth_dialog.cpp
+++ b/qt/osm_auth_dialog.cpp
@@ -102,8 +102,7 @@ void OsmAuthDialog::OnAction()
return;
}
- // TODO(AlexZ): Change to production server.
- OsmOAuth auth = osm::OsmOAuth::DevServerAuth();
+ OsmOAuth auth = osm::OsmOAuth::ServerAuth();
OsmOAuth::AuthResult const res = auth.AuthorizePassword(login, password);
if (res != OsmOAuth::AuthResult::OK)
{