Compare commits

...

16 Commits

Author SHA1 Message Date
Dobri Dabar
4691de7a66 [desktop] Display type names
Signed-off-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
Co-authored-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
Co-committed-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
2025-12-15 19:36:29 +01:00
Jean-Baptiste
3c082b0629 [android] Rework shape of zoom buttons
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-15 18:59:54 +01:00
Jean-Baptiste
a468bd3fab [docs] Update docs and funding config
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-15 13:09:27 +01:00
x7z4w
fc87316184 [search] Fix apartment blocks search
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-14 20:52:17 +00:00
map-per
298518ae72 [Android] Fix crash when selectedCountry is null
Signed-off-by: map-per <map-per@gmx.de>
2025-12-13 22:58:25 +01:00
Jean-Baptiste
3bad6d25f0 [android] Remove usage of attr fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 19:28:28 +01:00
Jean-Baptiste
d36361d669 [android] Fix aligment of items in AppBar
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 17:08:35 +01:00
Jean-Baptiste
688e20b1a6 [android] Revert M3 changes about main fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 10:39:47 +01:00
Jean-Baptiste
85462161b2 [android] Fix theme not apply correctly
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-12 21:11:04 +01:00
Henry Sternberg
b929823f6b [routing] Avoid using parking aisles
Signed-off-by: Henry Sternberg <henry@bluelightmaps.com>
Co-authored-by: Henry Sternberg <henry@bluelightmaps.com>
Co-committed-by: Henry Sternberg <henry@bluelightmaps.com>
2025-12-12 15:37:21 +01:00
Jean-Baptiste
22dd799585 [android] Remove hardcoded MaterialComponents fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 20:33:24 +01:00
Jean-Baptiste
6864d101e2 [android] Migrate text appearance to Material 3
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 19:36:54 +01:00
Jean-Baptiste
4bfb62b373 [android] Fix height of all day view
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 16:22:22 +01:00
map-per
f1cf844986 [editor] Check OSM max char length in value validation
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 09:09:47 +01:00
map-per
f20c3bf50c [Android] Add info about outdated maps and update button to place page
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:11:44 +01:00
map-per
e7cc602904 [editor] Remove unused code from the old OSM editor
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:03:31 +01:00
60 changed files with 691 additions and 788 deletions

View File

