From 38293194a70bb3f45dd0eac7750e69cd55e93e54 Mon Sep 17 00:00:00 2001 From: matheusgomesms Date: Tue, 26 May 2026 09:20:54 +0200 Subject: [PATCH 01/40] [generator] Add junction:ref and tests Signed-off-by: matheusgomesms --- .../generator_tests/tag_admixer_test.cpp | 89 +++++++++++++++++++ generator/tag_admixer.hpp | 48 ++++++++++ generator/translator_country.cpp | 3 + generator/translator_country.hpp | 1 + 4 files changed, 141 insertions(+) diff --git a/generator/generator_tests/tag_admixer_test.cpp b/generator/generator_tests/tag_admixer_test.cpp index f11b31a1c..4ed19aca4 100644 --- a/generator/generator_tests/tag_admixer_test.cpp +++ b/generator/generator_tests/tag_admixer_test.cpp @@ -2,6 +2,8 @@ #include "generator/tag_admixer.hpp" +#include "indexer/osm_element.hpp" + #include "platform/platform_tests_support/scoped_file.hpp" #include @@ -58,6 +60,93 @@ UNIT_TEST(CapitalsParserTests) TEST(capitals.find(140247101) == capitals.end(), ()); } +UNIT_TEST(JunctionRefEnricher_PropagatesJunctionRefFromNodeToWay) +{ + JunctionRefEnricher enricher; + + // Node: highway=motorway_junction with junction:ref + OsmElement node; + node.m_type = OsmElement::EntityType::Node; + node.m_id = 42; + node.AddTag("highway", "motorway_junction"); + node.AddTag("junction:ref", "17B"); + enricher.Process(node); + + // Way: highway=motorway_link whose first node is the junction node + OsmElement way; + way.m_type = OsmElement::EntityType::Way; + way.m_id = 100; + way.AddTag("highway", "motorway_link"); + way.m_nodes = {42, 43, 44}; + enricher.Process(way); + + TEST_EQUAL(way.GetTag("junction:ref"), "17B", ()); +} + +UNIT_TEST(JunctionRefEnricher_FallsBackToPlainRef) +{ + JunctionRefEnricher enricher; + + OsmElement node; + node.m_type = OsmElement::EntityType::Node; + node.m_id = 55; + node.AddTag("highway", "motorway_junction"); + node.AddTag("ref", "5"); + enricher.Process(node); + + OsmElement way; + way.m_type = OsmElement::EntityType::Way; + way.m_id = 200; + way.AddTag("highway", "motorway_link"); + way.m_nodes = {55, 56}; + enricher.Process(way); + + TEST_EQUAL(way.GetTag("junction:ref"), "5", ()); +} + +UNIT_TEST(JunctionRefEnricher_DoesNotOverwriteExistingRef) +{ + JunctionRefEnricher enricher; + + OsmElement node; + node.m_type = OsmElement::EntityType::Node; + node.m_id = 77; + node.AddTag("highway", "motorway_junction"); + node.AddTag("junction:ref", "99"); + enricher.Process(node); + + OsmElement way; + way.m_type = OsmElement::EntityType::Way; + way.m_id = 300; + way.AddTag("highway", "motorway_link"); + way.AddTag("junction:ref", "existing"); + way.m_nodes = {77, 78}; + enricher.Process(way); + + TEST_EQUAL(way.GetTag("junction:ref"), "existing", ()); +} + +UNIT_TEST(JunctionRefEnricher_IgnoresNonLinkWays) +{ + JunctionRefEnricher enricher; + + OsmElement node; + node.m_type = OsmElement::EntityType::Node; + node.m_id = 88; + node.AddTag("highway", "motorway_junction"); + node.AddTag("junction:ref", "7"); + enricher.Process(node); + + OsmElement way; + way.m_type = OsmElement::EntityType::Way; + way.m_id = 400; + way.AddTag("highway", "motorway"); + way.m_nodes = {88, 89}; + enricher.Process(way); + + TEST(way.GetTag("junction:ref").empty(), ()); +} + UNIT_TEST(TagsReplacer_Smoke) { { diff --git a/generator/tag_admixer.hpp b/generator/tag_admixer.hpp index cb636961c..5c8a3cd10 100644 --- a/generator/tag_admixer.hpp +++ b/generator/tag_admixer.hpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include class WaysParserHelper @@ -286,3 +288,49 @@ class OsmTagMixer private: std::map, std::vector> m_elements; }; + +class JunctionRefEnricher +{ +public: + void Process(OsmElement & element) + { + if (element.m_type == OsmElement::EntityType::Node) + { + if (element.GetTag("highway") != "motorway_junction") + return; + + // Prefer junction:ref; fall back to plain ref (the same value on well-tagged nodes). + auto ref = element.GetTag("junction:ref"); + if (ref.empty()) + ref = element.GetTag("ref"); + if (ref.empty()) + return; + + std::lock_guard lock(m_mutex); + m_junctionNodes[element.m_id] = std::move(ref); + } + else if (element.m_type == OsmElement::EntityType::Way) + { + if (element.m_nodes.empty()) + return; + // Only propagate to link roads branching off a motorway junction. + if (element.GetTag("highway").find("_link") == std::string::npos) + return; + // Don't overwrite an explicit junction:ref already on the way. + if (!element.GetTag("junction:ref").empty()) + return; + + std::lock_guard lock(m_mutex); + // Check front (normal direction) and back (oneway=-1 reversed ways). + auto it = m_junctionNodes.find(element.m_nodes.front()); + if (it == m_junctionNodes.end()) + it = m_junctionNodes.find(element.m_nodes.back()); + if (it != m_junctionNodes.end()) + element.AddTag("junction:ref", it->second); + } + } + +private: + std::mutex m_mutex; + std::unordered_map m_junctionNodes; +}; diff --git a/generator/translator_country.cpp b/generator/translator_country.cpp index cd47c9a05..b44cc0edd 100644 --- a/generator/translator_country.cpp +++ b/generator/translator_country.cpp @@ -79,6 +79,7 @@ TranslatorCountry::TranslatorCountry(std::shared_ptr , m_tagAdmixer(std::make_shared(info.GetIntermediateFileName("ways", ".csv"), info.GetIntermediateFileName(TOWNS_FILE))) , m_tagReplacer(std::make_shared(base::JoinPath(GetPlatform().ResourcesDir(), REPLACED_TAGS_FILE))) + , m_junctionRefEnricher(std::make_shared()) { /// @todo This option is not used, but may be useful in future? // if (needMixTags) @@ -134,6 +135,7 @@ std::shared_ptr TranslatorCountry::Clone() const copy->m_tagAdmixer = m_tagAdmixer; copy->m_tagReplacer = m_tagReplacer; copy->m_osmTagMixer = m_osmTagMixer; + copy->m_junctionRefEnricher = m_junctionRefEnricher; return copy; } @@ -144,6 +146,7 @@ void TranslatorCountry::Preprocess(OsmElement & element) m_tagAdmixer->Process(element); if (m_osmTagMixer) m_osmTagMixer->Process(element); + m_junctionRefEnricher->Process(element); CollectFromRelations(element); } diff --git a/generator/translator_country.hpp b/generator/translator_country.hpp index 798ab2f59..9276fb90e 100644 --- a/generator/translator_country.hpp +++ b/generator/translator_country.hpp @@ -43,5 +43,6 @@ class TranslatorCountry : public Translator std::shared_ptr m_tagAdmixer; std::shared_ptr m_tagReplacer; std::shared_ptr m_osmTagMixer; + std::shared_ptr m_junctionRefEnricher; }; } // namespace generator From 391a30e702c703da20b853a418f3dd5ac308c124 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 5 Feb 2026 17:46:11 +0100 Subject: [PATCH 02/40] [core] Add support of capacity:disabled and capacity:charging tags Signed-off-by: Jean-Baptiste --- generator/osm2meta.cpp | 11 +++++++++++ libs/indexer/feature_meta.cpp | 6 ++++++ libs/indexer/feature_meta.hpp | 2 ++ libs/indexer/map_object.cpp | 9 +++++++++ libs/indexer/map_object.hpp | 2 ++ 5 files changed, 30 insertions(+) diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 056b6679f..ddd769d3e 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -370,6 +370,15 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_rooms(std::string const { return v; } +std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity_disabled(std::string const & v) +{ + return v; +} + +std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity_charging(std::string const & v) +{ + return v; +} std::string MetadataTagProcessorImpl::ValidateAndFormat_charge(std::string v) { @@ -664,6 +673,8 @@ void MetadataTagProcessor::operator()(std::string const & k, std::string const & case Metadata::FMD_OUTDOOR_SEATING: valid = ValidateAndFormat_outdoor_seating(v); break; case Metadata::FMD_NETWORK: valid = ValidateAndFormat_operator(v); break; case Metadata::FMD_CHARGE_SOCKETS: m_chargeSockets.AggregateChargeSocketKey(k, v); break; + case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity_disabled(v); break; + case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity_charging(v); break; // Metadata types we do not get from OSM. case Metadata::FMD_CUISINE: diff --git a/libs/indexer/feature_meta.cpp b/libs/indexer/feature_meta.cpp index 42d307a61..bc1316520 100644 --- a/libs/indexer/feature_meta.cpp +++ b/libs/indexer/feature_meta.cpp @@ -200,6 +200,10 @@ bool Metadata::TypeFromString(string_view k, Metadata::EType & outType) outType = Metadata::FMD_CHARGE; else if (k == "population") outType = Metadata::FMD_POPULATION; + else if (k.starts_with("capacity:disabled")) + outType = Metadata::FMD_CAPACITY_DISABLED; + else if (k.starts_with("capacity:charging")) + outType = Metadata::FMD_CAPACITY_CHARGING; else return false; @@ -350,6 +354,8 @@ string ToString(Metadata::EType type) case Metadata::FMD_ROOMS: return "rooms"; case Metadata::FMD_CHARGE: return "charge"; case Metadata::FMD_POPULATION: return "population"; + case Metadata::FMD_CAPACITY_DISABLED: return "capacity:disabled"; + case Metadata::FMD_CAPACITY_CHARGING: return "capacity:charging"; case Metadata::FMD_COUNT: CHECK(false, ("FMD_COUNT can not be used as a type.")); }; diff --git a/libs/indexer/feature_meta.hpp b/libs/indexer/feature_meta.hpp index 79b4dd5c6..ddb533848 100644 --- a/libs/indexer/feature_meta.hpp +++ b/libs/indexer/feature_meta.hpp @@ -131,6 +131,8 @@ class Metadata : public MetadataBase FMD_ROOMS = 57, FMD_CHARGE = 58, FMD_POPULATION = 59, + FMD_CAPACITY_DISABLED = 60, + FMD_CAPACITY_CHARGING = 61, FMD_COUNT }; diff --git a/libs/indexer/map_object.cpp b/libs/indexer/map_object.cpp index cba8ac60b..57d6c62e1 100644 --- a/libs/indexer/map_object.cpp +++ b/libs/indexer/map_object.cpp @@ -265,6 +265,15 @@ std::string MapObject::GetRooms() const { return std::string(m_metadata.Get(MetadataID::FMD_ROOMS)); } +std::string MapObject::GetCapacityDisabled() const +{ + return std::string(m_metadata.Get(MetadataID::FMD_CAPACITY_DISABLED)); +} + +std::string MapObject::GetCapacityCharging() const +{ + return std::string(m_metadata.Get(MetadataID::FMD_CAPACITY_CHARGING)); +} std::string MapObject::GetPopulation() const { diff --git a/libs/indexer/map_object.hpp b/libs/indexer/map_object.hpp index 19407f74e..3837f564e 100644 --- a/libs/indexer/map_object.hpp +++ b/libs/indexer/map_object.hpp @@ -93,6 +93,8 @@ class MapObject std::string GetRooms() const; std::string GetPopulation() const; std::string GetOrganic() const; + std::string GetCapacityDisabled() const; + std::string GetCapacityCharging() const; /// @returns true if feature has ATM type. bool HasAtm() const; From 3bc00c42fa98696f6de6fde86f65770b56c0c44c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 5 Feb 2026 17:46:43 +0100 Subject: [PATCH 03/40] [android] Add capacity tags on the place page Signed-off-by: Jean-Baptiste --- .../widget/placepage/PlacePageView.java | 30 +++++++++++++++++++ .../layout/place_page_capacity_charging.xml | 21 +++++++++++++ .../layout/place_page_capacity_disabled.xml | 21 +++++++++++++ .../main/res/layout/place_page_details.xml | 4 +++ android/app/src/main/res/values/strings.xml | 7 +++++ .../sdk/bookmarks/data/Metadata.java | 6 ++-- 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/res/layout/place_page_capacity_charging.xml create mode 100644 android/app/src/main/res/layout/place_page_capacity_disabled.xml diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index 6aa323676..b3a09eb32 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -145,6 +145,10 @@ public class PlacePageView extends Fragment private MaterialTextView mTvCharge; private View mWheelchair; private MaterialTextView mTvWheelchair; + private View mCapacityDisabled; + private MaterialTextView mTvCapacityDisabled; + private View mCapacityCharging; + private MaterialTextView mTvCapacityCharging; private View mDriveThrough; private MaterialTextView mTvDriveThrough; private View mSelfService; @@ -326,6 +330,10 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mTvCharge = mFrame.findViewById(R.id.tv__place_charge); mWheelchair = mFrame.findViewById(R.id.ll__place_wheelchair); mTvWheelchair = mFrame.findViewById(R.id.tv__place_wheelchair); + mCapacityDisabled = mFrame.findViewById(R.id.ll__place_capacity_disabled); + mTvCapacityDisabled = mFrame.findViewById(R.id.tv__place_capacity_disabled); + mCapacityCharging = mFrame.findViewById(R.id.ll__place_capacity_charging); + mTvCapacityCharging = mFrame.findViewById(R.id.tv__place_capacity_charging); mDriveThrough = mFrame.findViewById(R.id.ll__place_drive_through); mTvDriveThrough = mFrame.findViewById(R.id.tv__place_drive_through); mSelfService = mFrame.findViewById(R.id.ll__place_self_service); @@ -353,6 +361,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mRooms.setOnLongClickListener(this); mCharge.setOnLongClickListener(this); mWheelchair.setOnLongClickListener(this); + mCapacityDisabled.setOnLongClickListener(this); + mCapacityCharging.setOnLongClickListener(this); mDriveThrough.setOnLongClickListener(this); mSelfService.setOnLongClickListener(this); mOutdoorSeating.setOnLongClickListener(this); @@ -706,6 +716,22 @@ private void refreshDetails() getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_WHEELCHAIR)); refreshMetadataOrHide(wheelchair, mWheelchair, mTvWheelchair); + final String capacityDisabled = + getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_CAPACITY_DISABLED)); + refreshMetadataOrHide(switch (capacityDisabled){ + case "yes" -> getString(R.string.capacity_disabled_yes); + case "no" -> getString(R.string.capacity_disabled_no); + default -> getString(R.string.capacity_disabled, capacityDisabled); + }, mCapacityDisabled, mTvCapacityDisabled); + + final String capacityCharging = + getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_CAPACITY_CHARGING)); + refreshMetadataOrHide(switch (capacityCharging){ + case "yes" -> getString(R.string.capacity_disabled_yes); + case "no" -> getString(R.string.capacity_disabled_no); + default -> getString(R.string.capacity_disabled, capacityCharging); + }, mCapacityCharging, mTvCapacityCharging); + final String driveThrough = mMapObject.getMetadata(Metadata.MetadataType.FMD_DRIVE_THROUGH); refreshMetadataOrHide(driveThrough.equals("yes") ? getString(R.string.drive_through) : "", mDriveThrough, mTvDriveThrough); @@ -1121,6 +1147,10 @@ else if (id == R.id.ll__place_charge) items.add(mTvCharge.getText().toString()); else if (id == R.id.ll__place_wheelchair) items.add(mTvWheelchair.getText().toString()); + else if (id == R.id.ll__place_capacity_disabled) + items.add(mTvCapacityDisabled.getText().toString()); + else if (id == R.id.ll__place_capacity_charging) + items.add(mTvCapacityCharging.getText().toString()); else if (id == R.id.ll__place_drive_through) items.add(mTvDriveThrough.getText().toString()); else if (id == R.id.ll__place_outdoor_seating) diff --git a/android/app/src/main/res/layout/place_page_capacity_charging.xml b/android/app/src/main/res/layout/place_page_capacity_charging.xml new file mode 100644 index 000000000..23fb19813 --- /dev/null +++ b/android/app/src/main/res/layout/place_page_capacity_charging.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/place_page_capacity_disabled.xml b/android/app/src/main/res/layout/place_page_capacity_disabled.xml new file mode 100644 index 000000000..343befb82 --- /dev/null +++ b/android/app/src/main/res/layout/place_page_capacity_disabled.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/place_page_details.xml b/android/app/src/main/res/layout/place_page_details.xml index eb51ab345..901c945a7 100644 --- a/android/app/src/main/res/layout/place_page_details.xml +++ b/android/app/src/main/res/layout/place_page_details.xml @@ -64,6 +64,10 @@ + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3f23b63ba..c2187ad09 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1019,6 +1019,13 @@ Not following route Recalculating the route… + Rebuilding route… + Number of spaces for disabled: %s + Accessible spaces for disabled + No spaces for disabled + Number of electric vehicle parking spaces: %s + Presence of parking spaces for electric vehicles + No spaces for electric vehicles Automatically open third-party links Open third-party links in CoMaps by default Keyboard incognito mode diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/bookmarks/data/Metadata.java b/android/sdk/src/main/java/app/organicmaps/sdk/bookmarks/data/Metadata.java index bf0234428..c8d36cb15 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/bookmarks/data/Metadata.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/bookmarks/data/Metadata.java @@ -76,7 +76,9 @@ public enum MetadataType FMD_CHARGE_SOCKETS(56), FMD_ROOMS(57), FMD_CHARGE(58), - FMD_POPULATION(59); + FMD_POPULATION(59), + FMD_CAPACITY_DISABLED(60), + FMD_CAPACITY_CHARGING(61); private final int mMetaType; MetadataType(int metadataType) @@ -85,7 +87,7 @@ public enum MetadataType } @NonNull - public static MetadataType fromInt(@IntRange(from = 1, to = 59) int metaType) + public static MetadataType fromInt(@IntRange(from = 1, to = 61) int metaType) { for (MetadataType type : values()) if (type.mMetaType == metaType) From 10ca1456ddba961c1694fdccf88f6fda4f9140d7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 5 Feb 2026 17:47:25 +0100 Subject: [PATCH 04/40] [iOS] Add capacity tag on the place page Signed-off-by: Jean-Baptiste --- .../CoreApi/PlacePageData/Common/PlacePageInfoData.h | 2 ++ .../LocalizedStrings/en-GB.lproj/Localizable.strings | 10 ++++++++++ .../LocalizedStrings/en.lproj/Localizable.strings | 12 +++++++++++- .../Components/PlacePageInfoViewController.swift | 10 ++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h index 572cd4016..8547bd265 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h @@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly, nullable) NSString *outdoorSeating; @property(nonatomic, readonly, nullable) NSString *network; @property(nonatomic, readonly, nullable) NSString *population; +@property(nonatomic, readonly, nullable) NSString *capacityDisabled; +@property(nonatomic, readonly, nullable) NSString *capacityCharging; - (NSDate * _Nullable)getMostRecentCheckDate; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index 0c1f4b4e9..5ac20c09e 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -878,3 +878,13 @@ "downloader_check_updates_error" = "Error checking for updates"; "editor_add_select_category_recent_subtitle" = "Recently Used"; "oh_unknown" = "Opening hours unknown"; + +/* To indicate the number of disabled spaces in a parking*/ +"capacity_disabled" = "Number of spaces for disabled: %@"; +"capacity_disabled_yes" = "Accessible spaces for disabled"; +"capacity_disabled_no" = "No spaces for disabled"; + +/* To indicate the number of charging spaces in a parking*/ +"capacity_charging" = "Number of electric vehicle parking spaces: %@"; +"capacity_charging_yes" = "Presence of parking spaces for electric vehicles"; +"capacity_charging_no" = "No spaces for electric vehicles"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index b14f6d3a7..707216b57 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -878,4 +878,14 @@ "contribute_to_osm" = "Contribute to OpenStreetMap"; "contribute_to_osm_add_place" = "Add Place"; "contribute_to_osm_update_map" = "Update the Map to Contribute"; -"contribute_to_osm_update_map_description" = "You need to update the %@ map to the latest version before you can contribute to the OpenStreetMap data"; \ No newline at end of file +"contribute_to_osm_update_map_description" = "You need to update the %@ map to the latest version before you can contribute to the OpenStreetMap data"; + +/* To indicate the number of disabled spaces in a parking*/ +"capacity_disabled" = "Number of spaces for disabled: %@"; +"capacity_disabled_yes" = "Accessible spaces for disabled"; +"capacity_disabled_no" = "No spaces for disabled"; + +/* To indicate the number of charging spaces in a parking*/ +"capacity_charging" = "Number of electric vehicle parking spaces: %@"; +"capacity_charging_yes" = "Presence of parking spaces for electric vehicles"; +"capacity_charging_no" = "No spaces for electric vehicles"; diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift index c747999bc..45207be92 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift @@ -69,6 +69,8 @@ class PlacePageInfoViewController: UIViewController { private var driveThroughView: InfoItemView? private var networkView: InfoItemView? private var populationView: InfoItemView? + private var capacityDisabledView: InfoItemView? + private var capacityChargingView: InfoItemView? weak var placePageInfoData: PlacePageInfoData! weak var delegate: PlacePageInfoViewControllerDelegate? @@ -223,6 +225,14 @@ class PlacePageInfoViewController: UIViewController { if let wheelchair = placePageInfoData.wheelchair { wheelchairView = createInfoItem(wheelchair, icon: UIImage(systemName: "figure.roll")) } + + if let capacityDisabled = placePageInfoData.capacityDisabled { + capacityDisabledView = createInfoItem(capacityDisabled, icon: UIImage(named: "ic_placepage_wheelchair")) + } + + if let capacityCharging = placePageInfoData.capacityCharging { + capacityChargingView = createInfoItem(capacityCharging, icon: UIImage(named: "ic_placepage__charging")) + } if let selfService = placePageInfoData.selfService { selfServiceView = createInfoItem(selfService, icon: UIImage(named: "service.slash")) From d5793d8338dec698683cfd2994c0d3256d5b5c2d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 12 Feb 2026 20:41:10 +0100 Subject: [PATCH 05/40] Improve translations Signed-off-by: Jean-Baptiste --- .../widget/placepage/PlacePageView.java | 4 ++-- .../layout/place_page_capacity_charging.xml | 4 ++-- .../layout/place_page_capacity_disabled.xml | 4 ++-- android/app/src/main/res/values/strings.xml | 12 +++++------ generator/osm2meta.cpp | 21 ++++++++----------- .../PlacePageData/Common/PlacePageInfoData.mm | 12 +++++++++++ .../en-GB.lproj/Localizable.strings | 12 +++++------ .../en.lproj/Localizable.strings | 12 +++++------ 8 files changed, 45 insertions(+), 36 deletions(-) diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index b3a09eb32..24403f430 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -718,7 +718,7 @@ private void refreshDetails() final String capacityDisabled = getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_CAPACITY_DISABLED)); - refreshMetadataOrHide(switch (capacityDisabled){ + refreshMetadataOrHide(switch (capacityDisabled) { case "yes" -> getString(R.string.capacity_disabled_yes); case "no" -> getString(R.string.capacity_disabled_no); default -> getString(R.string.capacity_disabled, capacityDisabled); @@ -726,7 +726,7 @@ private void refreshDetails() final String capacityCharging = getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_CAPACITY_CHARGING)); - refreshMetadataOrHide(switch (capacityCharging){ + refreshMetadataOrHide(switch (capacityCharging) { case "yes" -> getString(R.string.capacity_disabled_yes); case "no" -> getString(R.string.capacity_disabled_no); default -> getString(R.string.capacity_disabled, capacityCharging); diff --git a/android/app/src/main/res/layout/place_page_capacity_charging.xml b/android/app/src/main/res/layout/place_page_capacity_charging.xml index 23fb19813..38c1cb209 100644 --- a/android/app/src/main/res/layout/place_page_capacity_charging.xml +++ b/android/app/src/main/res/layout/place_page_capacity_charging.xml @@ -17,5 +17,5 @@ android:id="@+id/tv__place_capacity_charging" android:textAlignment="viewStart" style="@style/PlacePageMetadataText" - tools:text="Capacity charging" /> - \ No newline at end of file + tools:text="@string/capacity_charging" /> + diff --git a/android/app/src/main/res/layout/place_page_capacity_disabled.xml b/android/app/src/main/res/layout/place_page_capacity_disabled.xml index 343befb82..38a5f4e58 100644 --- a/android/app/src/main/res/layout/place_page_capacity_disabled.xml +++ b/android/app/src/main/res/layout/place_page_capacity_disabled.xml @@ -17,5 +17,5 @@ android:id="@+id/tv__place_capacity_disabled" android:textAlignment="viewStart" style="@style/PlacePageMetadataText" - tools:text="Capacity disabled" /> - \ No newline at end of file + tools:text="@string/capacity_disabled" /> + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index c2187ad09..b82d07c19 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1020,12 +1020,6 @@ Recalculating the route… Rebuilding route… - Number of spaces for disabled: %s - Accessible spaces for disabled - No spaces for disabled - Number of electric vehicle parking spaces: %s - Presence of parking spaces for electric vehicles - No spaces for electric vehicles Automatically open third-party links Open third-party links in CoMaps by default Keyboard incognito mode @@ -1035,4 +1029,10 @@ The app has not been installed from Google Play, you can\'t use it in your car. Press the setting to get more details on our FAQ. Recently Used + Number of disabled parking spaces: %s + Has disabled parking spaces + No disabled parking spaces + Spaces with chargers: %s + Has spaces reserved for electric vehicles + No spaces reserved for electric vehicles diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index ddd769d3e..1b271cb67 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -370,15 +370,6 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_rooms(std::string const { return v; } -std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity_disabled(std::string const & v) -{ - return v; -} - -std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity_charging(std::string const & v) -{ - return v; -} std::string MetadataTagProcessorImpl::ValidateAndFormat_charge(std::string v) { @@ -411,7 +402,13 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_brand(std::string const std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity(std::string const & v) { - return v; + strings::AsciiToLower(v); + if (v == "yes" || v == "no" || v == "unknown") + return v; + + strings::NormalizeDigits(v); + if (isdigit(v)) + return v; } std::string MetadataTagProcessorImpl::ValidateAndFormat_local_ref(std::string const & v) @@ -673,8 +670,8 @@ void MetadataTagProcessor::operator()(std::string const & k, std::string const & case Metadata::FMD_OUTDOOR_SEATING: valid = ValidateAndFormat_outdoor_seating(v); break; case Metadata::FMD_NETWORK: valid = ValidateAndFormat_operator(v); break; case Metadata::FMD_CHARGE_SOCKETS: m_chargeSockets.AggregateChargeSocketKey(k, v); break; - case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity_disabled(v); break; - case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity_charging(v); break; + case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity(v); break; + case Metadata::FMD_CAPACITY_CHARGING: ValidateAndFormat_capacity(v); break; // Metadata types we do not get from OSM. case Metadata::FMD_CUISINE: diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm index c7c6e2be7..da94add03 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm @@ -142,6 +142,18 @@ - (instancetype)initWithRawData:(Info const &)rawData ohLocalization:(id Date: Fri, 5 Jun 2026 22:40:17 +0200 Subject: [PATCH 06/40] [core] Fixed not building Signed-off-by: Yannik Bloscheck --- generator/osm2meta.cpp | 6 +++--- generator/osm2meta.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 1b271cb67..89102efac 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -400,15 +400,15 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_brand(std::string const return v; } -std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity(std::string const & v) +std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity(std::string v) { strings::AsciiToLower(v); if (v == "yes" || v == "no" || v == "unknown") return v; strings::NormalizeDigits(v); - if (isdigit(v)) - return v; + //if (isdigit(v)) + return v; } std::string MetadataTagProcessorImpl::ValidateAndFormat_local_ref(std::string const & v) diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index db53898e6..31c81063c 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -42,7 +42,7 @@ struct MetadataTagProcessorImpl std::string ValidateAndFormat_airport_iata(std::string const & v) const; static std::string ValidateAndFormat_brand(std::string const & v); std::string ValidateAndFormat_duration(std::string const & v) const; - static std::string ValidateAndFormat_capacity(std::string const & v); + static std::string ValidateAndFormat_capacity(std::string v); static std::string ValidateAndFormat_rooms(std::string const & v); static std::string ValidateAndFormat_charge(std::string v); static std::string ValidateAndFormat_drive_through(std::string v); From c846287039110fa865ea73a9fb17c3e2c19ae04b Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Fri, 5 Jun 2026 22:55:07 +0200 Subject: [PATCH 07/40] [ios] Make code work Signed-off-by: Yannik Bloscheck --- .../PlacePageData/Common/PlacePageInfoData.mm | 28 +++-- .../Contents.json | 12 ++ .../figure.roll.viewfinder.svg | 109 ++++++++++++++++++ .../Contents.json | 12 ++ .../powerplug.portrait.viewfinder.svg | 109 ++++++++++++++++++ .../en-GB.lproj/Localizable.strings | 6 +- .../en-GB.lproj/Localizable.stringsdict | 32 +++++ .../en.lproj/Localizable.strings | 6 +- .../en.lproj/Localizable.stringsdict | 32 +++++ .../PlacePageInfoViewController.swift | 16 +-- 10 files changed, 334 insertions(+), 28 deletions(-) create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/figure.roll.viewfinder.symbolset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/figure.roll.viewfinder.symbolset/figure.roll.viewfinder.svg create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/powerplug.portrait.viewfinder.svg diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm index da94add03..4c2040436 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm @@ -142,18 +142,22 @@ - (instancetype)initWithRawData:(Info const &)rawData ohLocalization:(id + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.7.0 + Requires Xcode 26 or greater + Generated from viewfinder + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/Contents.json b/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/Contents.json new file mode 100644 index 000000000..ab1bcd364 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "powerplug.portrait.viewfinder.svg", + "idiom" : "universal" + } + ] +} diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/powerplug.portrait.viewfinder.svg b/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/powerplug.portrait.viewfinder.svg new file mode 100644 index 000000000..edc016ef7 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/powerplug.portrait.viewfinder.symbolset/powerplug.portrait.viewfinder.svg @@ -0,0 +1,109 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.7.0 + Requires Xcode 26 or greater + Generated from viewfinder + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index 26bee3ad6..39b6ff4c6 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -879,12 +879,10 @@ "editor_add_select_category_recent_subtitle" = "Recently Used"; "oh_unknown" = "Opening hours unknown"; -/* To indicate the number of disabled spaces in a parking*/ -"capacity_disabled" = "Number of disabled parking spaces: %@"; +/* To indicate the number of disabled spaces in a parking */ "capacity_disabled_yes" = "Has disabled parking spaces"; "capacity_disabled_no" = "No disabled parking spaces"; -/* To indicate the number of charging spaces in a parking*/ -"capacity_charging" = "Spaces with chargers: %@"; +/* To indicate the number of charging spaces in a parking */ "capacity_charging_yes" = "Has spaces reserved for electric vehicles"; "capacity_charging_no" = "No spaces reserved for electric vehicles"; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.stringsdict b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.stringsdict index 7ca6067ba..be0246191 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.stringsdict +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.stringsdict @@ -88,5 +88,37 @@ %lld rooms + capacity_disabled + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld disabled parking spaces + one + %lld disabled parking space + + + capacity_charging + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld spaces with chargers + one + %lld space with chargers + + diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index 8d823f317..1f5a9d54a 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -880,12 +880,10 @@ "contribute_to_osm_update_map" = "Update the Map to Contribute"; "contribute_to_osm_update_map_description" = "You need to update the %@ map to the latest version before you can contribute to the OpenStreetMap data"; -/* To indicate the number of disabled spaces in a parking*/ -"capacity_disabled" = "Number of disabled parking spaces: %@"; +/* To indicate the number of disabled spaces in a parking */ "capacity_disabled_yes" = "Has disabled parking spaces"; "capacity_disabled_no" = "No disabled parking spaces"; -/* To indicate the number of charging spaces in a parking*/ -"capacity_charging" = "Spaces with chargers: %@"; +/* To indicate the number of charging spaces in a parking */ "capacity_charging_yes" = "Has spaces reserved for electric vehicles"; "capacity_charging_no" = "No spaces reserved for electric vehicles"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict index f636c3701..cf75f7fe7 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict @@ -84,5 +84,37 @@ %lld room + capacity_disabled + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld disabled parking spaces + one + %lld disabled parking space + + + capacity_charging + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld spaces with chargers + one + %lld space with chargers + + diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift index 45207be92..b9ed476ff 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift @@ -213,6 +213,14 @@ class PlacePageInfoViewController: UIViewController { if let capacity = placePageInfoData.capacity { capacityView = createInfoItem(capacity, icon: UIImage(systemName: "viewfinder")) } + + if let capacityCharging = placePageInfoData.capacityCharging { + capacityChargingView = createInfoItem(capacityCharging, icon: UIImage(named: "powerplug.portrait.viewfinder")) + } + + if let capacityDisabled = placePageInfoData.capacityDisabled { + capacityDisabledView = createInfoItem(capacityDisabled, icon: UIImage(named: "figure.roll.viewfinder")) + } if let rooms = placePageInfoData.rooms { roomsView = createInfoItem(rooms, icon: UIImage(systemName: "bed.double.fill")) @@ -225,14 +233,6 @@ class PlacePageInfoViewController: UIViewController { if let wheelchair = placePageInfoData.wheelchair { wheelchairView = createInfoItem(wheelchair, icon: UIImage(systemName: "figure.roll")) } - - if let capacityDisabled = placePageInfoData.capacityDisabled { - capacityDisabledView = createInfoItem(capacityDisabled, icon: UIImage(named: "ic_placepage_wheelchair")) - } - - if let capacityCharging = placePageInfoData.capacityCharging { - capacityChargingView = createInfoItem(capacityCharging, icon: UIImage(named: "ic_placepage__charging")) - } if let selfService = placePageInfoData.selfService { selfServiceView = createInfoItem(selfService, icon: UIImage(named: "service.slash")) From 5ce046cdd03735a13fd5db92a4034539dd945b37 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Fri, 5 Jun 2026 23:33:54 +0200 Subject: [PATCH 08/40] [core] More exact matching of special capacity tags Signed-off-by: Yannik Bloscheck --- libs/indexer/feature_meta.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/indexer/feature_meta.cpp b/libs/indexer/feature_meta.cpp index bc1316520..59f9a55ce 100644 --- a/libs/indexer/feature_meta.cpp +++ b/libs/indexer/feature_meta.cpp @@ -200,9 +200,9 @@ bool Metadata::TypeFromString(string_view k, Metadata::EType & outType) outType = Metadata::FMD_CHARGE; else if (k == "population") outType = Metadata::FMD_POPULATION; - else if (k.starts_with("capacity:disabled")) + else if (k == "capacity:disabled") outType = Metadata::FMD_CAPACITY_DISABLED; - else if (k.starts_with("capacity:charging")) + else if (k == "capacity:charging") outType = Metadata::FMD_CAPACITY_CHARGING; else return false; From c2e637c407b7e65332a5cea78540c9dff8708059 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Fri, 5 Jun 2026 23:52:37 +0200 Subject: [PATCH 09/40] [core] Fix validation not being set Signed-off-by: Yannik Bloscheck --- generator/osm2meta.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 89102efac..607197e95 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -670,8 +670,8 @@ void MetadataTagProcessor::operator()(std::string const & k, std::string const & case Metadata::FMD_OUTDOOR_SEATING: valid = ValidateAndFormat_outdoor_seating(v); break; case Metadata::FMD_NETWORK: valid = ValidateAndFormat_operator(v); break; case Metadata::FMD_CHARGE_SOCKETS: m_chargeSockets.AggregateChargeSocketKey(k, v); break; - case Metadata::FMD_CAPACITY_DISABLED: ValidateAndFormat_capacity(v); break; - case Metadata::FMD_CAPACITY_CHARGING: ValidateAndFormat_capacity(v); break; + case Metadata::FMD_CAPACITY_DISABLED: valid = ValidateAndFormat_capacity(v); break; + case Metadata::FMD_CAPACITY_CHARGING: valid = ValidateAndFormat_capacity(v); break; // Metadata types we do not get from OSM. case Metadata::FMD_CUISINE: From 9c166cc3be58e98de268c4e11770fab700870faf Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Sat, 6 Jun 2026 00:45:26 +0200 Subject: [PATCH 10/40] [core] Limit special capacity tags to certain values Signed-off-by: Yannik Bloscheck --- generator/osm2meta.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 607197e95..976ca7eec 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -407,8 +407,11 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity(std::string v) return v; strings::NormalizeDigits(v); - //if (isdigit(v)) - return v; + unsigned int i = 0; + if (strings::to_uint(v, i)) + return v; + else + return {}; } std::string MetadataTagProcessorImpl::ValidateAndFormat_local_ref(std::string const & v) From a2a661fd4d101b8d041db0d6580c933d2ac697e7 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Sat, 6 Jun 2026 10:28:45 +0200 Subject: [PATCH 11/40] [core] Drop unnecessary unknown case Signed-off-by: Yannik Bloscheck --- generator/osm2meta.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 976ca7eec..f3bc8cd07 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -403,7 +403,7 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_brand(std::string const std::string MetadataTagProcessorImpl::ValidateAndFormat_capacity(std::string v) { strings::AsciiToLower(v); - if (v == "yes" || v == "no" || v == "unknown") + if (v == "yes" || v == "no") return v; strings::NormalizeDigits(v); From 88b4d3df8f9501845b5444c34537ac06034897f9 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Sat, 6 Jun 2026 11:18:34 +0200 Subject: [PATCH 12/40] [ios] Make capacity translation more neutral and unified Signed-off-by: Yannik Bloscheck --- .../PlacePageData/Common/PlacePageInfoData.mm | 2 +- .../af.lproj/Localizable.strings | 2 -- .../ar.lproj/Localizable.strings | 2 -- .../az.lproj/Localizable.strings | 2 -- .../be.lproj/Localizable.strings | 2 -- .../bg.lproj/Localizable.strings | 2 -- .../ca.lproj/Localizable.strings | 2 -- .../cs.lproj/Localizable.strings | 2 -- .../da.lproj/Localizable.strings | 2 -- .../de.lproj/Localizable.strings | 2 -- .../el.lproj/Localizable.strings | 2 -- .../en-GB.lproj/Localizable.strings | 14 +++++------- .../en-GB.lproj/Localizable.stringsdict | 22 ++++++++++++++++--- .../en.lproj/Localizable.strings | 14 +++++------- .../en.lproj/Localizable.stringsdict | 22 ++++++++++++++++--- .../es-MX.lproj/Localizable.strings | 2 -- .../es.lproj/Localizable.strings | 2 -- .../et.lproj/Localizable.strings | 2 -- .../eu.lproj/Localizable.strings | 2 -- .../fa.lproj/Localizable.strings | 2 -- .../fi.lproj/Localizable.strings | 2 -- .../fr.lproj/Localizable.strings | 2 -- .../he.lproj/Localizable.strings | 2 -- .../hi.lproj/Localizable.strings | 2 -- .../hu.lproj/Localizable.strings | 2 -- .../id.lproj/Localizable.strings | 2 -- .../it.lproj/Localizable.strings | 2 -- .../ja.lproj/Localizable.strings | 2 -- .../ko.lproj/Localizable.strings | 2 -- .../lt.lproj/Localizable.strings | 2 -- .../lv.lproj/Localizable.strings | 2 -- .../mr.lproj/Localizable.strings | 2 -- .../mt.lproj/Localizable.strings | 2 -- .../nb.lproj/Localizable.strings | 2 -- .../nl.lproj/Localizable.strings | 2 -- .../pl.lproj/Localizable.strings | 2 -- .../pt-BR.lproj/Localizable.strings | 2 -- .../pt.lproj/Localizable.strings | 2 -- .../ro.lproj/Localizable.strings | 2 -- .../ru.lproj/Localizable.strings | 2 -- .../sk.lproj/Localizable.strings | 2 -- .../sr.lproj/Localizable.strings | 2 -- .../sv.lproj/Localizable.strings | 2 -- .../sw.lproj/Localizable.strings | 2 -- .../th.lproj/Localizable.strings | 2 -- .../tr.lproj/Localizable.strings | 2 -- .../uk.lproj/Localizable.strings | 2 -- .../vi.lproj/Localizable.strings | 2 -- .../zh-Hans.lproj/Localizable.strings | 2 -- .../zh-Hant.lproj/Localizable.strings | 2 -- 50 files changed, 51 insertions(+), 113 deletions(-) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm index 4c2040436..2496334ad 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm @@ -138,7 +138,7 @@ - (instancetype)initWithRawData:(Info const &)rawData ohLocalization:(idNSStringFormatValueTypeKey lld other - %lld disabled parking spaces + %lld dedicated disabled spaces one - %lld disabled parking space + %lld dedicated disabled space capacity_charging @@ -117,7 +117,23 @@ other %lld spaces with chargers one - %lld space with chargers + %lld space with a charger + + + capacity + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld spaces + one + %lld space diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index 1f5a9d54a..56b4b791f 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -690,8 +690,6 @@ "yes_available" = "Yes"; /* E.g. "WiFi:No" */ "no_available" = "No"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacity: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Network: %@"; "trip_finished" = "You have arrived!"; @@ -880,10 +878,10 @@ "contribute_to_osm_update_map" = "Update the Map to Contribute"; "contribute_to_osm_update_map_description" = "You need to update the %@ map to the latest version before you can contribute to the OpenStreetMap data"; -/* To indicate the number of disabled spaces in a parking */ -"capacity_disabled_yes" = "Has disabled parking spaces"; -"capacity_disabled_no" = "No disabled parking spaces"; +/* To indicate the number of disabled spaces */ +"capacity_disabled_yes" = "Has dedicated disabled spaces"; +"capacity_disabled_no" = "No dedicated disabled spaces"; -/* To indicate the number of charging spaces in a parking */ -"capacity_charging_yes" = "Has spaces reserved for electric vehicles"; -"capacity_charging_no" = "No spaces reserved for electric vehicles"; +/* To indicate the number of charging spaces */ +"capacity_charging_yes" = "Has spaces with chargers"; +"capacity_charging_no" = "No spaces with chargers"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict index cf75f7fe7..68207e057 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.stringsdict @@ -95,9 +95,9 @@ NSStringFormatValueTypeKey lld other - %lld disabled parking spaces + %lld dedicated disabled spaces one - %lld disabled parking space + %lld dedicated disabled space capacity_charging @@ -113,7 +113,23 @@ other %lld spaces with chargers one - %lld space with chargers + %lld space with a charger + + + capacity + + NSStringLocalizedFormatKey + %#@nevertranslate@ + nevertranslate + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + lld + other + %lld spaces + one + %lld space diff --git a/iphone/Maps/LocalizedStrings/es-MX.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/es-MX.lproj/Localizable.strings index 56a7e5646..db7a74d7c 100644 --- a/iphone/Maps/LocalizedStrings/es-MX.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/es-MX.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Sí"; /* E.g. "WiFi:No" */ "no_available" = "No"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacidad: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Red: %@"; "trip_finished" = "¡Ya llegó!"; diff --git a/iphone/Maps/LocalizedStrings/es.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/es.lproj/Localizable.strings index 80bb363f3..405e51ee5 100644 --- a/iphone/Maps/LocalizedStrings/es.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/es.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Sí"; /* E.g. "WiFi:No" */ "no_available" = "No"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacidad: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Red: %@"; "trip_finished" = "¡Has llegado!"; diff --git a/iphone/Maps/LocalizedStrings/et.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/et.lproj/Localizable.strings index fdc6e0daa..57fb648c7 100644 --- a/iphone/Maps/LocalizedStrings/et.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/et.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "olemas"; /* E.g. "WiFi:No" */ "no_available" = "puudub"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kohti: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Võrgustik: %@"; "trip_finished" = "Oled kohal!"; diff --git a/iphone/Maps/LocalizedStrings/eu.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/eu.lproj/Localizable.strings index ab072d1e2..b9ed43bdd 100644 --- a/iphone/Maps/LocalizedStrings/eu.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/eu.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Bai"; /* E.g. "WiFi:No" */ "no_available" = "Ez"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Edukiera: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Sarea: %@"; "trip_finished" = "Iritsi zara!"; diff --git a/iphone/Maps/LocalizedStrings/fa.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/fa.lproj/Localizable.strings index 00dffa429..69648749c 100644 --- a/iphone/Maps/LocalizedStrings/fa.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/fa.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "بلی"; /* E.g. "WiFi:No" */ "no_available" = "خیر"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "ظرفیت: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "شبکه: %@"; "trip_finished" = "شما به مقصد رسیدید!"; diff --git a/iphone/Maps/LocalizedStrings/fi.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/fi.lproj/Localizable.strings index 376121ff2..d79b2eb7f 100644 --- a/iphone/Maps/LocalizedStrings/fi.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/fi.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Kyllä"; /* E.g. "WiFi:No" */ "no_available" = "Ei"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapasiteetti: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Verkko: %@"; "trip_finished" = "Olet perillä!"; diff --git a/iphone/Maps/LocalizedStrings/fr.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/fr.lproj/Localizable.strings index 9fbbd8d68..37897cb5d 100644 --- a/iphone/Maps/LocalizedStrings/fr.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/fr.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Oui"; /* E.g. "WiFi:No" */ "no_available" = "Non"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacité : %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Réseau : %@"; "trip_finished" = "Vous êtes arrivé(e) !"; diff --git a/iphone/Maps/LocalizedStrings/he.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/he.lproj/Localizable.strings index c013d2834..9c683f4f4 100644 --- a/iphone/Maps/LocalizedStrings/he.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/he.lproj/Localizable.strings @@ -673,8 +673,6 @@ "yes_available" = "כן"; /* E.g. "WiFi:No" */ "no_available" = "לא"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "קיבולת: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "רשת: %@"; "trip_finished" = "הגעת!"; diff --git a/iphone/Maps/LocalizedStrings/hi.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/hi.lproj/Localizable.strings index 9264eddfc..03338000a 100644 --- a/iphone/Maps/LocalizedStrings/hi.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/hi.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "हॉं"; /* E.g. "WiFi:No" */ "no_available" = "नहीं"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "क्षमता: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "नेटवर्क: %@"; "trip_finished" = "You have arrived!"; diff --git a/iphone/Maps/LocalizedStrings/hu.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/hu.lproj/Localizable.strings index b5afd92c1..d98538d92 100644 --- a/iphone/Maps/LocalizedStrings/hu.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/hu.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Igen"; /* E.g. "WiFi:No" */ "no_available" = "Nem"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapacitás: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Hálózat: %@"; "trip_finished" = "Megérkezett!"; diff --git a/iphone/Maps/LocalizedStrings/id.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/id.lproj/Localizable.strings index fffa5010e..1863ebdc8 100644 --- a/iphone/Maps/LocalizedStrings/id.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/id.lproj/Localizable.strings @@ -679,8 +679,6 @@ "yes_available" = "Ya"; /* E.g. "WiFi:No" */ "no_available" = "Tidak"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapasitas: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Jaringan: %@"; "trip_finished" = "Anda telah sampai!"; diff --git a/iphone/Maps/LocalizedStrings/it.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/it.lproj/Localizable.strings index f46b7d4b3..0645f97d3 100644 --- a/iphone/Maps/LocalizedStrings/it.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/it.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Sì"; /* E.g. "WiFi:No" */ "no_available" = "No"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacità: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Rete: %@"; "trip_finished" = "Sei arrivato!"; diff --git a/iphone/Maps/LocalizedStrings/ja.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/ja.lproj/Localizable.strings index a3441487e..7602b272b 100644 --- a/iphone/Maps/LocalizedStrings/ja.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/ja.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "はい"; /* E.g. "WiFi:No" */ "no_available" = "いいえ"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "定員:%@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "ネットワーク: %@"; "trip_finished" = "到着しました!"; diff --git a/iphone/Maps/LocalizedStrings/ko.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/ko.lproj/Localizable.strings index fbb200f1f..ce8f2b53c 100644 --- a/iphone/Maps/LocalizedStrings/ko.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/ko.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "네"; /* E.g. "WiFi:No" */ "no_available" = "아니오"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "수용 인원: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "네트워크: %@"; "trip_finished" = "도착했습니다!"; diff --git a/iphone/Maps/LocalizedStrings/lt.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/lt.lproj/Localizable.strings index 4e9a2eabe..03c1ecc12 100644 --- a/iphone/Maps/LocalizedStrings/lt.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/lt.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Taip"; /* E.g. "WiFi:No" */ "no_available" = "Ne"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Talpa: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Tinklas: %@"; "trip_finished" = "Jūs pasiekėte kelionės tikslą!"; diff --git a/iphone/Maps/LocalizedStrings/lv.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/lv.lproj/Localizable.strings index 217be0148..0f0d621d9 100644 --- a/iphone/Maps/LocalizedStrings/lv.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/lv.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Pieejams"; /* E.g. "WiFi:No" */ "no_available" = "Nav pieejams"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Ietilpība: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Tīkls: %@"; "trip_finished" = "Ieradāties galamērķī!"; diff --git a/iphone/Maps/LocalizedStrings/mr.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/mr.lproj/Localizable.strings index ec9da331e..a5d478280 100644 --- a/iphone/Maps/LocalizedStrings/mr.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/mr.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "होय"; /* E.g. "WiFi:No" */ "no_available" = "नाही"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "क्षमता: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "नेटवर्क: %@"; "trip_finished" = "तुम्ही पोचलात!"; diff --git a/iphone/Maps/LocalizedStrings/mt.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/mt.lproj/Localizable.strings index 9489db2a2..54ffed6d3 100644 --- a/iphone/Maps/LocalizedStrings/mt.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/mt.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Iva"; /* E.g. "WiFi:No" */ "no_available" = "Le"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacity: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Network: %@"; "trip_finished" = "Wasalt!"; diff --git a/iphone/Maps/LocalizedStrings/nb.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/nb.lproj/Localizable.strings index c33d1dfd0..318c83669 100644 --- a/iphone/Maps/LocalizedStrings/nb.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/nb.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Ja"; /* E.g. "WiFi:No" */ "no_available" = "Nei"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapasitet: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Nettverk: %@"; "trip_finished" = "Du har ankommet!"; diff --git a/iphone/Maps/LocalizedStrings/nl.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/nl.lproj/Localizable.strings index 1037ddc25..a637d52c4 100644 --- a/iphone/Maps/LocalizedStrings/nl.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/nl.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Ja"; /* E.g. "WiFi:No" */ "no_available" = "Nee"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capaciteit: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Netwerk: %@"; "trip_finished" = "Bestemming bereikt!"; diff --git a/iphone/Maps/LocalizedStrings/pl.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/pl.lproj/Localizable.strings index 873a37897..06df23cb7 100644 --- a/iphone/Maps/LocalizedStrings/pl.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/pl.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Tak"; /* E.g. "WiFi:No" */ "no_available" = "Nie"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Pojemność: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Sieć: %@"; "trip_finished" = "Jesteś na miejscu!"; diff --git a/iphone/Maps/LocalizedStrings/pt-BR.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/pt-BR.lproj/Localizable.strings index f9ff5ed0c..641423c88 100644 --- a/iphone/Maps/LocalizedStrings/pt-BR.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/pt-BR.lproj/Localizable.strings @@ -688,8 +688,6 @@ "yes_available" = "Sim"; /* E.g. "WiFi:No" */ "no_available" = "Não"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacidade: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Rede: %@"; "trip_finished" = "Você chegou!"; diff --git a/iphone/Maps/LocalizedStrings/pt.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/pt.lproj/Localizable.strings index fdb3600aa..ecfd8a880 100644 --- a/iphone/Maps/LocalizedStrings/pt.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/pt.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Sim"; /* E.g. "WiFi:No" */ "no_available" = "Não"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacidade: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Rede: %@"; "trip_finished" = "Chegou ao destino!"; diff --git a/iphone/Maps/LocalizedStrings/ro.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/ro.lproj/Localizable.strings index c3dcf395a..6f73d8ed2 100644 --- a/iphone/Maps/LocalizedStrings/ro.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/ro.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Da"; /* E.g. "WiFi:No" */ "no_available" = "Nu"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Capacitate: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Rețea: %@"; "trip_finished" = "Ai ajuns!"; diff --git a/iphone/Maps/LocalizedStrings/ru.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/ru.lproj/Localizable.strings index ba21445cc..ac845c76c 100644 --- a/iphone/Maps/LocalizedStrings/ru.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/ru.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Есть"; /* E.g. "WiFi:No" */ "no_available" = "Нет"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Вместимость: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Сеть: %@"; "trip_finished" = "Вы прибыли!"; diff --git a/iphone/Maps/LocalizedStrings/sk.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/sk.lproj/Localizable.strings index 248738b81..0d4acbfa2 100644 --- a/iphone/Maps/LocalizedStrings/sk.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/sk.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Áno"; /* E.g. "WiFi:No" */ "no_available" = "Nie"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapacita: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Sieť: %@"; "trip_finished" = "Prišli ste do cieľa!"; diff --git a/iphone/Maps/LocalizedStrings/sr.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/sr.lproj/Localizable.strings index cca6c3dbf..eb513014d 100644 --- a/iphone/Maps/LocalizedStrings/sr.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/sr.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Да"; /* E.g. "WiFi:No" */ "no_available" = "Не"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Број места: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Мрежа: %@"; "trip_finished" = "Стигли сте!"; diff --git a/iphone/Maps/LocalizedStrings/sv.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/sv.lproj/Localizable.strings index 6da513fdd..a86b01ef6 100644 --- a/iphone/Maps/LocalizedStrings/sv.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/sv.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Ja"; /* E.g. "WiFi:No" */ "no_available" = "Nej"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapacitet: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Nätverk: %@"; "trip_finished" = "Du är framme!"; diff --git a/iphone/Maps/LocalizedStrings/sw.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/sw.lproj/Localizable.strings index be1b16712..2810cc782 100644 --- a/iphone/Maps/LocalizedStrings/sw.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/sw.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Ndio"; /* E.g. "WiFi:No" */ "no_available" = "Hapana"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Uwezo: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Mtandao: %@"; "trip_finished" = "Umefika!"; diff --git a/iphone/Maps/LocalizedStrings/th.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/th.lproj/Localizable.strings index 5f40b1f4b..320288df3 100644 --- a/iphone/Maps/LocalizedStrings/th.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/th.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "ใช่"; /* E.g. "WiFi:No" */ "no_available" = "ไม่ใช่"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "ความจุ: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "เครือข่าย: %@"; "trip_finished" = "คุณมาถึงแล้ว!"; diff --git a/iphone/Maps/LocalizedStrings/tr.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/tr.lproj/Localizable.strings index cd47a902e..6cead9885 100644 --- a/iphone/Maps/LocalizedStrings/tr.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/tr.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Var"; /* E.g. "WiFi:No" */ "no_available" = "Yok"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Kapasite: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Ağ: %@"; "trip_finished" = "Vardınız!"; diff --git a/iphone/Maps/LocalizedStrings/uk.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/uk.lproj/Localizable.strings index cd2b8d929..43e503cfa 100644 --- a/iphone/Maps/LocalizedStrings/uk.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/uk.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Так"; /* E.g. "WiFi:No" */ "no_available" = "Ні"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Місткість: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Мережа: %@"; "trip_finished" = "Ви прибули!"; diff --git a/iphone/Maps/LocalizedStrings/vi.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/vi.lproj/Localizable.strings index 1d11765c5..1308a8daf 100644 --- a/iphone/Maps/LocalizedStrings/vi.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/vi.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "Có"; /* E.g. "WiFi:No" */ "no_available" = "Không"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "Công suất: %@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "Mạng: %@"; "trip_finished" = "Bạn đã tới nơi!"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/Localizable.strings index 2fa86c27e..783b7addb 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "是"; /* E.g. "WiFi:No" */ "no_available" = "否"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "容量:%@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "网络:%@"; "trip_finished" = "您已到达目的地!"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/Localizable.strings index 73d301e84..4234ba114 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/Localizable.strings @@ -681,8 +681,6 @@ "yes_available" = "是"; /* E.g. "WiFi:No" */ "no_available" = "否"; -/* To indicate the capacity of car parkings, bicycle parkings, electric vehicle charging stations... */ -"capacity" = "容量:%@"; /* To indicate the network of ATMs, bicycle rentals, electric vehicle charging stations... */ "network" = "網路: %@"; "trip_finished" = "您已到達目的地!"; From 25a8a5be6cab6c4063ee97f9b44e4eb593cf6e0e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Fri, 5 Jun 2026 17:47:35 +0200 Subject: [PATCH 13/40] [generator] Add support of subject:wikipedia Signed-off-by: Jean-Baptiste --- libs/indexer/feature_meta.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/indexer/feature_meta.cpp b/libs/indexer/feature_meta.cpp index 59f9a55ce..2957c91f4 100644 --- a/libs/indexer/feature_meta.cpp +++ b/libs/indexer/feature_meta.cpp @@ -150,7 +150,7 @@ bool Metadata::TypeFromString(string_view k, Metadata::EType & outType) // Process only _main_ tag here, needed for editor ser/des. Actual postcode parsing happens in GetNameAndType. else if (k == "addr:postcode") outType = Metadata::FMD_POSTCODE; - else if (k == "wikipedia") + else if (k == "wikipedia" || k == "subject:wikipedia") outType = Metadata::FMD_WIKIPEDIA; else if (k == "wikimedia_commons") outType = Metadata::FMD_WIKIMEDIA_COMMONS; From 1cd747d808f07103586c10170487783b1fe3afee Mon Sep 17 00:00:00 2001 From: matheusgomesms Date: Mon, 25 May 2026 12:24:42 +0200 Subject: [PATCH 14/40] [generator] Add traffic lights direction Signed-off-by: matheusgomesms --- generator/osm2type.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/generator/osm2type.cpp b/generator/osm2type.cpp index c8db4956d..c49233459 100644 --- a/generator/osm2type.cpp +++ b/generator/osm2type.cpp @@ -280,6 +280,8 @@ class CachedTypes SmallChargingStation, MotorcarChargingStation, CarlessChargingStation, + TrafficSignalForward, + TrafficSignalBackward, Count }; @@ -325,6 +327,8 @@ class CachedTypes {SmallChargingStation, {"amenity", "charging_station", "small"}}, {MotorcarChargingStation, {"amenity", "charging_station", "motorcar"}}, {CarlessChargingStation, {"amenity", "charging_station", "carless"}}, + {TrafficSignalForward, {"highway", "traffic_signals", "forward"}}, + {TrafficSignalBackward, {"highway", "traffic_signals", "backward"}}, }; m_types.resize(static_cast(Count)); @@ -1389,6 +1393,17 @@ void PostprocessElement(OsmElement * p, FeatureBuilderParams & params) params.PopExactType(smallChargingStation); } } + + uint32_t const trafficSignalBase = classif().GetTypeByPath({"highway", "traffic_signals"}); + if (params.IsTypeExist(trafficSignalBase)) + { + TagProcessor(p).ApplyRules({ + {"traffic_signals:direction", "forward", + [&] { params.AddType(types.Get(CachedTypes::TrafficSignalForward)); }}, + {"traffic_signals:direction", "backward", + [&] { params.AddType(types.Get(CachedTypes::TrafficSignalBackward)); }}, + }); + } } } // namespace From c5e2718715997c4e1bd18ab67b7af43c6a6dd03c Mon Sep 17 00:00:00 2001 From: matheusgomesms Date: Mon, 25 May 2026 12:24:54 +0200 Subject: [PATCH 15/40] [core] Display only traffic lights en route and same direction Signed-off-by: matheusgomesms --- libs/map/routing_manager.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp index 671240c63..d81146474 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -624,28 +624,45 @@ void RoutingManager::CollectFeaturesAlongRoute(vector const & segm feature::TypesHolder types(ft); // Does this feature match the type we passed in? - if (!types.Has(featureType)) + if (!types.HasWithSubclass(featureType)) return; m2::PointD const pt = feature::GetCenter(ft); + // Find the closest route segment and record whether the route + // travels in the forward direction along the underlying road way. double minSqDist = numeric_limits::max(); + bool closestSegIsForward = true; m2::PointD prev = startPt; for (auto const & s : segments) { m2::PointD const curr = s.GetJunction().GetPoint(); m2::ParametrizedSegment seg(prev, curr); - minSqDist = min(minSqDist, seg.SquaredDistanceToPoint(pt)); - - // Performance break + double const d = seg.SquaredDistanceToPoint(pt); + if (d < minSqDist) + { + minSqDist = d; + closestSegIsForward = s.GetSegment().IsForward(); + } if (minSqDist < kSearchRadiusMercator * kSearchRadiusMercator) break; prev = curr; } - if (minSqDist < kSearchRadiusMercator * kSearchRadiusMercator) - outFeatures.emplace_back(pt, ft.GetID()); + if (minSqDist >= kSearchRadiusMercator * kSearchRadiusMercator) + return; + + // If the signal has a direction subtype, only include it when it + // faces the direction the route is travelling on that road. + static uint32_t const kForwardType = classif().GetTypeByPath({"highway", "traffic_signals", "forward"}); + static uint32_t const kBackwardType = classif().GetTypeByPath({"highway", "traffic_signals", "backward"}); + if (types.Has(kForwardType) && !closestSegIsForward) + return; + if (types.Has(kBackwardType) && closestSegIsForward) + return; + + outFeatures.emplace_back(pt, ft.GetID()); }, queryRect, scales::GetUpperScale()); }; From 46761872e40edfa8d339a042dafaafa230e8709b Mon Sep 17 00:00:00 2001 From: bmgru Date: Mon, 8 Jun 2026 11:13:28 +0200 Subject: [PATCH 16/40] Rhone-Alpes step10 isolines Signed-off-by: bmgru --- data/conf/isolines/countries-to-generate.json | 18 +++++++++--------- data/conf/isolines/isolines-profiles.json | 12 ++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/data/conf/isolines/countries-to-generate.json b/data/conf/isolines/countries-to-generate.json index 5346c4633..a22755ae7 100644 --- a/data/conf/isolines/countries-to-generate.json +++ b/data/conf/isolines/countries-to-generate.json @@ -2852,7 +2852,7 @@ { "key": "France_Rhone-Alpes_Ain", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2860,7 +2860,7 @@ { "key": "France_Rhone-Alpes_Ardeche", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2868,7 +2868,7 @@ { "key": "France_Rhone-Alpes_Drome", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2876,7 +2876,7 @@ { "key": "France_Rhone-Alpes_Haute-Savoie", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2884,7 +2884,7 @@ { "key": "France_Rhone-Alpes_Isere", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2892,7 +2892,7 @@ { "key": "France_Rhone-Alpes_Loire", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2900,7 +2900,7 @@ { "key": "France_Rhone-Alpes_Rhone", "value": { - "profileName": "high", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -2908,7 +2908,7 @@ { "key": "France_Rhone-Alpes_Savoie", "value": { - "profileName": "high_f2", + "profileName": "high10", "tileCoordsSubset": [], "tilesAreBanned": false } @@ -9949,4 +9949,4 @@ } } ] -} \ No newline at end of file +} diff --git a/data/conf/isolines/isolines-profiles.json b/data/conf/isolines/isolines-profiles.json index c9eb4eacf..d3de4457a 100644 --- a/data/conf/isolines/isolines-profiles.json +++ b/data/conf/isolines/isolines-profiles.json @@ -1,5 +1,17 @@ { "profiles": [ + { + "key": "high10", + "value": { + "alitudesStep": 10, + "gaussianFilterRFactor": 1.0, + "gaussianFilterStDev": 2.0, + "latLonStepFactor": 1, + "maxIsolinesLength": 500, + "medianFilterR": 1, + "simplificationZoom": 17 + } + }, { "key": "high", "value": { From 619e1694778fcc60c4d3fe5f3d00cf6a81cf226a Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Sun, 26 Apr 2026 17:08:37 -0700 Subject: [PATCH 17/40] [addresses] Add OpenAddresses Canada preprocessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Python preprocessor that converts an OpenAddresses Canada ZIP (collection-ca.zip) into per-MWM-region .tempaddr binary files compatible with the existing AddressEnricher pipeline introduced in OM PR #8502. Fixes bug in AddressEnricher where discrete-point addresses (m_from == m_to) produced labels like "42 - 42" instead of "42". Changes: - tools/python/maps_generator/generator/openaddresses_preprocessor.py: new CLI + importable module; reads ca/countrywide-addresses-country.geojson from the ZIP, maps province codes to MWM region names via countries.txt, writes one .tempaddr file per MWM sub-region (e.g. Canada_British Columbia_Vancouver.tempaddr). Pass --countries-txt to split into sub-regions; omit for province-level files. - tools/python/maps_generator/tests/test_openaddresses_preprocessor.py: 53 unit tests covering all binary encoding helpers and record round-trip. - generator/address_enricher.cpp: fix m_from == m_to producing "42 - 42" labels. - generator/generator_tests/addresses_tests.cpp: unit test for the above fix. Tested end-to-end: BC o5m + Canada_British Columbia_Vancouver.tempaddr → m_addedSingle=594619 addresses injected into the MWM. Closes #723 Signed-off-by: lone-cloud --- generator/address_enricher.cpp | 3 +- generator/generator_tests/addresses_tests.cpp | 11 + .../generator/openaddresses_preprocessor.py | 236 +++++++++++++ .../tests/test_openaddresses_preprocessor.py | 333 ++++++++++++++++++ 4 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 tools/python/maps_generator/generator/openaddresses_preprocessor.py create mode 100644 tools/python/maps_generator/tests/test_openaddresses_preprocessor.py diff --git a/generator/address_enricher.cpp b/generator/address_enricher.cpp index 804ed224f..d3cf8c02f 100644 --- a/generator/address_enricher.cpp +++ b/generator/address_enricher.cpp @@ -154,7 +154,8 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c if (!res.from && !res.to && res.addrsInside == 0) { ++m_stats.m_addedSingle; - addNode(e.m_points.front(), e.m_from + " - " + e.m_to); + auto const hn = (e.m_from == e.m_to) ? e.m_from : e.m_from + " - " + e.m_to; + addNode(e.m_points.front(), hn); } else ++m_stats.m_existSingle; diff --git a/generator/generator_tests/addresses_tests.cpp b/generator/generator_tests/addresses_tests.cpp index 7101685d5..63b1a9540 100644 --- a/generator/generator_tests/addresses_tests.cpp +++ b/generator/generator_tests/addresses_tests.cpp @@ -18,3 +18,14 @@ UNIT_TEST(GenerateAddresses_AddressInfo_FormatRange) info.m_house2 = "bar"; TEST_EQUAL(info.FormatRange(), "", ()); } + +UNIT_TEST(AddressEnricher_DiscretePoint_HouseNumberLabel) +{ + auto makeLabel = [](std::string const & from, std::string const & to) -> std::string + { return (from == to) ? from : from + " - " + to; }; + + TEST_EQUAL(makeLabel("42", "42"), "42", ()); + TEST_EQUAL(makeLabel("100", "100"), "100", ()); + TEST_EQUAL(makeLabel("100", "198"), "100 - 198", ()); + TEST_EQUAL(makeLabel("1", "99"), "1 - 99", ()); +} diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py new file mode 100644 index 000000000..689bd5bea --- /dev/null +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +"""Converts an OpenAddresses Canada ZIP to CoMaps .tempaddr address files.""" + +import argparse +import io +import json +import math +import os +import sys +import zipfile + +_COORD_BITS: int = 30 +_COORD_SIZE: float = (1 << _COORD_BITS) - 1 + +_MIN_X: float = -180.0 +_MAX_X: float = 180.0 +_MIN_Y: float = -180.0 +_MAX_Y: float = 180.0 + +_INTERPOL_NONE: int = 0 + +_REGION_TO_MWM_PREFIX: dict[str, str] = { + "AB": "Canada_Alberta", + "BC": "Canada_British Columbia", + "MB": "Canada_Manitoba", + "NB": "Canada_New Brunswick", + "NL": "Canada_Newfoundland and Labrador", + "NS": "Canada_Nova Scotia", + "NT": "Canada_Northwest Territories", + "NU": "Canada_Nunavut", + "ON": "Canada_Ontario", + "PE": "Canada_Prince Edward Island", + "QC": "Canada_Quebec", + "SK": "Canada_Saskatchewan", + "YT": "Canada_Yukon", +} + +_GEOJSON_PATH: str = "ca/countrywide-addresses-country.geojson" +_ADDR_EXT: str = ".tempaddr" + + +def _write_varuint(f: io.RawIOBase, value: int) -> None: + value = int(value) & 0xFFFFFFFFFFFFFFFF + while value > 127: + f.write(bytes([(value & 127) | 128])) + value >>= 7 + f.write(bytes([value])) + + +def _zigzag_encode(v: int) -> int: + v = int(v) + return ((v << 1) ^ (v >> 63)) & 0xFFFFFFFFFFFFFFFF + + +def _write_varint(f: io.RawIOBase, value: int) -> None: + _write_varuint(f, _zigzag_encode(value)) + + +def _write_string(f: io.RawIOBase, s: str) -> None: + encoded = s.encode("utf-8") + _write_varuint(f, len(encoded)) + if encoded: + f.write(encoded) + + +def _lat_to_y(lat: float) -> float: + lat = max(-86.0, min(86.0, lat)) + sinx = math.sin(math.radians(lat)) + res = math.degrees(0.5 * math.log((1.0 + sinx) / (1.0 - sinx))) + return max(_MIN_Y, min(_MAX_Y, res)) + + +def _double_to_uint32(x: float, min_v: float, max_v: float) -> int: + if x <= min_v: + d = 0.0 + elif x >= max_v: + d = _COORD_SIZE + else: + d = (x - min_v) / (max_v - min_v) * _COORD_SIZE + return int(0.5 + d) + + +def _perfect_shuffle(x: int) -> int: + x &= 0xFFFFFFFFFFFFFFFF + x = ((x & 0x00000000FFFF0000) << 16) | ((x >> 16) & 0x00000000FFFF0000) | (x & 0xFFFF00000000FFFF) + x = ((x & 0x0000FF000000FF00) << 8) | ((x >> 8) & 0x0000FF000000FF00) | (x & 0xFF0000FFFF0000FF) + x = ((x & 0x00F000F000F000F0) << 4) | ((x >> 4) & 0x00F000F000F000F0) | (x & 0xF00FF00FF00FF00F) + x = ((x & 0x0C0C0C0C0C0C0C0C) << 2) | ((x >> 2) & 0x0C0C0C0C0C0C0C0C) | (x & 0xC3C3C3C3C3C3C3C3) + x = ((x & 0x2222222222222222) << 1) | ((x >> 1) & 0x2222222222222222) | (x & 0x9999999999999999) + return x & 0xFFFFFFFFFFFFFFFF + + +def _point_to_int64(lon: float, lat: float) -> int: + ux = _double_to_uint32(lon, _MIN_X, _MAX_X) + uy = _double_to_uint32(_lat_to_y(lat), _MIN_Y, _MAX_Y) + return int(_perfect_shuffle((uy << 32) | ux)) + + +def _collect_mwm_names(countries_txt: str) -> list[str]: + with open(countries_txt, encoding="utf-8") as f: + root = json.load(f) + names: list[str] = [] + stack = [root] + while stack: + node = stack.pop() + if isinstance(node, dict): + n = node.get("id", "") + if n: + names.append(n) + v = node.get("g", node.get("v", [])) + if isinstance(v, list): + stack.extend(v) + elif isinstance(node, list): + stack.extend(node) + return names + + +def _build_region_mwm_map(countries_txt: str | None) -> dict[str, list[str]]: + if countries_txt is not None: + all_names = _collect_mwm_names(countries_txt) + else: + all_names = None + + result: dict[str, list[str]] = {} + for region, prefix in _REGION_TO_MWM_PREFIX.items(): + if all_names is None: + result[region] = [prefix] + else: + matches = [ + n for n in all_names + if n == prefix or n.startswith(prefix + "_") + ] + result[region] = matches if matches else [prefix] + return result + + +def process(zip_path: str, output_dir: str, countries_txt: str | None = None) -> None: + os.makedirs(output_dir, exist_ok=True) + + region_to_mwms = _build_region_mwm_map(countries_txt) + + writers: dict[str, io.BufferedWriter] = {} + + def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: + result = [] + for name in mwm_names: + if name not in writers: + path = os.path.join(output_dir, name + _ADDR_EXT) + writers[name] = open(path, "wb") + result.append(writers[name]) + return result + + total = 0 + skipped_incomplete = 0 + skipped_region = 0 + + try: + with zipfile.ZipFile(zip_path, "r") as zf: + with zf.open(_GEOJSON_PATH) as raw: + for line_bytes in raw: + line_bytes = line_bytes.rstrip(b"\n\r") + if not line_bytes: + continue + total += 1 + + try: + feat = json.loads(line_bytes) + except json.JSONDecodeError: + skipped_incomplete += 1 + continue + + props = feat.get("properties", {}) + number = props.get("number", "").strip() + street = props.get("street", "").strip() + postcode = props.get("postcode", "").strip() + region = props.get("region", "").strip().upper() + + if not number or not street: + skipped_incomplete += 1 + continue + + geom = feat.get("geometry") or {} + coords = geom.get("coordinates") + if not coords or len(coords) < 2: + skipped_incomplete += 1 + continue + + mwm_names = region_to_mwms.get(region) + if mwm_names is None: + skipped_region += 1 + continue + + lon, lat = float(coords[0]), float(coords[1]) + ipoint = _point_to_int64(lon, lat) + + record = bytearray() + buf = io.BytesIO() + _write_string(buf, number) + _write_string(buf, number) + _write_string(buf, street) + _write_string(buf, postcode) + buf.write(bytes([_INTERPOL_NONE])) + _write_varuint(buf, 1) + _write_varint(buf, ipoint) + record = buf.getvalue() + + for f in get_writers(mwm_names): + f.write(record) + + finally: + for f in writers.values(): + f.close() + + print( + f"Processed {total} entries: " + f"wrote {total - skipped_incomplete - skipped_region}, " + f"skipped incomplete: {skipped_incomplete}, " + f"skipped unknown region: {skipped_region}", + file=sys.stderr, + ) + print(f"Output: {len(writers)} file(s) in {output_dir}", file=sys.stderr) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Convert OpenAddresses Canada ZIP to CoMaps .tempaddr files" + ) + parser.add_argument("zip_file", help="Path to OpenAddresses collection ZIP (e.g. collection-ca.zip)") + parser.add_argument("output_dir", help="Output directory for .tempaddr files (set as ADDRESSES_PATH)") + parser.add_argument("--countries-txt", help="Path to countries.txt to generate per-sub-region files") + args = parser.parse_args() + process(args.zip_file, args.output_dir, args.countries_txt) + + +if __name__ == "__main__": + main() diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py new file mode 100644 index 000000000..331f369d6 --- /dev/null +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -0,0 +1,333 @@ +import importlib.util +import io +import math +import os +import struct +import unittest + +_HERE = os.path.dirname(__file__) +_PREPROCESSOR = os.path.normpath( + os.path.join(_HERE, "..", "generator", "openaddresses_preprocessor.py") +) +_spec = importlib.util.spec_from_file_location("openaddresses_preprocessor", _PREPROCESSOR) +_mod = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_mod) + +_double_to_uint32 = _mod._double_to_uint32 +_lat_to_y = _mod._lat_to_y +_perfect_shuffle = _mod._perfect_shuffle +_point_to_int64 = _mod._point_to_int64 +_write_string = _mod._write_string +_write_varint = _mod._write_varint +_write_varuint = _mod._write_varuint +_zigzag_encode = _mod._zigzag_encode +_COORD_BITS = _mod._COORD_BITS +_COORD_SIZE = _mod._COORD_SIZE +_MIN_X = _mod._MIN_X +_MAX_X = _mod._MAX_X +_MIN_Y = _mod._MIN_Y +_MAX_Y = _mod._MAX_Y + + + +class TestWriteVaruint(unittest.TestCase): + def _encode(self, value): + f = io.BytesIO() + _write_varuint(f, value) + return f.getvalue() + + def test_zero(self): + self.assertEqual(self._encode(0), b'\x00') + + def test_one(self): + self.assertEqual(self._encode(1), b'\x01') + + def test_127(self): + self.assertEqual(self._encode(127), b'\x7f') + + def test_128(self): + self.assertEqual(self._encode(128), b'\x80\x01') + + def test_300(self): + self.assertEqual(self._encode(300), b'\xac\x02') + + def test_16383(self): + self.assertEqual(self._encode(16383), b'\xff\x7f') + + def test_16384(self): + self.assertEqual(self._encode(16384), b'\x80\x80\x01') + + + +class TestZigzagEncode(unittest.TestCase): + def test_zero(self): + self.assertEqual(_zigzag_encode(0), 0) + + def test_minus_one(self): + self.assertEqual(_zigzag_encode(-1), 1) + + def test_one(self): + self.assertEqual(_zigzag_encode(1), 2) + + def test_minus_two(self): + self.assertEqual(_zigzag_encode(-2), 3) + + def test_large_positive(self): + self.assertEqual(_zigzag_encode(2147483647), 4294967294) + + def test_large_negative(self): + self.assertEqual(_zigzag_encode(-2147483648), 4294967295) + + + +class TestWriteVarint(unittest.TestCase): + def _encode(self, value): + f = io.BytesIO() + _write_varint(f, value) + return f.getvalue() + + def test_zero(self): + self.assertEqual(self._encode(0), b'\x00') + + def test_minus_one(self): + self.assertEqual(self._encode(-1), b'\x01') + + def test_one(self): + self.assertEqual(self._encode(1), b'\x02') + + def test_minus_64(self): + self.assertEqual(self._encode(-64), b'\x7f') + + def test_64(self): + self.assertEqual(self._encode(64), b'\x80\x01') + + + +class TestWriteString(unittest.TestCase): + def _encode(self, s): + f = io.BytesIO() + _write_string(f, s) + return f.getvalue() + + def test_empty(self): + self.assertEqual(self._encode(""), b'\x00') + + def test_ascii(self): + data = self._encode("42") + self.assertEqual(data, b'\x02' + b'42') + + def test_ascii_street(self): + data = self._encode("Main St") + self.assertEqual(data, b'\x07' + b'Main St') + + def test_utf8(self): + encoded = "café".encode("utf-8") + data = self._encode("café") + self.assertEqual(data, bytes([len(encoded)]) + encoded) + + def test_length_127(self): + s = "x" * 127 + data = self._encode(s) + self.assertEqual(data[0:1], b'\x7f') + self.assertEqual(data[1:], s.encode()) + + def test_length_128(self): + s = "x" * 128 + data = self._encode(s) + self.assertEqual(data[0:2], b'\x80\x01') + self.assertEqual(data[2:], s.encode()) + + + +class TestLatToY(unittest.TestCase): + def test_equator(self): + self.assertAlmostEqual(_lat_to_y(0.0), 0.0, places=10) + + def test_symmetry(self): + for lat in [10.0, 45.0, 60.0, 80.0]: + self.assertAlmostEqual(_lat_to_y(-lat), -_lat_to_y(lat), places=10) + + def test_monotone(self): + lats = [0, 10, 30, 49, 60, 80] + ys = [_lat_to_y(l) for l in lats] + for i in range(len(ys) - 1): + self.assertLess(ys[i], ys[i + 1]) + + def test_clamped_min(self): + self.assertEqual(_lat_to_y(-90.0), _MIN_Y) + + def test_clamped_max(self): + self.assertEqual(_lat_to_y(90.0), _MAX_Y) + + def test_known_value_45(self): + y = _lat_to_y(45.0) + self.assertGreater(y, 49.0) + self.assertLess(y, 52.0) + + def test_vancouver(self): + y = _lat_to_y(49.25) + self.assertGreater(y, 45.0) + self.assertLess(y, 60.0) + + + +class TestDoubleToUint32(unittest.TestCase): + def test_min_boundary(self): + self.assertEqual(_double_to_uint32(_MIN_X, _MIN_X, _MAX_X), 0) + + def test_max_boundary(self): + self.assertEqual(_double_to_uint32(_MAX_X, _MIN_X, _MAX_X), int(_COORD_SIZE)) + + def test_below_min_clamps(self): + self.assertEqual(_double_to_uint32(-200.0, _MIN_X, _MAX_X), 0) + + def test_above_max_clamps(self): + self.assertEqual(_double_to_uint32(200.0, _MIN_X, _MAX_X), int(_COORD_SIZE)) + + def test_center(self): + mid = _double_to_uint32(0.0, _MIN_X, _MAX_X) + expected = round(_COORD_SIZE / 2) + self.assertAlmostEqual(mid, expected, delta=1) + + def test_result_in_range(self): + for lon in [-180, -90, 0, 49.25, 90, 180]: + val = _double_to_uint32(lon, _MIN_X, _MAX_X) + self.assertGreaterEqual(val, 0) + self.assertLessEqual(val, int(_COORD_SIZE)) + + + +class TestPerfectShuffle(unittest.TestCase): + def test_zero(self): + self.assertEqual(_perfect_shuffle(0), 0) + + def test_low_32_bit0(self): + self.assertEqual(_perfect_shuffle(1) & 1, 1) + + def test_high_32_bit0(self): + self.assertEqual((_perfect_shuffle(1 << 32) >> 1) & 1, 1) + + def test_output_fits_64bits(self): + self.assertEqual(_perfect_shuffle(0xFFFFFFFFFFFFFFFF), 0xFFFFFFFFFFFFFFFF) + + + +class TestPointToInt64(unittest.TestCase): + def test_result_is_nonnegative(self): + for lon, lat in [(0, 0), (-123.1, 49.25), (180, 86), (-180, -86)]: + result = _point_to_int64(lon, lat) + self.assertGreaterEqual(result, 0) + + def test_real_coords_nonzero(self): + for lon, lat in [(-123.1, 49.25), (-75.7, 45.4), (-114.1, 51.0)]: + self.assertGreater(_point_to_int64(lon, lat), 0) + + def test_deterministic(self): + p1 = _point_to_int64(-123.1, 49.25) + p2 = _point_to_int64(-123.1, 49.25) + self.assertEqual(p1, p2) + + def test_different_coords_differ(self): + p1 = _point_to_int64(-123.1, 49.25) + p2 = _point_to_int64(-75.7, 45.4) + self.assertNotEqual(p1, p2) + + def test_fits_in_int64(self): + v = _point_to_int64(-123.1, 49.25) + self.assertLessEqual(v, 0x7FFFFFFFFFFFFFFF) + + +class TestRecordRoundTrip(unittest.TestCase): + + def _build_record(self, number, street, postcode, lon, lat): + f = io.BytesIO() + _write_string(f, number) + _write_string(f, number) + _write_string(f, street) + _write_string(f, postcode) + f.write(bytes([_mod._INTERPOL_NONE])) + _write_varuint(f, 1) + _write_varint(f, _point_to_int64(lon, lat)) + return f.getvalue() + + def _parse_record(self, data): + pos = [0] + + def read_varuint(): + result = 0 + shift = 0 + while True: + b = data[pos[0]] + pos[0] += 1 + result |= (b & 0x7F) << shift + if not (b & 0x80): + break + shift += 7 + return result + + def read_varint(): + v = read_varuint() + return (v >> 1) ^ -(v & 1) + + def read_string(): + length = read_varuint() + s = data[pos[0]:pos[0] + length].decode("utf-8") + pos[0] += length + return s + + m_from = read_string() + m_to = read_string() + m_street = read_string() + m_postcode = read_string() + interpol = data[pos[0]]; pos[0] += 1 + count = read_varuint() + point = read_varint() + return { + "m_from": m_from, "m_to": m_to, "m_street": m_street, + "m_postcode": m_postcode, "interpol": interpol, + "count": count, "point": point, + } + + def test_from_equals_to(self): + data = self._build_record("42", "Main St", "V5K 0A1", -123.1, 49.25) + rec = self._parse_record(data) + self.assertEqual(rec["m_from"], "42") + self.assertEqual(rec["m_to"], "42") + self.assertEqual(rec["m_from"], rec["m_to"]) + + def test_street_and_postcode(self): + data = self._build_record("100", "Oak Ave", "V6B 2W2", -123.0, 49.3) + rec = self._parse_record(data) + self.assertEqual(rec["m_street"], "Oak Ave") + self.assertEqual(rec["m_postcode"], "V6B 2W2") + + def test_interpol_none(self): + data = self._build_record("1", "A St", "", -123.0, 49.0) + rec = self._parse_record(data) + self.assertEqual(rec["interpol"], 0) + + def test_point_count_one(self): + data = self._build_record("1", "A St", "", -123.0, 49.0) + rec = self._parse_record(data) + self.assertEqual(rec["count"], 1) + + def test_point_round_trips(self): + lon, lat = -123.1, 49.25 + expected = _point_to_int64(lon, lat) + data = self._build_record("1", "A", "", lon, lat) + rec = self._parse_record(data) + self.assertEqual(rec["point"], expected) + + def test_empty_postcode(self): + data = self._build_record("5", "Elm Rd", "", -75.7, 45.4) + rec = self._parse_record(data) + self.assertEqual(rec["m_postcode"], "") + + def test_record_fully_consumed(self): + data = self._build_record("99", "King St", "K1A 0A6", -75.7, 45.4) + rec = self._parse_record(data) + self.assertEqual(rec["m_from"], "99") + + +if __name__ == "__main__": + unittest.main() From 72e386d8bc2a71d42103866899dd8d34bfac35f6 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Sun, 26 Apr 2026 18:30:02 -0700 Subject: [PATCH 18/40] tests: fix ruff lint in test_openaddresses_preprocessor Signed-off-by: lone-cloud --- .../tests/test_openaddresses_preprocessor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index 331f369d6..27c354b8e 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -1,8 +1,6 @@ import importlib.util import io -import math import os -import struct import unittest _HERE = os.path.dirname(__file__) @@ -149,7 +147,7 @@ def test_symmetry(self): def test_monotone(self): lats = [0, 10, 30, 49, 60, 80] - ys = [_lat_to_y(l) for l in lats] + ys = [_lat_to_y(lat_val) for lat_val in lats] for i in range(len(ys) - 1): self.assertLess(ys[i], ys[i + 1]) @@ -279,7 +277,8 @@ def read_string(): m_to = read_string() m_street = read_string() m_postcode = read_string() - interpol = data[pos[0]]; pos[0] += 1 + interpol = data[pos[0]] + pos[0] += 1 count = read_varuint() point = read_varint() return { From ad6ae3a7647c4ff2facfdee29646548a4bbd1a5e Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Sun, 26 Apr 2026 19:28:11 -0700 Subject: [PATCH 19/40] =?UTF-8?q?refactor(openaddresses):=20auto-derive=20?= =?UTF-8?q?region=E2=86=92MWM=20mapping=20via=20pycountry=20ISO=203166-2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No --mapping flag required. The countrywide geojson is auto-detected from the ZIP (e.g. ca/countrywide-addresses-country.geojson), the country code is inferred from the ZIP directory prefix, and subdivision names are looked up via pycountry to construct MWM names like 'Canada_British Columbia'. A --mapping JSON override is still accepted for countries whose OA data does not use ISO 3166-2 region codes in the countrywide geojson. Signed-off-by: lone-cloud --- .../generator/openaddresses_preprocessor.py | 98 ++++++++++++++----- tools/python/maps_generator/requirements.txt | 1 + 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 689bd5bea..ad279e03f 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Converts an OpenAddresses Canada ZIP to CoMaps .tempaddr address files.""" +"""Converts an OpenAddresses ZIP to CoMaps .tempaddr address files.""" import argparse import io @@ -19,23 +19,6 @@ _INTERPOL_NONE: int = 0 -_REGION_TO_MWM_PREFIX: dict[str, str] = { - "AB": "Canada_Alberta", - "BC": "Canada_British Columbia", - "MB": "Canada_Manitoba", - "NB": "Canada_New Brunswick", - "NL": "Canada_Newfoundland and Labrador", - "NS": "Canada_Nova Scotia", - "NT": "Canada_Northwest Territories", - "NU": "Canada_Nunavut", - "ON": "Canada_Ontario", - "PE": "Canada_Prince Edward Island", - "QC": "Canada_Quebec", - "SK": "Canada_Saskatchewan", - "YT": "Canada_Yukon", -} - -_GEOJSON_PATH: str = "ca/countrywide-addresses-country.geojson" _ADDR_EXT: str = ".tempaddr" @@ -115,14 +98,53 @@ def _collect_mwm_names(countries_txt: str) -> list[str]: return names -def _build_region_mwm_map(countries_txt: str | None) -> dict[str, list[str]]: +def _find_countrywide_geojson(zf: zipfile.ZipFile) -> str: + for name in zf.namelist(): + parts = name.split("/") + if len(parts) == 2 and "countrywide" in parts[1] and parts[1].endswith(".geojson"): + return name + raise ValueError( + "No countrywide geojson found in ZIP (expected /countrywide*.geojson). " + "Use --mapping to specify 'geojson_path' and 'region_to_mwm_prefix' manually." + ) + + +def _build_auto_mapping(geojson_path: str) -> dict[str, str]: + try: + import pycountry + except ImportError as exc: + raise ImportError( + "pycountry is required for automatic region mapping. " + "Install it (`pip install pycountry`) or use --mapping." + ) from exc + country_code = geojson_path.split("/")[0].upper() + country = pycountry.countries.get(alpha_2=country_code) + if country is None: + raise ValueError( + f"Cannot determine country from geojson path '{geojson_path}'. Use --mapping." + ) + subdivisions = pycountry.subdivisions.get(country_code=country_code) + if not subdivisions: + raise ValueError( + f"No ISO 3166-2 subdivisions found for '{country_code}'. Use --mapping." + ) + return { + s.code.split("-", 1)[1]: f"{country.name}_{s.name}" + for s in subdivisions + } + + +def _build_region_mwm_map( + region_to_mwm_prefix: dict[str, str], + countries_txt: str | None, +) -> dict[str, list[str]]: if countries_txt is not None: all_names = _collect_mwm_names(countries_txt) else: all_names = None result: dict[str, list[str]] = {} - for region, prefix in _REGION_TO_MWM_PREFIX.items(): + for region, prefix in region_to_mwm_prefix.items(): if all_names is None: result[region] = [prefix] else: @@ -134,11 +156,14 @@ def _build_region_mwm_map(countries_txt: str | None) -> dict[str, list[str]]: return result -def process(zip_path: str, output_dir: str, countries_txt: str | None = None) -> None: +def process( + zip_path: str, + output_dir: str, + mapping: dict | None = None, + countries_txt: str | None = None, +) -> None: os.makedirs(output_dir, exist_ok=True) - region_to_mwms = _build_region_mwm_map(countries_txt) - writers: dict[str, io.BufferedWriter] = {} def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: @@ -156,7 +181,16 @@ def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: try: with zipfile.ZipFile(zip_path, "r") as zf: - with zf.open(_GEOJSON_PATH) as raw: + if mapping is not None: + geojson_path: str = mapping["geojson_path"] + region_to_mwm_prefix: dict[str, str] = mapping["region_to_mwm_prefix"] + else: + geojson_path = _find_countrywide_geojson(zf) + region_to_mwm_prefix = _build_auto_mapping(geojson_path) + + region_to_mwms = _build_region_mwm_map(region_to_mwm_prefix, countries_txt) + + with zf.open(geojson_path) as raw: for line_bytes in raw: line_bytes = line_bytes.rstrip(b"\n\r") if not line_bytes: @@ -223,13 +257,25 @@ def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: def main() -> None: parser = argparse.ArgumentParser( - description="Convert OpenAddresses Canada ZIP to CoMaps .tempaddr files" + description="Convert an OpenAddresses ZIP to CoMaps .tempaddr files" ) parser.add_argument("zip_file", help="Path to OpenAddresses collection ZIP (e.g. collection-ca.zip)") parser.add_argument("output_dir", help="Output directory for .tempaddr files (set as ADDRESSES_PATH)") + parser.add_argument( + "--mapping", + help=( + "Path to a JSON mapping file with 'geojson_path' and 'region_to_mwm_prefix' keys. " + "If omitted, the countrywide geojson is auto-detected from the ZIP and regions are " + "derived from ISO 3166-2 subdivisions via pycountry." + ), + ) parser.add_argument("--countries-txt", help="Path to countries.txt to generate per-sub-region files") args = parser.parse_args() - process(args.zip_file, args.output_dir, args.countries_txt) + mapping = None + if args.mapping: + with open(args.mapping, encoding="utf-8") as f: + mapping = json.load(f) + process(args.zip_file, args.output_dir, mapping, args.countries_txt) if __name__ == "__main__": diff --git a/tools/python/maps_generator/requirements.txt b/tools/python/maps_generator/requirements.txt index 6618a235f..f8532e17e 100644 --- a/tools/python/maps_generator/requirements.txt +++ b/tools/python/maps_generator/requirements.txt @@ -6,3 +6,4 @@ filelock==3.0.10 beautifulsoup4==4.9.1 requests>=2.31.0 requests_file==1.5.1 +pycountry>=22.3.5 From 44c6f5703e177a724dcd9967afbb6c2832db9ba9 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Sun, 26 Apr 2026 19:51:09 -0700 Subject: [PATCH 20/40] tests: add coverage for _find_countrywide_geojson and _build_auto_mapping TestFindCountrywideGeojson: 5 cases covering detection, nested path rejection, non-geojson rejection, and ValueError on missing countrywide file. TestBuildAutoMapping: 6 cases covering CA provinces, AU states, MWM name format, case-insensitive prefix, and ValueError for unknown country code. Signed-off-by: lone-cloud --- .../tests/test_openaddresses_preprocessor.py | 100 +++++++++++++++--- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index 27c354b8e..439f6c8c2 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -2,6 +2,7 @@ import io import os import unittest +import zipfile _HERE = os.path.dirname(__file__) _PREPROCESSOR = os.path.normpath( @@ -11,20 +12,22 @@ _mod = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(_mod) -_double_to_uint32 = _mod._double_to_uint32 -_lat_to_y = _mod._lat_to_y -_perfect_shuffle = _mod._perfect_shuffle -_point_to_int64 = _mod._point_to_int64 -_write_string = _mod._write_string -_write_varint = _mod._write_varint -_write_varuint = _mod._write_varuint -_zigzag_encode = _mod._zigzag_encode -_COORD_BITS = _mod._COORD_BITS -_COORD_SIZE = _mod._COORD_SIZE -_MIN_X = _mod._MIN_X -_MAX_X = _mod._MAX_X -_MIN_Y = _mod._MIN_Y -_MAX_Y = _mod._MAX_Y +_double_to_uint32 = _mod._double_to_uint32 +_lat_to_y = _mod._lat_to_y +_perfect_shuffle = _mod._perfect_shuffle +_point_to_int64 = _mod._point_to_int64 +_write_string = _mod._write_string +_write_varint = _mod._write_varint +_write_varuint = _mod._write_varuint +_zigzag_encode = _mod._zigzag_encode +_find_countrywide_geojson = _mod._find_countrywide_geojson +_build_auto_mapping = _mod._build_auto_mapping +_COORD_BITS = _mod._COORD_BITS +_COORD_SIZE = _mod._COORD_SIZE +_MIN_X = _mod._MIN_X +_MAX_X = _mod._MAX_X +_MIN_Y = _mod._MIN_Y +_MAX_Y = _mod._MAX_Y @@ -328,5 +331,74 @@ def test_record_fully_consumed(self): self.assertEqual(rec["m_from"], "99") +class TestFindCountrywideGeojson(unittest.TestCase): + def _make_zip(self, names): + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w") as zf: + for name in names: + zf.writestr(name, b"") + buf.seek(0) + return zipfile.ZipFile(buf, "r") + + def test_finds_countrywide(self): + zf = self._make_zip(["ca/countrywide-addresses-country.geojson", "ca/other.geojson"]) + self.assertEqual(_find_countrywide_geojson(zf), "ca/countrywide-addresses-country.geojson") + zf.close() + + def test_finds_countrywide_simple_name(self): + zf = self._make_zip(["au/countrywide.geojson"]) + self.assertEqual(_find_countrywide_geojson(zf), "au/countrywide.geojson") + zf.close() + + def test_ignores_nested_paths(self): + zf = self._make_zip(["ca/sub/countrywide.geojson", "ca/statewide.geojson"]) + with self.assertRaises(ValueError): + _find_countrywide_geojson(zf) + zf.close() + + def test_raises_when_not_found(self): + zf = self._make_zip(["ca/statewide.geojson", "ca/other.csv"]) + with self.assertRaises(ValueError): + _find_countrywide_geojson(zf) + zf.close() + + def test_ignores_non_geojson(self): + zf = self._make_zip(["ca/countrywide.csv", "ca/countrywide.zip"]) + with self.assertRaises(ValueError): + _find_countrywide_geojson(zf) + zf.close() + + +class TestBuildAutoMapping(unittest.TestCase): + def test_canada_bc(self): + mapping = _build_auto_mapping("ca/countrywide.geojson") + self.assertIn("BC", mapping) + self.assertEqual(mapping["BC"], "Canada_British Columbia") + + def test_canada_all_provinces(self): + mapping = _build_auto_mapping("ca/countrywide.geojson") + for code in ("AB", "BC", "MB", "NB", "NL", "NS", "NT", "NU", "ON", "PE", "QC", "SK", "YT"): + self.assertIn(code, mapping) + + def test_canada_mwm_name_format(self): + mapping = _build_auto_mapping("ca/countrywide.geojson") + for mwm_name in mapping.values(): + self.assertTrue(mwm_name.startswith("Canada_"), mwm_name) + + def test_australia_vic(self): + mapping = _build_auto_mapping("au/countrywide.geojson") + self.assertIn("VIC", mapping) + self.assertEqual(mapping["VIC"], "Australia_Victoria") + + def test_case_insensitive_country_prefix(self): + mapping_lower = _build_auto_mapping("ca/countrywide.geojson") + mapping_upper = _build_auto_mapping("CA/countrywide.geojson") + self.assertEqual(mapping_lower, mapping_upper) + + def test_unknown_country_raises(self): + with self.assertRaises(ValueError): + _build_auto_mapping("xx/countrywide.geojson") + + if __name__ == "__main__": unittest.main() From 7cba95a0422d9ee80392d7f8dca71df16654dd74 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 09:10:12 -0700 Subject: [PATCH 21/40] openaddresses: replace ISO 3166-2 mapping with poly-based point-in-polygon The previous approach mapped OA region codes (e.g. BC, ON) to MWM names via ISO 3166-2 subdivisions. This was incorrect because provinces/states are often split into multiple MWM regions (e.g. Canada_British_Columbia vs Canada_British_Columbia_Vancouver), causing addresses to land in the wrong file or be duplicated across all sub-regions. Replace with point-in-polygon lookup against the CoMaps .poly border files (data/borders/). Each address coordinate is tested against all borders to find the correct MWM. Uses Shapely STRtree when available for performance, with a pure-Python ray-casting fallback. Also fixes _find_countrywide_geojson -> _find_countrywide_geojsons (plural) to support multi-country collection ZIPs. Remove: --mapping, --countries-txt flags, _build_auto_mapping, _build_region_mwm_map, _collect_mwm_names (pycountry dependency) Add: --borders-dir flag (required), _parse_poly, _ray_cast, _BorderIndex Signed-off-by: lone-cloud --- .../generator/openaddresses_preprocessor.py | 400 +++++++++++------- .../tests/test_openaddresses_preprocessor.py | 263 +++++++++--- 2 files changed, 453 insertions(+), 210 deletions(-) diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index ad279e03f..81c663acc 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -1,5 +1,18 @@ #!/usr/bin/env python3 -"""Converts an OpenAddresses ZIP to CoMaps .tempaddr address files.""" +"""Converts an OpenAddresses ZIP to CoMaps .tempaddr address files. + +Each address is assigned to a MWM region by point-in-polygon lookup against +the .poly border files in the CoMaps borders directory. This correctly handles +countries where provinces/states are split into multiple MWM regions (e.g. +Canada_British_Columbia_Vancouver vs Canada_British_Columbia_Northeast). + +Usage: + python3 openaddresses_preprocessor.py --borders-dir + +The borders directory should contain the .poly files from the CoMaps repository +(data/borders/). Shapely is used for spatial operations if available; a pure +Python ray-casting fallback is used otherwise. +""" import argparse import io @@ -22,6 +35,10 @@ _ADDR_EXT: str = ".tempaddr" +# --------------------------------------------------------------------------- +# Binary encoding helpers (unchanged) +# --------------------------------------------------------------------------- + def _write_varuint(f: io.RawIOBase, value: int) -> None: value = int(value) & 0xFFFFFFFFFFFFFFFF while value > 127: @@ -74,106 +91,184 @@ def _perfect_shuffle(x: int) -> int: def _point_to_int64(lon: float, lat: float) -> int: - ux = _double_to_uint32(lon, _MIN_X, _MAX_X) + ux = _double_to_uint32(lon, _MIN_X, _MAX_X) uy = _double_to_uint32(_lat_to_y(lat), _MIN_Y, _MAX_Y) return int(_perfect_shuffle((uy << 32) | ux)) -def _collect_mwm_names(countries_txt: str) -> list[str]: - with open(countries_txt, encoding="utf-8") as f: - root = json.load(f) - names: list[str] = [] - stack = [root] - while stack: - node = stack.pop() - if isinstance(node, dict): - n = node.get("id", "") - if n: - names.append(n) - v = node.get("g", node.get("v", [])) - if isinstance(v, list): - stack.extend(v) - elif isinstance(node, list): - stack.extend(node) - return names - - -def _find_countrywide_geojson(zf: zipfile.ZipFile) -> str: +# --------------------------------------------------------------------------- +# .poly file parsing +# --------------------------------------------------------------------------- + +def _parse_poly(path: str) -> list[list[tuple[float, float]]]: + """Parse an OSM .poly file into a list of rings. + + Each ring is a list of (lon, lat) tuples. Rings whose index line starts + with '!' are holes and are returned as-is (callers decide how to handle + them; for point-in-polygon purposes holes are ignored — a point inside a + hole is still inside the outer polygon for MWM assignment). + """ + rings: list[list[tuple[float, float]]] = [] + current: list[tuple[float, float]] | None = None + with open(path, encoding="utf-8") as fh: + for i, line in enumerate(fh): + stripped = line.strip() + if i == 0: + # region name header — skip + continue + if stripped == "END": + if current is not None: + rings.append(current) + current = None + # final END closes the file + continue + if stripped.lstrip("!").isdigit(): + # ring index line (e.g. "1", "!2") — start a new ring + current = [] + continue + if current is not None and stripped: + parts = stripped.split() + if len(parts) >= 2: + try: + lon = float(parts[0]) + lat = float(parts[1]) + current.append((lon, lat)) + except ValueError: + pass + return rings + + +def _bbox(ring: list[tuple[float, float]]) -> tuple[float, float, float, float]: + lons = [p[0] for p in ring] + lats = [p[1] for p in ring] + return (min(lons), min(lats), max(lons), max(lats)) + + +def _ray_cast(lon: float, lat: float, ring: list[tuple[float, float]]) -> bool: + """Ray-casting point-in-polygon test.""" + inside = False + n = len(ring) + j = n - 1 + for i in range(n): + xi, yi = ring[i] + xj, yj = ring[j] + if ((yi > lat) != (yj > lat)) and (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi): + inside = not inside + j = i + return inside + + +# --------------------------------------------------------------------------- +# Border index +# --------------------------------------------------------------------------- + +class _BorderIndex: + """Spatial index over .poly border files for fast point-in-polygon lookup. + + Uses Shapely + STRtree when available; falls back to bounding-box + pre-filtering + pure-Python ray-casting otherwise. + """ + + def __init__(self, borders_dir: str) -> None: + poly_files = [ + f for f in os.listdir(borders_dir) if f.endswith(".poly") + ] + if not poly_files: + raise ValueError(f"No .poly files found in {borders_dir}") + + self._names: list[str] = [] + self._rings: list[list[list[tuple[float, float]]]] = [] + self._bboxes: list[tuple[float, float, float, float]] = [] + + print(f"Loading {len(poly_files)} border polygons...", file=sys.stderr) + for fn in poly_files: + mwm_name = fn[:-5] # strip .poly + rings = _parse_poly(os.path.join(borders_dir, fn)) + if not rings: + continue + # Use the first (outer) ring for bbox + bb = _bbox(rings[0]) + self._names.append(mwm_name) + self._rings.append(rings) + self._bboxes.append(bb) + + self._use_shapely = False + try: + from shapely.geometry import Point, Polygon + from shapely.strtree import STRtree + + shapely_polys = [] + for rings in self._rings: + outer = rings[0] + holes = rings[1:] if len(rings) > 1 else [] + shapely_polys.append(Polygon(outer, holes)) + self._strtree = STRtree(shapely_polys) + self._shapely_polys = shapely_polys + self._use_shapely = True + print("Using Shapely for spatial index.", file=sys.stderr) + except ImportError: + print( + "Shapely not found; using pure-Python fallback (slower). " + "Install shapely for better performance.", + file=sys.stderr, + ) + + def find(self, lon: float, lat: float) -> str | None: + """Return the MWM name for the region containing (lon, lat), or None.""" + if self._use_shapely: + from shapely.geometry import Point + pt = Point(lon, lat) + candidates = self._strtree.query(pt) + for idx in candidates: + if self._shapely_polys[idx].contains(pt): + return self._names[idx] + return None + else: + for i, (minx, miny, maxx, maxy) in enumerate(self._bboxes): + if minx <= lon <= maxx and miny <= lat <= maxy: + if _ray_cast(lon, lat, self._rings[i][0]): + return self._names[i] + return None + + +# --------------------------------------------------------------------------- +# ZIP helpers +# --------------------------------------------------------------------------- + +def _find_countrywide_geojsons(zf: zipfile.ZipFile) -> list[str]: + """Return all countrywide geojson paths found in the ZIP.""" + found = [] for name in zf.namelist(): parts = name.split("/") if len(parts) == 2 and "countrywide" in parts[1] and parts[1].endswith(".geojson"): - return name - raise ValueError( - "No countrywide geojson found in ZIP (expected /countrywide*.geojson). " - "Use --mapping to specify 'geojson_path' and 'region_to_mwm_prefix' manually." - ) - - -def _build_auto_mapping(geojson_path: str) -> dict[str, str]: - try: - import pycountry - except ImportError as exc: - raise ImportError( - "pycountry is required for automatic region mapping. " - "Install it (`pip install pycountry`) or use --mapping." - ) from exc - country_code = geojson_path.split("/")[0].upper() - country = pycountry.countries.get(alpha_2=country_code) - if country is None: + found.append(name) + if not found: raise ValueError( - f"Cannot determine country from geojson path '{geojson_path}'. Use --mapping." + "No countrywide geojson found in ZIP (expected /countrywide*.geojson)." ) - subdivisions = pycountry.subdivisions.get(country_code=country_code) - if not subdivisions: - raise ValueError( - f"No ISO 3166-2 subdivisions found for '{country_code}'. Use --mapping." - ) - return { - s.code.split("-", 1)[1]: f"{country.name}_{s.name}" - for s in subdivisions - } - - -def _build_region_mwm_map( - region_to_mwm_prefix: dict[str, str], - countries_txt: str | None, -) -> dict[str, list[str]]: - if countries_txt is not None: - all_names = _collect_mwm_names(countries_txt) - else: - all_names = None + return found - result: dict[str, list[str]] = {} - for region, prefix in region_to_mwm_prefix.items(): - if all_names is None: - result[region] = [prefix] - else: - matches = [ - n for n in all_names - if n == prefix or n.startswith(prefix + "_") - ] - result[region] = matches if matches else [prefix] - return result +# --------------------------------------------------------------------------- +# Main processing +# --------------------------------------------------------------------------- def process( zip_path: str, output_dir: str, - mapping: dict | None = None, - countries_txt: str | None = None, + borders_dir: str, ) -> None: os.makedirs(output_dir, exist_ok=True) + index = _BorderIndex(borders_dir) + writers: dict[str, io.BufferedWriter] = {} - def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: - result = [] - for name in mwm_names: - if name not in writers: - path = os.path.join(output_dir, name + _ADDR_EXT) - writers[name] = open(path, "wb") - result.append(writers[name]) - return result + def get_writer(mwm_name: str) -> io.BufferedWriter: + if mwm_name not in writers: + path = os.path.join(output_dir, mwm_name + _ADDR_EXT) + writers[mwm_name] = open(path, "wb") + return writers[mwm_name] total = 0 skipped_incomplete = 0 @@ -181,65 +276,56 @@ def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: try: with zipfile.ZipFile(zip_path, "r") as zf: - if mapping is not None: - geojson_path: str = mapping["geojson_path"] - region_to_mwm_prefix: dict[str, str] = mapping["region_to_mwm_prefix"] - else: - geojson_path = _find_countrywide_geojson(zf) - region_to_mwm_prefix = _build_auto_mapping(geojson_path) - - region_to_mwms = _build_region_mwm_map(region_to_mwm_prefix, countries_txt) - - with zf.open(geojson_path) as raw: - for line_bytes in raw: - line_bytes = line_bytes.rstrip(b"\n\r") - if not line_bytes: - continue - total += 1 - - try: - feat = json.loads(line_bytes) - except json.JSONDecodeError: - skipped_incomplete += 1 - continue - - props = feat.get("properties", {}) - number = props.get("number", "").strip() - street = props.get("street", "").strip() - postcode = props.get("postcode", "").strip() - region = props.get("region", "").strip().upper() - - if not number or not street: - skipped_incomplete += 1 - continue - - geom = feat.get("geometry") or {} - coords = geom.get("coordinates") - if not coords or len(coords) < 2: - skipped_incomplete += 1 - continue - - mwm_names = region_to_mwms.get(region) - if mwm_names is None: - skipped_region += 1 - continue - - lon, lat = float(coords[0]), float(coords[1]) - ipoint = _point_to_int64(lon, lat) - - record = bytearray() - buf = io.BytesIO() - _write_string(buf, number) - _write_string(buf, number) - _write_string(buf, street) - _write_string(buf, postcode) - buf.write(bytes([_INTERPOL_NONE])) - _write_varuint(buf, 1) - _write_varint(buf, ipoint) - record = buf.getvalue() - - for f in get_writers(mwm_names): - f.write(record) + geojson_paths = _find_countrywide_geojsons(zf) + for geojson_path in geojson_paths: + print(f"Processing {geojson_path}...", file=sys.stderr) + with zf.open(geojson_path) as raw: + for line_bytes in raw: + line_bytes = line_bytes.rstrip(b"\n\r") + if not line_bytes: + continue + total += 1 + + try: + feat = json.loads(line_bytes) + except json.JSONDecodeError: + skipped_incomplete += 1 + continue + + props = feat.get("properties", {}) + number = props.get("number", "").strip() + street = props.get("street", "").strip() + postcode = props.get("postcode", "").strip() + + if not number or not street: + skipped_incomplete += 1 + continue + + geom = feat.get("geometry") or {} + coords = geom.get("coordinates") + if not coords or len(coords) < 2: + skipped_incomplete += 1 + continue + + lon, lat = float(coords[0]), float(coords[1]) + + mwm_name = index.find(lon, lat) + if mwm_name is None: + skipped_region += 1 + continue + + ipoint = _point_to_int64(lon, lat) + + buf = io.BytesIO() + _write_string(buf, number) + _write_string(buf, number) + _write_string(buf, street) + _write_string(buf, postcode) + buf.write(bytes([_INTERPOL_NONE])) + _write_varuint(buf, 1) + _write_varint(buf, ipoint) + + get_writer(mwm_name).write(buf.getvalue()) finally: for f in writers.values(): @@ -249,7 +335,7 @@ def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: f"Processed {total} entries: " f"wrote {total - skipped_incomplete - skipped_region}, " f"skipped incomplete: {skipped_incomplete}, " - f"skipped unknown region: {skipped_region}", + f"skipped no region: {skipped_region}", file=sys.stderr, ) print(f"Output: {len(writers)} file(s) in {output_dir}", file=sys.stderr) @@ -257,25 +343,27 @@ def get_writers(mwm_names: list[str]) -> list[io.BufferedWriter]: def main() -> None: parser = argparse.ArgumentParser( - description="Convert an OpenAddresses ZIP to CoMaps .tempaddr files" + description=( + "Convert an OpenAddresses collection ZIP to CoMaps .tempaddr files. " + "Addresses are assigned to MWM regions via point-in-polygon lookup " + "against the CoMaps .poly border files." + ) + ) + parser.add_argument( + "zip_file", + help="Path to OpenAddresses collection ZIP (e.g. collection-ca.zip)", + ) + parser.add_argument( + "output_dir", + help="Output directory for .tempaddr files (set as ADDRESSES_PATH in the generator)", ) - parser.add_argument("zip_file", help="Path to OpenAddresses collection ZIP (e.g. collection-ca.zip)") - parser.add_argument("output_dir", help="Output directory for .tempaddr files (set as ADDRESSES_PATH)") parser.add_argument( - "--mapping", - help=( - "Path to a JSON mapping file with 'geojson_path' and 'region_to_mwm_prefix' keys. " - "If omitted, the countrywide geojson is auto-detected from the ZIP and regions are " - "derived from ISO 3166-2 subdivisions via pycountry." - ), + "--borders-dir", + required=True, + help="Path to directory containing CoMaps .poly border files (data/borders/)", ) - parser.add_argument("--countries-txt", help="Path to countries.txt to generate per-sub-region files") args = parser.parse_args() - mapping = None - if args.mapping: - with open(args.mapping, encoding="utf-8") as f: - mapping = json.load(f) - process(args.zip_file, args.output_dir, mapping, args.countries_txt) + process(args.zip_file, args.output_dir, args.borders_dir) if __name__ == "__main__": diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index 439f6c8c2..40f821abc 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -1,6 +1,8 @@ import importlib.util import io import os +import tempfile +import textwrap import unittest import zipfile @@ -12,22 +14,25 @@ _mod = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(_mod) -_double_to_uint32 = _mod._double_to_uint32 -_lat_to_y = _mod._lat_to_y -_perfect_shuffle = _mod._perfect_shuffle -_point_to_int64 = _mod._point_to_int64 -_write_string = _mod._write_string -_write_varint = _mod._write_varint -_write_varuint = _mod._write_varuint -_zigzag_encode = _mod._zigzag_encode -_find_countrywide_geojson = _mod._find_countrywide_geojson -_build_auto_mapping = _mod._build_auto_mapping -_COORD_BITS = _mod._COORD_BITS -_COORD_SIZE = _mod._COORD_SIZE -_MIN_X = _mod._MIN_X -_MAX_X = _mod._MAX_X -_MIN_Y = _mod._MIN_Y -_MAX_Y = _mod._MAX_Y +_double_to_uint32 = _mod._double_to_uint32 +_lat_to_y = _mod._lat_to_y +_perfect_shuffle = _mod._perfect_shuffle +_point_to_int64 = _mod._point_to_int64 +_write_string = _mod._write_string +_write_varint = _mod._write_varint +_write_varuint = _mod._write_varuint +_zigzag_encode = _mod._zigzag_encode +_find_countrywide_geojsons = _mod._find_countrywide_geojsons +_parse_poly = _mod._parse_poly +_ray_cast = _mod._ray_cast +_bbox = _mod._bbox +_BorderIndex = _mod._BorderIndex +_COORD_BITS = _mod._COORD_BITS +_COORD_SIZE = _mod._COORD_SIZE +_MIN_X = _mod._MIN_X +_MAX_X = _mod._MAX_X +_MIN_Y = _mod._MIN_Y +_MAX_Y = _mod._MAX_Y @@ -331,7 +336,34 @@ def test_record_fully_consumed(self): self.assertEqual(rec["m_from"], "99") -class TestFindCountrywideGeojson(unittest.TestCase): +# --------------------------------------------------------------------------- +# Helper: write a .poly file to a temp directory +# --------------------------------------------------------------------------- + +def _write_poly(directory: str, name: str, rings: list) -> str: + """Write a minimal .poly file and return its path. + + rings is a list of lists of (lon, lat) tuples. The first ring is outer; + subsequent rings are holes (prefixed with '!'). + """ + path = os.path.join(directory, name + ".poly") + with open(path, "w") as fh: + fh.write(name + "\n") + for i, ring in enumerate(rings): + prefix = "!" if i > 0 else "" + fh.write(f"{prefix}{i + 1}\n") + for lon, lat in ring: + fh.write(f" {lon:.6E} {lat:.6E}\n") + fh.write("END\n") + fh.write("END\n") + return path + + +# Simple unit square: (0,0)-(1,0)-(1,1)-(0,1) +_SQUARE = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] + + +class TestFindCountrywideGeojsons(unittest.TestCase): def _make_zip(self, names): buf = io.BytesIO() with zipfile.ZipFile(buf, "w") as zf: @@ -340,64 +372,187 @@ def _make_zip(self, names): buf.seek(0) return zipfile.ZipFile(buf, "r") - def test_finds_countrywide(self): + def test_finds_single(self): zf = self._make_zip(["ca/countrywide-addresses-country.geojson", "ca/other.geojson"]) - self.assertEqual(_find_countrywide_geojson(zf), "ca/countrywide-addresses-country.geojson") + result = _find_countrywide_geojsons(zf) + self.assertEqual(result, ["ca/countrywide-addresses-country.geojson"]) zf.close() - def test_finds_countrywide_simple_name(self): - zf = self._make_zip(["au/countrywide.geojson"]) - self.assertEqual(_find_countrywide_geojson(zf), "au/countrywide.geojson") + def test_finds_multiple_countries(self): + zf = self._make_zip(["ca/countrywide.geojson", "au/countrywide.geojson", "ca/other.csv"]) + result = _find_countrywide_geojsons(zf) + self.assertIn("ca/countrywide.geojson", result) + self.assertIn("au/countrywide.geojson", result) + self.assertEqual(len(result), 2) zf.close() def test_ignores_nested_paths(self): zf = self._make_zip(["ca/sub/countrywide.geojson", "ca/statewide.geojson"]) with self.assertRaises(ValueError): - _find_countrywide_geojson(zf) + _find_countrywide_geojsons(zf) zf.close() def test_raises_when_not_found(self): zf = self._make_zip(["ca/statewide.geojson", "ca/other.csv"]) with self.assertRaises(ValueError): - _find_countrywide_geojson(zf) + _find_countrywide_geojsons(zf) zf.close() def test_ignores_non_geojson(self): zf = self._make_zip(["ca/countrywide.csv", "ca/countrywide.zip"]) with self.assertRaises(ValueError): - _find_countrywide_geojson(zf) + _find_countrywide_geojsons(zf) zf.close() -class TestBuildAutoMapping(unittest.TestCase): - def test_canada_bc(self): - mapping = _build_auto_mapping("ca/countrywide.geojson") - self.assertIn("BC", mapping) - self.assertEqual(mapping["BC"], "Canada_British Columbia") - - def test_canada_all_provinces(self): - mapping = _build_auto_mapping("ca/countrywide.geojson") - for code in ("AB", "BC", "MB", "NB", "NL", "NS", "NT", "NU", "ON", "PE", "QC", "SK", "YT"): - self.assertIn(code, mapping) - - def test_canada_mwm_name_format(self): - mapping = _build_auto_mapping("ca/countrywide.geojson") - for mwm_name in mapping.values(): - self.assertTrue(mwm_name.startswith("Canada_"), mwm_name) - - def test_australia_vic(self): - mapping = _build_auto_mapping("au/countrywide.geojson") - self.assertIn("VIC", mapping) - self.assertEqual(mapping["VIC"], "Australia_Victoria") - - def test_case_insensitive_country_prefix(self): - mapping_lower = _build_auto_mapping("ca/countrywide.geojson") - mapping_upper = _build_auto_mapping("CA/countrywide.geojson") - self.assertEqual(mapping_lower, mapping_upper) - - def test_unknown_country_raises(self): - with self.assertRaises(ValueError): - _build_auto_mapping("xx/countrywide.geojson") +class TestParsePoly(unittest.TestCase): + def _write(self, content: str) -> str: + fd, path = tempfile.mkstemp(suffix=".poly") + os.write(fd, content.encode()) + os.close(fd) + return path + + def test_single_ring(self): + content = textwrap.dedent("""\ + TestRegion + 1 + 0.0 0.0 + 1.0 0.0 + 1.0 1.0 + 0.0 1.0 + END + END + """) + path = self._write(content) + rings = _parse_poly(path) + os.unlink(path) + self.assertEqual(len(rings), 1) + self.assertEqual(len(rings[0]), 4) + self.assertAlmostEqual(rings[0][0][0], 0.0) + self.assertAlmostEqual(rings[0][0][1], 0.0) + + def test_scientific_notation(self): + content = textwrap.dedent("""\ + TestRegion + 1 + -1.208514E+02 4.900030E+01 + -1.208608E+02 4.900030E+01 + END + END + """) + path = self._write(content) + rings = _parse_poly(path) + os.unlink(path) + self.assertEqual(len(rings), 1) + self.assertAlmostEqual(rings[0][0][0], -120.8514, places=3) + self.assertAlmostEqual(rings[0][0][1], 49.0003, places=3) + + def test_multiple_rings(self): + content = textwrap.dedent("""\ + TestRegion + 1 + 0.0 0.0 + 1.0 0.0 + 1.0 1.0 + END + !2 + 0.2 0.2 + 0.8 0.2 + 0.8 0.8 + END + END + """) + path = self._write(content) + rings = _parse_poly(path) + os.unlink(path) + self.assertEqual(len(rings), 2) + self.assertEqual(len(rings[0]), 3) + self.assertEqual(len(rings[1]), 3) + + def test_empty_file_returns_no_rings(self): + content = "TestRegion\nEND\n" + path = self._write(content) + rings = _parse_poly(path) + os.unlink(path) + self.assertEqual(rings, []) + + +class TestRayCast(unittest.TestCase): + def test_center_inside(self): + self.assertTrue(_ray_cast(0.5, 0.5, _SQUARE)) + + def test_outside(self): + self.assertFalse(_ray_cast(2.0, 2.0, _SQUARE)) + + def test_outside_negative(self): + self.assertFalse(_ray_cast(-1.0, 0.5, _SQUARE)) + + def test_near_edge(self): + self.assertTrue(_ray_cast(0.01, 0.5, _SQUARE)) + self.assertFalse(_ray_cast(-0.01, 0.5, _SQUARE)) + + def test_triangle(self): + triangle = [(0.0, 0.0), (2.0, 0.0), (1.0, 2.0)] + self.assertTrue(_ray_cast(1.0, 0.5, triangle)) + self.assertFalse(_ray_cast(0.1, 1.5, triangle)) + + +class TestBbox(unittest.TestCase): + def test_unit_square(self): + self.assertEqual(_bbox(_SQUARE), (0.0, 0.0, 1.0, 1.0)) + + def test_single_point(self): + self.assertEqual(_bbox([(3.0, 4.0)]), (3.0, 4.0, 3.0, 4.0)) + + def test_negative_coords(self): + ring = [(-120.0, 49.0), (-121.0, 50.0), (-119.0, 48.0)] + minx, miny, maxx, maxy = _bbox(ring) + self.assertAlmostEqual(minx, -121.0) + self.assertAlmostEqual(miny, 48.0) + self.assertAlmostEqual(maxx, -119.0) + self.assertAlmostEqual(maxy, 50.0) + + +class TestBorderIndex(unittest.TestCase): + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + # Region A: unit square (0,0)-(1,1) + _write_poly(self.tmpdir, "RegionA", [_SQUARE]) + # Region B: unit square (2,0)-(3,1) + _write_poly(self.tmpdir, "RegionB", [[(2.0, 0.0), (3.0, 0.0), (3.0, 1.0), (2.0, 1.0)]]) + + def tearDown(self): + import shutil + shutil.rmtree(self.tmpdir) + + def test_point_in_region_a(self): + idx = _BorderIndex(self.tmpdir) + self.assertEqual(idx.find(0.5, 0.5), "RegionA") + + def test_point_in_region_b(self): + idx = _BorderIndex(self.tmpdir) + self.assertEqual(idx.find(2.5, 0.5), "RegionB") + + def test_point_outside_all(self): + idx = _BorderIndex(self.tmpdir) + self.assertIsNone(idx.find(5.0, 5.0)) + + def test_point_between_regions(self): + idx = _BorderIndex(self.tmpdir) + self.assertIsNone(idx.find(1.5, 0.5)) + + def test_no_poly_files_raises(self): + empty = tempfile.mkdtemp() + try: + with self.assertRaises(ValueError): + _BorderIndex(empty) + finally: + os.rmdir(empty) + + def test_mwm_name_strips_poly_extension(self): + idx = _BorderIndex(self.tmpdir) + result = idx.find(0.5, 0.5) + self.assertFalse(result.endswith(".poly")) if __name__ == "__main__": From de19a8f3a0b453c1a7c7f2b7c57a4ed80ea87920 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 09:23:55 -0700 Subject: [PATCH 22/40] openaddresses: remove pycountry from requirements No longer used after replacing ISO 3166-2 mapping with poly-based PIP. Signed-off-by: lone-cloud --- tools/python/maps_generator/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/python/maps_generator/requirements.txt b/tools/python/maps_generator/requirements.txt index f8532e17e..6618a235f 100644 --- a/tools/python/maps_generator/requirements.txt +++ b/tools/python/maps_generator/requirements.txt @@ -6,4 +6,3 @@ filelock==3.0.10 beautifulsoup4==4.9.1 requests>=2.31.0 requests_file==1.5.1 -pycountry>=22.3.5 From 5f482d8ff46a5904cd394b36666b7652895480c2 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 11:02:37 -0700 Subject: [PATCH 23/40] generator: handle alphanumeric house numbers from OA data in AddressEnricher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OA data contains entries with alphanumeric house numbers (e.g. "123A"). GetHNRange() returns kInvalidRange for these since they can't be used for range matching or interpolation. Previously, ProcessRawEntries would pass such entries directly to Match(), which had a CHECK(range != kInvalidRange) that would abort generation. Fix: guard before Match() — single-point alphanumeric entries are emitted directly as address nodes; multi-point entries are logged and skipped. Also adds AddressEnricher_GetHNRange_Alphanumeric test to verify that ParseHouseNumber strips letter suffixes and still returns a numeric range, and that fully non-parseable strings return kInvalidRange. Signed-off-by: lone-cloud --- generator/address_enricher.cpp | 40 +++++++++++++------ generator/generator_tests/addresses_tests.cpp | 20 ++++++++++ 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/generator/address_enricher.cpp b/generator/address_enricher.cpp index d3cf8c02f..d1a6ca94e 100644 --- a/generator/address_enricher.cpp +++ b/generator/address_enricher.cpp @@ -119,19 +119,6 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c for (auto const i : iPoints) e.m_points.push_back(Int64ToPointObsolete(i, kPointCoordBits)); - auto const res = Match(e); - if (!res.street) - { - ++m_stats.m_noStreet; - LOG(LDEBUG, ("No street found:", e.m_street, mercator::ToLatLon(e.m_points.front()))); - continue; - } - if (res.interpol) - { - ++m_stats.m_existInterpol; - continue; - } - auto const addNode = [&](m2::PointD const & p, std::string hn) { feature::FeatureBuilder fb; @@ -149,6 +136,33 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c fn(std::move(fb)); }; + if (e.GetHNRange() == Entry::kInvalidRange) + { + // Alphanumeric HNs can't be range-matched or interpolated; emit single-point entries directly. + if (e.m_points.size() == 1) + { + ++m_stats.m_addedSingle; + auto const hn = (e.m_from == e.m_to) ? e.m_from : e.m_from + " - " + e.m_to; + addNode(e.m_points.front(), hn); + } + else + LOG(LDEBUG, ("Invalid HN range for multi-point entry, skipping:", e.m_from, e.m_to, e.m_street)); + continue; + } + + auto const res = Match(e); + if (!res.street) + { + ++m_stats.m_noStreet; + LOG(LDEBUG, ("No street found:", e.m_street, mercator::ToLatLon(e.m_points.front()))); + continue; + } + if (res.interpol) + { + ++m_stats.m_existInterpol; + continue; + } + if (e.m_points.size() == 1) { if (!res.from && !res.to && res.addrsInside == 0) diff --git a/generator/generator_tests/addresses_tests.cpp b/generator/generator_tests/addresses_tests.cpp index 63b1a9540..784bf8f34 100644 --- a/generator/generator_tests/addresses_tests.cpp +++ b/generator/generator_tests/addresses_tests.cpp @@ -1,5 +1,6 @@ #include "testing/testing.hpp" +#include "generator/address_enricher.hpp" #include "generator/addresses_collector.hpp" UNIT_TEST(GenerateAddresses_AddressInfo_FormatRange) @@ -29,3 +30,22 @@ UNIT_TEST(AddressEnricher_DiscretePoint_HouseNumberLabel) TEST_EQUAL(makeLabel("100", "198"), "100 - 198", ()); TEST_EQUAL(makeLabel("1", "99"), "1 - 99", ()); } + +UNIT_TEST(AddressEnricher_GetHNRange_Alphanumeric) +{ + using RawEntry = generator::AddressEnricher::RawEntryBase; + RawEntry e; + + e.m_from = "100"; e.m_to = "198"; + TEST_NOT_EQUAL(e.GetHNRange(), RawEntry::kInvalidRange, ()); + + e.m_from = "1"; e.m_to = "1"; + TEST_NOT_EQUAL(e.GetHNRange(), RawEntry::kInvalidRange, ()); + + e.m_from = "123A"; e.m_to = "123A"; + TEST_NOT_EQUAL(e.GetHNRange(), RawEntry::kInvalidRange, ()); + TEST_EQUAL(e.GetHNRange(), std::make_pair(uint64_t(123), uint64_t(123)), ()); + + e.m_from = "foo"; e.m_to = "bar"; + TEST_EQUAL(e.GetHNRange(), RawEntry::kInvalidRange, ()); +} From 55f7d5b666b06138bbc784f50184b9fd35157a4f Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 11:51:35 -0700 Subject: [PATCH 24/40] style: remove useless label test, banners, obvious comments; use logging Signed-off-by: lone-cloud --- generator/generator_tests/addresses_tests.cpp | 11 ----- .../generator/openaddresses_preprocessor.py | 45 +++++-------------- .../tests/test_openaddresses_preprocessor.py | 4 -- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/generator/generator_tests/addresses_tests.cpp b/generator/generator_tests/addresses_tests.cpp index 784bf8f34..fdf759f4a 100644 --- a/generator/generator_tests/addresses_tests.cpp +++ b/generator/generator_tests/addresses_tests.cpp @@ -20,17 +20,6 @@ UNIT_TEST(GenerateAddresses_AddressInfo_FormatRange) TEST_EQUAL(info.FormatRange(), "", ()); } -UNIT_TEST(AddressEnricher_DiscretePoint_HouseNumberLabel) -{ - auto makeLabel = [](std::string const & from, std::string const & to) -> std::string - { return (from == to) ? from : from + " - " + to; }; - - TEST_EQUAL(makeLabel("42", "42"), "42", ()); - TEST_EQUAL(makeLabel("100", "100"), "100", ()); - TEST_EQUAL(makeLabel("100", "198"), "100 - 198", ()); - TEST_EQUAL(makeLabel("1", "99"), "1 - 99", ()); -} - UNIT_TEST(AddressEnricher_GetHNRange_Alphanumeric) { using RawEntry = generator::AddressEnricher::RawEntryBase; diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 81c663acc..853e77909 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -17,11 +17,13 @@ import argparse import io import json +import logging import math import os -import sys import zipfile +logger = logging.getLogger("maps_generator") + _COORD_BITS: int = 30 _COORD_SIZE: float = (1 << _COORD_BITS) - 1 @@ -35,10 +37,6 @@ _ADDR_EXT: str = ".tempaddr" -# --------------------------------------------------------------------------- -# Binary encoding helpers (unchanged) -# --------------------------------------------------------------------------- - def _write_varuint(f: io.RawIOBase, value: int) -> None: value = int(value) & 0xFFFFFFFFFFFFFFFF while value > 127: @@ -96,10 +94,6 @@ def _point_to_int64(lon: float, lat: float) -> int: return int(_perfect_shuffle((uy << 32) | ux)) -# --------------------------------------------------------------------------- -# .poly file parsing -# --------------------------------------------------------------------------- - def _parse_poly(path: str) -> list[list[tuple[float, float]]]: """Parse an OSM .poly file into a list of rings. @@ -158,10 +152,6 @@ def _ray_cast(lon: float, lat: float, ring: list[tuple[float, float]]) -> bool: return inside -# --------------------------------------------------------------------------- -# Border index -# --------------------------------------------------------------------------- - class _BorderIndex: """Spatial index over .poly border files for fast point-in-polygon lookup. @@ -180,13 +170,12 @@ def __init__(self, borders_dir: str) -> None: self._rings: list[list[list[tuple[float, float]]]] = [] self._bboxes: list[tuple[float, float, float, float]] = [] - print(f"Loading {len(poly_files)} border polygons...", file=sys.stderr) + logger.info(f"Loading {len(poly_files)} border polygons...") for fn in poly_files: - mwm_name = fn[:-5] # strip .poly + mwm_name = fn[:-5] rings = _parse_poly(os.path.join(borders_dir, fn)) if not rings: continue - # Use the first (outer) ring for bbox bb = _bbox(rings[0]) self._names.append(mwm_name) self._rings.append(rings) @@ -205,12 +194,11 @@ def __init__(self, borders_dir: str) -> None: self._strtree = STRtree(shapely_polys) self._shapely_polys = shapely_polys self._use_shapely = True - print("Using Shapely for spatial index.", file=sys.stderr) + logger.info("Using Shapely for spatial index.") except ImportError: - print( + logger.info( "Shapely not found; using pure-Python fallback (slower). " - "Install shapely for better performance.", - file=sys.stderr, + "Install shapely for better performance." ) def find(self, lon: float, lat: float) -> str | None: @@ -231,10 +219,6 @@ def find(self, lon: float, lat: float) -> str | None: return None -# --------------------------------------------------------------------------- -# ZIP helpers -# --------------------------------------------------------------------------- - def _find_countrywide_geojsons(zf: zipfile.ZipFile) -> list[str]: """Return all countrywide geojson paths found in the ZIP.""" found = [] @@ -249,10 +233,6 @@ def _find_countrywide_geojsons(zf: zipfile.ZipFile) -> list[str]: return found -# --------------------------------------------------------------------------- -# Main processing -# --------------------------------------------------------------------------- - def process( zip_path: str, output_dir: str, @@ -278,7 +258,7 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: with zipfile.ZipFile(zip_path, "r") as zf: geojson_paths = _find_countrywide_geojsons(zf) for geojson_path in geojson_paths: - print(f"Processing {geojson_path}...", file=sys.stderr) + logger.info(f"Processing {geojson_path}...") with zf.open(geojson_path) as raw: for line_bytes in raw: line_bytes = line_bytes.rstrip(b"\n\r") @@ -331,14 +311,13 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: for f in writers.values(): f.close() - print( + logger.info( f"Processed {total} entries: " f"wrote {total - skipped_incomplete - skipped_region}, " f"skipped incomplete: {skipped_incomplete}, " - f"skipped no region: {skipped_region}", - file=sys.stderr, + f"skipped no region: {skipped_region}" ) - print(f"Output: {len(writers)} file(s) in {output_dir}", file=sys.stderr) + logger.info(f"Output: {len(writers)} file(s) in {output_dir}") def main() -> None: diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index 40f821abc..39f6574da 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -336,10 +336,6 @@ def test_record_fully_consumed(self): self.assertEqual(rec["m_from"], "99") -# --------------------------------------------------------------------------- -# Helper: write a .poly file to a temp directory -# --------------------------------------------------------------------------- - def _write_poly(directory: str, name: str, rings: list) -> str: """Write a minimal .poly file and return its path. From ed86047599cab08bfe206c8d2be492e8f9d2dba0 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 14:15:57 -0700 Subject: [PATCH 25/40] openaddresses: make shapely a hard dep, drop pure-Python fallback - Add shapely>=2.0 to requirements.txt - Remove _bbox and _ray_cast fallback functions from preprocessor - Simplify _BorderIndex to shapely-only (STRtree + Polygon.contains) - Move shapely imports to module level - Drop TestRayCast and TestBbox (tested removed fallback code) - Add TestProcess integration tests covering the full process() pipeline Signed-off-by: lone-cloud --- .../generator/openaddresses_preprocessor.py | 90 ++-------- tools/python/maps_generator/requirements.txt | 1 + .../tests/test_openaddresses_preprocessor.py | 165 +++++++++++++----- 3 files changed, 144 insertions(+), 112 deletions(-) diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 853e77909..527ad56d0 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -10,8 +10,7 @@ python3 openaddresses_preprocessor.py --borders-dir The borders directory should contain the .poly files from the CoMaps repository -(data/borders/). Shapely is used for spatial operations if available; a pure -Python ray-casting fallback is used otherwise. +(data/borders/). Shapely is required for spatial operations. """ import argparse @@ -22,6 +21,9 @@ import os import zipfile +from shapely.geometry import Point, Polygon +from shapely.strtree import STRtree + logger = logging.getLogger("maps_generator") _COORD_BITS: int = 30 @@ -108,16 +110,13 @@ def _parse_poly(path: str) -> list[list[tuple[float, float]]]: for i, line in enumerate(fh): stripped = line.strip() if i == 0: - # region name header — skip continue if stripped == "END": if current is not None: rings.append(current) current = None - # final END closes the file continue - if stripped.lstrip("!").isdigit(): - # ring index line (e.g. "1", "!2") — start a new ring + if stripped.lstrip("!").isdigit(): # "!N" = hole ring current = [] continue if current is not None and stripped: @@ -132,32 +131,8 @@ def _parse_poly(path: str) -> list[list[tuple[float, float]]]: return rings -def _bbox(ring: list[tuple[float, float]]) -> tuple[float, float, float, float]: - lons = [p[0] for p in ring] - lats = [p[1] for p in ring] - return (min(lons), min(lats), max(lons), max(lats)) - - -def _ray_cast(lon: float, lat: float, ring: list[tuple[float, float]]) -> bool: - """Ray-casting point-in-polygon test.""" - inside = False - n = len(ring) - j = n - 1 - for i in range(n): - xi, yi = ring[i] - xj, yj = ring[j] - if ((yi > lat) != (yj > lat)) and (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi): - inside = not inside - j = i - return inside - - class _BorderIndex: - """Spatial index over .poly border files for fast point-in-polygon lookup. - - Uses Shapely + STRtree when available; falls back to bounding-box - pre-filtering + pure-Python ray-casting otherwise. - """ + """Spatial index over .poly border files for point-in-polygon lookup.""" def __init__(self, borders_dir: str) -> None: poly_files = [ @@ -167,56 +142,27 @@ def __init__(self, borders_dir: str) -> None: raise ValueError(f"No .poly files found in {borders_dir}") self._names: list[str] = [] - self._rings: list[list[list[tuple[float, float]]]] = [] - self._bboxes: list[tuple[float, float, float, float]] = [] + self._polys: list[Polygon] = [] logger.info(f"Loading {len(poly_files)} border polygons...") for fn in poly_files: - mwm_name = fn[:-5] rings = _parse_poly(os.path.join(borders_dir, fn)) if not rings: continue - bb = _bbox(rings[0]) - self._names.append(mwm_name) - self._rings.append(rings) - self._bboxes.append(bb) - - self._use_shapely = False - try: - from shapely.geometry import Point, Polygon - from shapely.strtree import STRtree - - shapely_polys = [] - for rings in self._rings: - outer = rings[0] - holes = rings[1:] if len(rings) > 1 else [] - shapely_polys.append(Polygon(outer, holes)) - self._strtree = STRtree(shapely_polys) - self._shapely_polys = shapely_polys - self._use_shapely = True - logger.info("Using Shapely for spatial index.") - except ImportError: - logger.info( - "Shapely not found; using pure-Python fallback (slower). " - "Install shapely for better performance." - ) + outer = rings[0] + holes = rings[1:] if len(rings) > 1 else [] + self._names.append(fn[:-5]) + self._polys.append(Polygon(outer, holes)) + + self._strtree = STRtree(self._polys) def find(self, lon: float, lat: float) -> str | None: """Return the MWM name for the region containing (lon, lat), or None.""" - if self._use_shapely: - from shapely.geometry import Point - pt = Point(lon, lat) - candidates = self._strtree.query(pt) - for idx in candidates: - if self._shapely_polys[idx].contains(pt): - return self._names[idx] - return None - else: - for i, (minx, miny, maxx, maxy) in enumerate(self._bboxes): - if minx <= lon <= maxx and miny <= lat <= maxy: - if _ray_cast(lon, lat, self._rings[i][0]): - return self._names[i] - return None + pt = Point(lon, lat) + for idx in self._strtree.query(pt): + if self._polys[idx].contains(pt): + return self._names[idx] + return None def _find_countrywide_geojsons(zf: zipfile.ZipFile) -> list[str]: diff --git a/tools/python/maps_generator/requirements.txt b/tools/python/maps_generator/requirements.txt index 6618a235f..417198ec4 100644 --- a/tools/python/maps_generator/requirements.txt +++ b/tools/python/maps_generator/requirements.txt @@ -6,3 +6,4 @@ filelock==3.0.10 beautifulsoup4==4.9.1 requests>=2.31.0 requests_file==1.5.1 +shapely>=2.0 diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index 39f6574da..b5b0bf74f 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -1,5 +1,6 @@ import importlib.util import io +import json import os import tempfile import textwrap @@ -24,8 +25,6 @@ _zigzag_encode = _mod._zigzag_encode _find_countrywide_geojsons = _mod._find_countrywide_geojsons _parse_poly = _mod._parse_poly -_ray_cast = _mod._ray_cast -_bbox = _mod._bbox _BorderIndex = _mod._BorderIndex _COORD_BITS = _mod._COORD_BITS _COORD_SIZE = _mod._COORD_SIZE @@ -35,7 +34,6 @@ _MAX_Y = _mod._MAX_Y - class TestWriteVaruint(unittest.TestCase): def _encode(self, value): f = io.BytesIO() @@ -355,7 +353,6 @@ def _write_poly(directory: str, name: str, rings: list) -> str: return path -# Simple unit square: (0,0)-(1,0)-(1,1)-(0,1) _SQUARE = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] @@ -473,42 +470,6 @@ def test_empty_file_returns_no_rings(self): self.assertEqual(rings, []) -class TestRayCast(unittest.TestCase): - def test_center_inside(self): - self.assertTrue(_ray_cast(0.5, 0.5, _SQUARE)) - - def test_outside(self): - self.assertFalse(_ray_cast(2.0, 2.0, _SQUARE)) - - def test_outside_negative(self): - self.assertFalse(_ray_cast(-1.0, 0.5, _SQUARE)) - - def test_near_edge(self): - self.assertTrue(_ray_cast(0.01, 0.5, _SQUARE)) - self.assertFalse(_ray_cast(-0.01, 0.5, _SQUARE)) - - def test_triangle(self): - triangle = [(0.0, 0.0), (2.0, 0.0), (1.0, 2.0)] - self.assertTrue(_ray_cast(1.0, 0.5, triangle)) - self.assertFalse(_ray_cast(0.1, 1.5, triangle)) - - -class TestBbox(unittest.TestCase): - def test_unit_square(self): - self.assertEqual(_bbox(_SQUARE), (0.0, 0.0, 1.0, 1.0)) - - def test_single_point(self): - self.assertEqual(_bbox([(3.0, 4.0)]), (3.0, 4.0, 3.0, 4.0)) - - def test_negative_coords(self): - ring = [(-120.0, 49.0), (-121.0, 50.0), (-119.0, 48.0)] - minx, miny, maxx, maxy = _bbox(ring) - self.assertAlmostEqual(minx, -121.0) - self.assertAlmostEqual(miny, 48.0) - self.assertAlmostEqual(maxx, -119.0) - self.assertAlmostEqual(maxy, 50.0) - - class TestBorderIndex(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() @@ -551,5 +512,129 @@ def test_mwm_name_strips_poly_extension(self): self.assertFalse(result.endswith(".poly")) +class TestProcess(unittest.TestCase): + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + self.borders = tempfile.mkdtemp() + self.output = tempfile.mkdtemp() + _write_poly(self.borders, "TestRegion", [_SQUARE]) + + def tearDown(self): + import shutil + shutil.rmtree(self.tmpdir) + shutil.rmtree(self.borders) + shutil.rmtree(self.output) + + def _make_zip(self, features): + path = os.path.join(self.tmpdir, "test.zip") + lines = "\n".join(json.dumps(f) for f in features) + with zipfile.ZipFile(path, "w") as zf: + zf.writestr("cc/countrywide.geojson", lines) + return path + + def _feat(self, lon, lat, number="42", street="Main St", postcode="V5K 0A1"): + return { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [lon, lat]}, + "properties": {"number": number, "street": street, "postcode": postcode}, + } + + def _read_records(self, path): + with open(path, "rb") as fh: + data = fh.read() + pos = [0] + records = [] + + def read_varuint(): + result = 0 + shift = 0 + while True: + b = data[pos[0]] + pos[0] += 1 + result |= (b & 0x7F) << shift + if not (b & 0x80): + break + shift += 7 + return result + + def read_varint(): + v = read_varuint() + return (v >> 1) ^ -(v & 1) + + def read_string(): + n = read_varuint() + s = data[pos[0]:pos[0] + n].decode("utf-8") + pos[0] += n + return s + + while pos[0] < len(data): + m_from = read_string() + m_to = read_string() + m_street = read_string() + m_postcode = read_string() + interpol = data[pos[0]] + pos[0] += 1 + count = read_varuint() + point = read_varint() + records.append({ + "m_from": m_from, "m_to": m_to, "m_street": m_street, + "m_postcode": m_postcode, "interpol": interpol, + "count": count, "point": point, + }) + return records + + def test_valid_feature_produces_record(self): + zip_path = self._make_zip([self._feat(0.5, 0.5)]) + _mod.process(zip_path, self.output, self.borders) + out_file = os.path.join(self.output, "TestRegion.tempaddr") + self.assertTrue(os.path.exists(out_file)) + records = self._read_records(out_file) + self.assertEqual(len(records), 1) + r = records[0] + self.assertEqual(r["m_from"], "42") + self.assertEqual(r["m_to"], "42") + self.assertEqual(r["m_street"], "Main St") + self.assertEqual(r["m_postcode"], "V5K 0A1") + self.assertEqual(r["interpol"], 0) + self.assertEqual(r["count"], 1) + self.assertEqual(r["point"], _point_to_int64(0.5, 0.5)) + + def test_feature_outside_region_not_written(self): + zip_path = self._make_zip([self._feat(5.0, 5.0)]) + _mod.process(zip_path, self.output, self.borders) + self.assertEqual(os.listdir(self.output), []) + + def test_incomplete_feature_skipped(self): + zip_path = self._make_zip([ + self._feat(0.5, 0.5, number=""), + self._feat(0.5, 0.5, street=""), + ]) + _mod.process(zip_path, self.output, self.borders) + self.assertEqual(os.listdir(self.output), []) + + def test_multiple_features_same_region(self): + zip_path = self._make_zip([ + self._feat(0.2, 0.2, number="1", street="A St"), + self._feat(0.8, 0.8, number="2", street="B Ave"), + ]) + _mod.process(zip_path, self.output, self.borders) + out_file = os.path.join(self.output, "TestRegion.tempaddr") + records = self._read_records(out_file) + self.assertEqual(len(records), 2) + streets = {r["m_street"] for r in records} + self.assertEqual(streets, {"A St", "B Ave"}) + + def test_mixed_valid_and_invalid(self): + zip_path = self._make_zip([ + self._feat(0.5, 0.5), + self._feat(5.0, 5.0), + self._feat(0.3, 0.3, number=""), + ]) + _mod.process(zip_path, self.output, self.borders) + out_file = os.path.join(self.output, "TestRegion.tempaddr") + records = self._read_records(out_file) + self.assertEqual(len(records), 1) + + if __name__ == "__main__": unittest.main() From 7803839487b3cdaaed149fd6edde37c0c3b5785b Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 27 Apr 2026 22:39:52 -0700 Subject: [PATCH 26/40] Add lone-cloud to CONTRIBUTORS Signed-off-by: lone-cloud --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 06ec4aa4c..7f4a9e1fa 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -16,6 +16,7 @@ Gonzalo Pesquero Harry Bond Konstantin Pastbin @pastk Leonardo Bishop +lone-cloud Marko Kocic Matheus Gomes @NoelClick From 5940640121071e9864de1681efb199125ab8d912 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Wed, 29 Apr 2026 01:11:17 -0700 Subject: [PATCH 27/40] openaddresses: handle US county/state address files and null property values _find_countrywide_geojsons() only matched depth-2 'countrywide.geojson' files, so US collection ZIPs (which use depth-3 county-level files like 'marion-addresses-county.geojson') produced zero addresses. Fixes: - Replace _find_countrywide_geojsons() with _find_address_geojsons() which matches any .geojson whose basename contains 'addresses' or 'countrywide', regardless of directory depth. Covers both CA-style (countrywide.geojson) and US-style (county/state-level addresses files). - Null-safe property reads: OA data can contain JSON null for number/street/ postcode fields; 'props.get(key, "")' returns None for null, crashing .strip(). Fixed with '(props.get(key) or "").strip()'. Tested with collection-us-west.zip: US_Oregon_Portland.mwm went from 0 to 693,072 added addresses. collection-ca.zip still works correctly. Signed-off-by: lone-cloud --- .../generator/openaddresses_preprocessor.py | 31 +++++-- .../tests/test_openaddresses_preprocessor.py | 84 +++++++++++++++---- 2 files changed, 92 insertions(+), 23 deletions(-) diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 527ad56d0..8d5743165 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -165,16 +165,29 @@ def find(self, lon: float, lat: float) -> str | None: return None -def _find_countrywide_geojsons(zf: zipfile.ZipFile) -> list[str]: - """Return all countrywide geojson paths found in the ZIP.""" +def _find_address_geojsons(zf: zipfile.ZipFile) -> list[str]: + """Return all address geojson paths found in the ZIP. + + OA batch output names files ``--.geojson``, + so address files always contain "addresses" in the basename. Countrywide + sources without a sub-national directory may also be named + ``countrywide.geojson`` (no "addresses" component), so we accept either. + + Matches any depth — covers both ``/countrywide.geojson`` (Canada-style) + and ``//-addresses-.geojson`` (US-style) as + well as any future layouts. + """ found = [] for name in zf.namelist(): - parts = name.split("/") - if len(parts) == 2 and "countrywide" in parts[1] and parts[1].endswith(".geojson"): + if not name.endswith(".geojson"): + continue + basename = name.split("/")[-1] + if "addresses" in basename or "countrywide" in basename: found.append(name) if not found: raise ValueError( - "No countrywide geojson found in ZIP (expected /countrywide*.geojson)." + "No address geojson files found in ZIP. " + "Expected files whose basename contains 'addresses' or 'countrywide'." ) return found @@ -202,7 +215,7 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: try: with zipfile.ZipFile(zip_path, "r") as zf: - geojson_paths = _find_countrywide_geojsons(zf) + geojson_paths = _find_address_geojsons(zf) for geojson_path in geojson_paths: logger.info(f"Processing {geojson_path}...") with zf.open(geojson_path) as raw: @@ -219,9 +232,9 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: continue props = feat.get("properties", {}) - number = props.get("number", "").strip() - street = props.get("street", "").strip() - postcode = props.get("postcode", "").strip() + number = (props.get("number") or "").strip() + street = (props.get("street") or "").strip() + postcode = (props.get("postcode") or "").strip() if not number or not street: skipped_incomplete += 1 diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index b5b0bf74f..b3f5ef6d8 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -23,7 +23,7 @@ _write_varint = _mod._write_varint _write_varuint = _mod._write_varuint _zigzag_encode = _mod._zigzag_encode -_find_countrywide_geojsons = _mod._find_countrywide_geojsons +_find_address_geojsons = _mod._find_address_geojsons _parse_poly = _mod._parse_poly _BorderIndex = _mod._BorderIndex _COORD_BITS = _mod._COORD_BITS @@ -356,7 +356,7 @@ def _write_poly(directory: str, name: str, rings: list) -> str: _SQUARE = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] -class TestFindCountrywideGeojsons(unittest.TestCase): +class TestFindAddressGeojsons(unittest.TestCase): def _make_zip(self, names): buf = io.BytesIO() with zipfile.ZipFile(buf, "w") as zf: @@ -365,36 +365,92 @@ def _make_zip(self, names): buf.seek(0) return zipfile.ZipFile(buf, "r") - def test_finds_single(self): + # --- Canada-style countrywide files --- + + def test_finds_canada_countrywide(self): zf = self._make_zip(["ca/countrywide-addresses-country.geojson", "ca/other.geojson"]) - result = _find_countrywide_geojsons(zf) + result = _find_address_geojsons(zf) self.assertEqual(result, ["ca/countrywide-addresses-country.geojson"]) zf.close() - def test_finds_multiple_countries(self): + def test_finds_multiple_countrywide(self): zf = self._make_zip(["ca/countrywide.geojson", "au/countrywide.geojson", "ca/other.csv"]) - result = _find_countrywide_geojsons(zf) + result = _find_address_geojsons(zf) self.assertIn("ca/countrywide.geojson", result) self.assertIn("au/countrywide.geojson", result) self.assertEqual(len(result), 2) zf.close() - def test_ignores_nested_paths(self): - zf = self._make_zip(["ca/sub/countrywide.geojson", "ca/statewide.geojson"]) + # --- US-style county/state address files --- + + def test_finds_us_county_addresses(self): + zf = self._make_zip([ + "us/or/marion-addresses-county.geojson", + "us/or/marion-parcels-county.geojson", + "us/or/marion-parcels-county.geojson.meta", + ]) + result = _find_address_geojsons(zf) + self.assertEqual(result, ["us/or/marion-addresses-county.geojson"]) + zf.close() + + def test_finds_us_statewide_addresses(self): + zf = self._make_zip([ + "us/or/statewide-addresses-state.geojson", + "us/or/statewide-buildings-state.geojson", + ]) + result = _find_address_geojsons(zf) + self.assertEqual(result, ["us/or/statewide-addresses-state.geojson"]) + zf.close() + + def test_finds_multiple_us_counties(self): + zf = self._make_zip([ + "us/or/marion-addresses-county.geojson", + "us/or/yamhill-addresses-county.geojson", + "us/wa/king-addresses-county.geojson", + "us/or/marion-parcels-county.geojson", + ]) + result = _find_address_geojsons(zf) + self.assertIn("us/or/marion-addresses-county.geojson", result) + self.assertIn("us/or/yamhill-addresses-county.geojson", result) + self.assertIn("us/wa/king-addresses-county.geojson", result) + self.assertEqual(len(result), 3) + zf.close() + + def test_finds_depth2_addresses_without_countrywide(self): + # e.g. mx/national-addresses-country.geojson — depth 2, no "countrywide" + zf = self._make_zip(["mx/national-addresses-country.geojson", "mx/national-buildings-country.geojson"]) + result = _find_address_geojsons(zf) + self.assertEqual(result, ["mx/national-addresses-country.geojson"]) + zf.close() + + def test_ignores_meta_files(self): + zf = self._make_zip([ + "us/or/marion-addresses-county.geojson", + "us/or/marion-addresses-county.geojson.meta", + ]) + result = _find_address_geojsons(zf) + self.assertEqual(result, ["us/or/marion-addresses-county.geojson"]) + zf.close() + + def test_ignores_non_address_geojsons(self): + zf = self._make_zip([ + "us/or/marion-parcels-county.geojson", + "us/or/statewide-buildings-state.geojson", + ]) with self.assertRaises(ValueError): - _find_countrywide_geojsons(zf) + _find_address_geojsons(zf) zf.close() - def test_raises_when_not_found(self): - zf = self._make_zip(["ca/statewide.geojson", "ca/other.csv"]) + def test_raises_when_empty_zip(self): + zf = self._make_zip(["ca/other.csv", "readme.txt"]) with self.assertRaises(ValueError): - _find_countrywide_geojsons(zf) + _find_address_geojsons(zf) zf.close() - def test_ignores_non_geojson(self): + def test_ignores_non_geojson_extension(self): zf = self._make_zip(["ca/countrywide.csv", "ca/countrywide.zip"]) with self.assertRaises(ValueError): - _find_countrywide_geojsons(zf) + _find_address_geojsons(zf) zf.close() From 85138455007981e10e679478a71efd5839ef508c Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 4 May 2026 21:58:56 -0700 Subject: [PATCH 28/40] generator: tag imported address features for editor intercept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProcessRawEntries now writes FMD_CUSTOM_IDS with kImportedSourceKey on every emitted feature. framework.cpp detects this tag in GetEditableMapObject and routes to CreatePoint instead of the normal OSM edit flow, pre-populating house number and postcode. Works for any external address source (OA, TIGER, etc.) — no per-source enum or CLI flag needed. Also: deduplicate OA entries in preprocessor by (number, street, unit, lat/lon at 3 dp); add OpenAddresses attribution to copyright.html. Signed-off-by: lone-cloud --- data/copyright.html | 1 + generator/address_enricher.cpp | 6 +++++ libs/indexer/imported_source.hpp | 10 ++++++++ libs/map/framework.cpp | 25 +++++++++++++++++-- .../generator/openaddresses_preprocessor.py | 21 ++++++++++++++-- 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 libs/indexer/imported_source.hpp diff --git a/data/copyright.html b/data/copyright.html index aa691f520..e58494b10 100644 --- a/data/copyright.html +++ b/data/copyright.html @@ -96,6 +96,7 @@

Map data © OpenStreetMap contributors, ODbL.

+

Address data in some regions © OpenAddresses contributors.

Картографические данные © участники OpenStreetMap, ODbL.

Dữ liệu bản đồ © Cộng tác viên của OpenStreetMap, ODbL.

Harita verileri © OpenStreetMap katkıları, ODbL.

diff --git a/generator/address_enricher.cpp b/generator/address_enricher.cpp index d1a6ca94e..7eb1e293c 100644 --- a/generator/address_enricher.cpp +++ b/generator/address_enricher.cpp @@ -4,9 +4,12 @@ #include "search/house_numbers_matcher.hpp" +#include "indexer/custom_keyvalue.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/imported_source.hpp" #include "indexer/search_string_utils.hpp" + #include "geometry/distance_on_sphere.hpp" #include "geometry/mercator.hpp" #include "geometry/parametrized_segment.hpp" @@ -125,6 +128,9 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c fb.SetCenter(p); fb.SetType(m_addrType); + indexer::CustomKeyValue kv; + kv.Add(indexer::kImportedSourceKey, 1 /* any nonzero value; only presence is checked */); + fb.GetMetadata().Set(feature::Metadata::FMD_CUSTOM_IDS, kv.ToString()); auto & params = fb.GetParams(); params.SetStreet(e.m_street); params.SetPostcode(e.m_postcode); diff --git a/libs/indexer/imported_source.hpp b/libs/indexer/imported_source.hpp new file mode 100644 index 000000000..415b66aec --- /dev/null +++ b/libs/indexer/imported_source.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace indexer +{ + +inline constexpr uint8_t kImportedSourceKey = 0; + +} // namespace indexer diff --git a/libs/map/framework.cpp b/libs/map/framework.cpp index a8a62d4e9..a171b4ac3 100644 --- a/libs/map/framework.cpp +++ b/libs/map/framework.cpp @@ -43,6 +43,7 @@ #include "indexer/categories_holder.hpp" #include "indexer/classificator.hpp" +#include "indexer/custom_keyvalue.hpp" #include "indexer/data_source.hpp" #include "indexer/edit_journal.hpp" #include "indexer/editable_map_object.hpp" @@ -54,6 +55,7 @@ #include "indexer/feature_utils.hpp" #include "indexer/feature_visibility.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/imported_source.hpp" #include "indexer/map_style_reader.hpp" #include "indexer/scales.hpp" #include "indexer/transliteration_loader.hpp" @@ -2974,9 +2976,28 @@ bool Framework::GetEditableMapObject(FeatureID const & fid, osm::EditableMapObje return false; emo = {}; - emo.SetFromFeatureType(*ft); auto const & editor = osm::Editor::Instance(); - emo.SetEditableProperties(editor.GetEditableProperties(*ft)); + + bool const isImported = indexer::CustomKeyValue(ft->GetMetadata(feature::Metadata::FMD_CUSTOM_IDS)) + .Get(indexer::kImportedSourceKey).has_value(); + if (isImported) + { + // Imported address features have no real OSM IDs. Treat editing as creating a new OSM node + // pre-populated with the imported address data. + auto const addrType = classif().GetTypeByPath({"building", "address"}); + if (!editor.CreatePoint(addrType, feature::GetCenter(*ft), fid.m_mwmId, emo)) + { + LOG(LERROR, ("Imported address: CreatePoint failed for feature:", fid)); + return false; + } + emo.SetHouseNumber(ft->GetHouseNumber()); + emo.SetPostcode(std::string(ft->GetMetadata(feature::Metadata::FMD_POSTCODE))); + } + else + { + emo.SetFromFeatureType(*ft); + emo.SetEditableProperties(editor.GetEditableProperties(*ft)); + } auto const & dataSource = m_featuresFetcher.GetDataSource(); search::ReverseGeocoder const coder(dataSource); diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 8d5743165..5daf18f2a 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -212,6 +212,15 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: total = 0 skipped_incomplete = 0 skipped_region = 0 + skipped_duplicate = 0 + + # Deduplication key: (number, street, unit, lat_3dp, lon_3dp). + # Rounding to 3 decimal places (~110 m) catches the same address appearing + # in multiple overlapping OA sources (e.g. us/or/marion and + # us/or/marion_and_polk) while still preserving distinct unit entries at + # the same building. mwm_name is omitted — it is fully determined by + # the rounded coordinates and adds no dedup precision. + seen: set[tuple[str, str, str, float, float]] = set() try: with zipfile.ZipFile(zip_path, "r") as zf: @@ -234,6 +243,7 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: props = feat.get("properties", {}) number = (props.get("number") or "").strip() street = (props.get("street") or "").strip() + unit = (props.get("unit") or "").strip() postcode = (props.get("postcode") or "").strip() if not number or not street: @@ -253,6 +263,12 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: skipped_region += 1 continue + dedup_key = (number, street, unit, round(lat, 3), round(lon, 3)) + if dedup_key in seen: + skipped_duplicate += 1 + continue + seen.add(dedup_key) + ipoint = _point_to_int64(lon, lat) buf = io.BytesIO() @@ -272,9 +288,10 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: logger.info( f"Processed {total} entries: " - f"wrote {total - skipped_incomplete - skipped_region}, " + f"wrote {total - skipped_incomplete - skipped_region - skipped_duplicate}, " f"skipped incomplete: {skipped_incomplete}, " - f"skipped no region: {skipped_region}" + f"skipped no region: {skipped_region}, " + f"skipped duplicate: {skipped_duplicate}" ) logger.info(f"Output: {len(writers)} file(s) in {output_dir}") From 6164906b3e8309212fb66824e8e3f36666bebf1f Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Thu, 7 May 2026 22:35:39 -0700 Subject: [PATCH 29/40] feat: hide Edit button for non-ODbL OpenAddresses features OA address features from NC/ND-licensed sources should not show the Edit button since edits would be published to OSM under ODbL, which is incompatible with the source license. Changes: - openaddresses_preprocessor.py: fetch source JSON from OA GitHub to detect NC/ND license clauses; write editable flag byte to tempaddr; replace coordinate-based dedup with MWM-address dedup (ODbL sources sorted first so they win when the same address exists in both ODbL and NC/ND datasets) - address_enricher.hpp/cpp: read editable flag from tempaddr; store as kOpenAddressesEditableKey in FMD_CUSTOM_IDS metadata - place_page_info.cpp: ShouldShowEditPlace() returns false for building/address features with editable flag == 0 - imported_source.hpp: rename kImportedSourceKey to kOpenAddressesEditableKey to reflect its new semantics Signed-off-by: lone-cloud --- generator/address_enricher.cpp | 11 +- generator/address_enricher.hpp | 3 + libs/indexer/imported_source.hpp | 2 +- libs/map/framework.cpp | 2 +- libs/map/place_page_info.cpp | 17 +- .../generator/openaddresses_preprocessor.py | 148 ++++++++++++++-- .../tests/test_openaddresses_preprocessor.py | 158 +++++++++++++++++- 7 files changed, 319 insertions(+), 22 deletions(-) diff --git a/generator/address_enricher.cpp b/generator/address_enricher.cpp index 7eb1e293c..67a3b9610 100644 --- a/generator/address_enricher.cpp +++ b/generator/address_enricher.cpp @@ -9,7 +9,6 @@ #include "indexer/imported_source.hpp" #include "indexer/search_string_utils.hpp" - #include "geometry/distance_on_sphere.hpp" #include "geometry/mercator.hpp" #include "geometry/parametrized_segment.hpp" @@ -128,9 +127,11 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c fb.SetCenter(p); fb.SetType(m_addrType); - indexer::CustomKeyValue kv; - kv.Add(indexer::kImportedSourceKey, 1 /* any nonzero value; only presence is checked */); - fb.GetMetadata().Set(feature::Metadata::FMD_CUSTOM_IDS, kv.ToString()); + { + indexer::CustomKeyValue kv; + kv.Add(indexer::kOpenAddressesEditableKey, e.m_editable ? 1 : 0); + fb.GetMetadata().Set(feature::Metadata::FMD_CUSTOM_IDS, kv.ToString()); + } auto & params = fb.GetParams(); params.SetStreet(e.m_street); params.SetPostcode(e.m_postcode); @@ -178,7 +179,9 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c addNode(e.m_points.front(), hn); } else + { ++m_stats.m_existSingle; + } } else { diff --git a/generator/address_enricher.hpp b/generator/address_enricher.hpp index b8df2d8f3..f27370e4a 100644 --- a/generator/address_enricher.hpp +++ b/generator/address_enricher.hpp @@ -24,6 +24,7 @@ class AddressEnricher { std::string m_from, m_to, m_street, m_postcode; feature::InterpolType m_interpol = feature::InterpolType::None; + bool m_editable = true; /// @name Used to compare house numbers by its integer value. /// @{ @@ -41,6 +42,7 @@ class AddressEnricher rw::Write(sink, m_postcode); WriteToSink(sink, static_cast(m_interpol)); + WriteToSink(sink, static_cast(m_editable ? 1 : 0)); } template @@ -52,6 +54,7 @@ class AddressEnricher rw::Read(src, m_postcode); m_interpol = static_cast(ReadPrimitiveFromSource(src)); + m_editable = ReadPrimitiveFromSource(src) != 0; } }; diff --git a/libs/indexer/imported_source.hpp b/libs/indexer/imported_source.hpp index 415b66aec..1a90dca84 100644 --- a/libs/indexer/imported_source.hpp +++ b/libs/indexer/imported_source.hpp @@ -5,6 +5,6 @@ namespace indexer { -inline constexpr uint8_t kImportedSourceKey = 0; +inline constexpr uint8_t kOpenAddressesEditableKey = 0; } // namespace indexer diff --git a/libs/map/framework.cpp b/libs/map/framework.cpp index a171b4ac3..8f2521f07 100644 --- a/libs/map/framework.cpp +++ b/libs/map/framework.cpp @@ -2979,7 +2979,7 @@ bool Framework::GetEditableMapObject(FeatureID const & fid, osm::EditableMapObje auto const & editor = osm::Editor::Instance(); bool const isImported = indexer::CustomKeyValue(ft->GetMetadata(feature::Metadata::FMD_CUSTOM_IDS)) - .Get(indexer::kImportedSourceKey).has_value(); + .Get(indexer::kOpenAddressesEditableKey).value_or(0) != 0; if (isImported) { // Imported address features have no real OSM IDs. Treat editing as creating a new OSM node diff --git a/libs/map/place_page_info.cpp b/libs/map/place_page_info.cpp index cbe87ff03..74099b2ec 100644 --- a/libs/map/place_page_info.cpp +++ b/libs/map/place_page_info.cpp @@ -4,9 +4,12 @@ #include "map/bookmark_manager.hpp" #include "map/track.hpp" +#include "indexer/classificator.hpp" +#include "indexer/custom_keyvalue.hpp" #include "indexer/feature.hpp" #include "indexer/feature_utils.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/imported_source.hpp" #include "indexer/mwm_set.hpp" #include "indexer/road_shields_parser.hpp" @@ -305,7 +308,19 @@ void Info::SetBookmarkId(kml::MarkId bookmarkId) bool Info::ShouldShowEditPlace() const { // TODO(mgsergio): Does IsFeature() imply !IsMyPosition()? - return !IsMyPosition() && IsFeature(); + if (IsMyPosition() || !IsFeature()) + return false; + + static auto const kAddrType = classif().GetTypeByPath({"building", "address"}); + if (GetTypes().Has(kAddrType)) + { + auto const customIds = GetMetadata(feature::Metadata::FMD_CUSTOM_IDS); + if (!customIds.empty() && + indexer::CustomKeyValue(customIds).Get(indexer::kOpenAddressesEditableKey).value_or(0) == 0) + return false; + } + + return true; } kml::LocalizableString Info::FormatNewBookmarkName() const diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 5daf18f2a..0a4c1d05a 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -19,6 +19,8 @@ import logging import math import os +import re +import urllib.request import zipfile from shapely.geometry import Point, Polygon @@ -192,10 +194,115 @@ def _find_address_geojsons(zf: zipfile.ZipFile) -> list[str]: return found +_LAYER_SUFFIX_RE = re.compile( + r"-(?:addresses|buildings|parcels|centerlines)-[^/]+\.geojson$", + re.IGNORECASE, +) + +_NC_ND_SUBSTRINGS: tuple[str, ...] = ( + "non-commercial", "noncommercial", + "no derivatives", "no-derivatives", "noderivatives", + "-nc-", "-nc/", "-nd-", "-nd/", +) + + +def _source_key_from_geojson_path(geojson_path: str) -> str: + """Derive the OA source key from a GeoJSON path inside a collection ZIP. + + Examples:: + + 'ca/bc/city_of_victoria-addresses-county.geojson' -> 'ca/bc/city_of_victoria' + 'ca/countrywide.geojson' -> 'ca/countrywide' + """ + key = _LAYER_SUFFIX_RE.sub("", geojson_path) + if key == geojson_path: + key = geojson_path.removesuffix(".geojson") + return key + + +def _license_is_odbl_compatible(license_info) -> bool: + """Return True if a single OA license object is compatible with ODbL. + + Blocklist approach: only reject if explicit NC or ND terms are present. + license_info may be a dict or a bare URL string (both occur in OA data). + """ + if isinstance(license_info, str): + url = license_info.lower() + text = "" + else: + url = (license_info.get("url") or "").lower() + text = (license_info.get("text") or "").lower() + + for term in _NC_ND_SUBSTRINGS: + if term in url or term in text: + return False + + return True + + +_OA_GITHUB_RAW = "https://raw.githubusercontent.com/openaddresses/openaddresses/master" + +# Cache: source_key -> bool (editable or not). Populated lazily during a run. +_source_editable_cache: dict[str, bool] = {} + + +def _load_source_json(source_key: str, oa_sources_dir: str | None = None) -> dict | None: + """Load a source JSON from a local OA repo clone or the OpenAddresses GitHub repo.""" + if oa_sources_dir: + local_path = os.path.join(oa_sources_dir, "sources", source_key + ".json") + try: + with open(local_path, "rb") as f: + return json.loads(f.read().decode("utf-8")) + except FileNotFoundError: + logger.warning(f"Source JSON not found locally for {source_key!r} ({local_path})") + return None + except Exception as exc: + logger.warning(f"Cannot read local source JSON for {source_key!r} ({local_path}): {exc}") + return None + + url = f"{_OA_GITHUB_RAW}/sources/{source_key}.json" + try: + with urllib.request.urlopen(url, timeout=10) as resp: + return json.loads(resp.read().decode("utf-8")) + except Exception as exc: + logger.warning(f"Cannot fetch source JSON for {source_key!r} ({url}): {exc}") + return None + + +def _is_odbl_compatible_source(source_key: str, oa_sources_dir: str | None = None) -> bool: + """Return True if no address layer in the OA source JSON has an explicit + NC or ND license clause. Results are cached for the duration of the run. + + Returns True when the source JSON is missing or has no license field: + absence of an explicit restriction is not a restriction. + """ + if source_key in _source_editable_cache: + return _source_editable_cache[source_key] + + source = _load_source_json(source_key, oa_sources_dir) + if source is None: + _source_editable_cache[source_key] = True + return True + + address_layers = source.get("layers", {}).get("addresses", []) + for layer_entry in address_layers: + license_info = layer_entry.get("license") + if not license_info: + continue + if not _license_is_odbl_compatible(license_info): + logger.info(f"Source {source_key!r} has NC/ND license clause, marking as non-editable.") + _source_editable_cache[source_key] = False + return False + + _source_editable_cache[source_key] = True + return True + + def process( zip_path: str, output_dir: str, borders_dir: str, + oa_sources_dir: str | None = None, ) -> None: os.makedirs(output_dir, exist_ok=True) @@ -214,19 +321,27 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: skipped_region = 0 skipped_duplicate = 0 - # Deduplication key: (number, street, unit, lat_3dp, lon_3dp). - # Rounding to 3 decimal places (~110 m) catches the same address appearing - # in multiple overlapping OA sources (e.g. us/or/marion and - # us/or/marion_and_polk) while still preserving distinct unit entries at - # the same building. mwm_name is omitted — it is fully determined by - # the rounded coordinates and adds no dedup precision. - seen: set[tuple[str, str, str, float, float]] = set() + # Dedup: (mwm_name, number, street, unit). + # ODbL sources are processed first (sorted below), so the first time we + # see an address in a given MWM it wins. Any subsequent record for the + # same (mwm, number, street, unit) is dropped regardless of coordinates. + # This handles both same-region overlap (city + county covering the same + # building) and cross-region name collisions (e.g. a Spokane ODbL address + # and a Yakima county non-ODbL address with the same street name both + # landing in the same MWM polygon). + seen_mwm_addr: set[tuple[str, str, str, str]] = set() try: with zipfile.ZipFile(zip_path, "r") as zf: geojson_paths = _find_address_geojsons(zf) + # Process ODbL-compatible sources first so they win dedup over NC/ND sources. + # An address present in both ODbL and NC/ND data should remain editable. + geojson_paths = sorted(geojson_paths, + key=lambda p: (0 if _is_odbl_compatible_source(_source_key_from_geojson_path(p), oa_sources_dir) else 1)) for geojson_path in geojson_paths: - logger.info(f"Processing {geojson_path}...") + source_key = _source_key_from_geojson_path(geojson_path) + editable = _is_odbl_compatible_source(source_key, oa_sources_dir) + logger.info(f"Processing {geojson_path} (editable={editable})...") with zf.open(geojson_path) as raw: for line_bytes in raw: line_bytes = line_bytes.rstrip(b"\n\r") @@ -263,11 +378,11 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: skipped_region += 1 continue - dedup_key = (number, street, unit, round(lat, 3), round(lon, 3)) - if dedup_key in seen: + mwm_addr_key = (mwm_name, number, street, unit) + if mwm_addr_key in seen_mwm_addr: skipped_duplicate += 1 continue - seen.add(dedup_key) + seen_mwm_addr.add(mwm_addr_key) ipoint = _point_to_int64(lon, lat) @@ -277,6 +392,7 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: _write_string(buf, street) _write_string(buf, postcode) buf.write(bytes([_INTERPOL_NONE])) + buf.write(bytes([1 if editable else 0])) _write_varuint(buf, 1) _write_varint(buf, ipoint) @@ -317,8 +433,16 @@ def main() -> None: required=True, help="Path to directory containing CoMaps .poly border files (data/borders/)", ) + parser.add_argument( + "--oa-sources-dir", + default=None, + help=( + "Path to a local clone of github.com/openaddresses/openaddresses. " + "If set, license JSONs are read from disk instead of fetched from GitHub." + ), + ) args = parser.parse_args() - process(args.zip_file, args.output_dir, args.borders_dir) + process(args.zip_file, args.output_dir, args.borders_dir, args.oa_sources_dir) if __name__ == "__main__": diff --git a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py index b3f5ef6d8..b7f52f9bd 100644 --- a/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py +++ b/tools/python/maps_generator/tests/test_openaddresses_preprocessor.py @@ -243,13 +243,14 @@ def test_fits_in_int64(self): class TestRecordRoundTrip(unittest.TestCase): - def _build_record(self, number, street, postcode, lon, lat): + def _build_record(self, number, street, postcode, lon, lat, editable=True): f = io.BytesIO() _write_string(f, number) _write_string(f, number) _write_string(f, street) _write_string(f, postcode) f.write(bytes([_mod._INTERPOL_NONE])) + f.write(bytes([1 if editable else 0])) _write_varuint(f, 1) _write_varint(f, _point_to_int64(lon, lat)) return f.getvalue() @@ -285,12 +286,14 @@ def read_string(): m_postcode = read_string() interpol = data[pos[0]] pos[0] += 1 + editable = data[pos[0]] != 0 + pos[0] += 1 count = read_varuint() point = read_varint() return { "m_from": m_from, "m_to": m_to, "m_street": m_street, "m_postcode": m_postcode, "interpol": interpol, - "count": count, "point": point, + "editable": editable, "count": count, "point": point, } def test_from_equals_to(self): @@ -333,6 +336,16 @@ def test_record_fully_consumed(self): rec = self._parse_record(data) self.assertEqual(rec["m_from"], "99") + def test_editable_true(self): + data = self._build_record("1", "A St", "", -123.0, 49.0, editable=True) + rec = self._parse_record(data) + self.assertTrue(rec["editable"]) + + def test_editable_false(self): + data = self._build_record("1", "A St", "", -123.0, 49.0, editable=False) + rec = self._parse_record(data) + self.assertFalse(rec["editable"]) + def _write_poly(directory: str, name: str, rings: list) -> str: """Write a minimal .poly file and return its path. @@ -630,12 +643,14 @@ def read_string(): m_postcode = read_string() interpol = data[pos[0]] pos[0] += 1 + editable = data[pos[0]] != 0 + pos[0] += 1 count = read_varuint() point = read_varint() records.append({ "m_from": m_from, "m_to": m_to, "m_street": m_street, "m_postcode": m_postcode, "interpol": interpol, - "count": count, "point": point, + "editable": editable, "count": count, "point": point, }) return records @@ -692,5 +707,142 @@ def test_mixed_valid_and_invalid(self): self.assertEqual(len(records), 1) +class TestLicenseIsOdblCompatible(unittest.TestCase): + def _check(self, license_info): + return _mod._license_is_odbl_compatible(license_info) + + def test_clean_url_is_compatible(self): + self.assertTrue(self._check({"url": "https://creativecommons.org/licenses/by/4.0/"})) + + def test_nc_url_is_blocked(self): + self.assertFalse(self._check({"url": "https://creativecommons.org/licenses/by-nc/4.0/"})) + + def test_nd_url_is_blocked(self): + self.assertFalse(self._check({"url": "https://creativecommons.org/licenses/by-nd/4.0/"})) + + def test_nc_in_text_is_blocked(self): + self.assertFalse(self._check({"url": "https://example.com/license", "text": "non-commercial use only"})) + + def test_nd_in_text_is_blocked(self): + self.assertFalse(self._check({"url": "", "text": "no derivatives allowed"})) + + def test_bare_string_clean_is_compatible(self): + self.assertTrue(self._check("https://opendatacommons.org/licenses/odbl/")) + + def test_bare_string_nc_is_blocked(self): + self.assertFalse(self._check("https://creativecommons.org/licenses/by-nc-sa/4.0/")) + + def test_odbl_url_is_compatible(self): + self.assertTrue(self._check({"url": "https://opendatacommons.org/licenses/odbl/1.0/"})) + + +class TestSourceKeyFromGeojsonPath(unittest.TestCase): + def _key(self, path): + return _mod._source_key_from_geojson_path(path) + + def test_county_path(self): + self.assertEqual( + self._key("ca/bc/city_of_victoria-addresses-county.geojson"), + "ca/bc/city_of_victoria", + ) + + def test_city_path(self): + self.assertEqual( + self._key("us/wa/city_of_spokane-addresses-city.geojson"), + "us/wa/city_of_spokane", + ) + + def test_countrywide_path(self): + self.assertEqual( + self._key("ca/countrywide.geojson"), + "ca/countrywide", + ) + + def test_no_layer_suffix(self): + self.assertEqual( + self._key("us/or/portland.geojson"), + "us/or/portland", + ) + + +class TestIsOdblCompatibleSource(unittest.TestCase): + def setUp(self): + # Isolate cache between tests. + _mod._source_editable_cache.clear() + + def tearDown(self): + _mod._source_editable_cache.clear() + + def _mock_load(self, source_json): + """Patch _load_source_json to return source_json without HTTP.""" + import unittest.mock + return unittest.mock.patch.object(_mod, "_load_source_json", return_value=source_json) + + def test_odbl_layer_is_compatible(self): + source = { + "layers": { + "addresses": [ + {"license": {"url": "https://opendatacommons.org/licenses/odbl/1.0/"}} + ] + } + } + with self._mock_load(source): + self.assertTrue(_mod._is_odbl_compatible_source("ca/bc/test")) + + def test_nc_layer_is_blocked(self): + source = { + "layers": { + "addresses": [ + {"license": {"url": "https://creativecommons.org/licenses/by-nc/4.0/"}} + ] + } + } + with self._mock_load(source): + self.assertFalse(_mod._is_odbl_compatible_source("ca/bc/nc_source")) + + def test_missing_source_json_defaults_to_true(self): + with self._mock_load(None): + self.assertTrue(_mod._is_odbl_compatible_source("ca/bc/missing")) + + def test_no_license_field_defaults_to_true(self): + source = {"layers": {"addresses": [{"data": "something"}]}} + with self._mock_load(source): + self.assertTrue(_mod._is_odbl_compatible_source("ca/bc/nolicense")) + + def test_result_is_cached(self): + source = { + "layers": { + "addresses": [ + {"license": {"url": "https://creativecommons.org/licenses/by-nc/4.0/"}} + ] + } + } + import unittest.mock + mock_fn = unittest.mock.MagicMock(return_value=source) + with unittest.mock.patch.object(_mod, "_load_source_json", mock_fn): + _mod._is_odbl_compatible_source("ca/bc/cached") + _mod._is_odbl_compatible_source("ca/bc/cached") + self.assertEqual(mock_fn.call_count, 1) + + def test_local_sources_dir_reads_from_disk(self): + source = { + "layers": { + "addresses": [ + {"license": {"url": "https://creativecommons.org/licenses/by-nc/4.0/"}} + ] + } + } + with tempfile.TemporaryDirectory() as d: + source_path = os.path.join(d, "sources", "ca", "bc") + os.makedirs(source_path) + with open(os.path.join(source_path, "test.json"), "w") as f: + json.dump(source, f) + self.assertFalse(_mod._is_odbl_compatible_source("ca/bc/test", d)) + + def test_local_sources_dir_missing_file_defaults_to_true(self): + with tempfile.TemporaryDirectory() as d: + self.assertTrue(_mod._is_odbl_compatible_source("ca/bc/nonexistent", d)) + + if __name__ == "__main__": unittest.main() From 0edc1e47fb2a24736230212c9dce913eaa73bd94 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Thu, 14 May 2026 22:27:38 -0700 Subject: [PATCH 30/40] Add OpenAddresses CI workflow and preprocessor helper script Signed-off-by: lone-cloud --- .forgejo/workflows/mapgen-openaddresses.yml | 117 ++++++++++++++++++++ tools/unix/run_oa_preprocessor.sh | 80 +++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 .forgejo/workflows/mapgen-openaddresses.yml create mode 100755 tools/unix/run_oa_preprocessor.sh diff --git a/.forgejo/workflows/mapgen-openaddresses.yml b/.forgejo/workflows/mapgen-openaddresses.yml new file mode 100644 index 000000000..91415d8ea --- /dev/null +++ b/.forgejo/workflows/mapgen-openaddresses.yml @@ -0,0 +1,117 @@ +name: mapgen-openaddresses +on: + workflow_dispatch: # Manual trigger + inputs: + oa-collection-id: + description: 'OpenAddresses collection ID (e.g. 6 for Canada)' + required: true + default: '6' + type: string + +env: + DEBIAN_FRONTEND: noninteractive + TZ: Etc/UTC + ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} + ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }} + OA_API_TOKEN: ${{ secrets.OA_API_TOKEN }} + +jobs: + update-openaddresses: + name: Update OpenAddresses data + runs-on: mapfilemaker + container: + image: codeberg.org/comaps/maps_generator:f6d53d54f794 + volumes: + - /mnt/4tbexternal/:/mnt/4tbexternal/ + - /mnt/4tbexternal/osm-planet:/home/planet + concurrency: + group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - name: Resolve collection name + id: resolve + shell: bash + run: | + COLLECTION_ID="${{ inputs.oa-collection-id }}" + # Do NOT use -L: %{redirect_url} is only populated when curl does not follow the redirect. + DOWNLOAD_URL=$(curl -fsS \ + -H "Authorization: Bearer ${OA_API_TOKEN}" \ + -w "%{redirect_url}" -o /dev/null \ + "https://batch.openaddresses.io/api/collections/${COLLECTION_ID}/data") + if [ -z "${DOWNLOAD_URL}" ]; then + echo "ERROR: could not resolve download URL for collection ${COLLECTION_ID}" >&2 + exit 1 + fi + # Extract name from URL: https://.../collection-ca.zip -> ca + COLLECTION_NAME=$(basename "${DOWNLOAD_URL}" | sed 's/^collection-//;s/\.zip$//') + echo "Collection ${COLLECTION_ID} -> name: ${COLLECTION_NAME}, url: ${DOWNLOAD_URL}" + echo "collection-name=${COLLECTION_NAME}" >> "$GITHUB_OUTPUT" + echo "download-url=${DOWNLOAD_URL}" >> "$GITHUB_OUTPUT" + + - name: Download OpenAddresses collection + shell: bash + run: | + COLLECTION_NAME="${{ steps.resolve.outputs.collection-name }}" + DOWNLOAD_URL="${{ steps.resolve.outputs.download-url }}" + DEST="/home/planet/collection-oa-${COLLECTION_NAME}.zip" + + echo "Downloading collection ${COLLECTION_NAME} to ${DEST} ..." + [ -f "${DEST}" ] && mv -f "${DEST}" "${DEST}.bak" + curl -fsSL -o "${DEST}" "${DOWNLOAD_URL}" + echo "Downloaded $(du -sh "${DEST}" | cut -f1) to ${DEST}" + + curl -X POST https://comaps.zulipchat.com/api/v1/messages \ + -u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \ + --data-urlencode type=stream \ + --data-urlencode 'to="DevOps"' \ + --data-urlencode topic=codeberg-bot \ + --data-urlencode "content=OpenAddresses collection ${COLLECTION_NAME} downloaded." + + - name: Checkout main repo + shell: bash + run: | + echo "Cloning $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY branch $FORGEJO_REF_NAME" + cd ~ + git clone --depth 1 --recurse-submodules --shallow-submodules \ + -b $FORGEJO_REF_NAME --single-branch \ + $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY.git comaps + + - name: Install Python dependencies + shell: bash + # shapely was added to requirements.txt in this PR but the pinned container + # image predates it. Install at runtime until the image is rebuilt. + # TODO: remove this step once maps_generator image is rebuilt with new requirements.txt + run: | + pip install --quiet "shapely>=2.0" + + - name: Run OpenAddresses preprocessor + shell: bash + run: | + COLLECTION_NAME="${{ steps.resolve.outputs.collection-name }}" + ZIP="/home/planet/collection-oa-${COLLECTION_NAME}.zip" + OUTPUT_DIR="/home/planet/oa-out-${COLLECTION_NAME}" + BORDERS_DIR="${HOME}/comaps/data/borders" + + [ -f "${ZIP}" ] || { echo "ERROR: ${ZIP} not found — re-run the workflow to download it" >&2; exit 1; } + + echo "Backing up previous output ..." + rm -rf "${OUTPUT_DIR}.bak" + [ -d "${OUTPUT_DIR}" ] && mv -fT "${OUTPUT_DIR}" "${OUTPUT_DIR}.bak" || true + mkdir -p "${OUTPUT_DIR}" + + echo "Running preprocessor ..." + python3 ~/comaps/tools/python/maps_generator/generator/openaddresses_preprocessor.py \ + "${ZIP}" \ + "${OUTPUT_DIR}" \ + --borders-dir "${BORDERS_DIR}" + + echo "Preprocessor output:" + ls -lah "${OUTPUT_DIR}" | head -20 + echo "DONE" + + curl -X POST https://comaps.zulipchat.com/api/v1/messages \ + -u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \ + --data-urlencode type=stream \ + --data-urlencode 'to="DevOps"' \ + --data-urlencode topic=codeberg-bot \ + --data-urlencode "content=OpenAddresses preprocessor finished for collection ${COLLECTION_NAME}. Output at ${OUTPUT_DIR}." diff --git a/tools/unix/run_oa_preprocessor.sh b/tools/unix/run_oa_preprocessor.sh new file mode 100755 index 000000000..ff802b0e8 --- /dev/null +++ b/tools/unix/run_oa_preprocessor.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# run_oa_preprocessor.sh — Download an OpenAddresses collection and run the CoMaps preprocessor. +# +# Required environment variables: +# OA_API_TOKEN Bearer token for batch.openaddresses.io +# OA_COLLECTION_ID Integer collection ID (e.g. 6 for Canada) +# OA_COLLECTION_NAME Collection name used in filenames/paths (e.g. ca) +# COMAPS_DIR Path to the CoMaps source checkout +# +# Optional environment variables: +# OA_ZIP_PATH Where to save the downloaded ZIP (default: /tmp/collection-oa-${OA_COLLECTION_NAME}.zip) +# OA_OUTPUT_DIR Output dir for .tempaddr files (default: /tmp/oa-out-${OA_COLLECTION_NAME}) +# OA_SOURCES_DIR Local clone of github.com/openaddresses/openaddresses for offline license lookup +# SKIP_DOWNLOAD Set to 1 to skip the download step and use an existing ZIP + +set -euo pipefail + +: "${OA_API_TOKEN:?OA_API_TOKEN is required}" +: "${OA_COLLECTION_ID:?OA_COLLECTION_ID is required}" +: "${OA_COLLECTION_NAME:?OA_COLLECTION_NAME is required}" +: "${COMAPS_DIR:?COMAPS_DIR is required}" + +OA_ZIP_PATH="${OA_ZIP_PATH:-/tmp/collection-oa-${OA_COLLECTION_NAME}.zip}" +OA_OUTPUT_DIR="${OA_OUTPUT_DIR:-/tmp/oa-out-${OA_COLLECTION_NAME}}" +BORDERS_DIR="${COMAPS_DIR}/data/borders" +PREPROCESSOR="${COMAPS_DIR}/tools/python/maps_generator/generator/openaddresses_preprocessor.py" +SKIP_DOWNLOAD="${SKIP_DOWNLOAD:-0}" + +echo "=== OpenAddresses preprocessor for collection ${OA_COLLECTION_NAME} (ID: ${OA_COLLECTION_ID}) ===" + +# --- Download --- +if [ "${SKIP_DOWNLOAD}" != "1" ]; then + echo "Resolving download URL ..." + # Two-step download: the API returns a 302 redirect to the actual file host. + # We resolve the redirect URL first (without -L so %{redirect_url} is populated), + # then download from the resolved URL without the Bearer token. + # Sending the Authorization header directly to the file host may cause errors + # on some S3-compatible hosts that use their own auth in the URL. + DOWNLOAD_URL=$(curl -fsS \ + -H "Authorization: Bearer ${OA_API_TOKEN}" \ + -w "%{redirect_url}" -o /dev/null \ + "https://batch.openaddresses.io/api/collections/${OA_COLLECTION_ID}/data") + + if [ -z "${DOWNLOAD_URL}" ]; then + echo "ERROR: could not resolve download URL for collection ${OA_COLLECTION_ID}" >&2 + exit 1 + fi + + echo "Downloading ${DOWNLOAD_URL} -> ${OA_ZIP_PATH} ..." + [ -f "${OA_ZIP_PATH}" ] && mv -f "${OA_ZIP_PATH}" "${OA_ZIP_PATH}.bak" + curl -fsSL -o "${OA_ZIP_PATH}" "${DOWNLOAD_URL}" + echo "Downloaded $(du -sh "${OA_ZIP_PATH}" | cut -f1)" +else + echo "Skipping download (SKIP_DOWNLOAD=1), using existing: ${OA_ZIP_PATH}" + [ -f "${OA_ZIP_PATH}" ] || { echo "ERROR: ${OA_ZIP_PATH} does not exist" >&2; exit 1; } +fi + +# --- Run preprocessor --- +echo "Preparing output directory: ${OA_OUTPUT_DIR} ..." +rm -rf "${OA_OUTPUT_DIR}.bak" +[ -d "${OA_OUTPUT_DIR}" ] && mv -fT "${OA_OUTPUT_DIR}" "${OA_OUTPUT_DIR}.bak" || true +mkdir -p "${OA_OUTPUT_DIR}" + +PREPROCESSOR_ARGS=( + "${OA_ZIP_PATH}" + "${OA_OUTPUT_DIR}" + --borders-dir "${BORDERS_DIR}" +) + +if [ -n "${OA_SOURCES_DIR:-}" ]; then + PREPROCESSOR_ARGS+=(--oa-sources-dir "${OA_SOURCES_DIR}") +fi + +echo "Running preprocessor ..." +python3 "${PREPROCESSOR}" "${PREPROCESSOR_ARGS[@]}" + +echo "" +echo "=== Done. Output in ${OA_OUTPUT_DIR} ===" +echo "Set ADDRESSES_PATH=${OA_OUTPUT_DIR} in the [External] section of map_generator.ini" +ls -lah "${OA_OUTPUT_DIR}" | head -20 From b5c2ed3aa99e35c078dfd8df1b7bcf82aca4a67f Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Mon, 18 May 2026 11:29:06 -0700 Subject: [PATCH 31/40] preprocessor: fix 404s when fetching OA source license JSONs The collection ZIP uses hyphen-separated source names (e.g. rouyn-noranda) but the OpenAddresses GitHub repo uses underscore-separated names (e.g. rouyn_noranda.json). When the canonical key returns HTTP 404, retry with hyphens replaced by underscores before giving up. On total failure the warning now explicitly states the fallback behaviour (defaulting to ODbL-compatible) so callers know the run continues safely. Signed-off-by: lone-cloud --- .../generator/openaddresses_preprocessor.py | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 0a4c1d05a..5ce1fb565 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -20,6 +20,7 @@ import math import os import re +import urllib.error import urllib.request import zipfile @@ -260,13 +261,39 @@ def _load_source_json(source_key: str, oa_sources_dir: str | None = None) -> dic logger.warning(f"Cannot read local source JSON for {source_key!r} ({local_path}): {exc}") return None - url = f"{_OA_GITHUB_RAW}/sources/{source_key}.json" - try: - with urllib.request.urlopen(url, timeout=10) as resp: - return json.loads(resp.read().decode("utf-8")) - except Exception as exc: - logger.warning(f"Cannot fetch source JSON for {source_key!r} ({url}): {exc}") - return None + # The batch ZIP sometimes uses hyphens where the source repo uses underscores + # (e.g. "rouyn-noranda" vs "rouyn_noranda"). Try the original key first, then + # the underscore-normalised form as a fallback. + keys_to_try = [source_key] + normalised = source_key.replace("-", "_") + if normalised != source_key: + keys_to_try.append(normalised) + + for key in keys_to_try: + url = f"{_OA_GITHUB_RAW}/sources/{key}.json" + try: + with urllib.request.urlopen(url, timeout=10) as resp: + return json.loads(resp.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + if exc.code == 404: + continue + logger.warning( + f"Cannot fetch source JSON for {source_key!r} ({url}): {exc};" + " defaulting to ODbL-compatible" + ) + return None + except Exception as exc: + logger.warning( + f"Cannot fetch source JSON for {source_key!r} ({url}): {exc};" + " defaulting to ODbL-compatible" + ) + return None + + logger.warning( + f"Source JSON not found for {source_key!r} on GitHub (HTTP 404);" + " defaulting to ODbL-compatible" + ) + return None def _is_odbl_compatible_source(source_key: str, oa_sources_dir: str | None = None) -> bool: From 932292097c4ce25ca957d8c899626c88103a60a0 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Wed, 20 May 2026 09:45:32 -0700 Subject: [PATCH 32/40] address_enricher: add .tempaddr file-format version header Fixes a Reader::SizeException crash when the generator reads .tempaddr files produced before the m_editable field was added to RawEntryBase. Old files cause cascading read misalignment: m_editable consumes the iPoints count byte, leaving one stray byte at the end; the while loop re-enters and ReadVarUint hits EOF on the continuation read. Write a 2-byte header (magic=0xFF, version=0x01) at the start of every .tempaddr file. ProcessRawEntries validates it and aborts with a clear message instead of crashing with a cryptic SizeException. Signed-off-by: Egor Philippov --- generator/address_enricher.cpp | 8 ++++ generator/address_enricher.hpp | 4 ++ generator/address_parser/processor.cpp | 6 +++ generator/generator_tests/addresses_tests.cpp | 41 +++++++++++++++++++ .../generator/openaddresses_preprocessor.py | 1 + 5 files changed, 60 insertions(+) diff --git a/generator/address_enricher.cpp b/generator/address_enricher.cpp index 67a3b9610..884704f7c 100644 --- a/generator/address_enricher.cpp +++ b/generator/address_enricher.cpp @@ -109,6 +109,14 @@ void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn c { FileReader reader(path); ReaderSource src(reader); + + CHECK_GREATER_OR_EQUAL(src.Size(), 2, ("tempaddr file too small or empty:", path)); + uint8_t const magic = ReadPrimitiveFromSource(src); + CHECK_EQUAL(magic, kTempAddrMagic, + ("Old-format tempaddr file. Delete cached .tempaddr files and regenerate:", path)); + uint8_t const version = ReadPrimitiveFromSource(src); + CHECK_EQUAL(version, kTempAddrVersion, ("Unsupported tempaddr version:", version, path)); + while (src.Size()) { Entry e; diff --git a/generator/address_enricher.hpp b/generator/address_enricher.hpp index f27370e4a..62a0f5658 100644 --- a/generator/address_enricher.hpp +++ b/generator/address_enricher.hpp @@ -20,6 +20,10 @@ class AddressEnricher static double constexpr kDistanceThresholdM = 50.0; public: + // .tempaddr file-format header: magic byte + version. + static constexpr uint8_t kTempAddrMagic = 0xFF; + static constexpr uint8_t kTempAddrVersion = 1; + struct RawEntryBase { std::string m_from, m_to, m_street, m_postcode; diff --git a/generator/address_parser/processor.cpp b/generator/address_parser/processor.cpp index 230761c01..2743214e0 100644 --- a/generator/address_parser/processor.cpp +++ b/generator/address_parser/processor.cpp @@ -1,5 +1,6 @@ #include "processor.hpp" +#include "generator/address_enricher.hpp" #include "tiger_parser.hpp" #include "geometry/mercator.hpp" @@ -22,6 +23,11 @@ Processor::Processor(std::string const & dataPath, std::string const & outputPat FileWriter & Processor::GetWriter(std::string const & country) { auto res = m_country2writer.try_emplace(country, base::JoinPath(m_outputPath, country) + TEMP_ADDR_EXTENSION); + if (res.second) + { + uint8_t const header[2] = {generator::AddressEnricher::kTempAddrMagic, generator::AddressEnricher::kTempAddrVersion}; + res.first->second.Write(header, 2); + } return res.first->second; } diff --git a/generator/generator_tests/addresses_tests.cpp b/generator/generator_tests/addresses_tests.cpp index fdf759f4a..5431becfd 100644 --- a/generator/generator_tests/addresses_tests.cpp +++ b/generator/generator_tests/addresses_tests.cpp @@ -2,6 +2,18 @@ #include "generator/address_enricher.hpp" #include "generator/addresses_collector.hpp" +#include "generator/generator_tests_support/test_with_classificator.hpp" + +#include "platform/platform_tests_support/scoped_file.hpp" + +#include "coding/file_writer.hpp" +#include "coding/point_coding.hpp" +#include "coding/read_write_utils.hpp" + +#include "geometry/point2d.hpp" + +using generator::tests_support::TestWithClassificator; +using platform::tests_support::ScopedFile; UNIT_TEST(GenerateAddresses_AddressInfo_FormatRange) { @@ -38,3 +50,32 @@ UNIT_TEST(AddressEnricher_GetHNRange_Alphanumeric) e.m_from = "foo"; e.m_to = "bar"; TEST_EQUAL(e.GetHNRange(), RawEntry::kInvalidRange, ()); } + +UNIT_CLASS_TEST(TestWithClassificator, AddressEnricher_TempAddrFormat_Valid) +{ + using namespace generator; + + ScopedFile sf("test_format_header.tempaddr", ScopedFile::Mode::DoNotCreate); + { + FileWriter w(sf.GetFullPath()); + uint8_t const header[2] = {AddressEnricher::kTempAddrMagic, AddressEnricher::kTempAddrVersion}; + w.Write(header, 2); + + AddressEnricher::RawEntryBase e; + e.m_from = "12A"; + e.m_to = "12A"; + e.m_street = "Test St"; + e.m_postcode = "V1A 1A1"; + e.m_interpol = feature::InterpolType::None; + e.m_editable = true; + e.Save(w); + + std::vector const pts = {PointToInt64Obsolete({0.0, 0.0}, kPointCoordBits)}; + rw::Write(w, pts); + } + + AddressEnricher enricher; + int count = 0; + enricher.ProcessRawEntries(sf.GetFullPath(), [&count](feature::FeatureBuilder const &) { ++count; }); + TEST_EQUAL(count, 1, ()); +} diff --git a/tools/python/maps_generator/generator/openaddresses_preprocessor.py b/tools/python/maps_generator/generator/openaddresses_preprocessor.py index 5ce1fb565..534ad18c5 100644 --- a/tools/python/maps_generator/generator/openaddresses_preprocessor.py +++ b/tools/python/maps_generator/generator/openaddresses_preprocessor.py @@ -341,6 +341,7 @@ def get_writer(mwm_name: str) -> io.BufferedWriter: if mwm_name not in writers: path = os.path.join(output_dir, mwm_name + _ADDR_EXT) writers[mwm_name] = open(path, "wb") + writers[mwm_name].write(b"\xff\x01") # tempaddr format header: magic + version return writers[mwm_name] total = 0 From f64e0f395e96a0cc21ddbb23cced0c04a2c94b0c Mon Sep 17 00:00:00 2001 From: ProgramminCat <72707293+ProgramminCat@users.noreply.github.com> Date: Tue, 31 Mar 2026 08:14:15 -0400 Subject: [PATCH 33/40] [styles] Add amenity=stage Signed-off-by: ProgramminCat <72707293+ProgramminCat@users.noreply.github.com> --- android/sdk/src/main/res/values-fr/types_strings.xml | 1 + android/sdk/src/main/res/values/types_strings.xml | 1 + data/editor.config | 3 +++ data/mapcss-mapping.csv | 4 ++-- data/styles/default/dark/symbols/stage-m.svg | 9 +++++++++ data/styles/default/include/Icons.mapcss | 6 ++++++ .../default/include/priorities_4_overlays.prio.txt | 1 + data/styles/default/light/symbols/stage-m.svg | 9 +++++++++ .../outdoors/include/priorities_4_overlays.prio.txt | 1 + .../LocalizedStrings/en.lproj/LocalizableTypes.strings | 1 + .../LocalizedStrings/fr.lproj/LocalizableTypes.strings | 1 + 11 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 data/styles/default/dark/symbols/stage-m.svg create mode 100644 data/styles/default/light/symbols/stage-m.svg diff --git a/android/sdk/src/main/res/values-fr/types_strings.xml b/android/sdk/src/main/res/values-fr/types_strings.xml index 7bec9fb23..e72c0fdad 100644 --- a/android/sdk/src/main/res/values-fr/types_strings.xml +++ b/android/sdk/src/main/res/values-fr/types_strings.xml @@ -1390,6 +1390,7 @@ Poste de garde forestier Escape room Abri pour animaux + Scène Portillon Bureau des agents de sécurité Guérite de sécurité diff --git a/android/sdk/src/main/res/values/types_strings.xml b/android/sdk/src/main/res/values/types_strings.xml index feb7aedc5..e9d26dded 100644 --- a/android/sdk/src/main/res/values/types_strings.xml +++ b/android/sdk/src/main/res/values/types_strings.xml @@ -212,6 +212,7 @@ Waste Transfer Station Water Tank Refill Point Water Tank Refill Point + Stage Barrier Barrier Block diff --git a/data/editor.config b/data/editor.config index d9a423064..18eb2f484 100644 --- a/data/editor.config +++ b/data/editor.config @@ -521,6 +521,9 @@ + + + diff --git a/data/mapcss-mapping.csv b/data/mapcss-mapping.csv index 2b5a43e86..b51ba3ee2 100644 --- a/data/mapcss-mapping.csv +++ b/data/mapcss-mapping.csv @@ -340,7 +340,7 @@ waterway|drain|tunnel;[waterway=drain][tunnel?];;name;int_name;230; landuse|cemetery|christian;[landuse=cemetery][religion=christian];;name;int_name;231; highway|cycleway|tunnel;[highway=cycleway][tunnel?];;name;int_name;232; amenity|parking|permissive;[amenity=parking][access=permissive];;name;int_name;233; -deprecated:boundary|administrative|5:04.2024;[boundary=administrative][admin_level=5];x;name;int_name;234; +amenity|stage;234; highway|unclassified|tunnel;[highway=unclassified][tunnel?];;name;int_name;235; amenity|recycling|centre;[amenity=recycling][recycling_type=centre];;name;int_name;236; deprecated:amenity|recycling:01.2026;237;amenity|recycling|container @@ -1759,4 +1759,4 @@ leisure|escape_game;1628; amenity|luggage_locker;1629; building|guardhouse;[building=guardhouse],[amenity=security_booth],[amenity=checkpoint];;;;1630; office|security;1631; -shop|lighting;1632; +shop|lighting;1632; \ No newline at end of file diff --git a/data/styles/default/dark/symbols/stage-m.svg b/data/styles/default/dark/symbols/stage-m.svg new file mode 100644 index 000000000..d29a3c427 --- /dev/null +++ b/data/styles/default/dark/symbols/stage-m.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/styles/default/include/Icons.mapcss b/data/styles/default/include/Icons.mapcss index 0a1fea9a0..2bfbdea7d 100644 --- a/data/styles/default/include/Icons.mapcss +++ b/data/styles/default/include/Icons.mapcss @@ -1020,6 +1020,7 @@ node|z17-[amenity=social_facility], node|z17-[social_facility=food_bank], node|z17-[social_facility=soup_kitchen], node|z17-[amenity=animal_shelter], +node|z17-[amenity=stage], node|z17-[amenity=kindergarten], node|z17-[amenity=childcare], node|z16-[amenity=school], @@ -1222,6 +1223,11 @@ node|z17-[amenity=animal_shelter], node|z18-[amenity=animal_shelter], {font-size: 11;} +node|z17-[amenity=stage], +{icon-image: stage-m.svg;} +node|z18-[amenity=stage], +{font-size: 11;} + node|z16-[amenity=pharmacy], {icon-image: pharmacy-m.svg;icon-min-distance: 20;} node|z17-[amenity=pharmacy], diff --git a/data/styles/default/include/priorities_4_overlays.prio.txt b/data/styles/default/include/priorities_4_overlays.prio.txt index 1d898111d..19bf333e7 100644 --- a/data/styles/default/include/priorities_4_overlays.prio.txt +++ b/data/styles/default/include/priorities_4_overlays.prio.txt @@ -998,6 +998,7 @@ amenity-prep_school # icon z17- (also has captio amenity-recycling-centre # icon z16- (also has caption(optional) z16-, area z15-) amenity-sailing_school # icon z17- (also has caption(optional) z17-) amenity-veterinary # icon z16- (also has caption(optional) z16-) +amenity-stage # icon z17- (also has caption(optional) z17-) craft-electrician # icon z17- (also has caption(optional) z18-) craft-electronics_repair # icon z17- (also has caption(optional) z18-) craft-photographer # icon z17- (also has caption(optional) z18-) diff --git a/data/styles/default/light/symbols/stage-m.svg b/data/styles/default/light/symbols/stage-m.svg new file mode 100644 index 000000000..d29a3c427 --- /dev/null +++ b/data/styles/default/light/symbols/stage-m.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/styles/outdoors/include/priorities_4_overlays.prio.txt b/data/styles/outdoors/include/priorities_4_overlays.prio.txt index 1785479cc..be221829b 100644 --- a/data/styles/outdoors/include/priorities_4_overlays.prio.txt +++ b/data/styles/outdoors/include/priorities_4_overlays.prio.txt @@ -998,6 +998,7 @@ amenity-prep_school # icon z17- (also has captio amenity-recycling-centre # icon z16- (also has caption(optional) z16-, area z15-) amenity-sailing_school # icon z17- (also has caption(optional) z17-) amenity-veterinary # icon z16- (also has caption(optional) z16-) +amenity-stage # icon z17- (also has caption(optional) z17-) craft-electrician # icon z17- (also has caption(optional) z18-) craft-electronics_repair # icon z17- (also has caption(optional) z18-) craft-photographer # icon z17- (also has caption(optional) z18-) diff --git a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings index e44a3583f..ecc693b45 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings @@ -1516,3 +1516,4 @@ /* https://wiki.openstreetmap.org/wiki/Tag:leisure=sports_hall */ "type.leisure.sports_hall" = "Sports hall"; "type.xmas.tree" = "Christmas Tree"; +"type.amenity.stage" = "Stage"; \ No newline at end of file diff --git a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings index 60a9561d2..57fd82faa 100644 --- a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings @@ -694,6 +694,7 @@ "type.landuse.plant_nursery" = "Pépinière"; "type.leisure" = "Loisir"; "type.leisure.bandstand" = "Kiosque à musique"; +"type.amenity.stage" = "Scène"; "type.leisure.common" = "Terrain public"; "type.leisure.dog_park" = "Parc canin"; "type.leisure.escape_game" = "Escape room"; From 5cc8059e2cc153881854c5d9c5fab8136583b52d Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 15 Jun 2026 00:30:48 +0700 Subject: [PATCH 34/40] [strings] Add amenity-stage to search categories Signed-off-by: Konstantin Pastbin --- data/categories-strings/en.json/localize.json | 1 + 1 file changed, 1 insertion(+) diff --git a/data/categories-strings/en.json/localize.json b/data/categories-strings/en.json/localize.json index 848c65ee0..5498c03d8 100644 --- a/data/categories-strings/en.json/localize.json +++ b/data/categories-strings/en.json/localize.json @@ -512,6 +512,7 @@ "shop-wholesale|@shop": "Wholesale", "leisure-track": "Track", "leisure-bandstand": "5Bandstand", +"amenity-stage": "Stage", "power-plant": "Power Plant", "power-generator-wind": "Wind Generator", "shop-auction|@category_secondhand": "4Auction|Auctioneer|Auction House", From e2823fee538c9855c44d9ea794bdd607782339f4 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 15 Jun 2026 00:46:42 +0700 Subject: [PATCH 35/40] [styles] Add area fill to amenity-stage Signed-off-by: Konstantin Pastbin --- data/styles/default/include/Basemap.mapcss | 1 + data/styles/default/include/priorities_1_BG-by-size.prio.txt | 1 + data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/data/styles/default/include/Basemap.mapcss b/data/styles/default/include/Basemap.mapcss index 267ae714f..bee4e10a7 100644 --- a/data/styles/default/include/Basemap.mapcss +++ b/data/styles/default/include/Basemap.mapcss @@ -564,6 +564,7 @@ area|z15-[amenity=charging_station][motorcar?], area|z16-[amenity=charging_station], area|z15-[amenity=motorcycle_parking], area|z15-[amenity=bicycle_parking], +area|z17-[amenity=stage], {fill-opacity: 1; fill-color: @general_area;} diff --git a/data/styles/default/include/priorities_1_BG-by-size.prio.txt b/data/styles/default/include/priorities_1_BG-by-size.prio.txt index edcf5cedc..9e1697471 100644 --- a/data/styles/default/include/priorities_1_BG-by-size.prio.txt +++ b/data/styles/default/include/priorities_1_BG-by-size.prio.txt @@ -102,6 +102,7 @@ amenity-police # area z15- (also has icon z amenity-ranger_station # area z13- (also has icon z15-, caption(optional) z15-) amenity-recycling-centre # area z15- (also has icon z16-, caption(optional) z16-) amenity-social_facility # area z15- (also has icon z17-, caption(optional) z17-) +amenity-stage amenity-vehicle_inspection # area z15- (also has icon z16-, caption(optional) z16-) emergency-mountain_rescue # area z13- (also has icon z16-, caption(optional) z16-) highway-pedestrian-area # area z14- (also has line z13-, line::border z14-, pathtext z14-) diff --git a/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt b/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt index 6bd26284d..54c44cd11 100644 --- a/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt +++ b/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt @@ -102,6 +102,7 @@ amenity-police # area z15- (also has icon z amenity-ranger_station # area z13- (also has icon z13-, caption(optional) z14-) amenity-recycling-centre # area z15- (also has icon z16-, caption(optional) z16-) amenity-social_facility # area z15- (also has icon z17-, caption(optional) z17-) +amenity-stage amenity-vehicle_inspection # area z15- (also has icon z16-, caption(optional) z16-) emergency-mountain_rescue # area z13- (also has icon z12-, caption(optional) z12-) highway-pedestrian-area # area z14- (also has line z13-, line::border z14-, pathtext z14-) From baebdb2831a2c66373be394b6d2640e8b88bd73f Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 15 Jun 2026 00:47:01 +0700 Subject: [PATCH 36/40] [styles] Regen Signed-off-by: Konstantin Pastbin --- data/styles/default/include/priorities_1_BG-by-size.prio.txt | 2 +- data/styles/default/include/priorities_4_overlays.prio.txt | 3 ++- data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt | 2 +- data/styles/outdoors/include/priorities_4_overlays.prio.txt | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/styles/default/include/priorities_1_BG-by-size.prio.txt b/data/styles/default/include/priorities_1_BG-by-size.prio.txt index 9e1697471..1955be601 100644 --- a/data/styles/default/include/priorities_1_BG-by-size.prio.txt +++ b/data/styles/default/include/priorities_1_BG-by-size.prio.txt @@ -102,7 +102,7 @@ amenity-police # area z15- (also has icon z amenity-ranger_station # area z13- (also has icon z15-, caption(optional) z15-) amenity-recycling-centre # area z15- (also has icon z16-, caption(optional) z16-) amenity-social_facility # area z15- (also has icon z17-, caption(optional) z17-) -amenity-stage +amenity-stage # area z17- (also has icon z17-, caption(optional) z17-) amenity-vehicle_inspection # area z15- (also has icon z16-, caption(optional) z16-) emergency-mountain_rescue # area z13- (also has icon z16-, caption(optional) z16-) highway-pedestrian-area # area z14- (also has line z13-, line::border z14-, pathtext z14-) diff --git a/data/styles/default/include/priorities_4_overlays.prio.txt b/data/styles/default/include/priorities_4_overlays.prio.txt index 19bf333e7..38a01b440 100644 --- a/data/styles/default/include/priorities_4_overlays.prio.txt +++ b/data/styles/default/include/priorities_4_overlays.prio.txt @@ -997,8 +997,8 @@ amenity-payment_centre # icon z17- (also has captio amenity-prep_school # icon z17- (also has caption(optional) z17-) amenity-recycling-centre # icon z16- (also has caption(optional) z16-, area z15-) amenity-sailing_school # icon z17- (also has caption(optional) z17-) +amenity-stage # icon z17- (also has caption(optional) z17-, area z17-) amenity-veterinary # icon z16- (also has caption(optional) z16-) -amenity-stage # icon z17- (also has caption(optional) z17-) craft-electrician # icon z17- (also has caption(optional) z18-) craft-electronics_repair # icon z17- (also has caption(optional) z18-) craft-photographer # icon z17- (also has caption(optional) z18-) @@ -2089,6 +2089,7 @@ natural-tree # icon z17- (also has captio # amenity-prep_school # caption(optional) z17- (also has icon z17-) # amenity-recycling-centre # caption(optional) z16- (also has icon z16-, area z15-) # amenity-sailing_school # caption(optional) z17- (also has icon z17-) +# amenity-stage # caption(optional) z17- (also has icon z17-, area z17-) # amenity-veterinary # caption(optional) z16- (also has icon z16-) # craft-electrician # caption(optional) z18- (also has icon z17-) # craft-electronics_repair # caption(optional) z18- (also has icon z17-) diff --git a/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt b/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt index 54c44cd11..13e84a6e9 100644 --- a/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt +++ b/data/styles/outdoors/include/priorities_1_BG-by-size.prio.txt @@ -102,7 +102,7 @@ amenity-police # area z15- (also has icon z amenity-ranger_station # area z13- (also has icon z13-, caption(optional) z14-) amenity-recycling-centre # area z15- (also has icon z16-, caption(optional) z16-) amenity-social_facility # area z15- (also has icon z17-, caption(optional) z17-) -amenity-stage +amenity-stage # area z17- (also has icon z17-, caption(optional) z17-) amenity-vehicle_inspection # area z15- (also has icon z16-, caption(optional) z16-) emergency-mountain_rescue # area z13- (also has icon z12-, caption(optional) z12-) highway-pedestrian-area # area z14- (also has line z13-, line::border z14-, pathtext z14-) diff --git a/data/styles/outdoors/include/priorities_4_overlays.prio.txt b/data/styles/outdoors/include/priorities_4_overlays.prio.txt index be221829b..16888ccf3 100644 --- a/data/styles/outdoors/include/priorities_4_overlays.prio.txt +++ b/data/styles/outdoors/include/priorities_4_overlays.prio.txt @@ -997,8 +997,8 @@ amenity-payment_centre # icon z17- (also has captio amenity-prep_school # icon z17- (also has caption(optional) z17-) amenity-recycling-centre # icon z16- (also has caption(optional) z16-, area z15-) amenity-sailing_school # icon z17- (also has caption(optional) z17-) +amenity-stage # icon z17- (also has caption(optional) z17-, area z17-) amenity-veterinary # icon z16- (also has caption(optional) z16-) -amenity-stage # icon z17- (also has caption(optional) z17-) craft-electrician # icon z17- (also has caption(optional) z18-) craft-electronics_repair # icon z17- (also has caption(optional) z18-) craft-photographer # icon z17- (also has caption(optional) z18-) @@ -2092,6 +2092,7 @@ natural-tree # icon z15- (also has captio # amenity-prep_school # caption(optional) z17- (also has icon z17-) # amenity-recycling-centre # caption(optional) z16- (also has icon z16-, area z15-) # amenity-sailing_school # caption(optional) z17- (also has icon z17-) +# amenity-stage # caption(optional) z17- (also has icon z17-, area z17-) # amenity-veterinary # caption(optional) z16- (also has icon z16-) # craft-electrician # caption(optional) z18- (also has icon z17-) # craft-electronics_repair # caption(optional) z18- (also has icon z17-) From b833f1059ba72ec7fa83501ece4a14986257f5c6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 28 Apr 2026 22:41:18 +0300 Subject: [PATCH 37/40] [generator] Introduce routable highway-construction types Add highway-construction types reflecting the construction tag Include highway-construction in MWM routing section Introduce DecoderModel Relax rules for number of vehicle types in map data Signed-off-by: mvglasow --- data/mapcss-mapping.csv | 32 +- .../default/include/priorities_3_FG.prio.txt | 16 + .../include/priorities_4_overlays.prio.txt | 16 + .../outdoors/include/priorities_3_FG.prio.txt | 16 + .../include/priorities_4_overlays.prio.txt | 16 + .../vehicle/include/priorities_3_FG.prio.txt | 16 + generator/road_access_generator.cpp | 1 + generator/routing_index_generator.cpp | 12 +- libs/map/routing_manager.cpp | 2 +- libs/platform/mwm_version.hpp | 3 +- libs/routing/edge_estimator.cpp | 5 + libs/routing/index_graph_loader.cpp | 7 +- libs/routing/index_router.cpp | 14 +- libs/routing/road_access_serialization.cpp | 1 + libs/routing/road_access_serialization.hpp | 94 ++++-- .../routing/routes_builder/routes_builder.cpp | 2 +- .../routes_builder_tool/utils.cpp | 2 + libs/routing/routing_settings.cpp | 1 + libs/routing/vehicle_mask.cpp | 3 + libs/routing/vehicle_mask.hpp | 4 +- libs/routing_common/CMakeLists.txt | 2 + libs/routing_common/car_model.cpp | 5 + libs/routing_common/car_model_coefs.hpp | 1 + libs/routing_common/decoder_model.cpp | 293 ++++++++++++++++++ libs/routing_common/decoder_model.hpp | 51 +++ libs/routing_common/vehicle_model.cpp | 28 +- libs/routing_common/vehicle_model.hpp | 18 ++ 27 files changed, 612 insertions(+), 49 deletions(-) create mode 100644 libs/routing_common/decoder_model.cpp create mode 100644 libs/routing_common/decoder_model.hpp diff --git a/data/mapcss-mapping.csv b/data/mapcss-mapping.csv index b51ba3ee2..5afbc6b8e 100644 --- a/data/mapcss-mapping.csv +++ b/data/mapcss-mapping.csv @@ -916,22 +916,22 @@ leisure|sports_centre|sport|soccer;[leisure=sports_centre][sport=soccer];;name;i leisure|sports_centre|sport|four_square;[leisure=sports_centre][sport=four_square];;name;int_name;787; leisure|sports_centre|sport|boules;[leisure=sports_centre][sport=boules];;name;int_name;788; leisure|sports_centre|sport|pickleball;[leisure=sports_centre][sport=pickleball];;name;int_name;789; -deprecated|deprecated;790;x -deprecated|deprecated;791;x -deprecated|deprecated;792;x -deprecated|deprecated;793;x -deprecated|deprecated;794;x -deprecated|deprecated;795;x -deprecated|deprecated;796;x -deprecated|deprecated;797;x -deprecated|deprecated;798;x -deprecated|deprecated;799;x -deprecated|deprecated;800;x -deprecated|deprecated;801;x -deprecated|deprecated;802;x -deprecated|deprecated;803;x -deprecated|deprecated;804;x -deprecated|deprecated;805;x +highway|construction|motorway;[highway=construction][construction=motorway];;name;int_name;790; +highway|construction|motorway_link;[highway=construction][construction=motorway_link];;name;int_name;791; +highway|construction|trunk;[highway=construction][construction=trunk];;name;int_name;792; +highway|construction|trunk_link;[highway=construction][construction=trunk_link];;name;int_name;793; +highway|construction|primary;[highway=construction][construction=primary];;name;int_name;794; +highway|construction|primary_link;[highway=construction][construction=primary_link];;name;int_name;795; +highway|construction|secondary;[highway=construction][construction=secondary];;name;int_name;796; +highway|construction|secondary_link;[highway=construction][construction=secondary_link];;name;int_name;797; +highway|construction|tertiary;[highway=construction][construction=tertiary];;name;int_name;798; +highway|construction|tertiary_link;[highway=construction][construction=tertiary_link];;name;int_name;799; +highway|construction|residential;[highway=construction][construction=residential];;name;int_name;800; +highway|construction|unclassified;[highway=construction][construction=unclassified];;name;int_name;801; +highway|construction|service;[highway=construction][construction=service];;name;int_name;802; +highway|construction|living_street;[highway=construction][construction=living_street];;name;int_name;803; +highway|construction|road;[highway=construction][construction=road];;name;int_name;804; +highway|construction|track;[highway=construction][construction=track];;name;int_name;805; deprecated|deprecated;806;x deprecated|deprecated;807;x deprecated|deprecated;808;x diff --git a/data/styles/default/include/priorities_3_FG.prio.txt b/data/styles/default/include/priorities_3_FG.prio.txt index 7fe56ebeb..9befbf7a1 100644 --- a/data/styles/default/include/priorities_3_FG.prio.txt +++ b/data/styles/default/include/priorities_3_FG.prio.txt @@ -293,6 +293,22 @@ highway-track-tunnel # line z15- (also has line:: === 190 highway-construction # line z13- (also has pathtext z15-) +highway-construction-living_street # line z13- (also has pathtext z15-) +highway-construction-motorway # line z13- (also has pathtext z15-) +highway-construction-motorway_link # line z13- (also has pathtext z15-) +highway-construction-primary # line z13- (also has pathtext z15-) +highway-construction-primary_link # line z13- (also has pathtext z15-) +highway-construction-residential # line z13- (also has pathtext z15-) +highway-construction-road # line z13- (also has pathtext z15-) +highway-construction-secondary # line z13- (also has pathtext z15-) +highway-construction-secondary_link # line z13- (also has pathtext z15-) +highway-construction-service # line z13- (also has pathtext z15-) +highway-construction-tertiary # line z13- (also has pathtext z15-) +highway-construction-tertiary_link # line z13- (also has pathtext z15-) +highway-construction-track # line z13- (also has pathtext z15-) +highway-construction-trunk # line z13- (also has pathtext z15-) +highway-construction-trunk_link # line z13- (also has pathtext z15-) +highway-construction-unclassified # line z13- (also has pathtext z15-) leisure-track # line z15- (also has caption z16-) railway-abandoned # line z16- railway-construction # line z15- diff --git a/data/styles/default/include/priorities_4_overlays.prio.txt b/data/styles/default/include/priorities_4_overlays.prio.txt index 38a01b440..a17c766ac 100644 --- a/data/styles/default/include/priorities_4_overlays.prio.txt +++ b/data/styles/default/include/priorities_4_overlays.prio.txt @@ -838,6 +838,22 @@ attraction-maze # icon z16- (also has captio attraction-roller_coaster # icon z16- (also has caption(optional) z16-) attraction-water_slide # icon z17- (also has caption(optional) z17-) highway-construction # pathtext z15- (also has line z13-) +highway-construction-living_street # pathtext z15- (also has line z13-) +highway-construction-motorway # pathtext z15- (also has line z13-) +highway-construction-motorway_link # pathtext z15- (also has line z13-) +highway-construction-primary # pathtext z15- (also has line z13-) +highway-construction-primary_link # pathtext z15- (also has line z13-) +highway-construction-residential # pathtext z15- (also has line z13-) +highway-construction-road # pathtext z15- (also has line z13-) +highway-construction-secondary # pathtext z15- (also has line z13-) +highway-construction-secondary_link # pathtext z15- (also has line z13-) +highway-construction-service # pathtext z15- (also has line z13-) +highway-construction-tertiary # pathtext z15- (also has line z13-) +highway-construction-tertiary_link # pathtext z15- (also has line z13-) +highway-construction-track # pathtext z15- (also has line z13-) +highway-construction-trunk # pathtext z15- (also has line z13-) +highway-construction-trunk_link # pathtext z15- (also has line z13-) +highway-construction-unclassified # pathtext z15- (also has line z13-) highway-living_street # pathtext z14- (also has line z12-, line::border z14-) highway-living_street-bridge # pathtext z14- (also has line z12-, line::border z14-) highway-living_street-tunnel # pathtext z14- (also has line z12-, line::border z14-, line(casing) z16-) diff --git a/data/styles/outdoors/include/priorities_3_FG.prio.txt b/data/styles/outdoors/include/priorities_3_FG.prio.txt index c9dd78f72..0c0e76124 100644 --- a/data/styles/outdoors/include/priorities_3_FG.prio.txt +++ b/data/styles/outdoors/include/priorities_3_FG.prio.txt @@ -295,6 +295,22 @@ highway-track-tunnel # line z11- (also has line:: === 190 highway-construction # line z11- (also has pathtext z15-) +highway-construction-living_street # line z11- (also has pathtext z15-) +highway-construction-motorway # line z11- (also has pathtext z15-) +highway-construction-motorway_link # line z11- (also has pathtext z15-) +highway-construction-primary # line z11- (also has pathtext z15-) +highway-construction-primary_link # line z11- (also has pathtext z15-) +highway-construction-residential # line z11- (also has pathtext z15-) +highway-construction-road # line z11- (also has pathtext z15-) +highway-construction-secondary # line z11- (also has pathtext z15-) +highway-construction-secondary_link # line z11- (also has pathtext z15-) +highway-construction-service # line z11- (also has pathtext z15-) +highway-construction-tertiary # line z11- (also has pathtext z15-) +highway-construction-tertiary_link # line z11- (also has pathtext z15-) +highway-construction-track # line z11- (also has pathtext z15-) +highway-construction-trunk # line z11- (also has pathtext z15-) +highway-construction-trunk_link # line z11- (also has pathtext z15-) +highway-construction-unclassified # line z11- (also has pathtext z15-) leisure-track # line z15- (also has caption z16-) railway-abandoned # line z13- railway-construction # line z13- diff --git a/data/styles/outdoors/include/priorities_4_overlays.prio.txt b/data/styles/outdoors/include/priorities_4_overlays.prio.txt index 16888ccf3..a84009c40 100644 --- a/data/styles/outdoors/include/priorities_4_overlays.prio.txt +++ b/data/styles/outdoors/include/priorities_4_overlays.prio.txt @@ -838,6 +838,22 @@ attraction-maze # icon z16- (also has captio attraction-roller_coaster # icon z16- (also has caption(optional) z16-) attraction-water_slide # icon z17- (also has caption(optional) z17-) highway-construction # pathtext z15- (also has line z11-) +highway-construction-living_street # pathtext z15- (also has line z11-) +highway-construction-motorway # pathtext z15- (also has line z11-) +highway-construction-motorway_link # pathtext z15- (also has line z11-) +highway-construction-primary # pathtext z15- (also has line z11-) +highway-construction-primary_link # pathtext z15- (also has line z11-) +highway-construction-residential # pathtext z15- (also has line z11-) +highway-construction-road # pathtext z15- (also has line z11-) +highway-construction-secondary # pathtext z15- (also has line z11-) +highway-construction-secondary_link # pathtext z15- (also has line z11-) +highway-construction-service # pathtext z15- (also has line z11-) +highway-construction-tertiary # pathtext z15- (also has line z11-) +highway-construction-tertiary_link # pathtext z15- (also has line z11-) +highway-construction-track # pathtext z15- (also has line z11-) +highway-construction-trunk # pathtext z15- (also has line z11-) +highway-construction-trunk_link # pathtext z15- (also has line z11-) +highway-construction-unclassified # pathtext z15- (also has line z11-) highway-living_street # pathtext z14- (also has line z12-, line::border z12-) highway-living_street-bridge # pathtext z14- (also has line z12-, line::border z12-) highway-living_street-tunnel # pathtext z14- (also has line z12-, line::border z12-, line(casing) z16-) diff --git a/data/styles/vehicle/include/priorities_3_FG.prio.txt b/data/styles/vehicle/include/priorities_3_FG.prio.txt index 2a2904281..9068150fe 100644 --- a/data/styles/vehicle/include/priorities_3_FG.prio.txt +++ b/data/styles/vehicle/include/priorities_3_FG.prio.txt @@ -271,6 +271,22 @@ highway-track-no-access # line z16- === 130 highway-construction # line z13- +highway-construction-living_street # line z13- +highway-construction-motorway # line z13- +highway-construction-motorway_link # line z13- +highway-construction-primary # line z13- +highway-construction-primary_link # line z13- +highway-construction-residential # line z13- +highway-construction-road # line z13- +highway-construction-secondary # line z13- +highway-construction-secondary_link # line z13- +highway-construction-service # line z13- +highway-construction-tertiary # line z13- +highway-construction-tertiary_link # line z13- +highway-construction-track # line z13- +highway-construction-trunk # line z13- +highway-construction-trunk_link # line z13- +highway-construction-unclassified # line z13- railway-abandoned # line z16- railway-construction # line z16- railway-disused # line z16- diff --git a/generator/road_access_generator.cpp b/generator/road_access_generator.cpp index 9c5707c14..0420e0967 100644 --- a/generator/road_access_generator.cpp +++ b/generator/road_access_generator.cpp @@ -220,6 +220,7 @@ RoadAccessTagProcessor::RoadAccessTagProcessor(VehicleType vehicleType) : m_vehi switch (vehicleType) { case VehicleType::Car: + case VehicleType::Decoder: // Order is important here starting from most specific (motorcar) to generic (access). m_accessMappings.push_back(&kMotorCarTagMapping); m_accessMappings.push_back(&kMotorVehicleTagMapping); diff --git a/generator/routing_index_generator.cpp b/generator/routing_index_generator.cpp index c64982893..74d9564c3 100644 --- a/generator/routing_index_generator.cpp +++ b/generator/routing_index_generator.cpp @@ -22,6 +22,7 @@ #include "routing_common/bicycle_model.hpp" #include "routing_common/car_model.hpp" +#include "routing_common/decoder_model.hpp" #include "routing_common/pedestrian_model.hpp" #include "indexer/classificator.hpp" @@ -62,19 +63,17 @@ class VehicleMaskBuilder final : m_pedestrianModel(PedestrianModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_bicycleModel(BicycleModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_carModel(CarModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) - , m_constructionType(classif().GetTypeByPath({"highway", "construction"})) + , m_decoderModel(DecoderModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) { CHECK(m_pedestrianModel, ()); CHECK(m_bicycleModel, ()); CHECK(m_carModel, ()); + CHECK(m_decoderModel, ()); } VehicleMask CalcRoadMask(FeatureType & f) const { feature::TypesHolder const types(f); - if (types.HasWithSubclass(m_constructionType)) - return 0; - return CalcMask([&](VehicleModelInterface const & model) { return model.IsRoad(types); }); } @@ -95,6 +94,8 @@ class VehicleMaskBuilder final mask |= kBicycleMask; if (fn(*m_carModel)) mask |= kCarMask; + if (fn(*m_decoderModel)) + mask |= kDecoderMask; return mask; } @@ -102,8 +103,7 @@ class VehicleMaskBuilder final std::shared_ptr const m_pedestrianModel; std::shared_ptr const m_bicycleModel; std::shared_ptr const m_carModel; - - uint32_t const m_constructionType; + std::shared_ptr const m_decoderModel; }; class Processor final diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp index d81146474..059f69911 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -519,7 +519,7 @@ void RoutingManager::SetRouterImpl(RouterType type) VehicleType const vehicleType = GetVehicleType(type); - m_loadAltitudes = vehicleType != VehicleType::Car; + m_loadAltitudes = (vehicleType != VehicleType::Car) && (vehicleType != VehicleType::Decoder); auto const countryFileGetter = [this](m2::PointD const & p) -> string { diff --git a/libs/platform/mwm_version.hpp b/libs/platform/mwm_version.hpp index f58944f24..02186b4df 100644 --- a/libs/platform/mwm_version.hpp +++ b/libs/platform/mwm_version.hpp @@ -30,7 +30,8 @@ enum class Format // header, sdx section with header, dat section renamed to features, features section with // header). v11, // September 2020 (compressed string storage for metadata). - lastFormat = v11 + v12, // June 2026 (additional vehicle model DecoderModel, construction types allowed in routing section). + lastFormat = v12 }; std::string DebugPrint(Format f); diff --git a/libs/routing/edge_estimator.cpp b/libs/routing/edge_estimator.cpp index 8db4d1f92..2def821e4 100644 --- a/libs/routing/edge_estimator.cpp +++ b/libs/routing/edge_estimator.cpp @@ -826,6 +826,11 @@ shared_ptr EdgeEstimator::Create(VehicleType vehicleType, double case VehicleType::Bicycle: return make_shared(maxWeighSpeedKMpH, offroadSpeedKMpH); case VehicleType::Car: return make_shared(dataSourcePtr, numMwmIds, trafficStash, maxWeighSpeedKMpH, offroadSpeedKMpH); + /* + * VehicleType::Decoder is for use with the TraFF decoder, which creates its EdgeEstimator by + * explicitly calling the constructor for the appropriate subclass. + */ + case VehicleType::Decoder: CHECK(false, ("Creating EdgeEstimator for Decoder is not supported")); return nullptr; case VehicleType::Count: CHECK(false, ("Can't create EdgeEstimator for", vehicleType)); return nullptr; } UNREACHABLE(); diff --git a/libs/routing/index_graph_loader.cpp b/libs/routing/index_graph_loader.cpp index e2884f9af..f07674ca1 100644 --- a/libs/routing/index_graph_loader.cpp +++ b/libs/routing/index_graph_loader.cpp @@ -237,7 +237,12 @@ bool ReadRoadPenaltyFromMwm(MwmValue const & mwmValue, VehicleType vehicleType, // Read number of vehicle types uint32_t numVehicleTypes = ReadPrimitiveFromSource(src); - CHECK_EQUAL(numVehicleTypes, static_cast(VehicleType::Count), ()); + if (numVehicleTypes <= static_cast(vehicleType) + && vehicleType == VehicleType::Decoder + && numVehicleTypes > static_cast(VehicleType::Car)) + // This is expected for older mwm files (up to v11) - not an error + vehicleType = VehicleType::Car; + CHECK(numVehicleTypes > static_cast(vehicleType), ()); // Skip to the correct vehicle type for (uint32_t i = 0; i < static_cast(vehicleType); ++i) diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp index f56b756ba..7ed74bcf1 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -36,6 +36,7 @@ #include "routing_common/bicycle_model.hpp" #include "routing_common/car_model.hpp" #include "routing_common/num_mwm_id.hpp" +#include "routing_common/decoder_model.hpp" #include "routing_common/pedestrian_model.hpp" #include "platform/country_file.hpp" @@ -119,6 +120,7 @@ shared_ptr CreateVehicleModelFactory( case VehicleType::Transit: return make_shared(countryParentNameGetterFn); case VehicleType::Bicycle: return make_shared(countryParentNameGetterFn); case VehicleType::Car: return make_shared(countryParentNameGetterFn); + case VehicleType::Decoder: return make_shared(countryParentNameGetterFn); case VehicleType::Count: CHECK(false, ("Can't create VehicleModelFactoryInterface for", vehicleType)); return nullptr; } UNREACHABLE(); @@ -132,7 +134,14 @@ unique_ptr CreateDirectionsEngine(VehicleType vehicleType, sha case VehicleType::Pedestrian: case VehicleType::Transit: return make_unique(dataSource, numMwmIds); case VehicleType::Bicycle: - case VehicleType::Car: return make_unique(dataSource, numMwmIds); + case VehicleType::Car: + case VehicleType::Decoder: + /* + * For `VehicleType::Decoder` the directions engine serves no useful purpose. + * We could return `nullptr` here, but we would have to go through every usage of + * `m_directionsEngine` and ensure it can handle a null pointer. + */ + return make_unique(dataSource, numMwmIds); case VehicleType::Count: CHECK(false, ("Can't create DirectionsEngine for", vehicleType)); return nullptr; } UNREACHABLE(); @@ -566,7 +575,7 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints, FakeEnding startFakeEnding = m_guides.GetFakeEnding(i); FakeEnding finishFakeEnding = m_guides.GetFakeEnding(i + 1); - bool isStartSegmentStrictForward = (m_vehicleType == VehicleType::Car); + bool isStartSegmentStrictForward = ((m_vehicleType == VehicleType::Car) || (m_vehicleType == VehicleType::Decoder)); if (startFakeEnding.m_projections.empty() || finishFakeEnding.m_projections.empty()) { bool const isFirstSubroute = (i == checkpoints.GetPassedIdx()); @@ -1817,6 +1826,7 @@ void IndexRouter::SetupAlgorithmMode(IndexGraphStarter & starter, bool guidesAct case VehicleType::Bicycle: starter.GetGraph().SetMode(WorldGraphMode::Joints); break; case VehicleType::Transit: starter.GetGraph().SetMode(WorldGraphMode::NoLeaps); break; case VehicleType::Car: + case VehicleType::Decoder: starter.GetGraph().SetMode(AreMwmsNear(starter) ? WorldGraphMode::Joints : WorldGraphMode::LeapsOnly); break; case VehicleType::Count: CHECK(false, ("Unknown vehicle type:", m_vehicleType)); break; diff --git a/libs/routing/road_access_serialization.cpp b/libs/routing/road_access_serialization.cpp index 7e7191587..1dde0a865 100644 --- a/libs/routing/road_access_serialization.cpp +++ b/libs/routing/road_access_serialization.cpp @@ -11,6 +11,7 @@ std::string DebugPrint(RoadAccessSerializer::Header const & header) case RoadAccessSerializer::Header::TheFirstVersionRoadAccess: return "TheFirstVersionRoadAccess"; case RoadAccessSerializer::Header::WithoutAccessConditional: return "WithoutAccessConditional"; case RoadAccessSerializer::Header::WithAccessConditional: return "WithAccessConditional"; + case RoadAccessSerializer::Header::DecoderModelAccess: return "DecoderModelAccess"; } UNREACHABLE(); } diff --git a/libs/routing/road_access_serialization.hpp b/libs/routing/road_access_serialization.hpp index d0913f66e..a2b007c9b 100644 --- a/libs/routing/road_access_serialization.hpp +++ b/libs/routing/road_access_serialization.hpp @@ -33,11 +33,24 @@ class RoadAccessSerializer final using PointToAccessConditional = RoadAccess::PointToAccessConditional; using RoadAccessByVehicleType = std::array(VehicleType::Count)>; + /** + * @brief Values for the access version header. + * + * Each version introduced a breaking change to the way road access is stored in the MWM, + * requiring a different approach to deserialization. + * + * For any future format changes, append a new value with a descriptive name to this enum, + * and update `kLatestVersion` as well as `kVehicleTypeCount`. + * + * For any new values added here, the corresponding `DebugPrint()` function in the cpp file also + * needs to be updated. + */ enum class Header : uint32_t { - TheFirstVersionRoadAccess = 0, // Version of section roadaccess in 2017. - WithoutAccessConditional = 1, // Section roadaccess before conditional was implemented. - WithAccessConditional = 2 // Section roadaccess with conditional. + TheFirstVersionRoadAccess = 0, //< Version of section roadaccess in 2017. + WithoutAccessConditional = 1, //< Section roadaccess before conditional was implemented. + WithAccessConditional = 2, //< Section roadaccess with conditional. + DecoderModelAccess = 3 //< New vehicle model `DecoderModel` added. }; RoadAccessSerializer() = delete; @@ -59,16 +72,47 @@ class RoadAccessSerializer final switch (header) { case Header::TheFirstVersionRoadAccess: break; // Version of 2017. Unsupported. - case Header::WithoutAccessConditional: DeserializeAccess(src, vehicleType, roadAccess); break; + case Header::WithoutAccessConditional: DeserializeAccess(src, vehicleType, roadAccess, header); break; case Header::WithAccessConditional: - DeserializeAccess(src, vehicleType, roadAccess); - DeserializeAccessConditional(src, vehicleType, roadAccess); + case Header::DecoderModelAccess: + DeserializeAccess(src, vehicleType, roadAccess, header); + DeserializeAccessConditional(src, vehicleType, roadAccess, header); break; } } private: - inline static Header const kLatestVersion = Header::WithAccessConditional; + /** + * @brief Current version of the road access format. + * + * This should always be equal to the last value of `Header` and must be updated whenever format + * changes are introduced that require different processing. + * + * When changing this value, you must ensure backward compatibility with older versions. + */ + inline static Header const kLatestVersion = Header::DecoderModelAccess; + + /** + * @brief Number of vehicle types by version. + * + * Use `Header` as a numeric index into this array to determine the number of vehicle types for + * which access should be read upon encountering that version. + * + * When adding or removing vehicle types, append a new value to `Header` (see there), set + * `kLatestVersion` to that new value, and append an entry here, indicating the new number of + * vehicle types. You also need to review all functions in which `kVehicleTypeCount` is used and + * adapt them as needed to be compatible with both the old and new format. + */ + inline static std::array const kVehicleTypeCount = { + // TheFirstVersionRoadAccess (version of section roadaccess in 2017, no longer supported) + 0, + // WithoutAccessConditional (section roadaccess before conditional was implemented, 4 vehicle types) + 4, + // WithAccessConditional (section roadaccess with conditional, 4 vehicle types) + 4, + // DecoderModelAccess (new vehicle model `DecoderModel` and vehicle type added) + 5 + }; class AccessPosition { @@ -130,13 +174,18 @@ class RoadAccessSerializer final } template - static void DeserializeAccess(Source & src, VehicleType vehicleType, RoadAccess & roadAccess) + static void DeserializeAccess(Source & src, VehicleType vehicleType, RoadAccess & roadAccess, Header header) { - std::array(VehicleType::Count)> sectionSizes{}; - static_assert(static_cast(VehicleType::Count) == 4, - "It is assumed below that there are only 4 vehicle types and we store 4 numbers " - "of sections size. If you add or remove vehicle type you should up " - "|kLatestVersion| and save back compatibility here."); + std::vector sectionSizes(kVehicleTypeCount[static_cast(header)]); + static_assert(kVehicleTypeCount.size() == (static_cast(kLatestVersion) + 1), + "The number of entries in `kVehicleTypeCount` does not match `kLatestVersion`. " + "Ensure `kVehicleTypeCount` has an entry for each possible value of `Header`, " + "and `kLatestVersion` matches the highest possible value of `Header`."); + static_assert(static_cast(VehicleType::Count) == 5, + "The current version of the access header assumes there are exactly 5 vehicle " + "types. Adding or removing vehicle types is a breaking change and requires " + "increasing `kLatestVersion` and updating `kVehicleTypeCount`. See documentation " + "of `kVehicleTypeCount` for details."); for (auto & sectionSize : sectionSizes) sectionSize = ReadPrimitiveFromSource(src); @@ -186,13 +235,18 @@ class RoadAccessSerializer final } template - static void DeserializeAccessConditional(Source & src, VehicleType vehicleType, RoadAccess & roadAccess) + static void DeserializeAccessConditional(Source & src, VehicleType vehicleType, RoadAccess & roadAccess, Header header) { - std::array(VehicleType::Count)> sectionSizes{}; - static_assert(static_cast(VehicleType::Count) == 4, - "It is assumed below that there are only 4 vehicle types and we store 4 numbers " - "of sections size. If you add or remove vehicle type you should up " - "|kLatestVersion| and save back compatibility here."); + std::vector sectionSizes(kVehicleTypeCount[static_cast(header)]); + static_assert(kVehicleTypeCount.size() == (static_cast(kLatestVersion) + 1), + "The number of entries in `kVehicleTypeCount` does not match `kLatestVersion`. " + "Ensure `kVehicleTypeCount` has an entry for each possible value of `Header`, " + "and `kLatestVersion` matches the highest possible value of `Header`."); + static_assert(static_cast(VehicleType::Count) == 5, + "The current version of the access header assumes there are exactly 5 vehicle " + "types. Adding or removing vehicle types is a breaking change and requires " + "increasing `kLatestVersion` and updating `kVehicleTypeCount`. See documentation " + "of `kVehicleTypeCount` for details."); for (auto & sectionSize : sectionSizes) sectionSize = ReadPrimitiveFromSource(src); @@ -341,6 +395,8 @@ class RoadAccessSerializer final } // todo(@m) This code borrows heavily from traffic/traffic_info.hpp:SerializeTrafficKeys. + // SerializeTrafficKeys is from the defunct MapsWithMe traffic module and will be obsoleted by + // the new traffic feature currently under development. template static void SerializeSegments(Sink & sink, std::vector const & segments) { diff --git a/libs/routing/routes_builder/routes_builder.cpp b/libs/routing/routes_builder/routes_builder.cpp index 5c9a85b0b..78f6d77bc 100644 --- a/libs/routing/routes_builder/routes_builder.cpp +++ b/libs/routing/routes_builder/routes_builder.cpp @@ -281,7 +281,7 @@ void RoutesBuilder::Processor::InitRouter(VehicleType type) return cigSharedPtr->GetLimitRectForLeaf(countryId); }; - bool const loadAltitudes = type != VehicleType::Car; + bool const loadAltitudes = (type != VehicleType::Car) && (type != VehicleType::Decoder); if (!m_dataSource) m_dataSource = m_dataSourceStorage.GetDataSource(); diff --git a/libs/routing/routes_builder/routes_builder_tool/utils.cpp b/libs/routing/routes_builder/routes_builder_tool/utils.cpp index cdb24d5e3..8662f95bc 100644 --- a/libs/routing/routes_builder/routes_builder_tool/utils.cpp +++ b/libs/routing/routes_builder/routes_builder_tool/utils.cpp @@ -56,6 +56,8 @@ routing::VehicleType ConvertVehicleTypeFromString(std::string const & str) return routing::VehicleType::Bicycle; if (str == "transit") return routing::VehicleType::Transit; + if (str == "decoder") + return routing::VehicleType::Decoder; CHECK(false, ("Unknown vehicle type:", str)); UNREACHABLE(); diff --git a/libs/routing/routing_settings.cpp b/libs/routing/routing_settings.cpp index f18bbc679..e30b0647b 100644 --- a/libs/routing/routing_settings.cpp +++ b/libs/routing/routing_settings.cpp @@ -75,6 +75,7 @@ RoutingSettings GetRoutingSettings(VehicleType vehicleType) 3 /* m_notSoCloseMaxPointsCount */, 25.0 /* m_notSoCloseMaxDistMeters */}; case VehicleType::Car: + case VehicleType::Decoder: return {true /* useDirectionForRouteBuilding */, true /* m_matchRoute */, true /* m_soundDirection */, diff --git a/libs/routing/vehicle_mask.cpp b/libs/routing/vehicle_mask.cpp index 11c4395ec..22528d4ec 100644 --- a/libs/routing/vehicle_mask.cpp +++ b/libs/routing/vehicle_mask.cpp @@ -15,6 +15,7 @@ std::string DebugPrint(VehicleType vehicleType) case VehicleType::Bicycle: return "Bicycle"; case VehicleType::Car: return "Car"; case VehicleType::Transit: return "Transit"; + case VehicleType::Decoder: return "Decoder"; case VehicleType::Count: return "Count"; } UNREACHABLE(); @@ -35,6 +36,8 @@ void FromString(std::string_view s, VehicleType & vehicleType) vehicleType = VehicleType::Car; else if (s == "Transit") vehicleType = VehicleType::Transit; + else if (s == "Decoder") + vehicleType = VehicleType::Decoder; else { ASSERT(false, ("Could not read VehicleType from string", s)); diff --git a/libs/routing/vehicle_mask.hpp b/libs/routing/vehicle_mask.hpp index 61b31c3fb..6a7d2d146 100644 --- a/libs/routing/vehicle_mask.hpp +++ b/libs/routing/vehicle_mask.hpp @@ -13,7 +13,8 @@ enum class VehicleType Bicycle = 1, Car = 2, Transit = 3, - Count = 4 + Decoder = 4, + Count = 5 }; using VehicleMask = uint32_t; @@ -30,6 +31,7 @@ VehicleMask constexpr kPedestrianMask = GetVehicleMask(VehicleType::Pedestrian); VehicleMask constexpr kBicycleMask = GetVehicleMask(VehicleType::Bicycle); VehicleMask constexpr kCarMask = GetVehicleMask(VehicleType::Car); VehicleMask constexpr kTransitMask = GetVehicleMask(VehicleType::Transit); +VehicleMask constexpr kDecoderMask = GetVehicleMask(VehicleType::Decoder); std::string DebugPrint(VehicleType vehicleType); std::string ToString(VehicleType vehicleType); diff --git a/libs/routing_common/CMakeLists.txt b/libs/routing_common/CMakeLists.txt index f03636b2d..e031491ec 100644 --- a/libs/routing_common/CMakeLists.txt +++ b/libs/routing_common/CMakeLists.txt @@ -6,6 +6,8 @@ set(SRC car_model.cpp car_model.hpp car_model_coefs.hpp # This file is generated by running ../configure.sh script. + decoder_model.cpp + decoder_model.hpp maxspeed_conversion.cpp maxspeed_conversion.hpp num_mwm_id.hpp diff --git a/libs/routing_common/car_model.cpp b/libs/routing_common/car_model.cpp index 167bc0533..6f63c168e 100644 --- a/libs/routing_common/car_model.cpp +++ b/libs/routing_common/car_model.cpp @@ -23,6 +23,10 @@ using namespace routing; // of the route except for some edge cases. SpeedKMpH constexpr kSpeedOffroadKMpH = {0.01 /* weight */, kNotUsed /* eta */}; +/* + * The number of entries must match `kHighwayBasedFactors` and `kHighwayBasedSpeeds` + * in `car_model_coefs.hpp`. + */ VehicleModel::LimitsInitList const kDefaultOptions = { // {HighwayType, passThroughAllowed} {HighwayType::HighwayMotorway, true}, {HighwayType::HighwayMotorwayLink, true}, @@ -33,6 +37,7 @@ VehicleModel::LimitsInitList const kDefaultOptions = { {HighwayType::HighwayResidential, true}, {HighwayType::HighwayUnclassified, true}, {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, + // Non-highway types {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, {HighwayType::ManMadePier, true}}; diff --git a/libs/routing_common/car_model_coefs.hpp b/libs/routing_common/car_model_coefs.hpp index 6a466d946..447f6bbb5 100644 --- a/libs/routing_common/car_model_coefs.hpp +++ b/libs/routing_common/car_model_coefs.hpp @@ -3,6 +3,7 @@ #include "routing_common/vehicle_model.hpp" // These are default car model coefficients for open source developers. +// Both maps, as well as `car_model::kDefaultOptions` (in `car_model.cpp`), must have the same number of entries. namespace routing { diff --git a/libs/routing_common/decoder_model.cpp b/libs/routing_common/decoder_model.cpp new file mode 100644 index 000000000..b11e92f49 --- /dev/null +++ b/libs/routing_common/decoder_model.cpp @@ -0,0 +1,293 @@ +#include "routing_common/decoder_model.hpp" + +#include "indexer/classificator.hpp" + +namespace decoder_model +{ +using namespace routing; + +// See model specifics in different countries here: +// https://wiki.openstreetmap.org/wiki/OSM_tags_for_routing/Access-Restrictions + +// See road types here: +// https://wiki.openstreetmap.org/wiki/Key:highway + +/* + * One meter per second. The TraffEstimator works on distance in meters, not travel time. For code + * which works with speeds and assumes cost to be time-based, a speed of 1 m/s means such + * calculations will effectively return distances in meters. + */ +auto constexpr kOneMpSInKmpH = 3.6; + +/* + * Penalty factor for using a fake segment to get to a nearby road. + * Offroad penalty applies to direct distance whereas road penalty applies to roads, which can be up + * to around 3 times the direct distance (theoretically unlimited). Therefore, a factor of 3–4 times + * the penalty of a well-matched road may be needed to avoid competing with the correct route. + * On the other hand, a very high offroad penalty would give preference to a poorly matched route + * over a well-matched one if it is closer to the reference points. + * Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref). + * A well-matched road may still have a penalty of around 4 (twice the reduced attribute penalty, or + * once the full attribute penalty). + * A “wrong” road may also just have a penalty of 4 (e.g. road name mismatch, but road class and + * ramp type match). + * A value of 16 has worked well for the DE-B2R-SendlingSued-Passauerstrasse test case. (The + * DE-A10-Werder-GrossKreutz or DE-A115-PotsdamDrewitz-Nuthetal test cases gave incorrect results + * due to lack of fake segments, which was fixed through truncation and now works correctly even + * with an offroad penalty of 128.) + */ +auto constexpr kOffroadPenalty = 16; + +// |kSpeedOffroadKMpH| is a speed which is used for edges that don't lie on road features. +// For example for pure fake edges. In car routing, off road speed for calculation ETA is not used. +// The weight of such edges is considered as 0 seconds. It's especially actual when an airport is +// a start or finish. On the other hand, while route calculation the fake edges are considered +// as quite heavy. The idea behind that is to use the closest edge for the start and the finish +// of the route except for some edge cases. +SpeedKMpH constexpr kSpeedOffroadKMpH = kOneMpSInKmpH / kOffroadPenalty; + +HighwayBasedFactors const kDefaultFactors = { + // {highway class : InOutCityFactor(in city, out city)} + + {HighwayType::HighwayMotorway, InOutCityFactor(1.0)}, + {HighwayType::HighwayMotorwayLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrunk, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrunkLink, InOutCityFactor(1.0)}, + + {HighwayType::HighwayPrimary, InOutCityFactor(1.0)}, + {HighwayType::HighwayPrimaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwaySecondary, InOutCityFactor(1.0)}, + {HighwayType::HighwaySecondaryLink, InOutCityFactor(1.0)}, + + {HighwayType::HighwayTertiary, InOutCityFactor(1.0)}, + {HighwayType::HighwayTertiaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayUnclassified, InOutCityFactor(1.0)}, + + {HighwayType::HighwayResidential, InOutCityFactor(1.0)}, + {HighwayType::HighwayLivingStreet, InOutCityFactor(1.0)}, + + {HighwayType::HighwayService, InOutCityFactor(1.0)}, + {HighwayType::HighwayRoad, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrack, InOutCityFactor(1.0)}, + {HighwayType::ManMadePier, InOutCityFactor(1.0)}, + + {HighwayType::RouteFerry, InOutCityFactor(1.0)}, + {HighwayType::RouteShuttleTrain, InOutCityFactor(1.0)}, + + // Generic construction type + {HighwayType::HighwayConstruction, InOutCityFactor(1.0)}, + + // Construction types for each highway type: + {HighwayType::HighwayConstructionMotorway, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunk, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionResidential, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionUnclassified, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionService, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionRoad, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrack, InOutCityFactor(1.0)}, +}; + +HighwayBasedSpeeds const kDefaultSpeeds = { + // {highway class : InOutCitySpeedKMpH(in city(weight, eta), out city(weight eta))} + {HighwayType::HighwayMotorway, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayMotorwayLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrunk, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrunkLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayPrimary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayPrimaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwaySecondary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwaySecondaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTertiary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTertiaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayResidential, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayUnclassified, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayService, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayLivingStreet, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayRoad, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrack, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // The router truncates types to two levels, so we need this in addition to the long construction types + {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // Construction conterparts to each of the types above + // (needed for the map generator to include them in the routing section) + {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // Non-highway types + {HighwayType::RouteShuttleTrain, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::RouteFerry, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::ManMadePier, InOutCitySpeedKMpH(kOneMpSInKmpH)}}; + +/* + * The number of entries must match `kHighwayBasedFactors` and `kHighwayBasedSpeeds` + * in `decoder_model_coefs.hpp`. + */ +VehicleModel::LimitsInitList const kDefaultOptions = { + // {HighwayType, passThroughAllowed} + {HighwayType::HighwayMotorway, true}, {HighwayType::HighwayMotorwayLink, true}, + {HighwayType::HighwayTrunk, true}, {HighwayType::HighwayTrunkLink, true}, + {HighwayType::HighwayPrimary, true}, {HighwayType::HighwayPrimaryLink, true}, + {HighwayType::HighwaySecondary, true}, {HighwayType::HighwaySecondaryLink, true}, + {HighwayType::HighwayTertiary, true}, {HighwayType::HighwayTertiaryLink, true}, + {HighwayType::HighwayResidential, true}, {HighwayType::HighwayUnclassified, true}, + {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, + {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, + // The router truncates types to two levels, so we need this in addition to the long construction types + {HighwayType::HighwayConstruction, true}, + // Construction conterparts to each of the types above + // (needed for the map generator to include them in the routing section) + {HighwayType::HighwayConstructionMotorway, true}, {HighwayType::HighwayConstructionMotorwayLink, true}, + {HighwayType::HighwayConstructionTrunk, true}, {HighwayType::HighwayConstructionTrunkLink, true}, + {HighwayType::HighwayConstructionPrimary, true}, {HighwayType::HighwayConstructionPrimaryLink, true}, + {HighwayType::HighwayConstructionSecondary, true}, {HighwayType::HighwayConstructionSecondaryLink, true}, + {HighwayType::HighwayConstructionTertiary, true}, {HighwayType::HighwayConstructionTertiaryLink, true}, + {HighwayType::HighwayConstructionResidential, true}, {HighwayType::HighwayConstructionUnclassified, true}, + {HighwayType::HighwayConstructionService, true}, {HighwayType::HighwayConstructionLivingStreet, true}, + {HighwayType::HighwayConstructionRoad, true}, {HighwayType::HighwayConstructionTrack, true}, + // Non-highway types + {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, + {HighwayType::ManMadePier, true}}; + +VehicleModel::LimitsInitList NoPassThroughLivingStreet() +{ + auto res = kDefaultOptions; + for (auto & e : res) + if (e.m_type == HighwayType::HighwayLivingStreet) + e.m_isPassThroughAllowed = false; + return res; +} + +VehicleModel::LimitsInitList NoPassThroughService(VehicleModel::LimitsInitList res = kDefaultOptions) +{ + for (auto & e : res) + if (e.m_type == HighwayType::HighwayService) + e.m_isPassThroughAllowed = false; + return res; +} + +VehicleModel::LimitsInitList NoTrack() +{ + VehicleModel::LimitsInitList res; + res.reserve(kDefaultOptions.size() - 1); + for (auto const & e : kDefaultOptions) + if (e.m_type != HighwayType::HighwayTrack) + res.push_back(e); + return res; +} + +VehicleModel::LimitsInitList NoPassThroughTrack() +{ + auto res = kDefaultOptions; + for (auto & e : res) + if (e.m_type == HighwayType::HighwayTrack) + e.m_isPassThroughAllowed = false; + return res; +} + +/// @todo Should make some compare constrains (like in CarModel_TrackVsGravelTertiary test) +/// to better fit these factors with reality. I have no idea, how they were set. +VehicleModel::SurfaceInitList const kDecoderSurface = { + // {{surfaceType, surfaceType}, {weightFactor, etaFactor}} + {{"psurface", "paved_good"}, {1.0, 1.0}}, + {{"psurface", "paved_bad"}, {0.6, 0.7}}, + {{"psurface", "unpaved_good"}, {0.4, 0.7}}, + {{"psurface", "unpaved_bad"}, {0.2, 0.3}}}; +} // namespace decoder_model + +namespace routing +{ +DecoderModel::DecoderModel() : DecoderModel(decoder_model::kDefaultOptions) {} + +DecoderModel::DecoderModel(VehicleModel::LimitsInitList const & roadLimits) + : VehicleModel(classif(), roadLimits, decoder_model::kDecoderSurface, + {decoder_model::kDefaultSpeeds, decoder_model::kDefaultFactors}) +{ + ASSERT_EQUAL(decoder_model::kDefaultFactors.size(), decoder_model::kDefaultSpeeds.size(), ()); + ASSERT_EQUAL(decoder_model::kDefaultOptions.size(), decoder_model::kDefaultSpeeds.size(), ()); + + std::vector hwtagYesCar = {"hwtag", "yescar"}; + auto const & cl = classif(); + + m_noType = cl.GetTypeByPath({"hwtag", "nocar"}); + m_yesType = cl.GetTypeByPath(hwtagYesCar); + + // Set small track speed if highway is not in kDefaultSpeeds (path, pedestrian), but marked as yescar. + AddAdditionalRoadTypes(cl, {{std::move(hwtagYesCar), decoder_model::kDefaultSpeeds.at(HighwayType::HighwayTrack)}}); + + // Set max possible (reasonable) car speed. See EdgeEstimator::CalcHeuristic. + SpeedKMpH constexpr kMaxCarSpeedKMpH(200.0); + CHECK_LESS(m_maxModelSpeed, kMaxCarSpeedKMpH, ()); + m_maxModelSpeed = kMaxCarSpeedKMpH; +} + +SpeedKMpH DecoderModel::GetSpeed(FeatureTypes const & types, SpeedParams const & speedParams) const +{ + return GetTypeSpeedImpl(types, speedParams, true /* isCar */); +} + +SpeedKMpH const & DecoderModel::GetOffroadSpeed() const +{ + return decoder_model::kSpeedOffroadKMpH; +} + +// static +DecoderModel const & DecoderModel::AllLimitsInstance() +{ + static DecoderModel const instance; + return instance; +} + +// static +VehicleModel::LimitsInitList const & DecoderModel::GetOptions() +{ + return decoder_model::kDefaultOptions; +} + +DecoderModelFactory::DecoderModelFactory(CountryParentNameGetterFn const & countryParentNameGetterFn) + : VehicleModelFactory(countryParentNameGetterFn) +{ + using namespace decoder_model; + using std::make_shared; + + // Names must be the same with country names from countries.txt + m_models[""] = make_shared(); + + /* + * This section should mirror the definitions in `CarModel`. Any deviations should be considered + * a bug, unless there is a comment stating that this is intentional (and ideally also the reason + * it is needed). + */ + m_models["Austria"] = make_shared(NoPassThroughLivingStreet()); + m_models["Belarus"] = make_shared(NoPassThroughLivingStreet()); + m_models["Brazil"] = make_shared(NoPassThroughService(NoPassThroughTrack())); + m_models["Denmark"] = make_shared(NoTrack()); + m_models["Germany"] = make_shared(NoPassThroughTrack()); + m_models["Hungary"] = make_shared(NoPassThroughLivingStreet()); + m_models["Poland"] = make_shared(NoPassThroughService()); + m_models["Romania"] = make_shared(NoPassThroughLivingStreet()); + m_models["Russian Federation"] = make_shared(NoPassThroughService(NoPassThroughLivingStreet())); + m_models["Slovakia"] = make_shared(NoPassThroughLivingStreet()); + m_models["Ukraine"] = make_shared(NoPassThroughService(NoPassThroughLivingStreet())); +} +} // namespace routing diff --git a/libs/routing_common/decoder_model.hpp b/libs/routing_common/decoder_model.hpp new file mode 100644 index 000000000..005756c26 --- /dev/null +++ b/libs/routing_common/decoder_model.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "routing_common/vehicle_model.hpp" + +namespace routing +{ + +/** + * @brief A `VehicleModel` suitable for TraFF location decoding. + * + * Each instance can have its own set of feature type-specific access limitations. These specify + * which roads can be used by this vehicle, and which roads can only be used for destination + * traffic. + * + * `DecoderModel` is similar to `CarModel`, with the following differences: + * + * Construction types are routable and treated like regular roads (the decoder may still + * exclude them on a case-by-case basis). + * + * Speed is assumed to be 1 m/s for any routable road, and 1 m/s divided by offline penalty for + * offroad. + * + * Instances are typically retrieved from `DecoderModelFactory` or the `AllLimitsInstance()` method. + */ +class DecoderModel : public VehicleModel +{ +public: + DecoderModel(); + explicit DecoderModel(LimitsInitList const & roadLimits); + + // VehicleModelInterface overrides: + SpeedKMpH GetSpeed(FeatureTypes const & types, SpeedParams const & speedParams) const override; + SpeedKMpH const & GetOffroadSpeed() const override; + + /** + * @brief Returns an instance with the default access limitations. + */ + static DecoderModel const & AllLimitsInstance(); + + /** + * @brief Returns the default access limitations. + */ + static LimitsInitList const & GetOptions(); +}; + +class DecoderModelFactory : public VehicleModelFactory +{ +public: + DecoderModelFactory(CountryParentNameGetterFn const & countryParentNameGetterF); +}; +} // namespace routing diff --git a/libs/routing_common/vehicle_model.cpp b/libs/routing_common/vehicle_model.cpp index 4b4566271..2d56efee6 100644 --- a/libs/routing_common/vehicle_model.cpp +++ b/libs/routing_common/vehicle_model.cpp @@ -360,6 +360,23 @@ string DebugPrint(HighwayType type) case HighwayType::HighwayPedestrian: return "highway-pedestrian"; case HighwayType::HighwayTrunkLink: return "highway-trunk_link"; case HighwayType::HighwayPrimaryLink: return "highway-primary_link"; + case HighwayType::HighwayConstruction: return "highway-construction"; + case HighwayType::HighwayConstructionMotorway: return "highway-construction-motorway"; + case HighwayType::HighwayConstructionMotorwayLink: return "highway-construction-motorway_link"; + case HighwayType::HighwayConstructionTrunk: return "highway-construction-trunk"; + case HighwayType::HighwayConstructionTrunkLink: return "highway-construction-trunk-link"; + case HighwayType::HighwayConstructionPrimary: return "highway-construction-primary"; + case HighwayType::HighwayConstructionPrimaryLink: return "highway-construction-primary_link"; + case HighwayType::HighwayConstructionSecondary: return "highway-construction-secondary"; + case HighwayType::HighwayConstructionSecondaryLink: return "highway-construction-secondary_link"; + case HighwayType::HighwayConstructionTertiary: return "highway-construction-tertiary"; + case HighwayType::HighwayConstructionTertiaryLink: return "highway-construction-tertiary_link"; + case HighwayType::HighwayConstructionResidential: return "highway-construction-residential"; + case HighwayType::HighwayConstructionUnclassified: return "highway-construction-unclassified"; + case HighwayType::HighwayConstructionService: return "highway-construction-service"; + case HighwayType::HighwayConstructionLivingStreet: return "highway-construction-living_street"; + case HighwayType::HighwayConstructionRoad: return "highway-construction-road"; + case HighwayType::HighwayConstructionTrack: return "highway-construction-track"; case HighwayType::ManMadePier: return "man_made-pier"; case HighwayType::HighwayBridleway: return "highway-bridleway"; case HighwayType::HighwaySecondaryLink: return "highway-secondary_link"; @@ -389,7 +406,16 @@ void FromString(std::string_view s, HighwayType & highwayType) HighwayType::HighwayTrunk, HighwayType::HighwayPedestrian, HighwayType::HighwayTrunkLink, HighwayType::HighwayPrimaryLink, HighwayType::ManMadePier, HighwayType::HighwayBridleway, HighwayType::HighwaySecondaryLink, HighwayType::RouteFerry, HighwayType::HighwayTertiaryLink, - HighwayType::HighwayBusway, HighwayType::RouteShuttleTrain}; + HighwayType::HighwayBusway, HighwayType::RouteShuttleTrain, HighwayType::HighwayConstruction, + HighwayType::HighwayConstructionMotorway, HighwayType::HighwayConstructionMotorwayLink, + HighwayType::HighwayConstructionTrunk, HighwayType::HighwayConstructionTrunkLink, + HighwayType::HighwayConstructionPrimary, HighwayType::HighwayConstructionPrimaryLink, + HighwayType::HighwayConstructionSecondary, HighwayType::HighwayConstructionSecondaryLink, + HighwayType::HighwayConstructionTertiary, HighwayType::HighwayConstructionTertiaryLink, + HighwayType::HighwayConstructionResidential, HighwayType::HighwayConstructionUnclassified, + HighwayType::HighwayConstructionService, HighwayType::HighwayConstructionLivingStreet, + HighwayType::HighwayConstructionRoad, HighwayType::HighwayConstructionTrack, + }; for (auto type : allTypes) map[DebugPrint(type)] = type; diff --git a/libs/routing_common/vehicle_model.hpp b/libs/routing_common/vehicle_model.hpp index 9251b5c8a..ee68f41ab 100644 --- a/libs/routing_common/vehicle_model.hpp +++ b/libs/routing_common/vehicle_model.hpp @@ -54,6 +54,24 @@ enum class HighwayType : uint16_t HighwayTrunkLink = 90, HighwayPrimaryLink = 95, ManMadePier = 119, + // HighwayConstruction and its subtypes are needed for traffic message decoding + HighwayConstruction = 162, + HighwayConstructionMotorway = 789, + HighwayConstructionMotorwayLink = 790, + HighwayConstructionTrunk = 791, + HighwayConstructionTrunkLink = 792, + HighwayConstructionPrimary = 793, + HighwayConstructionPrimaryLink = 794, + HighwayConstructionSecondary = 795, + HighwayConstructionSecondaryLink = 796, + HighwayConstructionTertiary = 797, + HighwayConstructionTertiaryLink = 798, + HighwayConstructionResidential = 799, + HighwayConstructionUnclassified = 800, + HighwayConstructionService = 801, + HighwayConstructionLivingStreet = 802, + HighwayConstructionRoad = 803, + HighwayConstructionTrack = 804, HighwayBridleway = 167, HighwaySecondaryLink = 176, RouteFerry = 259, From 483e21de3f2e555630a27bcff7ccd30d56045f69 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 6 May 2026 00:23:02 +0300 Subject: [PATCH 38/40] [android] UI strings for new construction types Signed-off-by: mvglasow --- .../src/main/res/values-af/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ar/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-az/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-bg/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ca/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-cs/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-da/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-de/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-el/types_strings.xml | 17 ++++++++++++ .../main/res/values-es-rMX/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-es/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-et/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-eu/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-fa/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-fi/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-fr/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-gl/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-gsw/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-hi/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-hu/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-in/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-is/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-it/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-iw/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ja/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ko/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-kw/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-lt/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-mr/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-nb/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-nl/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-pl/types_strings.xml | 17 ++++++++++++ .../main/res/values-pt-rBR/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-pt/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ro/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ru/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-sk/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-sl/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-sr/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-sv/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-sw/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-ta/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-th/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-tr/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-uk/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-vi/types_strings.xml | 17 ++++++++++++ .../main/res/values-zh-rHK/types_strings.xml | 17 ++++++++++++ .../main/res/values-zh-rTW/types_strings.xml | 17 ++++++++++++ .../src/main/res/values-zh/types_strings.xml | 17 ++++++++++++ .../sdk/src/main/res/values/types_strings.xml | 17 ++++++++++++ .../af.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ar.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../az.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../bg.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ca.lproj/LocalizableTypes.strings | 26 +++++++++++++++---- .../cs.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../da.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../de.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../el.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../es-MX.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../es.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../et.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../eu.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../fa.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../fi.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../fr.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../gl.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../gsw.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../he.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../hi.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../hu.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../id.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../is.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../it.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ja.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ko.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../lt.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../mr.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../nb.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../nl.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../pl.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../pt-BR.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../pt.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ro.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ru.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../sk.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../sl.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../sr.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../sv.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../sw.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../ta.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../th.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../tr.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../uk.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../vi.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../zh-Hans.lproj/LocalizableTypes.strings | 16 ++++++++++++ .../zh-Hant.lproj/LocalizableTypes.strings | 16 ++++++++++++ 97 files changed, 1607 insertions(+), 5 deletions(-) diff --git a/android/sdk/src/main/res/values-af/types_strings.xml b/android/sdk/src/main/res/values-af/types_strings.xml index 7a5cf0ee5..4cc8bfa49 100644 --- a/android/sdk/src/main/res/values-af/types_strings.xml +++ b/android/sdk/src/main/res/values-af/types_strings.xml @@ -359,7 +359,24 @@ Tonnel Bushalte + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou Fietspad Brug diff --git a/android/sdk/src/main/res/values-ar/types_strings.xml b/android/sdk/src/main/res/values-ar/types_strings.xml index 10edd1006..536a3f089 100644 --- a/android/sdk/src/main/res/values-ar/types_strings.xml +++ b/android/sdk/src/main/res/values-ar/types_strings.xml @@ -380,7 +380,24 @@ نفق موقف حافلات + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء مسار للدراجات جسر diff --git a/android/sdk/src/main/res/values-az/types_strings.xml b/android/sdk/src/main/res/values-az/types_strings.xml index fd4f9fed8..775844b63 100644 --- a/android/sdk/src/main/res/values-az/types_strings.xml +++ b/android/sdk/src/main/res/values-az/types_strings.xml @@ -370,7 +370,24 @@ Tunel Avtobus dayanacağı + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol Velosiped yolu Körpü diff --git a/android/sdk/src/main/res/values-bg/types_strings.xml b/android/sdk/src/main/res/values-bg/types_strings.xml index 06fc1e3ed..da11056df 100644 --- a/android/sdk/src/main/res/values-bg/types_strings.xml +++ b/android/sdk/src/main/res/values-bg/types_strings.xml @@ -956,7 +956,24 @@ Виетнамска Пътека за юздечки Пътека за юздечки + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане Жилищна улица Пътека Пътека diff --git a/android/sdk/src/main/res/values-ca/types_strings.xml b/android/sdk/src/main/res/values-ca/types_strings.xml index 0a4e997b9..6df496e75 100644 --- a/android/sdk/src/main/res/values-ca/types_strings.xml +++ b/android/sdk/src/main/res/values-ca/types_strings.xml @@ -935,7 +935,24 @@ Centre comercial Desfibril·lador Carretera + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció Continent Quiosc diff --git a/android/sdk/src/main/res/values-cs/types_strings.xml b/android/sdk/src/main/res/values-cs/types_strings.xml index d91b9835d..52dc70d58 100644 --- a/android/sdk/src/main/res/values-cs/types_strings.xml +++ b/android/sdk/src/main/res/values-cs/types_strings.xml @@ -327,7 +327,24 @@ Tunel Autobusová zastávka + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci Most diff --git a/android/sdk/src/main/res/values-da/types_strings.xml b/android/sdk/src/main/res/values-da/types_strings.xml index dbe7a04fe..f4d7e6e72 100644 --- a/android/sdk/src/main/res/values-da/types_strings.xml +++ b/android/sdk/src/main/res/values-da/types_strings.xml @@ -317,7 +317,24 @@ Tunnel Busstoppested + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse Bro diff --git a/android/sdk/src/main/res/values-de/types_strings.xml b/android/sdk/src/main/res/values-de/types_strings.xml index 4893a5e81..e63974097 100644 --- a/android/sdk/src/main/res/values-de/types_strings.xml +++ b/android/sdk/src/main/res/values-de/types_strings.xml @@ -417,7 +417,24 @@ Tunnel Bushaltestelle + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau Radweg Brücke diff --git a/android/sdk/src/main/res/values-el/types_strings.xml b/android/sdk/src/main/res/values-el/types_strings.xml index 45f47f764..353073ae1 100644 --- a/android/sdk/src/main/res/values-el/types_strings.xml +++ b/android/sdk/src/main/res/values-el/types_strings.xml @@ -335,7 +335,24 @@ Σήραγγα Στάση λεωφορείου + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή Γέφυρα diff --git a/android/sdk/src/main/res/values-es-rMX/types_strings.xml b/android/sdk/src/main/res/values-es-rMX/types_strings.xml index fb4916e55..ef0f8710d 100644 --- a/android/sdk/src/main/res/values-es-rMX/types_strings.xml +++ b/android/sdk/src/main/res/values-es-rMX/types_strings.xml @@ -57,7 +57,24 @@ Puente Túnel + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción Puente diff --git a/android/sdk/src/main/res/values-es/types_strings.xml b/android/sdk/src/main/res/values-es/types_strings.xml index 5491fb38e..d77cdc2d2 100644 --- a/android/sdk/src/main/res/values-es/types_strings.xml +++ b/android/sdk/src/main/res/values-es/types_strings.xml @@ -385,7 +385,24 @@ Túnel Parada de bus + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción Ciclovía Puente diff --git a/android/sdk/src/main/res/values-et/types_strings.xml b/android/sdk/src/main/res/values-et/types_strings.xml index e34b63e97..cc220cd8e 100644 --- a/android/sdk/src/main/res/values-et/types_strings.xml +++ b/android/sdk/src/main/res/values-et/types_strings.xml @@ -382,7 +382,24 @@ Tunnel Bussipeatus + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee Jalgrattatee Jalgrattasild diff --git a/android/sdk/src/main/res/values-eu/types_strings.xml b/android/sdk/src/main/res/values-eu/types_strings.xml index 26a512e4c..324e6480c 100644 --- a/android/sdk/src/main/res/values-eu/types_strings.xml +++ b/android/sdk/src/main/res/values-eu/types_strings.xml @@ -373,7 +373,24 @@ Tunela Autobus geltokia + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea Bidegorri edo bizikleta-bidea Zubia diff --git a/android/sdk/src/main/res/values-fa/types_strings.xml b/android/sdk/src/main/res/values-fa/types_strings.xml index 5ff2035ee..656cf590e 100644 --- a/android/sdk/src/main/res/values-fa/types_strings.xml +++ b/android/sdk/src/main/res/values-fa/types_strings.xml @@ -244,7 +244,24 @@ تونل حمل و نقل + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است پل diff --git a/android/sdk/src/main/res/values-fi/types_strings.xml b/android/sdk/src/main/res/values-fi/types_strings.xml index e0fa7245b..eb775e21d 100644 --- a/android/sdk/src/main/res/values-fi/types_strings.xml +++ b/android/sdk/src/main/res/values-fi/types_strings.xml @@ -375,7 +375,24 @@ Tunneli Bussipysäkki + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie Pyörätie Silta diff --git a/android/sdk/src/main/res/values-fr/types_strings.xml b/android/sdk/src/main/res/values-fr/types_strings.xml index e72c0fdad..06570a226 100644 --- a/android/sdk/src/main/res/values-fr/types_strings.xml +++ b/android/sdk/src/main/res/values-fr/types_strings.xml @@ -370,7 +370,24 @@ Tunnel Arrêt de bus + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction Piste cyclable Pont diff --git a/android/sdk/src/main/res/values-gl/types_strings.xml b/android/sdk/src/main/res/values-gl/types_strings.xml index a07fca348..7bed81337 100644 --- a/android/sdk/src/main/res/values-gl/types_strings.xml +++ b/android/sdk/src/main/res/values-gl/types_strings.xml @@ -367,7 +367,24 @@ Puente Túnel Parada de autobús + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción Ciclovía Puente Ciclovía diff --git a/android/sdk/src/main/res/values-gsw/types_strings.xml b/android/sdk/src/main/res/values-gsw/types_strings.xml index 3ea62239b..04c5a508d 100644 --- a/android/sdk/src/main/res/values-gsw/types_strings.xml +++ b/android/sdk/src/main/res/values-gsw/types_strings.xml @@ -333,7 +333,24 @@ Brugg Tunnel Bushaltistell + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau Veloweg Brugg Veloweg diff --git a/android/sdk/src/main/res/values-hi/types_strings.xml b/android/sdk/src/main/res/values-hi/types_strings.xml index 9cc4513fd..055d40fe6 100644 --- a/android/sdk/src/main/res/values-hi/types_strings.xml +++ b/android/sdk/src/main/res/values-hi/types_strings.xml @@ -263,7 +263,24 @@ राजमार्ग बस स्टॉप + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क साइकिल मार्ग साइकिल मार्ग उत्थापक diff --git a/android/sdk/src/main/res/values-hu/types_strings.xml b/android/sdk/src/main/res/values-hu/types_strings.xml index 74d6596c7..82364be31 100644 --- a/android/sdk/src/main/res/values-hu/types_strings.xml +++ b/android/sdk/src/main/res/values-hu/types_strings.xml @@ -329,7 +329,24 @@ Alagút Buszmegálló + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés Híd diff --git a/android/sdk/src/main/res/values-in/types_strings.xml b/android/sdk/src/main/res/values-in/types_strings.xml index 81ffcc586..7490e05e7 100644 --- a/android/sdk/src/main/res/values-in/types_strings.xml +++ b/android/sdk/src/main/res/values-in/types_strings.xml @@ -323,7 +323,24 @@ Terowongan Halte bus + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun Menjembatani diff --git a/android/sdk/src/main/res/values-is/types_strings.xml b/android/sdk/src/main/res/values-is/types_strings.xml index 0ce70b142..40860f438 100644 --- a/android/sdk/src/main/res/values-is/types_strings.xml +++ b/android/sdk/src/main/res/values-is/types_strings.xml @@ -1046,7 +1046,24 @@ Snjóleikjagarður Þráðlaust net Setskýli + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu Svæði fyrir fótgangandi Svæði fyrir fótgangandi Sögulegur skriðdreki diff --git a/android/sdk/src/main/res/values-it/types_strings.xml b/android/sdk/src/main/res/values-it/types_strings.xml index 1bb8eefaa..d60ebf42e 100644 --- a/android/sdk/src/main/res/values-it/types_strings.xml +++ b/android/sdk/src/main/res/values-it/types_strings.xml @@ -382,7 +382,24 @@ Galleria Fermata dell\'autobus + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione Pista ciclabile Ponte diff --git a/android/sdk/src/main/res/values-iw/types_strings.xml b/android/sdk/src/main/res/values-iw/types_strings.xml index f52b636fe..0c19d9a4f 100644 --- a/android/sdk/src/main/res/values-iw/types_strings.xml +++ b/android/sdk/src/main/res/values-iw/types_strings.xml @@ -382,7 +382,24 @@ מִנהָרָה תחנת אוטובוס + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה שביל אופניים גשר לאופניים diff --git a/android/sdk/src/main/res/values-ja/types_strings.xml b/android/sdk/src/main/res/values-ja/types_strings.xml index 87d7c51b5..1176beea6 100644 --- a/android/sdk/src/main/res/values-ja/types_strings.xml +++ b/android/sdk/src/main/res/values-ja/types_strings.xml @@ -356,7 +356,24 @@ トンネル バス停 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 自転車道 自転車道(橋) diff --git a/android/sdk/src/main/res/values-ko/types_strings.xml b/android/sdk/src/main/res/values-ko/types_strings.xml index e62739e1a..9d1c1912d 100644 --- a/android/sdk/src/main/res/values-ko/types_strings.xml +++ b/android/sdk/src/main/res/values-ko/types_strings.xml @@ -330,7 +330,24 @@ 터널 버스 정류장 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 다리 diff --git a/android/sdk/src/main/res/values-kw/types_strings.xml b/android/sdk/src/main/res/values-kw/types_strings.xml index 8d8b9cb22..326064c6b 100644 --- a/android/sdk/src/main/res/values-kw/types_strings.xml +++ b/android/sdk/src/main/res/values-kw/types_strings.xml @@ -286,7 +286,24 @@ Pons Kowfordh Kyttrinva + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans Hyns rag Diwrosow Pons Hyns rag Diwrosow diff --git a/android/sdk/src/main/res/values-lt/types_strings.xml b/android/sdk/src/main/res/values-lt/types_strings.xml index 1b44e94f8..5f73fdd1b 100644 --- a/android/sdk/src/main/res/values-lt/types_strings.xml +++ b/android/sdk/src/main/res/values-lt/types_strings.xml @@ -466,7 +466,24 @@ Tiltas Tunelis Stotelė + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias Dviračių takas Tiltas Dviračių takas diff --git a/android/sdk/src/main/res/values-mr/types_strings.xml b/android/sdk/src/main/res/values-mr/types_strings.xml index b8628957d..967171b06 100644 --- a/android/sdk/src/main/res/values-mr/types_strings.xml +++ b/android/sdk/src/main/res/values-mr/types_strings.xml @@ -290,7 +290,24 @@ बोगदा बस थांबा + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता सायकल वाट पूल diff --git a/android/sdk/src/main/res/values-nb/types_strings.xml b/android/sdk/src/main/res/values-nb/types_strings.xml index c3b4a5905..36fdb7d5b 100644 --- a/android/sdk/src/main/res/values-nb/types_strings.xml +++ b/android/sdk/src/main/res/values-nb/types_strings.xml @@ -337,7 +337,24 @@ Tunnel Busstopp + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon Sykkelvei Bru diff --git a/android/sdk/src/main/res/values-nl/types_strings.xml b/android/sdk/src/main/res/values-nl/types_strings.xml index a11876861..801130ef4 100644 --- a/android/sdk/src/main/res/values-nl/types_strings.xml +++ b/android/sdk/src/main/res/values-nl/types_strings.xml @@ -379,7 +379,24 @@ Tunnel Bushalte + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw Fietspad Fietsbrug diff --git a/android/sdk/src/main/res/values-pl/types_strings.xml b/android/sdk/src/main/res/values-pl/types_strings.xml index d7902953e..fc7f7116d 100644 --- a/android/sdk/src/main/res/values-pl/types_strings.xml +++ b/android/sdk/src/main/res/values-pl/types_strings.xml @@ -380,7 +380,24 @@ Tunel Przystanek autobusowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy Droga rowerowa Most drogowy dla rowerów diff --git a/android/sdk/src/main/res/values-pt-rBR/types_strings.xml b/android/sdk/src/main/res/values-pt-rBR/types_strings.xml index afaba647a..d187e3702 100644 --- a/android/sdk/src/main/res/values-pt-rBR/types_strings.xml +++ b/android/sdk/src/main/res/values-pt-rBR/types_strings.xml @@ -333,7 +333,24 @@ Túnel Ponto de ônibus + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção Ciclovia Ponte para ciclistas diff --git a/android/sdk/src/main/res/values-pt/types_strings.xml b/android/sdk/src/main/res/values-pt/types_strings.xml index 1895275b5..d3452a50a 100644 --- a/android/sdk/src/main/res/values-pt/types_strings.xml +++ b/android/sdk/src/main/res/values-pt/types_strings.xml @@ -368,7 +368,24 @@ Túnel Paragem de autocarros + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção Ciclovia Ponte diff --git a/android/sdk/src/main/res/values-ro/types_strings.xml b/android/sdk/src/main/res/values-ro/types_strings.xml index 8cce5160e..a566ed311 100644 --- a/android/sdk/src/main/res/values-ro/types_strings.xml +++ b/android/sdk/src/main/res/values-ro/types_strings.xml @@ -322,7 +322,24 @@ Tunel Stație de autobuz + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție Pod diff --git a/android/sdk/src/main/res/values-ru/types_strings.xml b/android/sdk/src/main/res/values-ru/types_strings.xml index a498d2f31..b9f104442 100644 --- a/android/sdk/src/main/res/values-ru/types_strings.xml +++ b/android/sdk/src/main/res/values-ru/types_strings.xml @@ -386,7 +386,24 @@ Тоннель Остановка + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога Велодорожка Мост diff --git a/android/sdk/src/main/res/values-sk/types_strings.xml b/android/sdk/src/main/res/values-sk/types_strings.xml index c20f3af01..3fe46e90f 100644 --- a/android/sdk/src/main/res/values-sk/types_strings.xml +++ b/android/sdk/src/main/res/values-sk/types_strings.xml @@ -374,7 +374,24 @@ Tunel Autobusová zastávka + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe Cyklocesta Most diff --git a/android/sdk/src/main/res/values-sl/types_strings.xml b/android/sdk/src/main/res/values-sl/types_strings.xml index 0ab7d669b..02d2f9bde 100644 --- a/android/sdk/src/main/res/values-sl/types_strings.xml +++ b/android/sdk/src/main/res/values-sl/types_strings.xml @@ -498,7 +498,24 @@ Most Predor Avtobusno postajališče + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji Kolesarska steza Most Kolesarska steza diff --git a/android/sdk/src/main/res/values-sr/types_strings.xml b/android/sdk/src/main/res/values-sr/types_strings.xml index e4badaa1a..156b99187 100644 --- a/android/sdk/src/main/res/values-sr/types_strings.xml +++ b/android/sdk/src/main/res/values-sr/types_strings.xml @@ -383,7 +383,24 @@ Тунел Аутобуско стајалиште + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи Бициклистичка стаза Мост diff --git a/android/sdk/src/main/res/values-sv/types_strings.xml b/android/sdk/src/main/res/values-sv/types_strings.xml index 3e1aa59d5..7ba2636d8 100644 --- a/android/sdk/src/main/res/values-sv/types_strings.xml +++ b/android/sdk/src/main/res/values-sv/types_strings.xml @@ -325,7 +325,24 @@ Tunnel Busshållplats + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande Cykelbro diff --git a/android/sdk/src/main/res/values-sw/types_strings.xml b/android/sdk/src/main/res/values-sw/types_strings.xml index d8e1e523d..95b44a2ce 100644 --- a/android/sdk/src/main/res/values-sw/types_strings.xml +++ b/android/sdk/src/main/res/values-sw/types_strings.xml @@ -131,7 +131,24 @@ Daraja Mtaro + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa Daraja diff --git a/android/sdk/src/main/res/values-ta/types_strings.xml b/android/sdk/src/main/res/values-ta/types_strings.xml index 8ed26f4f7..65414639a 100644 --- a/android/sdk/src/main/res/values-ta/types_strings.xml +++ b/android/sdk/src/main/res/values-ta/types_strings.xml @@ -386,7 +386,24 @@ பாலம் சுரங்கப்பாதை பேருந்து நிறுத்தம் + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது சைக்கிள் பாதை பாலம் சைக்கிள் பாதை diff --git a/android/sdk/src/main/res/values-th/types_strings.xml b/android/sdk/src/main/res/values-th/types_strings.xml index 202ca4b7a..5291511e8 100644 --- a/android/sdk/src/main/res/values-th/types_strings.xml +++ b/android/sdk/src/main/res/values-th/types_strings.xml @@ -331,7 +331,24 @@ อุโมงค์ ป้ายรถเมล์ + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง สะพาน diff --git a/android/sdk/src/main/res/values-tr/types_strings.xml b/android/sdk/src/main/res/values-tr/types_strings.xml index f70ecdc6e..386bb6673 100644 --- a/android/sdk/src/main/res/values-tr/types_strings.xml +++ b/android/sdk/src/main/res/values-tr/types_strings.xml @@ -376,7 +376,24 @@ Tünel Otobüs Durağı + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol Bisiklet Yolu Köprü diff --git a/android/sdk/src/main/res/values-uk/types_strings.xml b/android/sdk/src/main/res/values-uk/types_strings.xml index e380e0325..4df751ab0 100644 --- a/android/sdk/src/main/res/values-uk/types_strings.xml +++ b/android/sdk/src/main/res/values-uk/types_strings.xml @@ -381,7 +381,24 @@ Тунель Зупинка + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується Велодоріжка Міст diff --git a/android/sdk/src/main/res/values-vi/types_strings.xml b/android/sdk/src/main/res/values-vi/types_strings.xml index 8b290ad6a..4f2668991 100644 --- a/android/sdk/src/main/res/values-vi/types_strings.xml +++ b/android/sdk/src/main/res/values-vi/types_strings.xml @@ -329,7 +329,24 @@ Đường hầm Bến xe buýt + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công Cầu diff --git a/android/sdk/src/main/res/values-zh-rHK/types_strings.xml b/android/sdk/src/main/res/values-zh-rHK/types_strings.xml index ece4baf03..2f0dc49e2 100644 --- a/android/sdk/src/main/res/values-zh-rHK/types_strings.xml +++ b/android/sdk/src/main/res/values-zh-rHK/types_strings.xml @@ -379,7 +379,24 @@ 隧道 巴士站 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 自行車道 diff --git a/android/sdk/src/main/res/values-zh-rTW/types_strings.xml b/android/sdk/src/main/res/values-zh-rTW/types_strings.xml index 73032d099..1d996d812 100644 --- a/android/sdk/src/main/res/values-zh-rTW/types_strings.xml +++ b/android/sdk/src/main/res/values-zh-rTW/types_strings.xml @@ -626,7 +626,24 @@ 橋樑 隧道 公車站 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 單車道 橋樑 單車道 diff --git a/android/sdk/src/main/res/values-zh/types_strings.xml b/android/sdk/src/main/res/values-zh/types_strings.xml index da409ba11..b5afc1da0 100644 --- a/android/sdk/src/main/res/values-zh/types_strings.xml +++ b/android/sdk/src/main/res/values-zh/types_strings.xml @@ -380,7 +380,24 @@ 隧道 公交站 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 自行车道 diff --git a/android/sdk/src/main/res/values/types_strings.xml b/android/sdk/src/main/res/values/types_strings.xml index e9d26dded..b824ce48e 100644 --- a/android/sdk/src/main/res/values/types_strings.xml +++ b/android/sdk/src/main/res/values/types_strings.xml @@ -457,7 +457,24 @@ Tunnel Bus Stop + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction Cycle Path Bridge diff --git a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings index 5668f88fb..1d08008fe 100644 --- a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tonnel"; "type.highway.bus_stop" = "Bushalte"; "type.highway.construction" = "Pad in aanbou"; +"type.highway.construction.motorway" = "Pad in aanbou"; +"type.highway.construction.motorway_link" = "Pad in aanbou"; +"type.highway.construction.trunk" = "Pad in aanbou"; +"type.highway.construction.trunk_link" = "Pad in aanbou"; +"type.highway.construction.primary" = "Pad in aanbou"; +"type.highway.construction.primary_link" = "Pad in aanbou"; +"type.highway.construction.secondary" = "Pad in aanbou"; +"type.highway.construction.secondary_link" = "Pad in aanbou"; +"type.highway.construction.tertiary" = "Pad in aanbou"; +"type.highway.construction.tertiary_link" = "Pad in aanbou"; +"type.highway.construction.residential" = "Pad in aanbou"; +"type.highway.construction.unclassified" = "Pad in aanbou"; +"type.highway.construction.service" = "Pad in aanbou"; +"type.highway.construction.living_street" = "Pad in aanbou"; +"type.highway.construction.road" = "Pad in aanbou"; +"type.highway.construction.track" = "Pad in aanbou"; "type.highway.cycleway" = "Fietspad"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brug"; diff --git a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings index d70392789..219747a83 100644 --- a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "نفق"; "type.highway.bus_stop" = "موقف حافلات"; "type.highway.construction" = "طريق تحت الإنشاء"; +"type.highway.construction.motorway" = "طريق تحت الإنشاء"; +"type.highway.construction.motorway_link" = "طريق تحت الإنشاء"; +"type.highway.construction.trunk" = "طريق تحت الإنشاء"; +"type.highway.construction.trunk_link" = "طريق تحت الإنشاء"; +"type.highway.construction.primary" = "طريق تحت الإنشاء"; +"type.highway.construction.primary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.secondary" = "طريق تحت الإنشاء"; +"type.highway.construction.secondary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.tertiary" = "طريق تحت الإنشاء"; +"type.highway.construction.tertiary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.residential" = "طريق تحت الإنشاء"; +"type.highway.construction.unclassified" = "طريق تحت الإنشاء"; +"type.highway.construction.service" = "طريق تحت الإنشاء"; +"type.highway.construction.living_street" = "طريق تحت الإنشاء"; +"type.highway.construction.road" = "طريق تحت الإنشاء"; +"type.highway.construction.track" = "طريق تحت الإنشاء"; "type.highway.cycleway" = "مسار للدراجات"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "جسر"; diff --git a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings index 7719763a1..7dc462b4d 100644 --- a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Avtobus dayanacağı"; "type.highway.construction" = "Tikili Yol"; +"type.highway.construction.motorway" = "Tikili Yol"; +"type.highway.construction.motorway_link" = "Tikili Yol"; +"type.highway.construction.trunk" = "Tikili Yol"; +"type.highway.construction.trunk_link" = "Tikili Yol"; +"type.highway.construction.primary" = "Tikili Yol"; +"type.highway.construction.primary_link" = "Tikili Yol"; +"type.highway.construction.secondary" = "Tikili Yol"; +"type.highway.construction.secondary_link" = "Tikili Yol"; +"type.highway.construction.tertiary" = "Tikili Yol"; +"type.highway.construction.tertiary_link" = "Tikili Yol"; +"type.highway.construction.residential" = "Tikili Yol"; +"type.highway.construction.unclassified" = "Tikili Yol"; +"type.highway.construction.service" = "Tikili Yol"; +"type.highway.construction.living_street" = "Tikili Yol"; +"type.highway.construction.road" = "Tikili Yol"; +"type.highway.construction.track" = "Tikili Yol"; "type.highway.cycleway" = "Velosiped yolu"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Körpü"; diff --git a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings index ea72fe3a5..614568e39 100644 --- a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Спирка"; "type.highway.construction" = "Път в процес на изграждане"; +"type.highway.construction.motorway" = "Път в процес на изграждане"; +"type.highway.construction.motorway_link" = "Път в процес на изграждане"; +"type.highway.construction.trunk" = "Път в процес на изграждане"; +"type.highway.construction.trunk_link" = "Път в процес на изграждане"; +"type.highway.construction.primary" = "Път в процес на изграждане"; +"type.highway.construction.primary_link" = "Път в процес на изграждане"; +"type.highway.construction.secondary" = "Път в процес на изграждане"; +"type.highway.construction.secondary_link" = "Път в процес на изграждане"; +"type.highway.construction.tertiary" = "Път в процес на изграждане"; +"type.highway.construction.tertiary_link" = "Път в процес на изграждане"; +"type.highway.construction.residential" = "Път в процес на изграждане"; +"type.highway.construction.unclassified" = "Път в процес на изграждане"; +"type.highway.construction.service" = "Път в процес на изграждане"; +"type.highway.construction.living_street" = "Път в процес на изграждане"; +"type.highway.construction.road" = "Път в процес на изграждане"; +"type.highway.construction.track" = "Път в процес на изграждане"; "type.highway.cycleway" = "Пътека за колела"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings index 136e865fb..a1e990f3f 100644 --- a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada d’autobús"; "type.highway.construction" = "Via en construcció"; +"type.highway.construction.motorway" = "Via en construcció"; +"type.highway.construction.motorway_link" = "Via en construcció"; +"type.highway.construction.trunk" = "Via en construcció"; +"type.highway.construction.trunk_link" = "Via en construcció"; +"type.highway.construction.primary" = "Via en construcció"; +"type.highway.construction.primary_link" = "Via en construcció"; +"type.highway.construction.secondary" = "Via en construcció"; +"type.highway.construction.secondary_link" = "Via en construcció"; +"type.highway.construction.tertiary" = "Via en construcció"; +"type.highway.construction.tertiary_link" = "Via en construcció"; +"type.highway.construction.residential" = "Via en construcció"; +"type.highway.construction.unclassified" = "Via en construcció"; +"type.highway.construction.service" = "Via en construcció"; +"type.highway.construction.living_street" = "Via en construcció"; +"type.highway.construction.road" = "Via en construcció"; +"type.highway.construction.track" = "Via en construcció"; "type.highway.cycleway" = "Carril bici"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; @@ -882,7 +898,7 @@ "type.place.country" = "País"; "type.place.county" = "Comtat"; "type.place.farm" = "Farm"; -"type.place.hamlet" = "Aldea"; +"type.place.hamlet" = "Hamlet"; "type.place.island" = "Illa"; "type.place.islet" = "Illot"; "type.place.isolated_dwelling" = "Habitatge aïllat"; @@ -900,7 +916,7 @@ /* Named part of a city or town, bigger than place=quarter (Wiki: https://wiki.openstreetmap.org/wiki/Tag:place%3Dsuburb) */ "type.place.suburb" = "Urbanització"; "type.place.town" = "Town"; -"type.place.village" = "Poble"; +"type.place.village" = "Village"; "type.power" = "Energia"; "type.power.generator" = "Generador d’energia"; "type.power.generator.solar" = "Generador solar"; @@ -1313,7 +1329,7 @@ "type.shop.tea" = "Tea Shop"; "type.shop.telecommunication" = "Telecommunication Shop"; "type.shop.ticket" = "Ticket Shop"; -"type.shop.toys" = "Botiga de joguines"; +"type.shop.toys" = "Botiga de jocs"; "type.shop.travel_agency" = "Agència de viatges"; "type.shop.tyres" = "Tyre Shop"; "type.shop.variety_store" = "Botiga de varietat"; @@ -1509,10 +1525,10 @@ /* https://wiki.openstreetmap.org/wiki/Tag:leisure=sports_hall */ "type.leisure.sports_hall" = "Pavelló esportiu"; "type.xmas.tree" = "Arbre de Nadal"; -"type.leisure.sports_centre.sport.four_square" = "Poliesportiu"; +"type.leisure.sports_centre.sport.four_square" = "Sports Centre"; "type.sport.four_square" = "Four Square"; "type.sport.boules" = "Boules"; -"type.leisure.sports_centre.sport.boules" = "Poliesportiu"; +"type.leisure.sports_centre.sport.boules" = "Sports Centre"; "type.sport.pickleball" = "Pickleball"; "type.leisure.sports_centre.sport.pickleball" = "Centre esportiu"; "type.organic.no" = "No organic options"; diff --git a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings index 704e41a3a..a05882e1c 100644 --- a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Silnice v rekonstrukci"; +"type.highway.construction.motorway" = "Silnice v rekonstrukci"; +"type.highway.construction.motorway_link" = "Silnice v rekonstrukci"; +"type.highway.construction.trunk" = "Silnice v rekonstrukci"; +"type.highway.construction.trunk_link" = "Silnice v rekonstrukci"; +"type.highway.construction.primary" = "Silnice v rekonstrukci"; +"type.highway.construction.primary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.secondary" = "Silnice v rekonstrukci"; +"type.highway.construction.secondary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.tertiary" = "Silnice v rekonstrukci"; +"type.highway.construction.tertiary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.residential" = "Silnice v rekonstrukci"; +"type.highway.construction.unclassified" = "Silnice v rekonstrukci"; +"type.highway.construction.service" = "Silnice v rekonstrukci"; +"type.highway.construction.living_street" = "Silnice v rekonstrukci"; +"type.highway.construction.road" = "Silnice v rekonstrukci"; +"type.highway.construction.track" = "Silnice v rekonstrukci"; "type.highway.cycleway" = "Cyklostezka"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings index 953a48e8f..b4b32dea0 100644 --- a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstoppested"; "type.highway.construction" = "Vej under anlæggelse"; +"type.highway.construction.motorway" = "Vej under anlæggelse"; +"type.highway.construction.motorway_link" = "Vej under anlæggelse"; +"type.highway.construction.trunk" = "Vej under anlæggelse"; +"type.highway.construction.trunk_link" = "Vej under anlæggelse"; +"type.highway.construction.primary" = "Vej under anlæggelse"; +"type.highway.construction.primary_link" = "Vej under anlæggelse"; +"type.highway.construction.secondary" = "Vej under anlæggelse"; +"type.highway.construction.secondary_link" = "Vej under anlæggelse"; +"type.highway.construction.tertiary" = "Vej under anlæggelse"; +"type.highway.construction.tertiary_link" = "Vej under anlæggelse"; +"type.highway.construction.residential" = "Vej under anlæggelse"; +"type.highway.construction.unclassified" = "Vej under anlæggelse"; +"type.highway.construction.service" = "Vej under anlæggelse"; +"type.highway.construction.living_street" = "Vej under anlæggelse"; +"type.highway.construction.road" = "Vej under anlæggelse"; +"type.highway.construction.track" = "Vej under anlæggelse"; "type.highway.cycleway" = "Cykelsti"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bro"; diff --git a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings index 3d639d4f3..5ad08bb08 100644 --- a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltestelle"; "type.highway.construction" = "Straße im Bau"; +"type.highway.construction.motorway" = "Straße im Bau"; +"type.highway.construction.motorway_link" = "Straße im Bau"; +"type.highway.construction.trunk" = "Straße im Bau"; +"type.highway.construction.trunk_link" = "Straße im Bau"; +"type.highway.construction.primary" = "Straße im Bau"; +"type.highway.construction.primary_link" = "Straße im Bau"; +"type.highway.construction.secondary" = "Straße im Bau"; +"type.highway.construction.secondary_link" = "Straße im Bau"; +"type.highway.construction.tertiary" = "Straße im Bau"; +"type.highway.construction.tertiary_link" = "Straße im Bau"; +"type.highway.construction.residential" = "Straße im Bau"; +"type.highway.construction.unclassified" = "Straße im Bau"; +"type.highway.construction.service" = "Straße im Bau"; +"type.highway.construction.living_street" = "Straße im Bau"; +"type.highway.construction.road" = "Straße im Bau"; +"type.highway.construction.track" = "Straße im Bau"; "type.highway.cycleway" = "Radweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brücke"; diff --git a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings index 0b4a7462b..4f2862d74 100644 --- a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Σήραγγα"; "type.highway.bus_stop" = "Στάση λεωφορείου"; "type.highway.construction" = "Οδός υπό κατασκευή"; +"type.highway.construction.motorway" = "Οδός υπό κατασκευή"; +"type.highway.construction.motorway_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.trunk" = "Οδός υπό κατασκευή"; +"type.highway.construction.trunk_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.primary" = "Οδός υπό κατασκευή"; +"type.highway.construction.primary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.secondary" = "Οδός υπό κατασκευή"; +"type.highway.construction.secondary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.tertiary" = "Οδός υπό κατασκευή"; +"type.highway.construction.tertiary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.residential" = "Οδός υπό κατασκευή"; +"type.highway.construction.unclassified" = "Οδός υπό κατασκευή"; +"type.highway.construction.service" = "Οδός υπό κατασκευή"; +"type.highway.construction.living_street" = "Οδός υπό κατασκευή"; +"type.highway.construction.road" = "Οδός υπό κατασκευή"; +"type.highway.construction.track" = "Οδός υπό κατασκευή"; "type.highway.cycleway" = "Ποδηλατόδρομος"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Γέφυρα"; diff --git a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings index 1eb8441e6..d1c65d8af 100644 --- a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construcción"; +"type.highway.construction.motorway" = "Vía en construcción"; +"type.highway.construction.motorway_link" = "Vía en construcción"; +"type.highway.construction.trunk" = "Vía en construcción"; +"type.highway.construction.trunk_link" = "Vía en construcción"; +"type.highway.construction.primary" = "Vía en construcción"; +"type.highway.construction.primary_link" = "Vía en construcción"; +"type.highway.construction.secondary" = "Vía en construcción"; +"type.highway.construction.secondary_link" = "Vía en construcción"; +"type.highway.construction.tertiary" = "Vía en construcción"; +"type.highway.construction.tertiary_link" = "Vía en construcción"; +"type.highway.construction.residential" = "Vía en construcción"; +"type.highway.construction.unclassified" = "Vía en construcción"; +"type.highway.construction.service" = "Vía en construcción"; +"type.highway.construction.living_street" = "Vía en construcción"; +"type.highway.construction.road" = "Vía en construcción"; +"type.highway.construction.track" = "Vía en construcción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings index c3532bcf1..8c79a939c 100644 --- a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de bus"; "type.highway.construction" = "Vía en construcción"; +"type.highway.construction.motorway" = "Vía en construcción"; +"type.highway.construction.motorway_link" = "Vía en construcción"; +"type.highway.construction.trunk" = "Vía en construcción"; +"type.highway.construction.trunk_link" = "Vía en construcción"; +"type.highway.construction.primary" = "Vía en construcción"; +"type.highway.construction.primary_link" = "Vía en construcción"; +"type.highway.construction.secondary" = "Vía en construcción"; +"type.highway.construction.secondary_link" = "Vía en construcción"; +"type.highway.construction.tertiary" = "Vía en construcción"; +"type.highway.construction.tertiary_link" = "Vía en construcción"; +"type.highway.construction.residential" = "Vía en construcción"; +"type.highway.construction.unclassified" = "Vía en construcción"; +"type.highway.construction.service" = "Vía en construcción"; +"type.highway.construction.living_street" = "Vía en construcción"; +"type.highway.construction.road" = "Vía en construcción"; +"type.highway.construction.track" = "Vía en construcción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings index 5b4ab5ec9..aa47fd6a9 100644 --- a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bussipeatus"; "type.highway.construction" = "Ehitusjärgus tee"; +"type.highway.construction.motorway" = "Ehitusjärgus tee"; +"type.highway.construction.motorway_link" = "Ehitusjärgus tee"; +"type.highway.construction.trunk" = "Ehitusjärgus tee"; +"type.highway.construction.trunk_link" = "Ehitusjärgus tee"; +"type.highway.construction.primary" = "Ehitusjärgus tee"; +"type.highway.construction.primary_link" = "Ehitusjärgus tee"; +"type.highway.construction.secondary" = "Ehitusjärgus tee"; +"type.highway.construction.secondary_link" = "Ehitusjärgus tee"; +"type.highway.construction.tertiary" = "Ehitusjärgus tee"; +"type.highway.construction.tertiary_link" = "Ehitusjärgus tee"; +"type.highway.construction.residential" = "Ehitusjärgus tee"; +"type.highway.construction.unclassified" = "Ehitusjärgus tee"; +"type.highway.construction.service" = "Ehitusjärgus tee"; +"type.highway.construction.living_street" = "Ehitusjärgus tee"; +"type.highway.construction.road" = "Ehitusjärgus tee"; +"type.highway.construction.track" = "Ehitusjärgus tee"; "type.highway.cycleway" = "Jalgrattatee"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Jalgrattasild"; diff --git a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings index 6d22a0339..c1a947c33 100644 --- a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunela"; "type.highway.bus_stop" = "Autobus geltokia"; "type.highway.construction" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.motorway" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.motorway_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.trunk" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.trunk_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.primary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.primary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.secondary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.secondary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.tertiary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.tertiary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.residential" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.unclassified" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.service" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.living_street" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.road" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.track" = "Eraikitzen ari diren errepidea"; "type.highway.cycleway" = "Bidegorri edo bizikleta-bidea"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Zubia"; diff --git a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings index 8cb29e7ac..f5931ee2d 100644 --- a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "تونل"; "type.highway.bus_stop" = "حمل و نقل"; "type.highway.construction" = "جاده در دست ساخت است"; +"type.highway.construction.motorway" = "جاده در دست ساخت است"; +"type.highway.construction.motorway_link" = "جاده در دست ساخت است"; +"type.highway.construction.trunk" = "جاده در دست ساخت است"; +"type.highway.construction.trunk_link" = "جاده در دست ساخت است"; +"type.highway.construction.primary" = "جاده در دست ساخت است"; +"type.highway.construction.primary_link" = "جاده در دست ساخت است"; +"type.highway.construction.secondary" = "جاده در دست ساخت است"; +"type.highway.construction.secondary_link" = "جاده در دست ساخت است"; +"type.highway.construction.tertiary" = "جاده در دست ساخت است"; +"type.highway.construction.tertiary_link" = "جاده در دست ساخت است"; +"type.highway.construction.residential" = "جاده در دست ساخت است"; +"type.highway.construction.unclassified" = "جاده در دست ساخت است"; +"type.highway.construction.service" = "جاده در دست ساخت است"; +"type.highway.construction.living_street" = "جاده در دست ساخت است"; +"type.highway.construction.road" = "جاده در دست ساخت است"; +"type.highway.construction.track" = "جاده در دست ساخت است"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "پل"; diff --git a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings index 022a1e421..0655af193 100644 --- a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunneli"; "type.highway.bus_stop" = "Bussipysäkki"; "type.highway.construction" = "Rakenteilla oleva tie"; +"type.highway.construction.motorway" = "Rakenteilla oleva tie"; +"type.highway.construction.motorway_link" = "Rakenteilla oleva tie"; +"type.highway.construction.trunk" = "Rakenteilla oleva tie"; +"type.highway.construction.trunk_link" = "Rakenteilla oleva tie"; +"type.highway.construction.primary" = "Rakenteilla oleva tie"; +"type.highway.construction.primary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.secondary" = "Rakenteilla oleva tie"; +"type.highway.construction.secondary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.tertiary" = "Rakenteilla oleva tie"; +"type.highway.construction.tertiary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.residential" = "Rakenteilla oleva tie"; +"type.highway.construction.unclassified" = "Rakenteilla oleva tie"; +"type.highway.construction.service" = "Rakenteilla oleva tie"; +"type.highway.construction.living_street" = "Rakenteilla oleva tie"; +"type.highway.construction.road" = "Rakenteilla oleva tie"; +"type.highway.construction.track" = "Rakenteilla oleva tie"; "type.highway.cycleway" = "Pyörätie"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Silta"; diff --git a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings index 57fd82faa..e48d3c740 100644 --- a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Arrêt de bus"; "type.highway.construction" = "Route en construction"; +"type.highway.construction.motorway" = "Route en construction"; +"type.highway.construction.motorway_link" = "Route en construction"; +"type.highway.construction.trunk" = "Route en construction"; +"type.highway.construction.trunk_link" = "Route en construction"; +"type.highway.construction.primary" = "Route en construction"; +"type.highway.construction.primary_link" = "Route en construction"; +"type.highway.construction.secondary" = "Route en construction"; +"type.highway.construction.secondary_link" = "Route en construction"; +"type.highway.construction.tertiary" = "Route en construction"; +"type.highway.construction.tertiary_link" = "Route en construction"; +"type.highway.construction.residential" = "Route en construction"; +"type.highway.construction.unclassified" = "Route en construction"; +"type.highway.construction.service" = "Route en construction"; +"type.highway.construction.living_street" = "Route en construction"; +"type.highway.construction.road" = "Route en construction"; +"type.highway.construction.track" = "Route en construction"; "type.highway.cycleway" = "Piste cyclable"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; diff --git a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings index 997243f63..be0b960f7 100644 --- a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construción"; +"type.highway.construction.motorway" = "Vía en construción"; +"type.highway.construction.motorway_link" = "Vía en construción"; +"type.highway.construction.trunk" = "Vía en construción"; +"type.highway.construction.trunk_link" = "Vía en construción"; +"type.highway.construction.primary" = "Vía en construción"; +"type.highway.construction.primary_link" = "Vía en construción"; +"type.highway.construction.secondary" = "Vía en construción"; +"type.highway.construction.secondary_link" = "Vía en construción"; +"type.highway.construction.tertiary" = "Vía en construción"; +"type.highway.construction.tertiary_link" = "Vía en construción"; +"type.highway.construction.residential" = "Vía en construción"; +"type.highway.construction.unclassified" = "Vía en construción"; +"type.highway.construction.service" = "Vía en construción"; +"type.highway.construction.living_street" = "Vía en construción"; +"type.highway.construction.road" = "Vía en construción"; +"type.highway.construction.track" = "Vía en construción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings index a5485eec2..016a369ec 100644 --- a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltistell"; "type.highway.construction" = "Strass im Bau"; +"type.highway.construction.motorway" = "Strass im Bau"; +"type.highway.construction.motorway_link" = "Strass im Bau"; +"type.highway.construction.trunk" = "Strass im Bau"; +"type.highway.construction.trunk_link" = "Strass im Bau"; +"type.highway.construction.primary" = "Strass im Bau"; +"type.highway.construction.primary_link" = "Strass im Bau"; +"type.highway.construction.secondary" = "Strass im Bau"; +"type.highway.construction.secondary_link" = "Strass im Bau"; +"type.highway.construction.tertiary" = "Strass im Bau"; +"type.highway.construction.tertiary_link" = "Strass im Bau"; +"type.highway.construction.residential" = "Strass im Bau"; +"type.highway.construction.unclassified" = "Strass im Bau"; +"type.highway.construction.service" = "Strass im Bau"; +"type.highway.construction.living_street" = "Strass im Bau"; +"type.highway.construction.road" = "Strass im Bau"; +"type.highway.construction.track" = "Strass im Bau"; "type.highway.cycleway" = "Veloweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brugg"; diff --git a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings index a328e3dff..87580519d 100644 --- a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings @@ -448,6 +448,22 @@ "type.highway.busway.tunnel" = "מִנהָרָה"; "type.highway.bus_stop" = "תחנת אוטובוס"; "type.highway.construction" = "כביש בבניה"; +"type.highway.construction.motorway" = "כביש בבניה"; +"type.highway.construction.motorway_link" = "כביש בבניה"; +"type.highway.construction.trunk" = "כביש בבניה"; +"type.highway.construction.trunk_link" = "כביש בבניה"; +"type.highway.construction.primary" = "כביש בבניה"; +"type.highway.construction.primary_link" = "כביש בבניה"; +"type.highway.construction.secondary" = "כביש בבניה"; +"type.highway.construction.secondary_link" = "כביש בבניה"; +"type.highway.construction.tertiary" = "כביש בבניה"; +"type.highway.construction.tertiary_link" = "כביש בבניה"; +"type.highway.construction.residential" = "כביש בבניה"; +"type.highway.construction.unclassified" = "כביש בבניה"; +"type.highway.construction.service" = "כביש בבניה"; +"type.highway.construction.living_street" = "כביש בבניה"; +"type.highway.construction.road" = "כביש בבניה"; +"type.highway.construction.track" = "כביש בבניה"; "type.highway.cycleway" = "שביל אופניים"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "גשר לאופניים"; diff --git a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings index 78477b142..f943befaf 100644 --- a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "बस स्टॉप"; "type.highway.construction" = "निर्माणाधीन सड़क"; +"type.highway.construction.motorway" = "निर्माणाधीन सड़क"; +"type.highway.construction.motorway_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.trunk" = "निर्माणाधीन सड़क"; +"type.highway.construction.trunk_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.primary" = "निर्माणाधीन सड़क"; +"type.highway.construction.primary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.secondary" = "निर्माणाधीन सड़क"; +"type.highway.construction.secondary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.tertiary" = "निर्माणाधीन सड़क"; +"type.highway.construction.tertiary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.residential" = "निर्माणाधीन सड़क"; +"type.highway.construction.unclassified" = "निर्माणाधीन सड़क"; +"type.highway.construction.service" = "निर्माणाधीन सड़क"; +"type.highway.construction.living_street" = "निर्माणाधीन सड़क"; +"type.highway.construction.road" = "निर्माणाधीन सड़क"; +"type.highway.construction.track" = "निर्माणाधीन सड़क"; "type.highway.cycleway" = "साइकिल मार्ग"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings index ae232f6ee..01228eba0 100644 --- a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Alagút"; "type.highway.bus_stop" = "Buszmegálló"; "type.highway.construction" = "Útépítés"; +"type.highway.construction.motorway" = "Útépítés"; +"type.highway.construction.motorway_link" = "Útépítés"; +"type.highway.construction.trunk" = "Útépítés"; +"type.highway.construction.trunk_link" = "Útépítés"; +"type.highway.construction.primary" = "Útépítés"; +"type.highway.construction.primary_link" = "Útépítés"; +"type.highway.construction.secondary" = "Útépítés"; +"type.highway.construction.secondary_link" = "Útépítés"; +"type.highway.construction.tertiary" = "Útépítés"; +"type.highway.construction.tertiary_link" = "Útépítés"; +"type.highway.construction.residential" = "Útépítés"; +"type.highway.construction.unclassified" = "Útépítés"; +"type.highway.construction.service" = "Útépítés"; +"type.highway.construction.living_street" = "Útépítés"; +"type.highway.construction.road" = "Útépítés"; +"type.highway.construction.track" = "Útépítés"; "type.highway.cycleway" = "Kerékpárút"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Híd"; diff --git a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings index d8e10b31d..36b85543e 100644 --- a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings @@ -430,6 +430,22 @@ "type.highway.busway.tunnel" = "Terowongan"; "type.highway.bus_stop" = "Halte bus"; "type.highway.construction" = "Jalan sedang dibangun"; +"type.highway.construction.motorway" = "Jalan sedang dibangun"; +"type.highway.construction.motorway_link" = "Jalan sedang dibangun"; +"type.highway.construction.trunk" = "Jalan sedang dibangun"; +"type.highway.construction.trunk_link" = "Jalan sedang dibangun"; +"type.highway.construction.primary" = "Jalan sedang dibangun"; +"type.highway.construction.primary_link" = "Jalan sedang dibangun"; +"type.highway.construction.secondary" = "Jalan sedang dibangun"; +"type.highway.construction.secondary_link" = "Jalan sedang dibangun"; +"type.highway.construction.tertiary" = "Jalan sedang dibangun"; +"type.highway.construction.tertiary_link" = "Jalan sedang dibangun"; +"type.highway.construction.residential" = "Jalan sedang dibangun"; +"type.highway.construction.unclassified" = "Jalan sedang dibangun"; +"type.highway.construction.service" = "Jalan sedang dibangun"; +"type.highway.construction.living_street" = "Jalan sedang dibangun"; +"type.highway.construction.road" = "Jalan sedang dibangun"; +"type.highway.construction.track" = "Jalan sedang dibangun"; "type.highway.cycleway" = "Jalur Sepeda"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Menjembatani"; diff --git a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings index 5da574e46..1f86e9984 100644 --- a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Göng"; "type.highway.bus_stop" = "Strætisvagnabiðstöð"; "type.highway.construction" = "Vegur í byggingu"; +"type.highway.construction.motorway" = "Vegur í byggingu"; +"type.highway.construction.motorway_link" = "Vegur í byggingu"; +"type.highway.construction.trunk" = "Vegur í byggingu"; +"type.highway.construction.trunk_link" = "Vegur í byggingu"; +"type.highway.construction.primary" = "Vegur í byggingu"; +"type.highway.construction.primary_link" = "Vegur í byggingu"; +"type.highway.construction.secondary" = "Vegur í byggingu"; +"type.highway.construction.secondary_link" = "Vegur í byggingu"; +"type.highway.construction.tertiary" = "Vegur í byggingu"; +"type.highway.construction.tertiary_link" = "Vegur í byggingu"; +"type.highway.construction.residential" = "Vegur í byggingu"; +"type.highway.construction.unclassified" = "Vegur í byggingu"; +"type.highway.construction.service" = "Vegur í byggingu"; +"type.highway.construction.living_street" = "Vegur í byggingu"; +"type.highway.construction.road" = "Vegur í byggingu"; +"type.highway.construction.track" = "Vegur í byggingu"; "type.highway.cycleway" = "Hjólastígur"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brú"; diff --git a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings index 6106fe752..f52aaf6f6 100644 --- a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Galleria"; "type.highway.bus_stop" = "Fermata dell'autobus"; "type.highway.construction" = "Strada in costruzione"; +"type.highway.construction.motorway" = "Strada in costruzione"; +"type.highway.construction.motorway_link" = "Strada in costruzione"; +"type.highway.construction.trunk" = "Strada in costruzione"; +"type.highway.construction.trunk_link" = "Strada in costruzione"; +"type.highway.construction.primary" = "Strada in costruzione"; +"type.highway.construction.primary_link" = "Strada in costruzione"; +"type.highway.construction.secondary" = "Strada in costruzione"; +"type.highway.construction.secondary_link" = "Strada in costruzione"; +"type.highway.construction.tertiary" = "Strada in costruzione"; +"type.highway.construction.tertiary_link" = "Strada in costruzione"; +"type.highway.construction.residential" = "Strada in costruzione"; +"type.highway.construction.unclassified" = "Strada in costruzione"; +"type.highway.construction.service" = "Strada in costruzione"; +"type.highway.construction.living_street" = "Strada in costruzione"; +"type.highway.construction.road" = "Strada in costruzione"; +"type.highway.construction.track" = "Strada in costruzione"; "type.highway.cycleway" = "Pista ciclabile"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings index ea1d539d9..888a54fb6 100644 --- a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "トンネル"; "type.highway.bus_stop" = "バス停"; "type.highway.construction" = "工事中の道路"; +"type.highway.construction.motorway" = "工事中の道路"; +"type.highway.construction.motorway_link" = "工事中の道路"; +"type.highway.construction.trunk" = "工事中の道路"; +"type.highway.construction.trunk_link" = "工事中の道路"; +"type.highway.construction.primary" = "工事中の道路"; +"type.highway.construction.primary_link" = "工事中の道路"; +"type.highway.construction.secondary" = "工事中の道路"; +"type.highway.construction.secondary_link" = "工事中の道路"; +"type.highway.construction.tertiary" = "工事中の道路"; +"type.highway.construction.tertiary_link" = "工事中の道路"; +"type.highway.construction.residential" = "工事中の道路"; +"type.highway.construction.unclassified" = "工事中の道路"; +"type.highway.construction.service" = "工事中の道路"; +"type.highway.construction.living_street" = "工事中の道路"; +"type.highway.construction.road" = "工事中の道路"; +"type.highway.construction.track" = "工事中の道路"; "type.highway.cycleway" = "自転車道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "自転車道(橋)"; diff --git a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings index d5a276355..ffadacfaa 100644 --- a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "터널"; "type.highway.bus_stop" = "버스 정류장"; "type.highway.construction" = "공사 중 도로"; +"type.highway.construction.motorway" = "공사 중 도로"; +"type.highway.construction.motorway_link" = "공사 중 도로"; +"type.highway.construction.trunk" = "공사 중 도로"; +"type.highway.construction.trunk_link" = "공사 중 도로"; +"type.highway.construction.primary" = "공사 중 도로"; +"type.highway.construction.primary_link" = "공사 중 도로"; +"type.highway.construction.secondary" = "공사 중 도로"; +"type.highway.construction.secondary_link" = "공사 중 도로"; +"type.highway.construction.tertiary" = "공사 중 도로"; +"type.highway.construction.tertiary_link" = "공사 중 도로"; +"type.highway.construction.residential" = "공사 중 도로"; +"type.highway.construction.unclassified" = "공사 중 도로"; +"type.highway.construction.service" = "공사 중 도로"; +"type.highway.construction.living_street" = "공사 중 도로"; +"type.highway.construction.road" = "공사 중 도로"; +"type.highway.construction.track" = "공사 중 도로"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "다리"; diff --git a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings index 16fa86676..e994ff793 100644 --- a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunelis"; "type.highway.bus_stop" = "Stotelė"; "type.highway.construction" = "Tiesiamas kelias"; +"type.highway.construction.motorway" = "Tiesiamas kelias"; +"type.highway.construction.motorway_link" = "Tiesiamas kelias"; +"type.highway.construction.trunk" = "Tiesiamas kelias"; +"type.highway.construction.trunk_link" = "Tiesiamas kelias"; +"type.highway.construction.primary" = "Tiesiamas kelias"; +"type.highway.construction.primary_link" = "Tiesiamas kelias"; +"type.highway.construction.secondary" = "Tiesiamas kelias"; +"type.highway.construction.secondary_link" = "Tiesiamas kelias"; +"type.highway.construction.tertiary" = "Tiesiamas kelias"; +"type.highway.construction.tertiary_link" = "Tiesiamas kelias"; +"type.highway.construction.residential" = "Tiesiamas kelias"; +"type.highway.construction.unclassified" = "Tiesiamas kelias"; +"type.highway.construction.service" = "Tiesiamas kelias"; +"type.highway.construction.living_street" = "Tiesiamas kelias"; +"type.highway.construction.road" = "Tiesiamas kelias"; +"type.highway.construction.track" = "Tiesiamas kelias"; "type.highway.cycleway" = "Dviračių takas"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Tiltas"; diff --git a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings index fb72b39d6..fc9136b0e 100644 --- a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "बोगदा"; "type.highway.bus_stop" = "बस थांबा"; "type.highway.construction" = "बांधकामाधीन रस्ता"; +"type.highway.construction.motorway" = "बांधकामाधीन रस्ता"; +"type.highway.construction.motorway_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.trunk" = "बांधकामाधीन रस्ता"; +"type.highway.construction.trunk_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.primary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.primary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.secondary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.secondary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.tertiary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.tertiary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.residential" = "बांधकामाधीन रस्ता"; +"type.highway.construction.unclassified" = "बांधकामाधीन रस्ता"; +"type.highway.construction.service" = "बांधकामाधीन रस्ता"; +"type.highway.construction.living_street" = "बांधकामाधीन रस्ता"; +"type.highway.construction.road" = "बांधकामाधीन रस्ता"; +"type.highway.construction.track" = "बांधकामाधीन रस्ता"; "type.highway.cycleway" = "सायकल वाट"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "पूल"; diff --git a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings index 8b64e7d90..2bd154436 100644 --- a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstopp"; "type.highway.construction" = "Veikonstruksjon"; +"type.highway.construction.motorway" = "Veikonstruksjon"; +"type.highway.construction.motorway_link" = "Veikonstruksjon"; +"type.highway.construction.trunk" = "Veikonstruksjon"; +"type.highway.construction.trunk_link" = "Veikonstruksjon"; +"type.highway.construction.primary" = "Veikonstruksjon"; +"type.highway.construction.primary_link" = "Veikonstruksjon"; +"type.highway.construction.secondary" = "Veikonstruksjon"; +"type.highway.construction.secondary_link" = "Veikonstruksjon"; +"type.highway.construction.tertiary" = "Veikonstruksjon"; +"type.highway.construction.tertiary_link" = "Veikonstruksjon"; +"type.highway.construction.residential" = "Veikonstruksjon"; +"type.highway.construction.unclassified" = "Veikonstruksjon"; +"type.highway.construction.service" = "Veikonstruksjon"; +"type.highway.construction.living_street" = "Veikonstruksjon"; +"type.highway.construction.road" = "Veikonstruksjon"; +"type.highway.construction.track" = "Veikonstruksjon"; "type.highway.cycleway" = "Sykkelvei"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bru"; diff --git a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings index c5e440e82..b7f76c8b7 100644 --- a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushalte"; "type.highway.construction" = "Weg in aanbouw"; +"type.highway.construction.motorway" = "Weg in aanbouw"; +"type.highway.construction.motorway_link" = "Weg in aanbouw"; +"type.highway.construction.trunk" = "Weg in aanbouw"; +"type.highway.construction.trunk_link" = "Weg in aanbouw"; +"type.highway.construction.primary" = "Weg in aanbouw"; +"type.highway.construction.primary_link" = "Weg in aanbouw"; +"type.highway.construction.secondary" = "Weg in aanbouw"; +"type.highway.construction.secondary_link" = "Weg in aanbouw"; +"type.highway.construction.tertiary" = "Weg in aanbouw"; +"type.highway.construction.tertiary_link" = "Weg in aanbouw"; +"type.highway.construction.residential" = "Weg in aanbouw"; +"type.highway.construction.unclassified" = "Weg in aanbouw"; +"type.highway.construction.service" = "Weg in aanbouw"; +"type.highway.construction.living_street" = "Weg in aanbouw"; +"type.highway.construction.road" = "Weg in aanbouw"; +"type.highway.construction.track" = "Weg in aanbouw"; "type.highway.cycleway" = "Fietspad"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Fietsbrug"; diff --git a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings index 3f5effe26..fffcf7ef8 100644 --- a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Przystanek autobusowy"; "type.highway.construction" = "Droga w trakcie budowy"; +"type.highway.construction.motorway" = "Droga w trakcie budowy"; +"type.highway.construction.motorway_link" = "Droga w trakcie budowy"; +"type.highway.construction.trunk" = "Droga w trakcie budowy"; +"type.highway.construction.trunk_link" = "Droga w trakcie budowy"; +"type.highway.construction.primary" = "Droga w trakcie budowy"; +"type.highway.construction.primary_link" = "Droga w trakcie budowy"; +"type.highway.construction.secondary" = "Droga w trakcie budowy"; +"type.highway.construction.secondary_link" = "Droga w trakcie budowy"; +"type.highway.construction.tertiary" = "Droga w trakcie budowy"; +"type.highway.construction.tertiary_link" = "Droga w trakcie budowy"; +"type.highway.construction.residential" = "Droga w trakcie budowy"; +"type.highway.construction.unclassified" = "Droga w trakcie budowy"; +"type.highway.construction.service" = "Droga w trakcie budowy"; +"type.highway.construction.living_street" = "Droga w trakcie budowy"; +"type.highway.construction.road" = "Droga w trakcie budowy"; +"type.highway.construction.track" = "Droga w trakcie budowy"; "type.highway.cycleway" = "Droga rowerowa"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most drogowy dla rowerów"; diff --git a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings index 1d94e053a..92273713b 100644 --- a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Ponto de ônibus"; "type.highway.construction" = "Via em construção"; +"type.highway.construction.motorway" = "Via em construção"; +"type.highway.construction.motorway_link" = "Via em construção"; +"type.highway.construction.trunk" = "Via em construção"; +"type.highway.construction.trunk_link" = "Via em construção"; +"type.highway.construction.primary" = "Via em construção"; +"type.highway.construction.primary_link" = "Via em construção"; +"type.highway.construction.secondary" = "Via em construção"; +"type.highway.construction.secondary_link" = "Via em construção"; +"type.highway.construction.tertiary" = "Via em construção"; +"type.highway.construction.tertiary_link" = "Via em construção"; +"type.highway.construction.residential" = "Via em construção"; +"type.highway.construction.unclassified" = "Via em construção"; +"type.highway.construction.service" = "Via em construção"; +"type.highway.construction.living_street" = "Via em construção"; +"type.highway.construction.road" = "Via em construção"; +"type.highway.construction.track" = "Via em construção"; "type.highway.cycleway" = "Ciclovia"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte para ciclistas"; diff --git a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings index a016f4ab1..e6407bc5c 100644 --- a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Paragem de autocarros"; "type.highway.construction" = "Estrada em construção"; +"type.highway.construction.motorway" = "Estrada em construção"; +"type.highway.construction.motorway_link" = "Estrada em construção"; +"type.highway.construction.trunk" = "Estrada em construção"; +"type.highway.construction.trunk_link" = "Estrada em construção"; +"type.highway.construction.primary" = "Estrada em construção"; +"type.highway.construction.primary_link" = "Estrada em construção"; +"type.highway.construction.secondary" = "Estrada em construção"; +"type.highway.construction.secondary_link" = "Estrada em construção"; +"type.highway.construction.tertiary" = "Estrada em construção"; +"type.highway.construction.tertiary_link" = "Estrada em construção"; +"type.highway.construction.residential" = "Estrada em construção"; +"type.highway.construction.unclassified" = "Estrada em construção"; +"type.highway.construction.service" = "Estrada em construção"; +"type.highway.construction.living_street" = "Estrada em construção"; +"type.highway.construction.road" = "Estrada em construção"; +"type.highway.construction.track" = "Estrada em construção"; "type.highway.cycleway" = "Ciclovia"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings index 4329cf755..1e046583b 100644 --- a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Stație de autobuz"; "type.highway.construction" = "Drum în construcție"; +"type.highway.construction.motorway" = "Drum în construcție"; +"type.highway.construction.motorway_link" = "Drum în construcție"; +"type.highway.construction.trunk" = "Drum în construcție"; +"type.highway.construction.trunk_link" = "Drum în construcție"; +"type.highway.construction.primary" = "Drum în construcție"; +"type.highway.construction.primary_link" = "Drum în construcție"; +"type.highway.construction.secondary" = "Drum în construcție"; +"type.highway.construction.secondary_link" = "Drum în construcție"; +"type.highway.construction.tertiary" = "Drum în construcție"; +"type.highway.construction.tertiary_link" = "Drum în construcție"; +"type.highway.construction.residential" = "Drum în construcție"; +"type.highway.construction.unclassified" = "Drum în construcție"; +"type.highway.construction.service" = "Drum în construcție"; +"type.highway.construction.living_street" = "Drum în construcție"; +"type.highway.construction.road" = "Drum în construcție"; +"type.highway.construction.track" = "Drum în construcție"; "type.highway.cycleway" = "Pistă de Biciclete"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pod"; diff --git a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings index 217981b72..dea54d33f 100644 --- a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Тоннель"; "type.highway.bus_stop" = "Остановка"; "type.highway.construction" = "Строящаяся дорога"; +"type.highway.construction.motorway" = "Строящаяся дорога"; +"type.highway.construction.motorway_link" = "Строящаяся дорога"; +"type.highway.construction.trunk" = "Строящаяся дорога"; +"type.highway.construction.trunk_link" = "Строящаяся дорога"; +"type.highway.construction.primary" = "Строящаяся дорога"; +"type.highway.construction.primary_link" = "Строящаяся дорога"; +"type.highway.construction.secondary" = "Строящаяся дорога"; +"type.highway.construction.secondary_link" = "Строящаяся дорога"; +"type.highway.construction.tertiary" = "Строящаяся дорога"; +"type.highway.construction.tertiary_link" = "Строящаяся дорога"; +"type.highway.construction.residential" = "Строящаяся дорога"; +"type.highway.construction.unclassified" = "Строящаяся дорога"; +"type.highway.construction.service" = "Строящаяся дорога"; +"type.highway.construction.living_street" = "Строящаяся дорога"; +"type.highway.construction.road" = "Строящаяся дорога"; +"type.highway.construction.track" = "Строящаяся дорога"; "type.highway.cycleway" = "Велодорожка"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings index be537e420..a1fbc1c2a 100644 --- a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Cesta vo výstavbe"; +"type.highway.construction.motorway" = "Cesta vo výstavbe"; +"type.highway.construction.motorway_link" = "Cesta vo výstavbe"; +"type.highway.construction.trunk" = "Cesta vo výstavbe"; +"type.highway.construction.trunk_link" = "Cesta vo výstavbe"; +"type.highway.construction.primary" = "Cesta vo výstavbe"; +"type.highway.construction.primary_link" = "Cesta vo výstavbe"; +"type.highway.construction.secondary" = "Cesta vo výstavbe"; +"type.highway.construction.secondary_link" = "Cesta vo výstavbe"; +"type.highway.construction.tertiary" = "Cesta vo výstavbe"; +"type.highway.construction.tertiary_link" = "Cesta vo výstavbe"; +"type.highway.construction.residential" = "Cesta vo výstavbe"; +"type.highway.construction.unclassified" = "Cesta vo výstavbe"; +"type.highway.construction.service" = "Cesta vo výstavbe"; +"type.highway.construction.living_street" = "Cesta vo výstavbe"; +"type.highway.construction.road" = "Cesta vo výstavbe"; +"type.highway.construction.track" = "Cesta vo výstavbe"; "type.highway.cycleway" = "Cyklocesta"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings index 345fc9987..8f25c578e 100644 --- a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Predor"; "type.highway.bus_stop" = "Avtobusno postajališče"; "type.highway.construction" = "Cesta v gradnji"; +"type.highway.construction.motorway" = "Cesta v gradnji"; +"type.highway.construction.motorway_link" = "Cesta v gradnji"; +"type.highway.construction.trunk" = "Cesta v gradnji"; +"type.highway.construction.trunk_link" = "Cesta v gradnji"; +"type.highway.construction.primary" = "Cesta v gradnji"; +"type.highway.construction.primary_link" = "Cesta v gradnji"; +"type.highway.construction.secondary" = "Cesta v gradnji"; +"type.highway.construction.secondary_link" = "Cesta v gradnji"; +"type.highway.construction.tertiary" = "Cesta v gradnji"; +"type.highway.construction.tertiary_link" = "Cesta v gradnji"; +"type.highway.construction.residential" = "Cesta v gradnji"; +"type.highway.construction.unclassified" = "Cesta v gradnji"; +"type.highway.construction.service" = "Cesta v gradnji"; +"type.highway.construction.living_street" = "Cesta v gradnji"; +"type.highway.construction.road" = "Cesta v gradnji"; +"type.highway.construction.track" = "Cesta v gradnji"; "type.highway.cycleway" = "Kolesarska steza"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings index a1a1d7f36..944784c98 100644 --- a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Аутобуско стајалиште"; "type.highway.construction" = "Пут у изградњи"; +"type.highway.construction.motorway" = "Пут у изградњи"; +"type.highway.construction.motorway_link" = "Пут у изградњи"; +"type.highway.construction.trunk" = "Пут у изградњи"; +"type.highway.construction.trunk_link" = "Пут у изградњи"; +"type.highway.construction.primary" = "Пут у изградњи"; +"type.highway.construction.primary_link" = "Пут у изградњи"; +"type.highway.construction.secondary" = "Пут у изградњи"; +"type.highway.construction.secondary_link" = "Пут у изградњи"; +"type.highway.construction.tertiary" = "Пут у изградњи"; +"type.highway.construction.tertiary_link" = "Пут у изградњи"; +"type.highway.construction.residential" = "Пут у изградњи"; +"type.highway.construction.unclassified" = "Пут у изградњи"; +"type.highway.construction.service" = "Пут у изградњи"; +"type.highway.construction.living_street" = "Пут у изградњи"; +"type.highway.construction.road" = "Пут у изградњи"; +"type.highway.construction.track" = "Пут у изградњи"; "type.highway.cycleway" = "Бициклистичка стаза"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings index 84f8c105e..e55aed793 100644 --- a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busshållplats"; "type.highway.construction" = "Väg under uppförande"; +"type.highway.construction.motorway" = "Väg under uppförande"; +"type.highway.construction.motorway_link" = "Väg under uppförande"; +"type.highway.construction.trunk" = "Väg under uppförande"; +"type.highway.construction.trunk_link" = "Väg under uppförande"; +"type.highway.construction.primary" = "Väg under uppförande"; +"type.highway.construction.primary_link" = "Väg under uppförande"; +"type.highway.construction.secondary" = "Väg under uppförande"; +"type.highway.construction.secondary_link" = "Väg under uppförande"; +"type.highway.construction.tertiary" = "Väg under uppförande"; +"type.highway.construction.tertiary_link" = "Väg under uppförande"; +"type.highway.construction.residential" = "Väg under uppförande"; +"type.highway.construction.unclassified" = "Väg under uppförande"; +"type.highway.construction.service" = "Väg under uppförande"; +"type.highway.construction.living_street" = "Väg under uppförande"; +"type.highway.construction.road" = "Väg under uppförande"; +"type.highway.construction.track" = "Väg under uppförande"; "type.highway.cycleway" = "Cykelbana"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Cykelbro"; diff --git a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings index d713e8ce9..a8866d648 100644 --- a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Mtaro"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Barabara inatengenezwa"; +"type.highway.construction.motorway" = "Barabara inatengenezwa"; +"type.highway.construction.motorway_link" = "Barabara inatengenezwa"; +"type.highway.construction.trunk" = "Barabara inatengenezwa"; +"type.highway.construction.trunk_link" = "Barabara inatengenezwa"; +"type.highway.construction.primary" = "Barabara inatengenezwa"; +"type.highway.construction.primary_link" = "Barabara inatengenezwa"; +"type.highway.construction.secondary" = "Barabara inatengenezwa"; +"type.highway.construction.secondary_link" = "Barabara inatengenezwa"; +"type.highway.construction.tertiary" = "Barabara inatengenezwa"; +"type.highway.construction.tertiary_link" = "Barabara inatengenezwa"; +"type.highway.construction.residential" = "Barabara inatengenezwa"; +"type.highway.construction.unclassified" = "Barabara inatengenezwa"; +"type.highway.construction.service" = "Barabara inatengenezwa"; +"type.highway.construction.living_street" = "Barabara inatengenezwa"; +"type.highway.construction.road" = "Barabara inatengenezwa"; +"type.highway.construction.track" = "Barabara inatengenezwa"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Daraja"; diff --git a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings index de67ffef7..877fb95e1 100644 --- a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "சுரங்கப்பாதை"; "type.highway.bus_stop" = "பேருந்து நிறுத்தம்"; "type.highway.construction" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.motorway" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.motorway_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.trunk" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.trunk_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.primary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.primary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.secondary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.secondary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.tertiary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.tertiary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.residential" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.unclassified" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.service" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.living_street" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.road" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.track" = "சாலை கட்டுமானத்தில் உள்ளது"; "type.highway.cycleway" = "சைக்கிள் பாதை"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "பாலம்"; diff --git a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings index 4e1c9e6a5..8edea2256 100644 --- a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "อุโมงค์"; "type.highway.bus_stop" = "ป้ายรถเมล์"; "type.highway.construction" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.motorway" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.motorway_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.trunk" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.trunk_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.primary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.primary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.secondary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.secondary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.tertiary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.tertiary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.residential" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.unclassified" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.service" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.living_street" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.road" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.track" = "ทางกำลังอยู่ในการก่อสร้าง"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "สะพาน"; diff --git a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings index 06c50ce1c..3f451f099 100644 --- a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Tünel"; "type.highway.bus_stop" = "Otobüs Durağı"; "type.highway.construction" = "Yapım Aşamasında Yol"; +"type.highway.construction.motorway" = "Yapım Aşamasında Yol"; +"type.highway.construction.motorway_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.trunk" = "Yapım Aşamasında Yol"; +"type.highway.construction.trunk_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.primary" = "Yapım Aşamasında Yol"; +"type.highway.construction.primary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.secondary" = "Yapım Aşamasında Yol"; +"type.highway.construction.secondary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.tertiary" = "Yapım Aşamasında Yol"; +"type.highway.construction.tertiary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.residential" = "Yapım Aşamasında Yol"; +"type.highway.construction.unclassified" = "Yapım Aşamasında Yol"; +"type.highway.construction.service" = "Yapım Aşamasında Yol"; +"type.highway.construction.living_street" = "Yapım Aşamasında Yol"; +"type.highway.construction.road" = "Yapım Aşamasında Yol"; +"type.highway.construction.track" = "Yapım Aşamasında Yol"; "type.highway.cycleway" = "Bisiklet Yolu"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Köprü"; diff --git a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings index f0c9e933b..a6936c9b6 100644 --- a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "Тунель"; "type.highway.bus_stop" = "Зупинка"; "type.highway.construction" = "Дорога, що будується"; +"type.highway.construction.motorway" = "Дорога, що будується"; +"type.highway.construction.motorway_link" = "Дорога, що будується"; +"type.highway.construction.trunk" = "Дорога, що будується"; +"type.highway.construction.trunk_link" = "Дорога, що будується"; +"type.highway.construction.primary" = "Дорога, що будується"; +"type.highway.construction.primary_link" = "Дорога, що будується"; +"type.highway.construction.secondary" = "Дорога, що будується"; +"type.highway.construction.secondary_link" = "Дорога, що будується"; +"type.highway.construction.tertiary" = "Дорога, що будується"; +"type.highway.construction.tertiary_link" = "Дорога, що будується"; +"type.highway.construction.residential" = "Дорога, що будується"; +"type.highway.construction.unclassified" = "Дорога, що будується"; +"type.highway.construction.service" = "Дорога, що будується"; +"type.highway.construction.living_street" = "Дорога, що будується"; +"type.highway.construction.road" = "Дорога, що будується"; +"type.highway.construction.track" = "Дорога, що будується"; "type.highway.cycleway" = "Велодоріжка"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Міст"; diff --git a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings index 43dd71efb..bf5d4818f 100644 --- a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings @@ -453,6 +453,22 @@ "type.highway.bus_stop" = "Bến xe buýt"; "type.highway.construction" = "Đường đang thi công"; "type.highway.cycleway" = "Đường dành cho xe đạp"; +"type.highway.construction.motorway" = "Đường đang thi công"; +"type.highway.construction.motorway_link" = "Đường đang thi công"; +"type.highway.construction.trunk" = "Đường đang thi công"; +"type.highway.construction.trunk_link" = "Đường đang thi công"; +"type.highway.construction.primary" = "Đường đang thi công"; +"type.highway.construction.primary_link" = "Đường đang thi công"; +"type.highway.construction.secondary" = "Đường đang thi công"; +"type.highway.construction.secondary_link" = "Đường đang thi công"; +"type.highway.construction.tertiary" = "Đường đang thi công"; +"type.highway.construction.tertiary_link" = "Đường đang thi công"; +"type.highway.construction.residential" = "Đường đang thi công"; +"type.highway.construction.unclassified" = "Đường đang thi công"; +"type.highway.construction.service" = "Đường đang thi công"; +"type.highway.construction.living_street" = "Đường đang thi công"; +"type.highway.construction.road" = "Đường đang thi công"; +"type.highway.construction.track" = "Đường đang thi công"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Cầu"; "type.highway.cycleway.permissive" = "Đường dành cho xe đạp"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings index 28f2ea8fc..41dd629ba 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公交站"; "type.highway.construction" = "在建道路"; +"type.highway.construction.motorway" = "在建道路"; +"type.highway.construction.motorway_link" = "在建道路"; +"type.highway.construction.trunk" = "在建道路"; +"type.highway.construction.trunk_link" = "在建道路"; +"type.highway.construction.primary" = "在建道路"; +"type.highway.construction.primary_link" = "在建道路"; +"type.highway.construction.secondary" = "在建道路"; +"type.highway.construction.secondary_link" = "在建道路"; +"type.highway.construction.tertiary" = "在建道路"; +"type.highway.construction.tertiary_link" = "在建道路"; +"type.highway.construction.residential" = "在建道路"; +"type.highway.construction.unclassified" = "在建道路"; +"type.highway.construction.service" = "在建道路"; +"type.highway.construction.living_street" = "在建道路"; +"type.highway.construction.road" = "在建道路"; +"type.highway.construction.track" = "在建道路"; "type.highway.cycleway" = "自行车道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "桥"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings index 498107279..5a8818012 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings @@ -452,6 +452,22 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公車站"; "type.highway.construction" = "施工中道路"; +"type.highway.construction.motorway" = "施工中道路"; +"type.highway.construction.motorway_link" = "施工中道路"; +"type.highway.construction.trunk" = "施工中道路"; +"type.highway.construction.trunk_link" = "施工中道路"; +"type.highway.construction.primary" = "施工中道路"; +"type.highway.construction.primary_link" = "施工中道路"; +"type.highway.construction.secondary" = "施工中道路"; +"type.highway.construction.secondary_link" = "施工中道路"; +"type.highway.construction.tertiary" = "施工中道路"; +"type.highway.construction.tertiary_link" = "施工中道路"; +"type.highway.construction.residential" = "施工中道路"; +"type.highway.construction.unclassified" = "施工中道路"; +"type.highway.construction.service" = "施工中道路"; +"type.highway.construction.living_street" = "施工中道路"; +"type.highway.construction.road" = "施工中道路"; +"type.highway.construction.track" = "施工中道路"; "type.highway.cycleway" = "單車道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "橋樑"; From cb546338933c647b288e7a252dffff229e735aac Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 15 Jun 2026 12:07:37 +0700 Subject: [PATCH 39/40] Revert "[core] Display only traffic lights en route and same direction" This reverts commit c5e2718715997c4e1bd18ab67b7af43c6a6dd03c. --- libs/map/routing_manager.cpp | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp index 059f69911..8cd11745d 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -624,45 +624,28 @@ void RoutingManager::CollectFeaturesAlongRoute(vector const & segm feature::TypesHolder types(ft); // Does this feature match the type we passed in? - if (!types.HasWithSubclass(featureType)) + if (!types.Has(featureType)) return; m2::PointD const pt = feature::GetCenter(ft); - // Find the closest route segment and record whether the route - // travels in the forward direction along the underlying road way. double minSqDist = numeric_limits::max(); - bool closestSegIsForward = true; m2::PointD prev = startPt; for (auto const & s : segments) { m2::PointD const curr = s.GetJunction().GetPoint(); m2::ParametrizedSegment seg(prev, curr); - double const d = seg.SquaredDistanceToPoint(pt); - if (d < minSqDist) - { - minSqDist = d; - closestSegIsForward = s.GetSegment().IsForward(); - } + minSqDist = min(minSqDist, seg.SquaredDistanceToPoint(pt)); + + // Performance break if (minSqDist < kSearchRadiusMercator * kSearchRadiusMercator) break; prev = curr; } - if (minSqDist >= kSearchRadiusMercator * kSearchRadiusMercator) - return; - - // If the signal has a direction subtype, only include it when it - // faces the direction the route is travelling on that road. - static uint32_t const kForwardType = classif().GetTypeByPath({"highway", "traffic_signals", "forward"}); - static uint32_t const kBackwardType = classif().GetTypeByPath({"highway", "traffic_signals", "backward"}); - if (types.Has(kForwardType) && !closestSegIsForward) - return; - if (types.Has(kBackwardType) && closestSegIsForward) - return; - - outFeatures.emplace_back(pt, ft.GetID()); + if (minSqDist < kSearchRadiusMercator * kSearchRadiusMercator) + outFeatures.emplace_back(pt, ft.GetID()); }, queryRect, scales::GetUpperScale()); }; From f8eb59238afc7f984cd62f09d85040c17ac7519e Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 15 Jun 2026 12:08:48 +0700 Subject: [PATCH 40/40] Revert "[generator] Add traffic lights direction" This reverts commit 1cd747d808f07103586c10170487783b1fe3afee. --- generator/osm2type.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/generator/osm2type.cpp b/generator/osm2type.cpp index c49233459..c8db4956d 100644 --- a/generator/osm2type.cpp +++ b/generator/osm2type.cpp @@ -280,8 +280,6 @@ class CachedTypes SmallChargingStation, MotorcarChargingStation, CarlessChargingStation, - TrafficSignalForward, - TrafficSignalBackward, Count }; @@ -327,8 +325,6 @@ class CachedTypes {SmallChargingStation, {"amenity", "charging_station", "small"}}, {MotorcarChargingStation, {"amenity", "charging_station", "motorcar"}}, {CarlessChargingStation, {"amenity", "charging_station", "carless"}}, - {TrafficSignalForward, {"highway", "traffic_signals", "forward"}}, - {TrafficSignalBackward, {"highway", "traffic_signals", "backward"}}, }; m_types.resize(static_cast(Count)); @@ -1393,17 +1389,6 @@ void PostprocessElement(OsmElement * p, FeatureBuilderParams & params) params.PopExactType(smallChargingStation); } } - - uint32_t const trafficSignalBase = classif().GetTypeByPath({"highway", "traffic_signals"}); - if (params.IsTypeExist(trafficSignalBase)) - { - TagProcessor(p).ApplyRules({ - {"traffic_signals:direction", "forward", - [&] { params.AddType(types.Get(CachedTypes::TrafficSignalForward)); }}, - {"traffic_signals:direction", "backward", - [&] { params.AddType(types.Get(CachedTypes::TrafficSignalBackward)); }}, - }); - } } } // namespace