diff --git a/config/trees.json b/config/trees.json index 616708f982d..3f43341a732 100644 --- a/config/trees.json +++ b/config/trees.json @@ -9,120 +9,310 @@ "^(brand|brand:\\w+|operator|operator:\\w+|\\w+_name|\\w+_name:\\w+)$" ] }, - "keepKV": [ - "^amenity/bank$", - "^amenity/bicycle_rental$", - "^amenity/bureau_de_change$", - "^amenity/cafe$", - "^amenity/car_rental$", - "^amenity/cinema$", - "^amenity/dentist$", - "^amenity/fast_food$", - "^amenity/fuel$", - "^amenity/ice_cream$", - "^amenity/money_transfer$", - "^amenity/payment_(centre|terminal)$", - "^amenity/pharmacy$", - "^amenity/public_bookcase$", - "^amenity/restaurant$", - "^amenity/social_(centre|facility)$", - "^amenity/vending_machine$", - "^amenity/veterinary$", - "^healthcare/audiologist$", - "^healthcare/blood_donation$", - "^healthcare/counselling$", - "^healthcare/laboratory$", - "^healthcare/physiotherapist$", - "^leisure/adult_gaming_centre$", - "^leisure/amusement_arcade$", - "^leisure/bowling_alley$", - "^leisure/escape_game$", - "^leisure/fitness_centre$", - "^office/accountant$", - "^office/employment_agency$", - "^office/estate_agent$", - "^office/financial(_advisor)?$", - "^office/insurance$", - "^office/tax_advisor$", - "^office/telecommunication$", - "^shop/(?!(yes|no|banner_shop|gas|convenience;gas|lottery|mall|vacant))", - "^tourism/(h|m)otel$" - ], - "discardKVN": [ - "^amenity/bank\\|(atm|bank spółdzielczy|lbs|pko)$", - "^amenity/bank\\|(bank zachodni|bz) wbk$", - "^amenity/bank\\|(landesbau|bau|kreis|stadt)?sparkasse.*$", - "^amenity/bank\\|volks- und raiffeisenbank$", - "^amenity/bank\\|volksbank$", - "^amenity/bank\\|volksbank( |-)raiffeisenbank$", - "^amenity/bank\\|vr( |-)bank$", - "^amenity/bicycle_rental\\|nextbike( gmbh)?$", - "^amenity/bar\\|уют$", - "^amenity/cafe\\|((eiscaf(e|é)\\s)?(dolomiti|venezia)|kiosque(\\sà)?\\scafé|caf(e|é)\\sexpress|встреча|рандеву|чайхана|жемчужина|виктория|бер(е|ё)зка|лакомка|мечта|оазис|парус|сказка||||||)$", - "^amenity/cinema\\|октябрь$", - "^amenity/dentist\\|дантист$", - "^amenity/fast_food\\|(ali\\sbaba|antalya|asia(\\s|-)(bistro|imbiss|wok)|(berlin|city)\\sdöner|city\\s(grill|pizza)|kebabai|kfc/taco\\sbell)$", - "^amenity/fast_food\\|(marmaris|pizza\\s(house|time))$", - "^amenity/fuel\\|independent|gnv|gpl|posto|spbu|جایگاه\\scng$", - "^amenity/ice_cream\\|gelateria|venezia$", - "^amenity/pharmacy\\|(adler|bahnhof|brunnen|burg|bären|einhorn|engel|hirsch|hubertus|kur|linden|löwen)(-| )?apotheke$", - "^amenity/pharmacy\\|(centrum|farm(a|á)cia\\s(central(e)?|comunale))$", - "^amenity/pharmacy\\|(marien|markt|mohren|neue|park|rathaus|rats|rosen|schloss|sonnen|stadt|stern)(-| )?apotheke$", - "^amenity/pharmacy\\|(айболит|арніка|фармация|фармація)$", - "^amenity/pharmacy\\|pharmacie\\s(centrale|comunale|de\\s(l'hôtel\\sde\\sville|la\\s(gare|mairie|poste))|du\\s(centre|marché|parc))$", - "^amenity/restaurant\\|((gasthaus|gasthof|schwarzer)?\\s?(adler|löwen))$", - "^amenity/restaurant\\|(ali\\sbaba|adria|akropolis|amici|aroma|asia|athen|athos|bahnhof|bamboo|bären|bella\\s(vista|napoli))", - "^amenity/restaurant\\|(belvedere|brasserie|canteen|cantina|capri|carpe\\sdiem|casa\\s?(blanca|mia)|casino)", - "^amenity/restaurant\\|(china\\s(buffet|city|garden|house|king|palace|star|town|wok)|(da|la|las|el)\\s)", - "^amenity/restaurant\\|(delphi|deutsches\\shaus|dhaba|dionysos|dolce\\svita|dorfkrug|europa|frohsinn)", - "^amenity/restaurant\\|(golden\\sdragon|great\\swall)$", - "^amenity/restaurant\\|(grüner\\sbaum|hellas|hirsch(en)?|hong\\skong|happy garden)", - "^amenity/restaurant\\|(indian\\spalace|ichiban|il\\sgiardino|italia|jade\\sgarden|jägerhof)", - "^amenity/restaurant\\|(kirchenwirt|koliba|kreta|kreuz|krone|lamm|le\\s(bistrot|patio))", - "^amenity/restaurant\\|(l'escale|lemongrass|linde|lindenhof|little\\s(italy|india)|los\\s(amigos|arcos))", - "^amenity/restaurant\\|(lotus|löwen|maharaja|mamma\\smia|mandarin|marco\\spolo|marmaris|mediterraneo|mensa|milano|mykonos|mythos)", - "^amenity/restaurant\\|(namaste|napoli|new\\schina|oasis|ochsen|olympia|osaka|paradise|parrilla|peking|pho|pinocchio)", - "^amenity/restaurant\\|(pizza\\shouse|pizz(a|e)ria|pizza\\sking|pomodoro|portofino|poseidon|ratskeller|rhodos|ristorante\\s)", - "^amenity/restaurant\\|(roma|rose|rössl(e|i)|saffron|saigon|sakura|san\\smarco|santorini|schützenhaus|shalimar|shanghai|sonne|sphinx)", - "^amenity/restaurant\\|(sportheim|sternen|syrtaki|taj\\smahal|taste\\sof\\sindia|the\\skitchen|toscana|tokyo|traube)", - "^amenity/restaurant\\|(venezia|waldschänke|wok|zorbas|(zum|zur)\\s)", - "^amenity/restaurant\\|don camillo", - "^amenity/restaurant\\|永和豆漿$", - "^amenity/social_facility\\|dom pomocy społecznej$", - "^amenity/social_facility\\|maison de retraite$", - "^amenity/social_facility\\|safe haven$", - "^amenity/vending_machine\\|(schlauchomat|tobaccoland)$", - "^shop/(convenience|kiosk)\\|(777|bp|esso|exxon|independent|mobil|shell)$", - "^shop/bakery\\|(backshop|schneider|schäfer|каравай)$", - "^shop/bakery\\|(bäckerei\\s)?müller", - "^shop/clothes\\|трикотаж$", - "^shop/convenience\\|(24 часа|августина|альянс|апельсин|ассорти|бер(е|ё)зка|бриз|валентина|весна|визит|ветеран|водолей|гастроном|гермес|гурман|диана|добрый|дружба|домашний|елена)$", - "^shop/convenience\\|(almac[eé]n|despensa)$", - "^shop/convenience\\|(boutique d'alimentation|sup[eé]rette)$", - "^shop/convenience\\|(milk bar|юлия)$", - "^shop/convenience\\|(калинка|каштан|квартал|колобок|колос(ок)?|каравай|кооператор|корзинка|кулинария|лакомка|ласточка|лидер|луч|любава|любимый|людмила|люкс)$", - "^shop/convenience\\|(меркурий|мечта|мини маркет|мираж|белорусские продукты|продукты 24|натали|надежда|ника)$", - "^shop/convenience\\|(оазис|околица|олимп|парус|пеликан|престиж|подсолнух|радуга|росинка|ромашка|родник|рассвет|русь)$", - "^shop/convenience\\|(сельпо|семейный|сказка|солнышко|спутник|солнечный|светлана|удача|ивушка)$", - "^shop/convenience\\|(юбилейный|южный|эконом|шанс|чайка|центральный|хороший|хуторок|фаворит|фортуна|татьяна|тройка|эдем|уют)$", - "^shop/copyshop\\|xerox$", - "^shop/cosmetics\\|(vente de )?cosm[eé]tiques?$", - "^shop/fishing\\|рыболов$", - "^shop/florist\\|(мир цветов|орхидея)$", - "^shop/frame\\|rumah\\spenduduk$", - "^shop/hairdresser\\|(figaro|haarwerk|стиль|локон|шарм|fodrászat)$", - "^shop/hardware\\|(всё для дома|1000 мелочей|хозяюшка)$", - "^shop/jewelry\\|(585|jubiler)$", - "^shop/money_transfer|mobil(e)?\\smoney$", - "^shop/motorcycle\\|independent$", - "^shop/optician\\|(die brille|vision plus|оптика|[oó]ptica)$", - "^shop/supermarket\\|(famiglia cooperativa|семейный)$", - "^shop/tailor\\|couture\\s?(dame|homme|mixte)?$", - "^tourism/(h|m)otel\\|budget inn$", - "^tourism/hotel\\|(h(o|ô)tel\\s)?((zur\\s)?post|de france|de la gare|строитель)$", - "^tourism/hotel\\|(h(o|ô)tel\\s)?(astoria|belvedere|central|city|continental|europa)\\s?(h(o|ô)tel)?$", - "^tourism/hotel\\|(h(o|ô)tel\\s)?(grand|imperial|krone|palace|panorama|park|plaza|royal|victoria)\\s?(h(o|ô)tel)?$" - ] + "contains": { + "amenity/animal_boarding": {}, + "amenity/animal_shelter": {}, + "amenity/atm": {}, + "amenity/bank": { + "exclude": [ + "^(atm|bank spółdzielczy|lbs|pko)$", + "^(bank zachodni|bz) wbk$", + "^(landesbau|bau|kreis|stadt)?sparkasse.*$", + "^volks- und raiffeisenbank$", + "^volksbank$", + "^volksbank( |-)raiffeisenbank$", + "^vr( |-)bank$" + ] + }, + "amenity/bar": {"exclude": ["^уют$"]}, + "amenity/bicycle_rental": {"exclude": ["^nextbike( gmbh)?$"]}, + "amenity/bureau_de_change": {}, + "amenity/cafe": { + "exclude": [ + "^((eiscaf(e|é)\\s)?(dolomiti|venezia)|kiosque(\\sà)?\\scafé|caf(e|é)\\sexpress|встреча|рандеву|чайхана|жемчужина|виктория|бер(е|ё)зка|лакомка|мечта|оазис|парус|сказка)$" + ] + }, + "amenity/car_rental": {}, + "amenity/car_wash": {}, + "amenity/casino": {}, + "amenity/childcare": {}, + "amenity/cinema": {"exclude": ["^октябрь$"]}, + "amenity/college": {}, + "amenity/dentist": {"exclude": ["^дантист$"]}, + "amenity/doctors": {}, + "amenity/fast_food": { + "exclude": [ + "^(ali\\sbaba|antalya|asia(\\s|-)(bistro|imbiss|wok)|(berlin|city)\\sdöner|city\\s(grill|pizza)|kebabai|kfc/taco\\sbell)$", + "^(marmaris|pizza\\s(house|time))$" + ] + }, + "amenity/fuel": { + "exclude": ["^(independent|gnv|gpl|posto|spbu|جایگاه cng)$"] + }, + "amenity/gambling": {}, + "amenity/ice_cream": {"exclude": ["^(gelateria|venezia)$"]}, + "amenity/internet_cafe": {}, + "amenity/karaoke_box": {}, + "amenity/kindergarten": {}, + "amenity/language_school": {}, + "amenity/money_transfer": {}, + "amenity/motorcycle_rental": {}, + "amenity/music_school": {}, + "amenity/payment_centre": {}, + "amenity/payment_terminal": {}, + "amenity/pharmacy": { + "exclude": [ + "^(adler|bahnhof|brunnen|burg|bären|einhorn|engel|hirsch|hubertus|kur|linden|löwen)(-| )?apotheke$", + "^(centrum|farm(a|á)cia\\s(central(e)?|comunale))$", + "^(marien|markt|mohren|neue|park|rathaus|rats|rosen|schloss|sonnen|stadt|stern)(-| )?apotheke$", + "^(айболит|арніка|фармация|фармація)$", + "^pharmacie\\s(centrale|comunale|de\\s(l'hôtel\\sde\\sville|la\\s(gare|mairie|poste))|du\\s(centre|marché|parc))$" + ] + }, + "amenity/prep_school": {}, + "amenity/pub": {}, + "amenity/public_bookcase": {}, + "amenity/recycling": {}, + "amenity/restaurant": { + "exclude": [ + "^((gasthaus|gasthof|schwarzer)?\\s?(adler|löwen))$", + "^(ali\\sbaba|adria|akropolis|amici|aroma|asia|athen|athos|bahnhof|bamboo|bären|bella\\s(vista|napoli))", + "^(belvedere|brasserie|canteen|cantina|capri|carpe\\sdiem|casa\\s?(blanca|mia)|casino)", + "^(china\\s(buffet|city|garden|house|king|palace|star|town|wok)|(da|la|las|el)\\s)", + "^(delphi|deutsches\\shaus|dhaba|dionysos|dolce\\svita|dorfkrug|europa|frohsinn)", + "^(golden\\sdragon|great\\swall)$", + "^(grüner\\sbaum|hellas|hirsch(en)?|hong\\skong|happy garden)", + "^(indian\\spalace|ichiban|il\\sgiardino|italia|jade\\sgarden|jägerhof)", + "^(kirchenwirt|koliba|kreta|kreuz|krone|lamm|le\\s(bistrot|patio))", + "^(l'escale|lemongrass|linde|lindenhof|little\\s(italy|india)|los\\s(amigos|arcos))", + "^(lotus|löwen|maharaja|mamma\\smia|mandarin|marco\\spolo|marmaris|mediterraneo|mensa|milano|mykonos|mythos)", + "^(namaste|napoli|new\\schina|oasis|ochsen|olympia|osaka|paradise|parrilla|peking|pho|pinocchio)", + "^(pizza\\shouse|pizz(a|e)ria|pizza\\sking|pomodoro|portofino|poseidon|ratskeller|rhodos|ristorante\\s)", + "^(roma|rose|rössl(e|i)|saffron|saigon|sakura|san\\smarco|santorini|schützenhaus|shalimar|shanghai|sonne|sphinx)", + "^(sportheim|sternen|syrtaki|taj\\smahal|taste\\sof\\sindia|the\\skitchen|toscana|tokyo|traube)", + "^(venezia|waldschänke|wok|zorbas|(zum|zur)\\s)", + "^don camillo$", + "^永和豆漿$" + ] + }, + "amenity/school": {}, + "amenity/social_centre": {}, + "amenity/social_facility": { + "exclude": [ + "^dom pomocy społecznej$", + "^maison de retraite$", + "^safe haven$" + ] + }, + "amenity/university": {}, + "amenity/vehicle_inspection": {}, + "amenity/vending_machine": { + "exclude": ["^(schlauchomat|tobaccoland)$"] + }, + "amenity/veterinary": {}, + "craft/carpenter": {}, + "craft/cleaning": {}, + "craft/electronics_repair": {}, + "craft/plumber": {}, + "craft/signmaker": {}, + "craft/window_construction": {}, + "healthcare/audiologist": {}, + "healthcare/blood_donation": {}, + "healthcare/counselling": {}, + "healthcare/laboratory": {}, + "healthcare/physiotherapist": {}, + "healthcare/sample_collection": {}, + "landuse/residential": {}, + "leisure/adult_gaming_centre": {}, + "leisure/amusement_arcade": {}, + "leisure/bowling_alley": {}, + "leisure/dog_park": {}, + "leisure/escape_game": {}, + "leisure/fitness_centre": {}, + "leisure/indoor_play": {}, + "leisure/playground": {}, + "leisure/sports_centre": {}, + "leisure/trampoline_park": {}, + "office/bail_bond_agent": {}, + "office/consulting": {}, + "office/coworking": {}, + "office/employment_agency": {}, + "office/energy_supplier": {}, + "office/estate_agent": {}, + "office/financial_advisor": {}, + "office/financial": {}, + "office/insurance": {}, + "office/moving_company": {}, + "office/security": {}, + "office/tax_advisor": {}, + "office/telecommunication": {}, + "shop/agrarian": {}, + "shop/alcohol": {}, + "shop/anime": {}, + "shop/art": {}, + "shop/baby_goods": {}, + "shop/bag": {}, + "shop/bakery": { + "exclude": [ + "^(backshop|schneider|schäfer|каравай)$", + "^(bäckerei\\s)?müller" + ] + }, + "shop/beauty": {}, + "shop/bed": {}, + "shop/beverages": {}, + "shop/bicycle": {}, + "shop/boat": {}, + "shop/bookmaker": {}, + "shop/books": {}, + "shop/butcher": {}, + "shop/camera": {}, + "shop/candles": {}, + "shop/car_parts": {}, + "shop/car_repair": {}, + "shop/car": {}, + "shop/carpet": {}, + "shop/catalogue": {}, + "shop/charity": {}, + "shop/chemist": {}, + "shop/chocolate": {}, + "shop/clothes": {"exclude": ["^трикотаж$"]}, + "shop/coffee": {}, + "shop/computer": {}, + "shop/confectionery": {}, + "shop/convenience": { + "exclude": [ + "^(777|bp|esso|exxon|independent|mobil|shell)$", + "^(24 часа|августина|альянс|апельсин|ассорти|бер(е|ё)зка|бриз|валентина|весна|визит|ветеран|водолей|гастроном|гермес|гурман|диана|добрый|дружба|домашний|елена)$", + "^(almac[eé]n|despensa)$", + "^(boutique d'alimentation|sup[eé]rette)$", + "^(milk bar|юлия)$", + "^(калинка|каштан|квартал|колобок|колос(ок)?|каравай|кооператор|корзинка|кулинария|лакомка|ласточка|лидер|луч|любава|любимый|людмила|люкс)$", + "^(меркурий|мечта|мини маркет|мираж|белорусские продукты|продукты 24|натали|надежда|ника)$", + "^(оазис|околица|олимп|парус|пеликан|престиж|подсолнух|радуга|росинка|ромашка|родник|рассвет|русь)$", + "^(сельпо|семейный|сказка|солнышко|спутник|солнечный|светлана|удача|ивушка)$", + "^(юбилейный|южный|эконом|шанс|чайка|центральный|хороший|хуторок|фаворит|фортуна|татьяна|тройка|эдем|уют)$" + ] + }, + "shop/copyshop": {"exclude": ["^xerox$"]}, + "shop/cosmetics": {"exclude": ["^(vente de )?cosm[eé]tiques?$"]}, + "shop/country_store": {}, + "shop/craft": {}, + "shop/curtain": {}, + "shop/dairy": {}, + "shop/deli": {}, + "shop/department_store": {}, + "shop/doityourself": {}, + "shop/doors": {}, + "shop/dry_cleaning": {}, + "shop/e-cigarette": {}, + "shop/electrical": {}, + "shop/electronics": {}, + "shop/erotic": {}, + "shop/fabric": {}, + "shop/fashion_accessories": {}, + "shop/fishing": {"exclude": ["^рыболов$"]}, + "shop/flooring": {}, + "shop/florist": {"exclude": ["^(мир цветов|орхидея)$"]}, + "shop/frame": {"exclude": ["^rumah penduduk$"]}, + "shop/frozen_food": {}, + "shop/funeral_directors": {}, + "shop/furniture": {}, + "shop/games": {}, + "shop/garden_centre": {}, + "shop/gas": {}, + "shop/general": {}, + "shop/gift": {}, + "shop/greengrocer": {}, + "shop/grocery": {}, + "shop/hairdresser_supply": {}, + "shop/hairdresser": { + "exclude": ["^(figaro|haarwerk|стиль|локон|шарм|fodrászat)$"] + }, + "shop/hardware": { + "exclude": ["^(всё для дома|1000 мелочей|хозяюшка)$"] + }, + "shop/health_food": {}, + "shop/hearing_aids": {}, + "shop/herbalist": {}, + "shop/hifi": {}, + "shop/household_linen": {}, + "shop/houseware": {}, + "shop/interior_decoration": {}, + "shop/jewelry": {"exclude": ["^(585|jubiler)$"]}, + "shop/kiosk": { + "exclude": ["^(777|bp|esso|exxon|independent|mobil|shell)$"] + }, + "shop/kitchen": {}, + "shop/laundry": {}, + "shop/leather": {}, + "shop/locksmith": {}, + "shop/lottery": {}, + "shop/massage": {}, + "shop/medical_supply": {}, + "shop/mobile_phone": {}, + "shop/money_lender": {}, + "shop/motorcycle_repair": {}, + "shop/motorcycle": {"exclude": ["^independent$"]}, + "shop/music": {}, + "shop/musical_instrument": {}, + "shop/newsagent": {}, + "shop/nutrition_supplements": {}, + "shop/optician": { + "exclude": ["^(die brille|vision plus|оптика|[oó]ptica)$"] + }, + "shop/outdoor": {}, + "shop/outpost": {}, + "shop/paint": {}, + "shop/party": {}, + "shop/pastry": {}, + "shop/pawnbroker": {}, + "shop/perfumery": {}, + "shop/pet": {}, + "shop/photo": {}, + "shop/pottery": {}, + "shop/printer_ink": {}, + "shop/pyrotechnics": {}, + "shop/rental": {}, + "shop/repair": {}, + "shop/seafood": {}, + "shop/second_hand": {}, + "shop/shoes": {}, + "shop/spices": {}, + "shop/sports": {}, + "shop/stationery": {}, + "shop/storage_rental": {}, + "shop/supermarket": {"exclude": ["^(famiglia cooperativa|семейный)$"]}, + "shop/swimming_pool": {}, + "shop/tailor": {"exclude": ["^couture\\s?(dame|homme|mixte)?$"]}, + "shop/tea": {}, + "shop/telecommunication": {}, + "shop/ticket": {}, + "shop/tiles": {}, + "shop/tobacco": {}, + "shop/tool_hire": {}, + "shop/toys": {}, + "shop/trade": {}, + "shop/travel_agency": {}, + "shop/tyres": {}, + "shop/vacuum_cleaner": {}, + "shop/variety_store": {}, + "shop/video_games": {}, + "shop/video": {}, + "shop/watches": {}, + "shop/wholesale": {}, + "shop/wine": {}, + "tourism/caravan_site": {}, + "tourism/hotel": { + "exclude": [ + "^budget inn$", + "^(h(o|ô)tel\\s)?((zur\\s)?post|de france|de la gare|строитель)$", + "^(h(o|ô)tel\\s)?(astoria|belvedere|central|city|continental|europa)\\s?(h(o|ô)tel)?$", + "^(h(o|ô)tel\\s)?(grand|imperial|krone|palace|panorama|park|plaza|royal|victoria)\\s?(h(o|ô)tel)?$" + ] + }, + "tourism/motel": {"exclude": ["^budget inn$"]} + } }, "flags": { "emoji": "🚩", @@ -133,8 +323,7 @@ "^(country|country:\\w+|flag|flag:\\w+|subject|subject:\\w+)$" ] }, - "keepKV": ["^man_made/flagpole$"], - "discardKVN": [] + "contains": {"man_made/flagpole": {}} }, "operators": { "emoji": "💼", @@ -143,26 +332,37 @@ "primary": ["^(name|name:\\w+|operator|operator:\\w+)$"], "alternate": ["^(brand|brand:\\w+|\\w+_name|\\w+_name:\\w+)$"] }, - "discardKVN": [ - "^amenity/clinic\\|(consultorio|municipal|private(_profit)?|priv(e|é){0,2}|public)$", - "^amenity/hospital\\|(govern?ment( authorities)?|govt( hospital)?|municipal|private|public|private not for profit|village)$", - "^amenity/hospital\\|(центральна районна лікарня|црб)$", - "^amenity/hospital\\|(центральная районная )?больница$", - "^amenity/post_office\\|(costcutter|londis|mccoll'?s|one stop|premier( stores)?|whsmith)$", - "^amenity/post_office\\|(co-?op( food)?|(the )?co-?operative( food)?)$" - ], - "keepKV": [ - "^amenity/car_sharing$", - "^amenity/charging_station$", - "^amenity/clinic$", - "^amenity/fire_station$", - "^amenity/hospital$", - "^amenity/police$", - "^amenity/post_(depot|office)$", - "^emergency/ambulance_station$", - "^emergency/lifeboat_station$", - "^emergency/phone$" - ] + "contains": { + "amenity/bicycle_parking": {}, + "amenity/car_sharing": {}, + "amenity/charging_station": {}, + "amenity/clinic": { + "exclude": [ + "^(consultorio|municipal|private(_profit)?|priv(e|é){0,2}|public)$" + ] + }, + "amenity/fire_station": {}, + "amenity/hospital": { + "exclude": [ + "^(govern?ment( authorities)?|govt( hospital)?|municipal|private|public|private not for profit|village)$", + "^(центральна районна лікарня|црб)$", + "^(центральная районная )?больница$" + ] + }, + "amenity/parking": {}, + "amenity/police": {}, + "amenity/post_box": {}, + "amenity/post_depot": {}, + "amenity/post_office": { + "exclude": [ + "^(costcutter|londis|mccoll'?s|one stop|premier( stores)?|whsmith)$", + "^(co-?op( food)?|(the )?co-?operative( food)?)$" + ] + }, + "emergency/ambulance_station": {}, + "emergency/lifeboat_station": {}, + "emergency/phone": {} + } }, "transit": { "emoji": "🚇", @@ -173,21 +373,53 @@ "^(operator|operator:\\w+|network:\\w+|\\w+_name|\\w+_name:\\w+)$" ] }, - "keepKV": [ - "^route/(aerialway|bus|light_rail|subway|train|tram|trolleybus|walking_bus)$" - ], - "discardKVN": [ - "\\|[ilnr][chimsw][nm]$", - "\\|city$", - "\\|international$", - "\\|local$", - "\\|mtb$", - "\\|municipal$", - "\\|national$", - "\\|rail$", - "\\|regional$", - "\\|urban$" - ] + "contains": { + "route/aerialway": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/bus": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/ferry": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/light_rail": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/subway": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/train": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/tram": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/trolleybus": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + }, + "route/walking_bus": { + "exclude": [ + "^(city|international|local|mtb|municipal|national|rail|regional|urban)$" + ] + } + } } } } diff --git a/lib/file_tree.js b/lib/file_tree.js index 5cfee54eaef..a161d8c1fac 100644 --- a/lib/file_tree.js +++ b/lib/file_tree.js @@ -18,12 +18,20 @@ const itemsSchema = require('../schema/items.json'); // - validates data on read, generating any missing data // - cleans data on write, sorting and lowercasing all the keys and arrays +// cache: { +// id: {}, // Map of item id -> item +// category: {}, // Map of tkv -> category data +// path: { +// items: [], +// templates: [] +// } +// } exports.read = (cache, loco) => { cache = cache || {}; cache.id = cache.id || {}; + cache.category = cache.category || {}; cache.path = cache.path || {}; - cache.template = cache.template || {}; Object.keys(trees).forEach(t => { const tree = trees[t]; @@ -52,83 +60,93 @@ exports.read = (cache, loco) => { validate(file, input, itemsSchema); let seenkv = {}; - Object.keys(input).forEach(tkv => { - const parts = tkv.split('/', 3); // tkv = "tree/key/value" - const k = parts[1]; - const v = parts[2]; - const kv = `${k}/${v}`; - - // make sure each k/v pair appears in only one tree - const other = seenkv[kv]; - if (other && other !== t) { - console.error(colors.red(`Error - '${kv}' found in multiple trees: ${other} and ${t}.`)); - console.error(' ' + colors.yellow(file)); - process.exit(1); - } else { - seenkv[kv] = t; - } +// new : old + const tkv = input.path || Object.keys(input)[0]; + const parts = tkv.split('/', 3); // tkv = "tree/key/value" + const k = parts[1]; + const v = parts[2]; + const kv = `${k}/${v}`; + + // make sure t/k/v is unique + if (cache.category[tkv]) { + console.error(colors.red(`Error - '${tkv}' found in multiple files.`)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } else { + cache.category[tkv] = input; + cache.path[tkv] = { items: [], templates: [] }; + } + // make sure each k/v pair appears in only one tree + const other = seenkv[kv]; + if (other && other !== t) { + console.error(colors.red(`Error - '${kv}' found in multiple trees: ${other} and ${t}.`)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } else { + seenkv[kv] = t; + } - // check and merge each item - let seenName = {}; - input[tkv].forEach(item => { - itemCount++; + // check and merge each item + let seenName = {}; - if (item.templateSource) { // It's a template item - if (!cache.template[tkv]) cache.template[tkv] = []; - cache.template[tkv].push(item); - return; - } +// new : old + let items = input.items || input[tkv]; + items.forEach(item => { + itemCount++; - // check displayName for uniqueness within this category - if (seenName[item.displayName]) { - console.error(colors.red(`Error - duplicate displayName '${item.displayName}' in:`)); - console.error(' ' + colors.yellow(file)); - process.exit(1); - } else { - seenName[item.displayName] = true; - } + if (item.templateSource) { // It's a template item + cache.path[tkv].templates.push(item); + return; + } - // check locationSet - let locationID; - try { - const resolved = loco.resolveLocationSet(item.locationSet); - locationID = resolved.id; - if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) { - throw new Error(`locationSet ${locationID} resolves to an empty feature.`); - } - } catch (err) { - console.error(colors.red(`Error - ${err.message} in:`)); - console.error(' ' + colors.yellow(item.displayName)); - console.error(' ' + colors.yellow(file)); - process.exit(1); + // check displayName for uniqueness within this category + if (seenName[item.displayName]) { + console.error(colors.red(`Error - duplicate displayName '${item.displayName}' in:`)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } else { + seenName[item.displayName] = true; + } + + // check locationSet + let locationID; + try { + const resolved = loco.resolveLocationSet(item.locationSet); + locationID = resolved.id; + if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) { + throw new Error(`locationSet ${locationID} resolves to an empty feature.`); } + } catch (err) { + console.error(colors.red(`Error - ${err.message} in:`)); + console.error(' ' + colors.yellow(item.displayName)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } - // check tags - item.tags[k] = v; // sanity check: `k=v` must exist as a tag. + // check tags + item.tags[k] = v; // sanity check: `k=v` must exist as a tag. - // generate id - item.id = idgen(item, tkv, locationID); - if (!item.id) { - console.error(colors.red(`Error - Couldn't generate an id for:`)); - console.error(' ' + colors.yellow(item.displayName)); - console.error(' ' + colors.yellow(file)); - process.exit(1); - } + // generate id + item.id = idgen(item, tkv, locationID); + if (!item.id) { + console.error(colors.red(`Error - Couldn't generate an id for:`)); + console.error(' ' + colors.yellow(item.displayName)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } - // merge into caches - const existing = cache.id[item.id]; - if (existing) { - console.error(colors.red(`Error - Duplicate id '${item.id}' in:`)); - console.error(' ' + colors.yellow(item.displayName)); - console.error(' ' + colors.yellow(file)); - process.exit(1); - } else { - if (!cache.path[tkv]) cache.path[tkv] = []; - cache.path[tkv].push(item); - cache.id[item.id] = item; - } - }); + // merge into caches + const existing = cache.id[item.id]; + if (existing) { + console.error(colors.red(`Error - Duplicate id '${item.id}' in:`)); + console.error(' ' + colors.yellow(item.displayName)); + console.error(' ' + colors.yellow(file)); + process.exit(1); + } else { + cache.path[tkv].items.push(item); + cache.id[item.id] = item; + } }); }); @@ -141,34 +159,29 @@ exports.read = (cache, loco) => { exports.write = (cache) => { cache = cache || {}; - cache.id = cache.id || {}; + cache.category = cache.category || {}; cache.path = cache.path || {}; - cache.template = cache.template || {}; Object.keys(trees).forEach(t => { const tree = trees[t]; let itemCount = 0; let fileCount = 0; - // gather paths used by normal items and template items.. - const paths = new Set(); - Object.keys(cache.path).forEach(tkv => { if (tkv.split('/')[0] === t) paths.add(tkv); }); - Object.keys(cache.template).forEach(tkv => { if (tkv.split('/')[0] === t) paths.add(tkv); }); + Object.keys(cache.path).forEach(tkv => { + if (tkv.split('/')[0] !== t) return; - if (!paths.size) { - console.error(colors.red(`Error - No data to write for ${t}`)); - process.exit(1); - } + const parts = tkv.split('/', 3); // tkv = "tree/key/value" + const k = parts[1]; + const v = parts[2]; + const kv = `${k}/${v}`; - paths.forEach(tkv => { + const file = `./data/${tkv}.json`; fileCount++; - let templateItems = cache.template[tkv] || []; - let normalItems = cache.path[tkv] || []; + let templateItems = cache.path[tkv].templates || []; + let normalItems = cache.path[tkv].items || []; if (!templateItems.length && !normalItems.length) return; // nothing to do - const file = `./data/${tkv}.json`; - templateItems = templateItems .sort((a, b) => withLocale(a.templateSource, b.templateSource)) // sort templateItems by templateSource .map(item => { @@ -237,8 +250,30 @@ exports.write = (cache) => { }); +//old + // let output = {}; + // output[tkv] = templateItems.concat(normalItems); +//new let output = {}; - output[tkv] = templateItems.concat(normalItems); + output.path = tkv; + + +//new + let contains = tree.contains[kv]; + let excludeNames = contains && contains.exclude; + + // add properties for this category + let category = cache.category[tkv] || {}; + if (category.exclude || excludeNames) { + output.exclude = {}; + // if (category.exclude.named) output.exclude.named = category.exclude.named; + // if (category.exclude.generic) output.exclude.generic = category.exclude.generic; + + if (excludeNames) output.exclude.named = excludeNames; + } + + // add items + output.items = templateItems.concat(normalItems); try { fs.ensureFileSync(file); @@ -270,14 +305,10 @@ exports.expandTemplates = (cache, loco) => { cache = cache || {}; cache.id = cache.id || {}; cache.path = cache.path || {}; - cache.template = cache.template || {}; - - // gather paths used by template items.. - const paths = Object.keys(cache.template) || []; - paths.forEach(tkv => { + Object.keys(cache.path).forEach(tkv => { const file = `./data/${tkv}.json`; - let templateItems = cache.template[tkv] || []; + const templateItems = cache.path[tkv].templates || []; // expand each template item into real items.. templateItems.forEach(templateItem => { @@ -286,7 +317,7 @@ exports.expandTemplates = (cache, loco) => { const templateSource = templateItem.templateSource; const templateTags = templateItem.templateTags; - const sourceItems = cache.path[templateSource]; + const sourceItems = cache.path[templateSource].items; if (!Array.isArray(sourceItems)) { console.error(colors.red(`Error - template item references invalid source path '${templateSource}' in:`)); console.error(' ' + colors.yellow(file)); @@ -360,8 +391,7 @@ exports.expandTemplates = (cache, loco) => { console.error(' ' + colors.yellow(file)); process.exit(1); } else { - if (!cache.path[tkv]) cache.path[tkv] = []; - cache.path[tkv].push(item); + cache.path[tkv].items.push(item); cache.id[item.id] = item; } }); diff --git a/lib/matcher.js b/lib/matcher.js index 64e861f9577..62b9cc6fd7e 100644 --- a/lib/matcher.js +++ b/lib/matcher.js @@ -88,8 +88,8 @@ module.exports = () => { // `all` needs to be an Object indexed on a 'tree/key/value' path. // (The cache in `file_tree.js` makes this) // { - // 'brands/amenity/bank': [ {}, {}, … ], - // 'brands/amenity/bar': [ {}, {}, … ], + // 'brands/amenity/bank': { items: [ {}, {}, … ] }, + // 'brands/amenity/bar': { items: [ {}, {}, … ] }, // … // } // @@ -98,7 +98,7 @@ module.exports = () => { _matchIndex = { primary: {}, alternate: {} }; Object.keys(all).forEach(tkv => { - let items = all[tkv]; + let items = all[tkv].items; if (!Array.isArray(items) || !items.length) return; const parts = tkv.split('/', 3); // tkv = "tree/key/value" @@ -260,7 +260,7 @@ module.exports = () => { _locationSets = {}; Object.keys(all).forEach(tkv => { - let items = all[tkv]; + let items = all[tkv].items; if (!Array.isArray(items) || !items.length) return; items.forEach(item => { diff --git a/lib/sort_object.js b/lib/sort_object.js index cf4340ad6d6..31be79fbe2b 100644 --- a/lib/sort_object.js +++ b/lib/sort_object.js @@ -3,6 +3,8 @@ const withLocale = require('locale-compare')('en-US'); // Returns an object with sorted keys and sorted values. // (This is useful for file diffing) module.exports = (obj) => { + if (!obj) return null; + let sorted = {}; Object.keys(obj).sort(keyCompare).forEach(k => { sorted[k] = Array.isArray(obj[k]) ? obj[k].sort(withLocale) : obj[k]; diff --git a/schema/items.json b/schema/items.json index 3c877d128c2..fe2c8a0b38a 100644 --- a/schema/items.json +++ b/schema/items.json @@ -2,10 +2,18 @@ "title": "items.json", "description": "JSON Schema for name-suggestion-index items", "type": "object", - "additionalProperties": false, - "patternProperties": { + "required": [], + "additionalProperties": true, + "properties": { - "^\\S+/\\S+/\\S+$": { + "path": { + "description": "(required) The path for this category in the form tree/key/value", + "type": "string", + "pattern": "^\\S+/\\S+/\\S+$" + }, + + "items": { + "description": "(required) The items in this category", "type": "array", "uniqueItems": true, "items": { diff --git a/schema/trees.json b/schema/trees.json index 320f1b5c23b..535a6468d87 100644 --- a/schema/trees.json +++ b/schema/trees.json @@ -14,8 +14,8 @@ "^\\S+$": { "description": "(required) The name of the tree (e.g. 'brands')", "type": "object", - "additionalProperties": false, - "required": ["emoji", "mainTag", "keepKV", "discardKVN"], + "additionalProperties": true, + "required": ["emoji", "mainTag", "nameTags"], "properties": { "emoji": { @@ -36,7 +36,7 @@ "required": ["primary", "alternate"], "properties": { "primary": { - "description": "(required) Pattern for matching primary name tags (e.g. 'name')", + "description": "(required) Patterns for matching primary name tags (e.g. 'name')", "type": "array", "uniqueItems": true, "items": { @@ -45,7 +45,7 @@ } }, "alternate": { - "description": "(required) Pattern for matching alternate name tags (e.g. 'short_name', 'alt_name')", + "description": "(required) Patterns for matching alternate name tags (e.g. 'short_name', 'alt_name')", "type": "array", "uniqueItems": true, "items": { @@ -54,26 +54,6 @@ } } } - }, - - "keepKV": { - "description": "(required) An array of regular expressions that match k/v tuples to keep.", - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "format": "regex" - } - }, - - "discardKVN": { - "description": "(required) An array of regular expressions that match k/v|n tuples to discard.", - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "format": "regex" - } } } diff --git a/scripts/build_dist.js b/scripts/build_dist.js index 9c7ba7bce1a..e219d638092 100644 --- a/scripts/build_dist.js +++ b/scripts/build_dist.js @@ -131,7 +131,7 @@ function buildJSON() { const paths = Object.keys(_cache.path).sort(withLocale); paths.forEach(tkv => { - let items = _cache.path[tkv]; + let items = _cache.path[tkv].items; if (!Array.isArray(items) || !items.length) return; const parts = tkv.split('/', 3); // tkv = "tree/key/value" @@ -307,7 +307,7 @@ function buildXML() { const wdTag = trees[t].mainTag; // Include only items that have a wikidata tag and are not dissolved.. - let items = (_cache.path[tkv] || []) + let items = (_cache.path[tkv].items || []) .filter(item => { const qid = item.tags[wdTag]; if (!qid || !/^Q\d+$/.test(qid)) return false; // wikidata tag missing or looks wrong.. diff --git a/scripts/build_index.js b/scripts/build_index.js index 495322dfe64..4603adcbab8 100644 --- a/scripts/build_index.js +++ b/scripts/build_index.js @@ -33,7 +33,7 @@ loadCollected(); let _discard = {}; let _keep = {}; -runFilters(); +// runFilters(); let _cache = {}; loadIndex(); @@ -75,8 +75,8 @@ function loadConfig() { Object.values(data.trees).forEach(tree => { tree.nameTags.primary.forEach(pattern => checkRegex(file, pattern)); tree.nameTags.alternate.forEach(pattern => checkRegex(file, pattern)); - tree.keepKV.forEach(pattern => checkRegex(file, pattern)); - tree.discardKVN.forEach(pattern => checkRegex(file, pattern)); + // tree.keepKV.forEach(pattern => checkRegex(file, pattern)); + // tree.discardKVN.forEach(pattern => checkRegex(file, pattern)); }); } else if (which === 'genericWords') { @@ -93,9 +93,9 @@ function loadConfig() { nameTags: { primary: tree.nameTags.primary, alternate: tree.nameTags.alternate, - }, - keepKV: tree.keepKV.map(s => s.toLowerCase()).sort(withLocale), - discardKVN: tree.discardKVN.map(s => s.toLowerCase()).sort(withLocale) + } + // keepKV: tree.keepKV.map(s => s.toLowerCase()).sort(withLocale), + // discardKVN: tree.discardKVN.map(s => s.toLowerCase()).sort(withLocale) }; tree = cleaned; }); @@ -180,8 +180,9 @@ function runFilters() { let discard = _discard[t] = {}; let keep = _keep[t] = {}; +//todo // Start clean - shell.rm('-f', [`dist/filtered/${t}_keep.json`, `dist/filtered/${t}_discard.json`]); + // shell.rm('-f', [`dist/filtered/${t}_keep.json`, `dist/filtered/${t}_discard.json`]); // All the collected values start out in discard.. treeTags[t].forEach(tag => { @@ -214,7 +215,7 @@ function runFilters() { } }); - // filter by discardNames (move from keep -> discard) + // filter by genericWords (move from keep -> discard) _config.genericWords.forEach(s => { const re = new RegExp(s, 'i'); for (let kvn in keep) { @@ -230,8 +231,9 @@ function runFilters() { const keepCount = Object.keys(keep).length; console.log(`${tree.emoji} ${t}:\t${keepCount} keep, ${discardCount} discard`); - fs.writeFileSync(`dist/filtered/${t}_discard.json`, stringify(sortObject(discard)) + '\n'); - fs.writeFileSync(`dist/filtered/${t}_keep.json`, stringify(sortObject(keep)) + '\n'); +//todo + // fs.writeFileSync(`dist/filtered/${t}_discard.json`, stringify(sortObject(discard)) + '\n'); + // fs.writeFileSync(`dist/filtered/${t}_keep.json`, stringify(sortObject(keep)) + '\n'); }); @@ -278,6 +280,26 @@ function saveIndex() { fileTree.write(_cache); +// update trees.json +// const paths = Object.keys(_cache.path).forEach(tkv => { +// let items = _cache.path[tkv]; +// if (!Array.isArray(items) || !items.length) return; + +// const parts = tkv.split('/', 3); // tkv = "tree/key/value" +// const t = parts[0]; +// const k = parts[1]; +// const v = parts[2]; +// const kv = `${k}/${v}`; + +// const tree = _config.trees[t]; +// if (!tree.contains) tree.contains = {}; +// tree.contains[kv] = {}; +// }); + +// let data = { trees: _config.trees }; +// fs.writeFileSync('config/trees.json', stringify(data) + '\n'); + + console.timeEnd(END); } @@ -337,8 +359,9 @@ function mergeItems() { } // Insert into index.. - if (!_cache.path[tkv]) _cache.path[tkv] = []; - _cache.path[tkv].push(item); + if (!_cache.category[tkv]) _cache.category[tkv] = { path: tkv }; + if (!_cache.path[tkv]) _cache.path[tkv] = { items: [], templates: [] }; + _cache.path[tkv].items.push(item); totalNew++; }); @@ -348,7 +371,7 @@ function mergeItems() { // const paths = Object.keys(_cache.path).filter(tkv => tkv.split('/')[0] === t); paths.forEach(tkv => { - let items = _cache.path[tkv]; + let items = _cache.path[tkv].items; if (!Array.isArray(items) || !items.length) return; const parts = tkv.split('/', 3); // tkv = "tree/key/value" @@ -551,7 +574,7 @@ function checkItems(t) { const display = (val) => `${val.displayName} (${val.id})`; paths.forEach(tkv => { - const items = _cache.path[tkv]; + const items = _cache.path[tkv].items; if (!Array.isArray(items) || !items.length) return; const parts = tkv.split('/', 3); // tkv = "tree/key/value" diff --git a/scripts/build_wikidata.js b/scripts/build_wikidata.js index 5c06dfc3261..a29f01ed381 100644 --- a/scripts/build_wikidata.js +++ b/scripts/build_wikidata.js @@ -124,7 +124,10 @@ Object.keys(_cache.path).forEach(tkv => { const parts = tkv.split('/', 3); // tkv = "tree/key/value" const t = parts[0]; - _cache.path[tkv].forEach(item => { + const items = _cache.path[tkv].items; + if (!Array.isArray(items) || !items.length) return; + + items.forEach(item => { const tags = item.tags; ['brand', 'flag', 'operator', 'network', 'subject'].forEach(osmtag => { const wdTag = `${osmtag}:wikidata`; diff --git a/scripts/check_wikiTags.js b/scripts/check_wikiTags.js index 9b6583a02f8..34eba53ff24 100644 --- a/scripts/check_wikiTags.js +++ b/scripts/check_wikiTags.js @@ -53,7 +53,10 @@ function gatherData() { let wikipedia = {}; Object.keys(_cache.path).forEach(tkv => { - _cache.path[tkv].forEach(item => { + const items = _cache.path[tkv].items; + if (!Array.isArray(items) || !items.length) return; + + items.forEach(item => { ['brand:wikidata', 'flag:wikidata', 'operator:wikidata', 'network:wikidata'].forEach(t => { let qid = item.tags[t]; if (qid && /^Q\d+$/.test(qid)) {