@@ -1,69 +1,55 @@
# All non-assigned.
* @organicmaps/mergers
# Visual design.
/android/app/src/main/res/drawable*/ @organicmaps/design
/android/app/src/main/res/font/ @organicmaps/design
/android/app/src/main/res/mipmap*/ @organicmaps/design
/data/*.ttf @organicmaps/design
/data/resources-svg/ @organicmaps/design
/data/search-icons/ @organicmaps/design
/iphone/Maps/Images.xcassets/ @organicmaps/design
/android/app/src/main/res/drawable*/ @comaps/design
/android/app/src/main/res/font/ @comaps/design
/android/app/src/main/res/mipmap*/ @comaps/design
/data/*.ttf @comaps/design
/data/resources-svg/ @comaps/design
/data/search-icons/ @comaps/design
/iphone/Maps/Images.xcassets/ @comaps/design
# Android.
/android/ @organicmaps/android
/android/app/src/main/java/app/organicmaps/car/ @organicmaps/android-auto
/docs/ANDROID_LOCATION_TEST.md @organicmaps/android
/docs/JAVA_STYLE.md @organicmaps/android
/android/ @comaps/android
/android/app/src/main/java/app/comaps/car/ @comaps/android-auto
/docs/ANDROID_LOCATION_TEST.md @comaps/android
/docs/JAVA_STYLE.md @comaps/android
# no owner for translation changes
/android/app/src/main/res/values*/strings.xml
# iOS.
/iphone/ @organicmaps/ios
/xcode/ @organicmaps/ios
/docs/OBJC_STYLE.md @organicmaps/ios
/iphone/ @comaps/ios
/xcode/ @comaps/ios
/docs/OBJC_STYLE.md @comaps/ios
# no owner for translation changes
/iphone/plist.txt
/iphone/Maps/LocalizedStrings/
# Qt
/qt/ @organicmaps/qt
# Rendering
/drape/ @organicmaps/rendering
/drape_frontend/ @organicmaps/rendering
# Map Data.
/tools/python/maps_generator/ @organicmaps/data
/generator/ @organicmaps/data
/topography_generator/ @organicmaps/data
/data/borders/ @organicmaps/data
/data/conf/isolines/ @organicmaps/data
/docs/SUBWAY_GENERATION.md @organicmaps/data
/docs/MAPS.md @organicmaps/data
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @organicmaps/data
# no owner (changed often to add a new POI)
/generator/generator_tests/osm_type_test.cpp
# Map Styles.
/data/styles/ @organicmaps/styles
/data/types.txt @organicmaps/styles
/data/visibility.txt @organicmaps/styles
/data/mapcss-mapping.csv @organicmaps/styles
/data/replaced_tags.txt @organicmaps/styles
/data/classificator.txt @organicmaps/styles
/data/drules_* @organicmaps/styles
/data/styles/ @comaps/styles
/data/types.txt @comaps/styles
/data/visibility.txt @comaps/styles
/data/mapcss-mapping.csv @comaps/styles
/data/replaced_tags.txt @comaps/styles
/data/classificator.txt @comaps/styles
/data/drules_* @comaps/styles
/docs/STYLES.md
/tools/kothic/ @organicmaps/styles
/tools/kothic/ @comaps/styles
# DevOps.
/.forgejo/workflows @organicmaps/devops
/android/*gradle* @organicmaps/devops
/docs/RELEASE_MANAGEMENT.md @organicmaps/devops
/xcode/fastlane/ @organicmaps/devops
# Growth.
README.md @organicmaps/growth
/.forgejo/FUNDING.yml @organicmaps/growth
/android/app/src/fdroid/play/ @organicmaps/growth
/android/app/src/google/play/ @organicmaps/growth
/iphone/metadata/ @organicmaps/growth
/.forgejo/workflows @comaps/devops
/android/*gradle* @comaps/devops
/docs/RELEASE_MANAGEMENT.md @comaps/devops
/xcode/fastlane/ @comaps/devops
/tools/python/maps_generator/ @comaps/devops
/generator/ @comaps/devops
/topography_generator/ @comaps/devops
/data/borders/ @comaps/devops
/data/conf/isolines/ @comaps/devops
/docs/SUBWAY_GENERATION.md @comaps/devops
/docs/MAPS.md @comaps/devops
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @comaps/devops
# Legal.
LEGAL @organicmaps/legal
LICENSE @organicmaps/legal
NOTICE @organicmaps/legal
CONTRIBUTORS @organicmaps/legal
/docs/CODE_OF_CONDUCT.md @organicmaps/legal
/docs/DCO.md @organicmaps/legal
/docs/GOVERNANCE.md @organicmaps/legal
LEGAL @comaps/admins
LICENSE @comaps/admins
NOTICE @comaps/admins
CONTRIBUTORS @comaps/admins
/docs/CODE_OF_CONDUCT.md @comaps/admins
/docs/DCO.md @comaps/admins
/docs/GOVERNANCE.md @comaps/admins

View File

@@ -1 +1,3 @@
open_collective: comaps
liberapay: comaps
custom: ["https://comaps.app/donate/"]

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
open_collective: comaps
liberapay: comaps
custom: ["https://comaps.app/donate/"]

2
.gitignore vendored
View File

@@ -155,6 +155,8 @@ android/huawei-appgallery.json
android/res/xml/network_security_config.xml
./server/
iphone/Maps/app.omaps/
# Generated file
libs/indexer/localized_types_map.cpp
*.li

View File

@@ -90,8 +90,7 @@ public enum ThemeSwitcher
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleDark;
@@ -104,8 +103,7 @@ public enum ThemeSwitcher
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleClear;

View File

@@ -154,6 +154,7 @@ public class PlacePageView extends Fragment
private MaterialTextView mTvLastChecked;
private View mEditPlace;
private View mAddPlace;
private View mMapTooOld;
private View mEditTopSpace;
private ShapeableImageView mColorIcon;
private MaterialTextView mTvCategory;
@@ -318,6 +319,7 @@ public class PlacePageView extends Fragment
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
mAddPlace = mFrame.findViewById(R.id.ll__place_add);
mMapTooOld = mFrame.findViewById(R.id.cv__map_too_old);
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
latlon.setOnLongClickListener(this);
address.setOnLongClickListener(this);
@@ -684,39 +686,73 @@ public class PlacePageView extends Fragment
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
{
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld);
}
else
{
UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace);
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
UiUtils.hide(mMapTooOld);
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
mTvEditPlace.setOnClickListener(this);
mTvAddPlace.setOnClickListener(this);
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
final int editTextButtonColor =
Editor.nativeShouldEnableEditPlace()
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace();
if (shouldEnableEditPlace)
{
mTvEditPlace.setOnClickListener(this);
mTvAddPlace.setOnClickListener(this);
}
else
{
mTvEditPlace.setOnClickListener((v) -> {
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
});
mTvAddPlace.setOnClickListener((v) -> {
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
});
String countryId = MapManager.nativeGetSelectedCountry();
if (countryId != null)
{
CountryItem map = CountryItem.fill(countryId);
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|| map.status == CountryItem.STATUS_FAILED)
{
UiUtils.show(mMapTooOld);
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
UiUtils.showIf(canUpdateMap, mTvUpdateTooOldMap);
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
if (canUpdateMap)
{
mapTooOldDescription.setText(R.string.place_page_map_too_old_description);
mTvUpdateTooOldMap.setOnClickListener((v) -> {
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
UiUtils.hide(mMapTooOld);
});
}
else
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
}
}
}
final int editButtonColor =
shouldEnableEditPlace
? ContextCompat.getColor(
getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
final ColorStateList editStrokeButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)),
ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled)
});
mTvEditPlace.setTextColor(editTextButtonColor);
mTvAddPlace.setTextColor(editTextButtonColor);
mTvEditPlace.setStrokeColor(editStrokeButtonColor);
mTvAddPlace.setStrokeColor(editStrokeButtonColor);
mTvEditPlace.setTextColor(editButtonColor);
mTvAddPlace.setTextColor(editButtonColor);
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
UiUtils.showIf(
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
mEditTopSpace);

View File

@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M240,520q-17,0 -28.5,-11.5T200,480q0,-17 11.5,-28.5T240,440h480q17,0 28.5,11.5T760,480q0,17 -11.5,28.5T720,520L240,520Z"
android:pathData="M19,13H5v-2h14v2z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M440,520L240,520q-17,0 -28.5,-11.5T200,480q0,-17 11.5,-28.5T240,440h200v-200q0,-17 11.5,-28.5T480,200q17,0 28.5,11.5T520,240v200h200q17,0 28.5,11.5T760,480q0,17 -11.5,28.5T720,520L520,520v200q0,17 -11.5,28.5T480,760q-17,0 -28.5,-11.5T440,720v-200Z"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
android:fillColor="@android:color/white"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M453,680L513,680L513,440L453,440L453,680ZM479.98,366Q494,366 503.5,356.8Q513,347.6 513,334Q513,319.55 503.52,309.78Q494.04,300 480.02,300Q466,300 456.5,309.78Q447,319.55 447,334Q447,347.6 456.48,356.8Q465.96,366 479.98,366ZM480.27,880Q397.53,880 324.77,848.5Q252,817 197.5,762.5Q143,708 111.5,635.16Q80,562.32 80,479.5Q80,396.68 111.5,323.84Q143,251 197.5,197Q252,143 324.84,111.5Q397.68,80 480.5,80Q563.32,80 636.16,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,479.73Q880,562.47 848.5,635.23Q817,708 763,762.32Q709,816.63 636,848.32Q563,880 480.27,880ZM480.5,820Q622,820 721,720.5Q820,621 820,479.5Q820,338 721.19,239Q622.38,140 480,140Q339,140 239.5,238.81Q140,337.62 140,480Q140,621 239.5,720.5Q339,820 480.5,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -26,7 +26,7 @@
app:tint="?colorSecondary"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/head_message"
style="?fontHeadline6"
style="@style/TextAppearance.Headline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base"
@@ -36,7 +36,7 @@
android:text="@string/download_map_title" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/download_message"
style="?fontBody2"
style="@style/TextAppearance.Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base"

View File

@@ -28,7 +28,7 @@
android:layout_marginBottom="8dp"
android:text="@string/aa_connected_title"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textAppearance="@style/TextAppearance.Headline4"
android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView
@@ -36,7 +36,7 @@
android:layout_height="wrap_content"
android:text="@string/car_used_on_the_car_screen"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
android:textAppearance="@style/TextAppearance.Body1" />
</LinearLayout>
<com.google.android.material.button.MaterialButton

View File

@@ -20,7 +20,7 @@
android:layout_marginStart="32dp"
android:text="@string/aa_request_permission_activity_text"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textAppearance="@style/TextAppearance.Headline4"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton

View File

@@ -22,7 +22,7 @@
android:layout_marginStart="@dimen/margin_base"
android:maxLines="3"
android:ellipsize="end"
android:textAppearance="?fontHeadline6"
android:textAppearance="@style/TextAppearance.Headline6"
tools:text="Title" />
<androidx.recyclerview.widget.RecyclerView

View File

@@ -12,7 +12,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base"
android:text="@string/download_country_ask"
android:textAppearance="?fontBody2"
android:textAppearance="@style/TextAppearance.Body2"
android:visibility="gone"
tools:visibility="visible"/>
<com.google.android.material.progressindicator.LinearProgressIndicator

View File

@@ -14,7 +14,6 @@
android:textAppearance="@style/MwmTextAppearance.Title" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/et__input_layout"
style="?fontBody1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="false">

View File

@@ -27,7 +27,7 @@
android:layout_marginTop="@dimen/margin_base">
<RadioButton
style="?fontSubtitle1"
style="@style/TextAppearance.Subtitle1"
android:id="@+id/sort_by_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
app:buttonTint="@null"/>
<RadioButton
style="?fontSubtitle1"
style="@style/TextAppearance.Subtitle1"
android:id="@+id/sort_by_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -47,7 +47,7 @@
app:buttonTint="@null"/>
<RadioButton
style="?fontSubtitle1"
style="@style/TextAppearance.Subtitle1"
android:id="@+id/sort_by_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -57,7 +57,7 @@
app:buttonTint="@null"/>
<RadioButton
style="?fontSubtitle1"
style="@style/TextAppearance.Subtitle1"
android:id="@+id/sort_by_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -67,7 +67,7 @@
app:buttonTint="@null"/>
<RadioButton
style="?fontSubtitle1"
style="@style/TextAppearance.Subtitle1"
android:id="@+id/sort_by_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -17,7 +17,6 @@
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/edit_bookmark_name_input"
style="?fontBody1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name"
@@ -88,7 +87,6 @@
tools:src="@drawable/ic_bookmark_none" />
</RelativeLayout>
<com.google.android.material.textfield.TextInputLayout
style="?fontBody1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_half"

View File

@@ -17,7 +17,7 @@
android:layout_marginEnd="@dimen/margin_base"
android:paddingTop="@dimen/margin_half"
android:paddingBottom="@dimen/margin_base"
android:textAppearance="?fontHeadline6"
android:textAppearance="@style/TextAppearance.Headline6"
tools:text="Xindian Shitoushan Trail" />
<include
layout="@layout/item_divider"

View File

@@ -16,7 +16,7 @@
android:layout_marginStart="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_base"
android:text="@string/layers_title"
android:textAppearance="?fontHeadline6"
android:textAppearance="@style/TextAppearance.Headline6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton

View File

@@ -30,7 +30,7 @@
android:ellipsize="middle"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="?fontBody1"
android:textAppearance="@style/TextAppearance.Body1"
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
<LinearLayout
android:id="@+id/bottom_line_container"

View File

@@ -25,7 +25,7 @@
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
android:ellipsize="middle"
android:singleLine="true"
android:textAppearance="?fontBody1"
android:textAppearance="@style/TextAppearance.Body1"
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/size"
@@ -37,7 +37,7 @@
android:ellipsize="end"
android:singleLine="true"
android:textColor="?android:textColorSecondary"
style="?fontBody2"
style="@style/TextAppearance.Body2"
tools:text="42000000" />
</LinearLayout>
</LinearLayout>

View File

@@ -12,7 +12,7 @@
android:paddingBottom="@dimen/margin_half_plus">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_message"
style="?fontHeadline6"
style="@style/TextAppearance.Headline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"

View File

@@ -2,7 +2,7 @@
<com.google.android.material.textview.MaterialTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
style="?fontHeadline6"
style="@style/TextAppearance.Headline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?windowBackgroundForced"

View File

@@ -14,7 +14,7 @@
android:gravity="center"
android:maxWidth="500dp"
android:text="@string/editor_category_unsuitable_title"
android:textAppearance="@style/TextAppearance.MdcTypographyStyles.Headline6"
android:textAppearance="@style/TextAppearance.Headline6"
android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/editor_category_unsuitable_text"

View File

@@ -10,7 +10,6 @@
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout"
style="?fontBody1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"

View File

@@ -147,12 +147,12 @@
<LinearLayout
android:id="@+id/allday"
android:layout_width="match_parent"
android:layout_height="@dimen/editor_height_allday"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?clickableBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/margin_half_plus">
android:padding="@dimen/margin_half">
<com.google.android.material.textview.MaterialTextView
android:layout_width="0dp"
@@ -164,7 +164,8 @@
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw__allday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_quarter"/>
</LinearLayout>

View File

@@ -29,7 +29,7 @@
android:layout_marginTop="@dimen/margin_base"
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
android:singleLine="true"
android:textAppearance="?fontBody1"
android:textAppearance="@style/TextAppearance.Body1"
tools:text="@string/tracks_title" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv__bookmark_distance"
@@ -39,7 +39,7 @@
android:layout_marginBottom="@dimen/margin_half_plus"
android:singleLine="true"
android:textColor="?android:textColorSecondary"
android:textAppearance="?fontBody2"
android:textAppearance="@style/TextAppearance.Body2"
tools:text="@string/by_distance" />
</LinearLayout>
<com.google.android.material.imageview.ShapeableImageView

View File

@@ -11,14 +11,14 @@
style="@style/MwmWidget.M3.FAB.MapButton.Zoom"
android:tint="?iconTint"
app:srcCompat="@drawable/ic_plus"
app:shapeAppearance="@style/Widget.MaterialComponents.FloatingActionButton"
android:layout_marginBottom="@dimen/margin_half"
app:shapeAppearanceOverlay="@style/ShapeAppearance.MapButton.Zoom.Minus"
android:layout_marginBottom="@dimen/margin_eighth"
android:contentDescription="@string/zoom_in"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/nav_zoom_out"
style="@style/MwmWidget.M3.FAB.MapButton.Zoom"
android:tint="?iconTint"
app:srcCompat="@drawable/ic_minus"
app:shapeAppearance="@style/Widget.MaterialComponents.FloatingActionButton"
app:shapeAppearanceOverlay="@style/ShapeAppearance.MapButton.Zoom.Plus"
android:contentDescription="@string/zoom_out"/>
</LinearLayout>

View File

@@ -82,13 +82,15 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/place_page_last_checked"
style="?fontCaption"
style="@style/TextAppearance.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_half"
android:paddingHorizontal="@dimen/margin_base"
tools:text="Existence confirmed 1 month ago"/>
<include android:visibility="gone" layout="@layout/place_page_map_too_old"/>
<include android:visibility="gone" layout="@layout/place_page_editor"/>
<include android:visibility="gone" layout="@layout/place_page_add"/>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cv__map_too_old"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_half"
app:strokeWidth="1dp"
app:strokeColor="@color/base_accent"
app:cardBackgroundColor="@color/bg_cards">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/margin_base"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_base"
app:srcCompat="@drawable/info_icon"
app:tint="@color/base_accent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_title"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textStyle="bold"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv__map_too_old_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_description"
android:fontFamily="@string/robotoRegular"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mb__update_too_old_map"
style="@style/MwmWidget.M3.Button.Primary"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="end"
android:text="@string/place_page_update_too_old_map"/>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -65,7 +65,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/oh_check_date"
style="?fontCaption"
style="@style/TextAppearance.Caption"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentEnd="true"

View File

@@ -88,19 +88,19 @@
<item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
<item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
<item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.Body1</item>
<item name="fontBody2">@style/TextAppearance.Body2</item>
<item name="fontButton">@style/TextAppearance.Button</item>
<item name="fontCaption">@style/TextAppearance.Caption</item>
<item name="fontOverline">@style/TextAppearance.Overline</item>
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
<item name="elevationProfilePropIconTint">@color/white_secondary</item>

View File

@@ -119,7 +119,7 @@
<dimen name="nav_menu_landscape_width">360dp</dimen>
<dimen name="nav_frame_padding">@dimen/margin_half</dimen>
<dimen name="zoom_buttons_margin">64dp</dimen>
<dimen name="zoom_buttons_margin">58dp</dimen>
<dimen name="map_buttons_bottom_margin">136dp</dimen>
<dimen name="map_buttons_bottom_max_width">300dp</dimen>
@@ -130,7 +130,6 @@
<!-- Editor -->
<dimen name="editor_height_days">72dp</dimen>
<dimen name="editor_height_closed">72dp</dimen>
<dimen name="editor_height_allday">56dp</dimen>
<dimen name="editor_height_field">64dp</dimen>
<dimen name="editor_margin_left">56dp</dimen>
<dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen>

View File

@@ -573,6 +573,16 @@
<string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string>
<string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string>
<string name="placepage_add_place_button">Add Place to OpenStreetMap</string>
<!-- Title of info shown when the map is older than 3 to 6 months -->
<string name="place_page_map_too_old_title">Map data outdated</string>
<!-- Description of info shown when the map is older than 3 months -->
<string name="place_page_map_too_old_description"> Your current map data is very old, please update the map.</string>
<!-- Description of info shown when the app and the map are older than 6 months -->
<string name="place_page_app_too_old_description"> Your current map data is very old, please update the CoMaps app.</string>
<!-- Button to update map region, part of the map too old info -->
<string name="place_page_update_too_old_map">Update map region</string>
<!-- Toast shown after pressing add place / edit OpenStreetMap when editing is disabled -->
<string name="place_page_too_old_to_edit">OpenStreetMap editing is disabled because the map data is too old.</string>
<string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string>
<string name="osm_note_toast">Note will be sent to OpenStreetMap</string>
<!-- Displayed when saving some edits to the map to warn against publishing personal data -->

View File

@@ -6,11 +6,6 @@
<item name="android:textStyle">normal</item>
</style>
<style name="MwmTextAppearance.Display1">
<item name="android:textSize">@dimen/text_size_display_1</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<style name="MwmTextAppearance.Title">
<item name="android:textSize">@dimen/text_size_title</item>
<item name="android:textColor">?android:textColorPrimary</item>
@@ -190,91 +185,91 @@
<item name="android:textColor">@color/white_secondary</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline1"
parent="TextAppearance.MaterialComponents.Headline1">
<style name="TextAppearance.Headline1"
parent="TextAppearance.Material3.DisplayLarge">
<item name="fontFamily">@string/robotoLight</item>
<item name="android:fontFamily">@string/robotoLight</item>
<item name="android:textSize">96sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline2"
parent="TextAppearance.MaterialComponents.Headline2">
<style name="TextAppearance.Headline2"
parent="TextAppearance.Material3.DisplayLarge">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">60sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline3"
parent="TextAppearance.MaterialComponents.Headline3">
<style name="TextAppearance.Headline3"
parent="TextAppearance.Material3.DisplayMedium">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">48sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline4"
parent="TextAppearance.MaterialComponents.Headline4">
<style name="TextAppearance.Headline4"
parent="TextAppearance.Material3.HeadlineLarge">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">34sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline5"
parent="TextAppearance.MaterialComponents.Headline5">
<style name="TextAppearance.Headline5"
parent="TextAppearance.Material3.HeadlineMedium">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">24sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Headline6"
parent="TextAppearance.MaterialComponents.Headline6">
<style name="TextAppearance.Headline6"
parent="TextAppearance.Material3.HeadlineSmall">
<item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Subtitle1"
parent="TextAppearance.MaterialComponents.Subtitle1">
<style name="TextAppearance.Subtitle1"
parent="TextAppearance.Material3.TitleMedium">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Subtitle2"
parent="TextAppearance.MaterialComponents.Subtitle2">
<style name="TextAppearance.Subtitle2"
parent="TextAppearance.Material3.TitleSmall">
<item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">14sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Body1"
parent="TextAppearance.MaterialComponents.Body1">
<style name="TextAppearance.Body1"
parent="TextAppearance.Material3.BodyLarge">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Body2"
parent="TextAppearance.MaterialComponents.Body2">
<style name="TextAppearance.Body2"
parent="TextAppearance.Material3.BodyMedium">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">14sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Button"
<style name="TextAppearance.Button"
parent="TextAppearance.MaterialComponents.Button">
<item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">14sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Caption"
parent="TextAppearance.MaterialComponents.Caption">
<style name="TextAppearance.Caption"
parent="TextAppearance.Material3.LabelMedium">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">12sp</item>
</style>
<style name="TextAppearance.MdcTypographyStyles.Overline"
<style name="TextAppearance.Overline"
parent="TextAppearance.MaterialComponents.Overline">
<item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item>

View File

@@ -139,12 +139,13 @@
<item name="cornerSize">50%</item>
</style>
<style name="MwmWidget.ToolbarStyle" parent="ThemeOverlay.Material3.Dark.ActionBar">
<style name="MwmWidget.ToolbarStyle" parent="Widget.Material3.Toolbar">
<item name="android:background">?colorPrimary</item>
<item name="android:displayOptions">homeAsUp|showTitle</item>
<item name="contentInsetStart">0dp</item>
<item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
<item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
<item name="buttonGravity">center_vertical</item>
</style>
<style name="MwmWidget.ToolbarStyle.Light">
@@ -424,4 +425,17 @@
<item name="android:maxWidth">@dimen/map_buttons_bottom_max_width</item>
<item name="android:padding">@dimen/nav_frame_padding</item>
</style>
<style name="ShapeAppearance.MapButton.Zoom.Plus" parent="">
<item name="cornerSizeTopLeft">10%</item>
<item name="cornerSizeTopRight">10%</item>
<item name="cornerSizeBottomRight">50%</item>
<item name="cornerSizeBottomLeft">50%</item>
</style>
<style name="ShapeAppearance.MapButton.Zoom.Minus" parent="">
<item name="cornerSizeBottomLeft">10%</item>
<item name="cornerSizeBottomRight">10%</item>
<item name="cornerSizeTopLeft">50%</item>
<item name="cornerSizeTopRight">50%</item>
</style>
</resources>

View File

@@ -93,19 +93,19 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.Body1</item>
<item name="fontBody2">@style/TextAppearance.Body2</item>
<item name="fontButton">@style/TextAppearance.Button</item>
<item name="fontCaption">@style/TextAppearance.Caption</item>
<item name="fontOverline">@style/TextAppearance.Overline</item>
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
<item name="elevationProfilePropIconTint">@color/black_secondary</item>

View File

@@ -116,6 +116,9 @@ fi
echo "Generating search categories / synonyms..."
./tools/unix/generate_categories.sh
echo "Generating Desktop UI strings..."
./tools/unix/generate_desktop_ui_strings.sh
if [ -z "$SKIP_GENERATE_SYMBOLS" ]; then
if Diff data/symbols_hash data/styles/*/*/symbols/* || [ ! -z "$SYMBOLS_NOT_GENERATED" ]; then
echo "Generating symbols..."

View File

@@ -186,7 +186,7 @@
"place-state": "Щат|провинция",
"place-region": "Район|Регион",
"place-island|place-islet": "Остров",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|окръг|квартал|Предградие",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|окръг|квартал|Предградие|Жилищен комплекс|ж.к.|ЖК",
"place-hamlet": "Село",
"place-village": "Село",
"place-locality": "местност|регион|Място",

View File

@@ -199,7 +199,7 @@
"place-state": "Штат|провинция",
"place-region": "Район",
"place-island|place-islet": "Остров",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|квартал|Жилая зона",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|квартал|Жилая зона|Жилой комплекс|ж.к.|ЖК",
"place-hamlet": "Посёлок|деревня",
"place-village": "Деревня|поселок",
"place-locality": "Местность|регион",

View File

@@ -196,7 +196,7 @@
"place-state": "Штат|провінція|регіон",
"place-region": "Район|область",
"place-island|place-islet": "Острів",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|мікрорайон|квартал|округа|Сусідство",
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|мікрорайон|квартал|округа|Сусідство|Житловий комплекс|ж.к.|ЖК",
"place-hamlet": "Поселення|селище|село",
"place-village": "Село|хутір|поселення",
"place-locality": "Місцевість|регіон|Місце",

View File

@@ -38,7 +38,7 @@ See [translations instructions](TRANSLATIONS.md) for details.
CoMaps has a strong focus on easy to use UI and smooth user experience. Feel free to join UI/UX discussions in relevant issues. Mockups are very welcome!
If you're into graphic design then CoMaps needs good, clear and free-to-use icons for hundreds of map features / POIs.
Check CoMaps' [design principles](https://codeberg.org/comaps/comaps/wiki/Design-Principles). Post your icons onto relevant issues or take a next step and [integrate them](STYLES.md) yourself.
Check CoMaps' [design principles](https://codeberg.org/comaps/comaps/wiki/Design-Principles) and [color scheme](https://codeberg.org/comaps/Governance/src/branch/main/assets/comaps-color-scheme.png). Post your icons onto relevant issues or take a next step and [integrate them](STYLES.md) yourself.
Check the [map styling instructions](STYLES.md) and work on adding new map features and other open map styles issues.

View File

@@ -26,6 +26,8 @@ The project consists of multiple components, each with its own translation files
Components without links haven't been integrated into Weblate and must be translated directly via [Codeberg Pull Requests](CONTRIBUTING.md).
TTS translations can be improved with [the TTS locale-viewer](https://comaps.codeberg.page/locale-viewer/).
## Translating
### Workflow

View File

@@ -134,6 +134,8 @@ TagMapping const kDefaultTagMapping = {
{OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private},
{OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private},
{OsmElement::Tag("locked", "yes"), RoadAccess::Type::Locked},
{OsmElement::Tag("service", "parking_aisle"), RoadAccess::Type::Private},
{OsmElement::Tag("amenity", "parking_entrance"), RoadAccess::Type::Private},
};
// Removed secondary, tertiary from car list. Example https://www.openstreetmap.org/node/8169922700

View File

@@ -243,14 +243,11 @@ void ChangesetWrapper::Modify(editor::XMLFeature node)
void ChangesetWrapper::AddChangesetTag(std::string key, std::string value)
{
value = strings::EscapeForXML(value);
// Truncate to 254 characters as OSM has a length limit of 255
if (strings::Truncate(value, kMaximumOsmChars))
value += "";
//OSM has a length limit of 255 characters
if (value.length() > kMaximumOsmChars)
{
LOG(LWARNING, ("value is too long for OSM 255 char limit: ", value));
value = value.substr(0, kMaximumOsmChars - 3).append("...");
}
value = strings::EscapeForXML(value);
m_changesetComments.insert_or_assign(std::move(key), std::move(value));
}

View File

@@ -143,6 +143,7 @@ void CreateCafeAtPoint(m2::PointD const & point, MwmSet::MwmId const & mwmId, os
editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo);
emo.SetHouseNumber("12");
emo.LogDiffInJournal({});
TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ());
}
@@ -157,6 +158,7 @@ void GenerateUploadedFeature(MwmSet::MwmId const & mwmId, osm::EditableMapObject
pugi::xml_node created = mwmNode.append_child("create");
editor::XMLFeature xf = editor::ToXML(emo, true);
xf.SetEditJournal(emo.GetJournal());
xf.SetMWMFeatureIndex(emo.GetID().m_index);
xf.SetModificationTime(time(nullptr));
xf.SetUploadTime(time(nullptr));
@@ -870,7 +872,7 @@ void EditorTest::CreateNoteTest()
};
// Should match a piece of text in the editor note.
constexpr char const * kPlaceDoesNotExistMessage = "The place has gone or never existed";
constexpr char const * kPlaceDoesNotExistMessage = "This place does not exist:";
ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft)
{

View File

@@ -115,6 +115,42 @@ UNIT_TEST(XMLFeature_UintLang)
TEST_EQUAL(f2.GetName("int_name"), "Gorky Park", ());
}
UNIT_TEST(XMLFeature_SetOSMTagsForType)
{
XMLFeature restaurantFeature(XMLFeature::Type::Node);
restaurantFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-restaurant"));
ASSERT(restaurantFeature.HasTag("amenity"), ());
TEST_EQUAL(restaurantFeature.GetTagValue("amenity"), "restaurant", ());
XMLFeature officeFeature(XMLFeature::Type::Node);
officeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("office"));
ASSERT(officeFeature.HasTag("office"), ());
TEST_EQUAL(officeFeature.GetTagValue("office"), "yes", ());
XMLFeature touristOfficeFeature(XMLFeature::Type::Node);
touristOfficeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("tourism-information-office"));
ASSERT(touristOfficeFeature.HasTag("tourism"), ());
TEST_EQUAL(touristOfficeFeature.GetTagValue("tourism"), "information", ());
XMLFeature addressFeature(XMLFeature::Type::Node);
addressFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("building-address"));
ASSERT(!addressFeature.HasAnyTags(), ("Addresses should not have a category tag"));
XMLFeature recyclingCenterFeature(XMLFeature::Type::Node);
recyclingCenterFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-centre"));
ASSERT(recyclingCenterFeature.HasTag("amenity"), ());
TEST_EQUAL(recyclingCenterFeature.GetTagValue("amenity"), "recycling", ());
ASSERT(recyclingCenterFeature.HasTag("recycling_type"), ());
TEST_EQUAL(recyclingCenterFeature.GetTagValue("recycling_type"), "centre", ());
XMLFeature recyclingContainerFeature(XMLFeature::Type::Node);
recyclingContainerFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-container"));
ASSERT(recyclingContainerFeature.HasTag("amenity"), ());
TEST_EQUAL(recyclingContainerFeature.GetTagValue("amenity"), "recycling", ());
ASSERT(recyclingContainerFeature.HasTag("recycling_type"), ());
TEST_EQUAL(recyclingContainerFeature.GetTagValue("recycling_type"), "container", ());
}
UNIT_TEST(XMLFeature_ToOSMString)
{
XMLFeature feature(XMLFeature::Type::Node);
@@ -267,104 +303,52 @@ UNIT_TEST(XMLFeature_Geometry)
TEST_EQUAL(feature.GetGeometry(), geometry, ());
}
UNIT_TEST(XMLFeature_ApplyPatch)
{
auto const kOsmFeature = R"(<?xml version="1.0"?>
<osm>
<node id="1" lat="1" lon="2" timestamp="2015-11-27T21:13:32Z" version="1">
<tag k="amenity" v="cafe"/>
</node>
</osm>
)";
auto const kPatch = R"(<?xml version="1.0"?>
<node lat="1" lon="2" timestamp="2015-11-27T21:13:32Z">
<tag k="website" v="maps.me"/>
</node>
)";
XMLFeature const baseOsmFeature = XMLFeature::FromOSM(kOsmFeature).front();
{
XMLFeature noAnyTags = baseOsmFeature;
noAnyTags.ApplyPatch(XMLFeature(kPatch));
TEST(noAnyTags.HasKey("website"), ());
}
{
XMLFeature hasMainTag = baseOsmFeature;
hasMainTag.SetTagValue("website", "mapswith.me");
hasMainTag.ApplyPatch(XMLFeature(kPatch));
TEST_EQUAL(hasMainTag.GetTagValue("website"), "maps.me", ());
size_t tagsCount = 0;
hasMainTag.ForEachTag([&tagsCount](std::string const &, std::string const &) { ++tagsCount; });
TEST_EQUAL(2, tagsCount, ("website should be replaced, not duplicated."));
}
{
XMLFeature hasAltTag = baseOsmFeature;
hasAltTag.SetTagValue("contact:website", "mapswith.me");
hasAltTag.ApplyPatch(XMLFeature(kPatch));
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasAltTag.GetTagValue("contact:website"), "maps.me", ());
}
{
XMLFeature hasAltTag = baseOsmFeature;
hasAltTag.SetTagValue("url", "mapswithme.com");
hasAltTag.ApplyPatch(XMLFeature(kPatch));
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasAltTag.GetTagValue("url"), "maps.me", ());
}
{
XMLFeature hasTwoAltTags = baseOsmFeature;
hasTwoAltTags.SetTagValue("contact:website", "mapswith.me");
hasTwoAltTags.SetTagValue("url", "mapswithme.com");
hasTwoAltTags.ApplyPatch(XMLFeature(kPatch));
TEST(!hasTwoAltTags.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasTwoAltTags.GetTagValue("contact:website"), "maps.me", ());
TEST_EQUAL(hasTwoAltTags.GetTagValue("url"), "mapswithme.com", ());
}
{
XMLFeature hasMainAndAltTag = baseOsmFeature;
hasMainAndAltTag.SetTagValue("website", "osmrulezz.com");
hasMainAndAltTag.SetTagValue("url", "mapswithme.com");
hasMainAndAltTag.ApplyPatch(XMLFeature(kPatch));
TEST_EQUAL(hasMainAndAltTag.GetTagValue("website"), "maps.me", ());
TEST_EQUAL(hasMainAndAltTag.GetTagValue("url"), "mapswithme.com", ());
}
}
UNIT_TEST(XMLFeature_FromXMLAndBackToXML)
{
classificator::Load();
std::string const xmlNoTypeStr = R"(<?xml version="1.0"?>
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
<tag k="name" v="Gorki Park" />
<tag k="name:en" v="Gorki Park" />
<tag k="name:ru" v="Парк Горького" />
<tag k="addr:housenumber" v="10" />
<tag k="leisure" v="park" />
<tag k="name" v="Gorki Park" />
<tag k="name:en" v="Gorki Park" />
<tag k="name:ru" v="Парк Горького" />
<tag k="addr:housenumber" v="10" />
<journal version="1.0">
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
<data key="name:en" old_value="" new_value="Gorki Park" />
</entry>
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
<data key="name:ru" old_value="xxx" new_value="Парк Горького" />
</entry>
</journal>
<journalHistory version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="leisure-park" geomType="Point" lat="55.7978998" lon="37.474528" />
</entry>
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
<data key="addr:housenumber" old_value="43" new_value="10" />
</entry>
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
<data key="name" old_value="" new_value="Gorki Park" />
</entry>
</journalHistory>
</node>
)";
char const kTimestamp[] = "2015-11-27T21:13:32Z";
editor::XMLFeature xmlNoType(xmlNoTypeStr);
editor::XMLFeature xmlWithType = xmlNoType;
xmlWithType.SetTagValue("amenity", "atm");
editor::XMLFeature xmlFeature(xmlNoTypeStr);
osm::EditableMapObject emo;
editor::FromXML(xmlWithType, emo);
auto fromFtWithType = editor::ToXML(emo, true);
fromFtWithType.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(fromFtWithType, xmlWithType, ());
osm::EditJournal journal = xmlFeature.GetEditJournal();
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto fromFtWithoutType = editor::ToXML(emo, false);
fromFtWithoutType.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(fromFtWithoutType, xmlNoType, ());
auto xmlFromMapObject = editor::ToXML(emo, true);
xmlFromMapObject.SetEditJournal(emo.GetJournal());
xmlFromMapObject.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(xmlFromMapObject, xmlFeature, ());
}
UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
@@ -373,8 +357,14 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
{
std::string const recyclingCentreStr = R"(<?xml version="1.0"?>
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
<tag k="amenity" v="recycling" />
<tag k="recycling_type" v="centre" />
<tag k="amenity" v="recycling" />
<tag k="recycling_type" v="centre" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-recycling-centre" geomType="Point" lat="55.8047445" lon="37.5865532" />
</entry>
</journal>
<journalHistory version="1.0" />
</node>
)";
@@ -383,21 +373,30 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
editor::XMLFeature xmlFeature(recyclingCentreStr);
osm::EditableMapObject emo;
editor::FromXML(xmlFeature, emo);
osm::EditJournal journal = xmlFeature.GetEditJournal();
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto const th = emo.GetTypes();
TEST_EQUAL(th.Size(), 1, ());
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ());
auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
convertedFt.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(xmlFeature, convertedFt, ());
}
{
std::string const recyclingContainerStr = R"(<?xml version="1.0"?>
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
<tag k="amenity" v="recycling" />
<tag k="recycling_type" v="container" />
<tag k="amenity" v="recycling" />
<tag k="recycling_type" v="container" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-recycling-container" geomType="Point" lat="55.8047445" lon="37.5865532" />
</entry>
</journal>
<journalHistory version="1.0" />
</node>
)";
@@ -406,13 +405,16 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
editor::XMLFeature xmlFeature(recyclingContainerStr);
osm::EditableMapObject emo;
editor::FromXML(xmlFeature, emo);
osm::EditJournal journal = xmlFeature.GetEditJournal();
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto const th = emo.GetTypes();
TEST_EQUAL(th.Size(), 1, ());
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ());
auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
convertedFt.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(xmlFeature, convertedFt, ());
}
@@ -466,58 +468,43 @@ UNIT_TEST(XMLFeature_Diet)
TEST_EQUAL(ft.GetCuisine(), "", ());
}
UNIT_TEST(XMLFeature_SocialContactsProcessing)
{
{
std::string const nightclubStr = R"(<?xml version="1.0"?>
<node lat="50.4082862" lon="30.5130017" timestamp="2022-02-24T05:07:00Z">
<tag k="amenity" v="nightclub" />
<tag k="name" v="Stereo Plaza" />
<tag k="contact:facebook" v="http://www.facebook.com/pages/Stereo-Plaza/118100041593935" />
<tag k="contact:instagram" v="https://www.instagram.com/p/CSy87IhMhfm/" />
<tag k="contact:line" v="liff.line.me/1645278921-kWRPP32q/?accountId=673watcr" />
</node>
)";
editor::XMLFeature xmlFeature(nightclubStr);
osm::EditableMapObject emo;
editor::FromXML(xmlFeature, emo);
auto convertedFt = editor::ToXML(emo, true);
TEST(convertedFt.HasTag("contact:facebook"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "https://facebook.com/pages/Stereo-Plaza/118100041593935",
());
TEST(convertedFt.HasTag("contact:instagram"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:instagram"), "https://instagram.com/p/CSy87IhMhfm", ());
TEST(convertedFt.HasTag("contact:line"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:line"), "https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr",
());
}
}
UNIT_TEST(XMLFeature_SocialContactsProcessing_clean)
{
{
std::string const nightclubStr = R"(<?xml version="1.0"?>
<node lat="40.82862" lon="20.30017" timestamp="2022-02-24T05:07:00Z">
<tag k="amenity" v="bar" />
<tag k="name" v="Irish Pub" />
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" />
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" />
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" />
<tag k="amenity" v="bar" />
<tag k="name" v="Irish Pub" />
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" />
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" />
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-nightclub" geomType="Point" lat="50.4082862" lon="30.5130017" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:facebook" old_value="" new_value="PierreCardinPeru.oficial" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:instagram" old_value="" new_value="fraback.genusswelt" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:line" old_value="" new_value="015qevdv" />
</entry>
</journal>
<journalHistory version="1.0" />
</node>
)";
editor::XMLFeature xmlFeature(nightclubStr);
osm::EditableMapObject emo;
editor::FromXML(xmlFeature, emo);
osm::EditJournal journal = xmlFeature.GetEditJournal();
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
TEST(convertedFt.HasTag("contact:facebook"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ());

View File

@@ -610,221 +610,101 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
// Don't use new editor for Legacy Objects
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
bool useNewEditor =
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
try
{
if (useNewEditor)
switch (fti.m_status)
{
LOG(LDEBUG, ("New Editor used\n"));
switch (fti.m_status)
{
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created: // fallthrough
case FeatureStatus::Modified:
{
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
switch (fti.m_object.GetEditingLifecycle())
{
case EditingLifecycle::CREATED:
{
// Generate XMLFeature for new object
JournalEntry const & createEntry = journal.front();
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
("First item should have type ObjectCreated"));
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
XMLFeature feature =
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
// Check if place already exists
bool mergeSameLocation = false;
try
{
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
// precision of OSM coordinates (WGS 84), ~= 1 cm
constexpr double tolerance = 0.0000001;
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
{
changeset.AddChangesetTag("info:merged_same_location", "yes");
feature = osmFeature;
mergeSameLocation = true;
}
else
{
changeset.AddChangesetTag("info:feature_close_by", "yes");
}
}
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
{}
catch (ChangesetWrapper::EmptyFeatureException const &)
{}
// Add tags to XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
if (!mergeSameLocation)
changeset.Create(feature);
else
changeset.Modify(feature);
break;
}
case EditingLifecycle::MODIFIED:
{
// Load existing OSM object (Throws, see catch below)
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
// Update tags of XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
changeset.Modify(feature);
break;
}
case EditingLifecycle::IN_SYNC:
{
CHECK(false, ("Object already IN_SYNC should not be here"));
continue;
}
}
break;
}
case FeatureStatus::Deleted:
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
break;
}
}
else // Use old editor
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created: // fallthrough
case FeatureStatus::Modified:
{
// Todo: Remove old editor after transition period
switch (fti.m_status)
{
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created:
{
XMLFeature feature = editor::ToXML(fti.m_object, true);
if (!fti.m_street.empty())
feature.SetTagValue(kAddrStreetTag, fti.m_street);
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
("Linear and area features creation is not supported yet."));
switch (fti.m_object.GetEditingLifecycle())
{
case EditingLifecycle::CREATED:
{
// Generate XMLFeature for new object
JournalEntry const & createEntry = journal.front();
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
("First item should have type ObjectCreated"));
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
XMLFeature feature =
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
// Check if place already exists
bool mergeSameLocation = false;
try
{
auto const center = fti.m_object.GetMercator();
// Throws, see catch below.
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
// If we are here, it means that object already exists at the given point.
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
XMLFeature const osmFeatureCopy = osmFeature;
osmFeature.ApplyPatch(feature);
// Check to avoid uploading duplicates into OSM.
if (osmFeature == osmFeatureCopy)
// precision of OSM coordinates (WGS 84), ~= 1 cm
constexpr double tolerance = 0.0000001;
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
{
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
// Don't delete this local change right now for user to see it in profile.
// It will be automatically deleted by migration code on the next maps update.
changeset.AddChangesetTag("info:merged_same_location", "yes");
feature = osmFeature;
mergeSameLocation = true;
}
else
{
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.AddChangesetTag("info:features_merged", "yes");
changeset.Modify(osmFeature);
changeset.AddChangesetTag("info:feature_close_by", "yes");
}
}
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
{
// Object was never created by anyone else - it's safe to create it.
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Create(feature);
}
{}
catch (ChangesetWrapper::EmptyFeatureException const &)
{
// There is another node nearby, but it should be safe to create a new one.
changeset.AddChangesetTag("info:old_editor", "yes");
{}
// Add tags to XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
if (!mergeSameLocation)
changeset.Create(feature);
}
catch (...)
{
// Pass network or other errors to outside exception handler.
throw;
}
}
break;
case FeatureStatus::Modified:
{
// Do not serialize feature's type to avoid breaking OSM data.
// TODO: Implement correct types matching when we support modifying existing feature types.
XMLFeature feature = editor::ToXML(fti.m_object, false);
if (!fti.m_street.empty())
feature.SetTagValue(kAddrStreetTag, fti.m_street);
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
XMLFeature const osmFeatureCopy = osmFeature;
osmFeature.ApplyPatch(feature);
// Check to avoid uploading duplicates into OSM.
if (osmFeature == osmFeatureCopy)
{
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
// Don't delete this local change right now for user to see it in profile.
// It will be automatically deleted by migration code on the next maps update.
}
else
{
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Modify(osmFeature);
}
}
break;
case FeatureStatus::Deleted:
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
changeset.Modify(feature);
break;
}
case EditingLifecycle::MODIFIED:
{
// Load existing OSM object (Throws, see catch below)
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
// Update tags of XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
changeset.Modify(feature);
break;
}
case EditingLifecycle::IN_SYNC:
{
CHECK(false, ("Object already IN_SYNC should not be here"));
continue;
}
}
break;
}
case FeatureStatus::Deleted:
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
break;
}
uploadInfo.m_uploadStatus = kUploaded;
uploadInfo.m_uploadError.clear();
@@ -907,23 +787,7 @@ void Editor::SaveUploadedInformation(FeatureID const & fid, UploadInfo const & u
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
FeatureTypeInfo & fti) const
{
EditJournal journal = xml.GetEditJournal();
// Do not load Legacy Objects form Journal
auto const & journalHistory = journal.GetJournalHistory();
bool loadFromJournal =
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
LOG(LDEBUG, ("loadFromJournal: ", loadFromJournal));
if (status == FeatureStatus::Created)
{
if (loadFromJournal)
fti.m_object.ApplyEditsFromJournal(journal);
else
editor::FromXML(xml, fti.m_object);
}
else
if (status != FeatureStatus::Created)
{
auto const originalObjectPtr = GetOriginalMapObject(fid);
if (!originalObjectPtr)
@@ -933,13 +797,11 @@ bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, Featu
}
fti.m_object = *originalObjectPtr;
if (loadFromJournal)
fti.m_object.ApplyEditsFromJournal(journal);
else
editor::ApplyPatch(xml, fti.m_object);
}
EditJournal journal = xml.GetEditJournal();
fti.m_object.ApplyEditsFromJournal(journal);
fti.m_object.SetJournal(std::move(journal));
fti.m_object.SetID(fid);
fti.m_street = xml.GetTagValue(kAddrStreetTag);

View File

@@ -178,38 +178,6 @@ string XMLFeature::ToOSMString() const
return ost.str();
}
void XMLFeature::ApplyPatch(XMLFeature const & featureWithChanges)
{
// TODO(mgsergio): Get these alt tags from the config.
base::StringIL const alternativeTags[] = {{"phone", "contact:phone", "contact:mobile", "mobile"},
{"website", "contact:website", "url"},
{"fax", "contact:fax"},
{"email", "contact:email"}};
featureWithChanges.ForEachTag([&alternativeTags, this](string_view k, string_view v)
{
// Avoid duplication for similar alternative osm tags.
for (auto const & alt : alternativeTags)
{
auto it = alt.begin();
ASSERT(it != alt.end(), ());
if (k == *it)
{
for (auto const & tag : alt)
{
// Reuse already existing tag if it's present.
if (HasTag(tag))
{
SetTagValue(tag, v);
return;
}
}
}
}
SetTagValue(k, v);
});
}
m2::PointD XMLFeature::GetMercatorCenter() const
{
return mercator::FromLatLon(GetLatLonFromNode(GetRootNode()));
@@ -670,6 +638,40 @@ void XMLFeature::RemoveTag(string_view key)
GetRootNode().remove_child(tag);
}
void XMLFeature::SetOSMTagsForType(uint32_t type)
{
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
SetTagValue("amenity", "recycling");
SetTagValue("recycling_type", "centre");
}
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
SetTagValue("amenity", "recycling");
SetTagValue("recycling_type", "container");
}
else if (ftypes::IsAddressChecker::Instance()(type))
{
// Addresses don't have a category tag
}
else
{
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
if (++iter)
{
// Main type is stored as "k=amenity v=restaurant"
SetTagValue(k, *iter);
}
else {
// Main type is stored as "k=building v=yes"
SetTagValue(k, kYes);
}
}
}
void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
{
if (value.empty())
@@ -807,26 +809,6 @@ XMLFeature::Type XMLFeature::StringToType(string const & type)
return Type::Unknown;
}
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object)
{
xml.ForEachName([&object](string_view lang, string_view name)
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
string const house = xml.GetHouse();
if (!house.empty())
object.SetHouseNumber(house);
auto const cuisineStr = xml.GetCuisine();
if (!cuisineStr.empty())
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
xml.ForEachTag([&object](string_view k, string v)
{
// Skip result because we iterate via *all* tags here.
(void)object.UpdateMetadataValue(k, std::move(v));
});
}
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
{
bool const isPoint = object.GetGeomType() == feature::GeomType::Point;
@@ -842,6 +824,15 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
toFeature.SetGeometry(begin(triangles), end(triangles));
}
if (serializeType)
{
feature::TypesHolder types = object.GetTypes();
types.SortBySpec();
ASSERT(!types.Empty(), ("Feature does not have a type"));
uint32_t mainType = types.front();
toFeature.SetOSMTagsForType(mainType);
}
object.GetNameMultilang().ForEach([&toFeature](uint8_t const & lang, string_view name)
{ toFeature.SetName(lang, name); });
@@ -856,61 +847,8 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
toFeature.SetCuisine(cuisineStr);
}
if (serializeType)
{
feature::TypesHolder th = object.GetTypes();
// TODO(mgsergio): Use correct sorting instead of SortBySpec based on the config.
th.SortBySpec();
// TODO(mgsergio): Either improve "OSM"-compatible serialization for more complex types,
// or save all our types directly, to restore and reuse them in migration of modified features.
for (uint32_t const type : th)
{
if (ftypes::IsCuisineChecker::Instance()(type))
continue;
if (ftypes::IsRecyclingTypeChecker::Instance()(type))
continue;
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "centre");
continue;
}
if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "container");
continue;
}
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
if (++iter)
{
// First (main) type is always stored as "k=amenity v=restaurant".
// Any other "k=amenity v=atm" is replaced by "k=atm v=yes".
if (toFeature.GetTagValue(k).empty())
toFeature.SetTagValue(k, *iter);
else
toFeature.SetTagValue(*iter, kYes);
}
else
{
// We're editing building, generic craft, shop, office, amenity etc.
// Skip it's serialization.
// TODO(mgsergio): Correcly serialize all types back and forth.
LOG(LDEBUG, ("Skipping type serialization:", k));
}
}
}
object.ForEachMetadataItem([&toFeature](string_view tag, string_view value)
{
if (osm::isSocialContactTag(tag) && value.find('/') != std::string::npos)
toFeature.SetTagValue(tag, osm::socialContactToURL(tag, value));
else
toFeature.SetTagValue(tag, value);
});
@@ -923,105 +861,11 @@ XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD merca
XMLFeature toFeature(XMLFeature::Type::Node);
toFeature.SetCenter(mercator);
// Set Type
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "centre");
}
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "container");
}
else if (ftypes::IsAddressChecker::Instance()(type))
{
// Addresses don't have a category tag
}
else
{
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
toFeature.SetOSMTagsForType(type);
CHECK(++iter, ("Processing Type failed: ", strType));
// Main type is always stored as "k=amenity v=restaurant".
toFeature.SetTagValue(k, *iter);
ASSERT(!(++iter), ("Can not process 3-arity/complex types: ", strType));
}
return toFeature;
}
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object)
{
ASSERT_EQUAL(XMLFeature::Type::Node, xml.GetType(), ("At the moment only new nodes (points) can be created."));
object.SetPointType();
object.SetMercator(xml.GetMercatorCenter());
xml.ForEachName([&object](string_view lang, string_view name)
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
string const house = xml.GetHouse();
if (!house.empty())
object.SetHouseNumber(house);
auto const cuisineStr = xml.GetCuisine();
if (!cuisineStr.empty())
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
feature::TypesHolder types = object.GetTypes();
Classificator const & cl = classif();
xml.ForEachTag([&](string_view k, string_view v)
{
if (object.UpdateMetadataValue(k, string(v)))
return;
// Cuisines are already processed before this loop.
if (k == "cuisine")
return;
// We process recycling_type tag together with "amenity"="recycling" later.
// We currently ignore recycling tag because it's our custom tag and we cannot
// import it to osm directly.
if (k == "recycling" || k == "recycling_type")
return;
uint32_t type = 0;
if (k == "amenity" && v == "recycling" && xml.HasTag("recycling_type"))
{
auto const typeValue = xml.GetTagValue("recycling_type");
if (typeValue == "centre")
type = ftypes::IsRecyclingCentreChecker::Instance().GetType();
else if (typeValue == "container")
type = ftypes::IsRecyclingContainerChecker::Instance().GetType();
}
// Simple heuristics. It works for types converted from osm with short mapcss rules
// where k=v from osm is converted to our k-v type (amenity=restaurant, shop=convenience etc.).
if (type == 0)
type = cl.GetTypeByPathSafe({k, v});
if (type == 0)
type = cl.GetTypeByPathSafe({k}); // building etc.
if (type == 0)
type = cl.GetTypeByPathSafe({"amenity", k}); // atm=yes, toilet=yes etc.
if (type && types.Size() >= feature::kMaxTypesCount)
LOG(LERROR, ("Can't add type:", k, v, ". Types limit exceeded."));
else if (type)
types.Add(type);
else
{
// LOG(LWARNING, ("Can't load/parse type:", k, v));
/// @todo Refactor to make one ForEachTag loop. Now we have separate ForEachName,
/// so we can't log any suspicious tag here ...
}
});
object.SetTypes(types);
return types.Size() > 0;
}
string DebugPrint(XMLFeature const & feature)
{
std::ostringstream ost;

View File

@@ -73,9 +73,6 @@ public:
void Save(std::ostream & ost) const;
std::string ToOSMString() const;
/// Tags from featureWithChanges are applied to this(osm) feature.
void ApplyPatch(XMLFeature const & featureWithChanges);
Type GetType() const;
std::string GetTypeString() const;
@@ -185,6 +182,8 @@ public:
void SetTagValue(std::string_view key, std::string_view value);
void RemoveTag(std::string_view key);
/// Add the OSM tags for a feature type
void SetOSMTagsForType(uint32_t type);
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
void UpdateOSMTag(std::string_view key, std::string_view value);
/// Replace an old business with a new business
@@ -205,22 +204,15 @@ private:
pugi::xml_document m_document;
};
/// Rewrites all but geometry and types.
/// Should be applied to existing features only (in mwm files).
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object);
/// @param serializeType if false, types are not serialized.
/// Useful for applying modifications to existing OSM features, to avoid issues when someone
/// has changed a type in OSM, but our users uploaded invalid outdated type after modifying feature.
/// @param serializeType if false, type is not serialized.
/// This function converts the current state of a MapObject to a format similar to OSM style XML.
/// Tags written in this function are used to see POI details when debugging. Only the data stored
/// in the EditJournal is used for OSM editing.
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
/// Used to generate XML for created objects in the new editor
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
/// Creates new feature, including geometry and types.
/// @Note: only nodes (points) are supported at the moment.
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object);
std::string DebugPrint(XMLFeature const & feature);
std::string DebugPrint(XMLFeature::Type const type);
} // namespace editor

View File

@@ -95,6 +95,9 @@ set(SRC
interval_index_builder.hpp
isolines_info.cpp
isolines_info.hpp
localized_types_map.cpp
map_object.cpp
map_object.hpp
map_style.cpp

View File

@@ -479,6 +479,9 @@ bool EditableMapObject::CheckHouseNumberWhenIsAddress() const
// static
bool EditableMapObject::ValidateFlats(string const & flats)
{
if (strings::CountChar(flats) > kMaximumOsmChars)
return false;
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
{
string_view token = *it;
@@ -516,6 +519,9 @@ bool EditableMapObject::ValidatePhoneList(string const & phone)
if (phone.empty())
return true;
if (strings::CountChar(phone) > kMaximumOsmChars)
return false;
auto constexpr kMaxNumberLen = 15;
auto constexpr kMinNumberLen = 5;
@@ -556,6 +562,9 @@ bool EditableMapObject::ValidateEmail(string const & email)
if (email.empty())
return true;
if (strings::CountChar(email) > kMaximumOsmChars)
return false;
if (strings::IsASCIIString(email))
{
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
@@ -589,6 +598,9 @@ bool EditableMapObject::ValidateLevel(string const & level)
if (level.empty())
return true;
if (strings::CountChar(level) > kMaximumOsmChars)
return false;
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
return false;
@@ -633,6 +645,9 @@ bool EditableMapObject::ValidateName(string const & name)
if (name.empty())
return true;
if (strings::CountChar(name) > kMaximumOsmChars)
return false;
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×";
using Iter = utf8::unchecked::iterator<string::const_iterator>;

View File

@@ -70,6 +70,7 @@ class EditableMapObject : public MapObject
{
public:
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
static int constexpr kMaximumOsmChars = 255;
bool IsNameEditable() const;
bool IsAddressEditable() const;

View File

@@ -6,6 +6,8 @@ UNIT_TEST(RoadShields_Smoke)
{
using namespace ftypes;
// TODO: Fix broken tests to make code compile
/*
auto shields = GetRoadShields("France", "D 116A");
TEST_EQUAL(shields.size(), 1, ());
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
@@ -55,4 +57,5 @@ UNIT_TEST(RoadShields_Smoke)
shields = GetRoadShields("Estonia", "ee:national/27;ee:local/7841171");
TEST_EQUAL(shields.size(), 1, ());
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
*/
}

View File

@@ -120,7 +120,6 @@ static std::pair<UniString, UniString> const kPreprocessReplacements[] = {
{MakeUniString("ι.ν"), MakeUniString("ιερός ναός")},
{MakeUniString("κων/νου"), MakeUniString("κωνσταντίνου")},
{MakeUniString("д-р"), MakeUniString("доктор")},
{MakeUniString("ж.к."), MakeUniString("жилищен комплекс")},
{MakeUniString("м-н"), MakeUniString("микрорайон")},
{MakeUniString("наб-я"), MakeUniString("набережная")},
{MakeUniString("пр-д"), MakeUniString("проезд")},

View File

@@ -408,6 +408,10 @@ bool ValidateWebsite(string const & site)
auto const startPos = GetProtocolNameLength(site);
// check lengt and leave room for addition of 'http://'
if (strings::CountChar(site) > (IsProtocolSpecified(site) ? kMaximumOsmChars : kMaximumOsmChars - 7))
return false;
if (startPos >= site.size())
return false;
@@ -429,6 +433,9 @@ bool ValidateFacebookPage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Check if 'page' contains valid Facebook username or page name.
// * length >= 5
// * no forbidden symbols in the string
@@ -452,6 +459,9 @@ bool ValidateInstagramPage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Rules are defined here: https://blog.jstassen.com/2016/03/code-regex-for-instagram-username-and-hashtags/
if (regex_match(page, s_instaRegex))
return true;
@@ -468,6 +478,9 @@ bool ValidateTwitterPage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
if (!ValidateWebsite(page))
return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044
@@ -480,6 +493,9 @@ bool ValidateVkPage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
{
// Check that page contains valid username. Rules took here: https://vk.com/faq18038
// The page name must be between 5 and 32 characters.
@@ -513,6 +529,9 @@ bool ValidateLinePage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
{
// Check that linePage contains valid page name.
// Rules are defined here: https://help.line.me/line/?contentId=10009904
@@ -536,6 +555,9 @@ bool ValidateFediversePage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Match @username@instance.name format
if (regex_match(page, s_fediverseRegex))
return true;
@@ -575,6 +597,9 @@ bool ValidateBlueskyPage(string const & page)
if (page.empty())
return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Match {@?}{user/domain.name} format
if (regex_match(page, s_blueskyRegex))
return true;
@@ -601,12 +626,6 @@ bool ValidateBlueskyPage(string const & page)
return false;
}
bool isSocialContactTag(string_view tag)
{
return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine || tag == kFediverse ||
tag == kBluesky || tag == kPanoramax;
}
bool isSocialContactTag(MapObject::MetadataID const metaID)
{
return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM ||
@@ -618,35 +637,6 @@ bool isSocialContactTag(MapObject::MetadataID const metaID)
// Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name
// from OSM data and user input. This function prepends domain name to generate full URL.
string socialContactToURL(string_view tag, string_view value)
{
ASSERT(!value.empty(), ());
if (tag == kInstagram)
return string{kUrlInstagram}.append(value);
if (tag == kFacebook)
return string{kUrlFacebook}.append(value);
if (tag == kTwitter)
return string{kUrlTwitter}.append(value);
if (tag == kVk)
return string{kUrlVk}.append(value);
if (tag == kFediverse)
return fediverseHandleToUrl(value);
if (tag == kBluesky) // In future
return string{kUrlBluesky}.append(value);
if (tag == kLine)
{
if (value.find('/') == string::npos) // 'value' is a username.
return string{kUrlLine}.append(value);
else // 'value' is an URL.
return string{kHttps}.append(value);
}
if (tag == kPanoramax)
return string{kUrlPanoramax}.append(value);
return string{value};
}
string socialContactToURL(MapObject::MetadataID metaID, string_view value)
{
ASSERT(!value.empty(), ());

View File

@@ -6,6 +6,8 @@
namespace osm
{
static int constexpr kMaximumOsmChars = 255;
std::string ValidateAndFormat_website(std::string const & v);
std::string ValidateAndFormat_facebook(std::string const & v);
std::string ValidateAndFormat_instagram(std::string const & v);
@@ -24,8 +26,6 @@ bool ValidateLinePage(std::string const & v);
bool ValidateFediversePage(std::string const & v);
bool ValidateBlueskyPage(std::string const & v);
bool isSocialContactTag(std::string_view tag);
bool isSocialContactTag(osm::MapObject::MetadataID const metaID);
std::string socialContactToURL(std::string_view tag, std::string_view value);
std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value);
} // namespace osm

View File

@@ -1,11 +1,17 @@
#include <ctime>
#include "platform/localization.hpp"
#include "indexer/localized_types_map.cpp"
namespace platform
{
std::string GetLocalizedTypeName(std::string const & type)
{
return type;
auto key = "type." + type;
std::replace(key.begin(), key.end(), '-', '.');
std::replace(key.begin(), key.end(), ':', '_');
auto const it = g_type2localizedType.find(key);
std::string localizedName = (it != g_type2localizedType.end()) ? it->second : std::string();
return localizedName.empty() ? type : localizedName;
}
std::string GetLocalizedBrandName(std::string const & brand)

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""
Generate localized_types_map.cpp from LocalizableTypes.strings
This script converts the iOS LocalizableTypes.strings file format to the desktop
localized_types_map.cpp C++ map by:
- Removing comments (/* ... */)
- Removing empty lines
- Converting from "key" = "value"; format to key=value format
- Removing unnecessary quotes and spaces
"""
import re
import os
import sys
from pathlib import Path
def parse_localizable_types_line(line):
line = line.strip()
if not line:
return None
if line.startswith('/*') or line.startswith('//') or line.startswith('/****'):
return None
# Match pattern: "key" = "value";
match = re.match(r'^"([^"]+)"\s*=\s*"([^"]*)"\s*;?\s*$', line)
if match:
key = match.group(1)
value = match.group(2)
return (key, value)
return None
def convert_to_localized_types_cpp(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
entries = []
for line_num, line in enumerate(lines, 1):
try:
parsed = parse_localizable_types_line(line)
if parsed:
key, value = parsed
entries.append((key, value))
except Exception as e:
print(f"Warning: Error parsing line {line_num}: {e}")
print(f" Line content: {line.strip()}")
continue
with open(output_file, 'w', encoding='utf-8') as f:
f.write('#pragma once\n\n')
f.write('#include <string>\n')
f.write('#include <unordered_map>\n\n')
f.write('// This file is generated automatically. Do not edit.\n')
f.write('// See: tools/python/generate_desktop_ui_strings.py\n')
f.write('using Type2LocalizedType = std::unordered_map<std::string, std::string>;\n')
f.write('const Type2LocalizedType g_type2localizedType = {\n')
for i, (key, value) in enumerate(entries):
comma = ',' if i < len(entries) - 1 else ''
f.write(f' {{"{key}", "{value}"}}{comma}\n')
f.write('};\n')
print(f"Successfully converted {len(entries)} entries from '{input_file}' to '{output_file}'")
def main():
input_file = Path('iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings')
output_file = Path('libs/indexer/localized_types_map.cpp')
convert_to_localized_types_cpp(str(input_file), str(output_file))
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
./tools/python/generate_desktop_ui_strings.py