From 72620f3e47aea91777bff05a98f414d2a03102c6 Mon Sep 17 00:00:00 2001 From: Dmitry Naumov Date: Tue, 16 Apr 2024 18:33:51 +0500 Subject: [PATCH 01/47] Implement 3D model (https://github.com/osmandapp/OsmAnd-Issues/issues/1772) --- .../main/java/net/osmand/IndexConstants.java | 4 + OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java | 4 +- .../src/net/osmand/plus/AppInitializer.java | 2 + .../net/osmand/plus/OsmandApplication.java | 8 + .../osmand/plus/helpers/Model3dHelper.java | 192 ++++++++++++++++++ .../osmand/plus/profiles/LocationIcon.java | 25 ++- .../osmand/plus/profiles/NavigationIcon.java | 21 +- .../settings/backend/ApplicationMode.java | 42 ++-- .../settings/backend/ApplicationModeBean.java | 4 +- .../plus/settings/backend/OsmandSettings.java | 32 +-- .../fragments/ProfileAppearanceFragment.java | 105 ++++++---- .../RouteLineAppearanceFragment.java | 7 +- .../plus/views/AnimateMapMarkersThread.java | 10 + .../plus/views/layers/PointLocationLayer.java | 166 +++++++++++---- 14 files changed, 494 insertions(+), 128 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java diff --git a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java index 9b69eb2ad57..f0370304683 100644 --- a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java +++ b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java @@ -67,6 +67,8 @@ public class IndexConstants { public static final String AVOID_ROADS_FILE_EXT = ".geojson"; + public static final String OBJ_FILE_EXT = ".obj"; + public static final String POI_TABLE = "poi"; public static final String INDEX_DOWNLOAD_DOMAIN = "download.osmand.net"; @@ -105,6 +107,8 @@ public class IndexConstants { public static final String GEOTIFF_DIR = "geotiff/"; public static final String WEATHER_INDEX_DIR = "weather/"; public static final String WEATHER_FORECAST_DIR = "weather_forecast/"; + public static final String MODEL_3D_DIR = "models/"; public static final String VOICE_PROVIDER_SUFFIX = "-tts"; + public static final String MODEL_NAME_PREFIX = "model_"; } diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index dce03bc0d5b..0eb7daa04b8 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -2422,8 +2422,8 @@ public boolean getProfiles(List profiles) { for (ApplicationMode mode : ApplicationMode.allPossibleValues()) { ApplicationModeBean bean = mode.toModeBean(); AProfile aProfile = new AProfile(bean.stringKey, bean.userProfileName, bean.parent, bean.iconName, - bean.iconColor.name(), bean.routingProfile, bean.routeService.name(), bean.locIcon.name(), - bean.navIcon.name(), bean.order); + bean.iconColor.name(), bean.routingProfile, bean.routeService.name(), bean.locIcon, + bean.navIcon, bean.order); aProfile.setVersion(bean.version); profiles.add(aProfile); diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index c494429e8a2..78bbe18e1ce 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -45,6 +45,7 @@ import net.osmand.plus.helpers.DayNightHelper; import net.osmand.plus.helpers.LauncherShortcutsHelper; import net.osmand.plus.helpers.LockHelper; +import net.osmand.plus.helpers.Model3dHelper; import net.osmand.plus.helpers.TargetPointsHelper; import net.osmand.plus.helpers.WaypointHelper; import net.osmand.plus.importfiles.ImportHelper; @@ -348,6 +349,7 @@ public void onCreateApplication() { app.weatherHelper = startupInit(new WeatherHelper(app), WeatherHelper.class); app.dialogManager = startupInit(new DialogManager(), DialogManager.class); app.smartFolderHelper = startupInit(new SmartFolderHelper(app), SmartFolderHelper.class); + app.model3dHelper = startupInit(new Model3dHelper(app), Model3dHelper.class); initOpeningHoursParser(); } diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index e5eab5e59f5..d13db8de7d9 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -59,6 +59,7 @@ import net.osmand.plus.helpers.LocaleHelper; import net.osmand.plus.helpers.LocationServiceHelper; import net.osmand.plus.helpers.LockHelper; +import net.osmand.plus.helpers.Model3dHelper; import net.osmand.plus.helpers.TargetPointsHelper; import net.osmand.plus.helpers.WaypointHelper; import net.osmand.plus.importfiles.ImportHelper; @@ -205,6 +206,8 @@ public class OsmandApplication extends MultiDexApplication { DialogManager dialogManager; SmartFolderHelper smartFolderHelper; + Model3dHelper model3dHelper; + private final Map customRoutingConfigs = new ConcurrentHashMap<>(); private File externalStorageDirectory; private boolean externalStorageDirectoryReadOnly; @@ -587,6 +590,11 @@ public OfflineForecastHelper getOfflineForecastHelper() { return weatherHelper.getOfflineForecastHelper(); } + @NonNull + public Model3dHelper getModel3dHelper() { + return model3dHelper; + } + public CommandPlayer getPlayer() { return player; } diff --git a/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java b/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java new file mode 100644 index 00000000000..913b83675d4 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java @@ -0,0 +1,192 @@ +package net.osmand.plus.helpers; + +import android.os.AsyncTask; + +import net.osmand.CallbackWithObject; +import net.osmand.IndexConstants; +import net.osmand.StateChangedListener; +import net.osmand.core.jni.Model3D; +import net.osmand.core.jni.ObjParser; +import net.osmand.plus.AppInitEvents; +import net.osmand.plus.AppInitializeListener; +import net.osmand.plus.AppInitializer; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class Model3dHelper { + + private final OsmandApplication app; + private final OsmandSettings settings; + + @Nullable + private Model3D locationModel; + @Nullable + private Model3D navigationModel; + + @Nullable + private String locationModelName; + @Nullable + private String navigationModelName; + + private final StateChangedListener appModeListener; + private final StateChangedListener locationIconListener; + private final StateChangedListener navigationIconListener; + + public Model3dHelper(@NonNull OsmandApplication app) { + this.app = app; + this.settings = app.getSettings(); + this.appModeListener = newAppMode -> parseModels(); + this.locationIconListener = newIcon -> parseModels(); + this.navigationIconListener = newIcon -> parseModels(); + + settings.APPLICATION_MODE.addListener(appModeListener); + settings.LOCATION_ICON.addListener(locationIconListener); + settings.NAVIGATION_ICON.addListener(navigationIconListener); + + parseModels(); + } + + @Nullable + public Model3D getLocationModel() { + return locationModel; + } + + @Nullable + public Model3D getNavigationModel() { + return navigationModel; + } + + @Nullable + public String getLocationModelName() { + return locationModelName; + } + + @Nullable + public String getNavigationModelName() { + return navigationModelName; + } + + public void parseModels() { + if (app.isApplicationInitializing()) { + app.getAppInitializer().addListener(new AppInitializeListener() { + @Override + public void onProgress(@NonNull AppInitializer init, @NonNull AppInitEvents event) { + if (event == AppInitEvents.NATIVE_OPEN_GL_INITIALIZED) { + parseModelsImpl(); + init.removeListener(this); + } + } + }); + } else { + parseModelsImpl(); + } + } + + private void parseModelsImpl() { + if (!app.useOpenGlRenderer()) { + return; + } + + ApplicationMode appMode = settings.getApplicationMode(); + String locationModelName = appMode.getLocationIcon(); + String navigationModelName = appMode.getNavigationIcon(); + boolean sameIcon = locationModelName.equals(navigationModelName); + + String locationModelFileName = locationModelName.replace(IndexConstants.MODEL_NAME_PREFIX, ""); + String navigationModelFileName = navigationModelName.replace(IndexConstants.MODEL_NAME_PREFIX, ""); + + boolean parsingLocationIcon = false; + if (!locationModelName.equals(this.locationModelName) && isModelExist(app, locationModelFileName)) { + parsingLocationIcon = true; + parseModelInBackground(locationModelFileName, model -> { + this.locationModel = model; + this.locationModelName = locationModelName; + if (sameIcon) { + navigationModel = model; + this.navigationModelName = locationModelName; + } + return true; + }); + } + + if ((!sameIcon || parsingLocationIcon) && !navigationModelName.equals(this.navigationModelName) && isModelExist(app, navigationModelFileName)) { + parseModelInBackground(navigationModelFileName, model -> { + this.navigationModel = model; + this.navigationModelName = navigationModelName; + return true; + }); + } + } + + private void parseModelInBackground(@NonNull String modelName, @NonNull CallbackWithObject callback) { + String modelDir = new File(app.getAppPath(IndexConstants.MODEL_3D_DIR), modelName).getAbsolutePath(); + new Parse3dModelTask(modelDir, callback).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + } + + @NonNull + public static List listModels(@NonNull OsmandApplication app) { + List modelsDirNames = new ArrayList<>(); + File modelsDir = app.getAppPath(IndexConstants.MODEL_3D_DIR); + File[] modelsDirs = modelsDir.listFiles(); + if (!Algorithms.isEmpty(modelsDirs)) { + for (File model : modelsDirs) { + if (isModelExist(model)) { + modelsDirNames.add(IndexConstants.MODEL_NAME_PREFIX + model.getName()); + } + } + } + return modelsDirNames; + } + + public static boolean isModelExist(@NonNull OsmandApplication app, @NonNull String dirName) { + return isModelExist(new File(app.getAppPath(IndexConstants.MODEL_3D_DIR), dirName)); + } + + public static boolean isModelExist(@NonNull File dir) { + if (!dir.exists() || !dir.isDirectory()) { + return false; + } + + File[] modelFiles = dir.listFiles(); + if (!Algorithms.isEmpty(modelFiles)) { + for (File file : modelFiles) { + if (file.getName().endsWith(IndexConstants.OBJ_FILE_EXT)) { + return true; + } + } + } + + return false; + } + + private static class Parse3dModelTask extends AsyncTask { + + private final String modelDirPath; + private final CallbackWithObject callback; + + public Parse3dModelTask(@NonNull String modelDirPath, @NonNull CallbackWithObject callback) { + this.modelDirPath = modelDirPath; + this.callback = callback; + } + + @Override + protected Model3D doInBackground(Void... voids) { + ObjParser parser = new ObjParser(modelDirPath + "/model.obj", modelDirPath + "/" + "mtl"); + return parser.parse(); + } + + @Override + protected void onPostExecute(Model3D model3D) { + callback.processResult(model3D); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/profiles/LocationIcon.java b/OsmAnd/src/net/osmand/plus/profiles/LocationIcon.java index caa7215cec2..6bad8d87e5a 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/LocationIcon.java +++ b/OsmAnd/src/net/osmand/plus/profiles/LocationIcon.java @@ -1,14 +1,17 @@ package net.osmand.plus.profiles; -import androidx.annotation.DrawableRes; - +import net.osmand.IndexConstants; import net.osmand.plus.R; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + public enum LocationIcon { DEFAULT(R.drawable.map_location_default, R.drawable.map_location_default_view_angle), CAR(R.drawable.map_location_car, R.drawable.map_location_car_view_angle), - BICYCLE(R.drawable.map_location_bicycle, R.drawable.map_location_bicycle_view_angle); + BICYCLE(R.drawable.map_location_bicycle, R.drawable.map_location_bicycle_view_angle), + MODEL(R.drawable.map_location_default, R.drawable.map_location_car_view_angle); @DrawableRes private final int iconId; @@ -29,4 +32,20 @@ public int getIconId() { public int getHeadingIconId() { return headingIconId; } + + public static boolean isModel(@NonNull String name) { + return name.startsWith(IndexConstants.MODEL_NAME_PREFIX); + } + + @NonNull + public static LocationIcon fromName(@NonNull String name) { + if (isModel(name)) { + return MODEL; + } + try { + return valueOf(name); + } catch (IllegalArgumentException e) { + return DEFAULT; + } + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/profiles/NavigationIcon.java b/OsmAnd/src/net/osmand/plus/profiles/NavigationIcon.java index 3573a29fea6..939460c099e 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/NavigationIcon.java +++ b/OsmAnd/src/net/osmand/plus/profiles/NavigationIcon.java @@ -1,14 +1,17 @@ package net.osmand.plus.profiles; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import net.osmand.IndexConstants; import net.osmand.plus.R; public enum NavigationIcon { DEFAULT(R.drawable.map_navigation_default), NAUTICAL(R.drawable.map_navigation_nautical), - CAR(R.drawable.map_navigation_car); + CAR(R.drawable.map_navigation_car), + MODEL(R.drawable.map_navigation_default); NavigationIcon(@DrawableRes int iconId) { this.iconId = iconId; @@ -21,4 +24,20 @@ public enum NavigationIcon { public int getIconId() { return iconId; } + + public static boolean isModel(@NonNull String name) { + return name.startsWith(IndexConstants.MODEL_NAME_PREFIX); + } + + @NonNull + public static NavigationIcon fromName(@NonNull String name) { + if (isModel(name)) { + return MODEL; + } + try { + return valueOf(name); + } catch (IllegalArgumentException e) { + return DEFAULT; + } + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationMode.java b/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationMode.java index 96d925568db..c3638da4750 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationMode.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationMode.java @@ -1,22 +1,11 @@ package net.osmand.plus.settings.backend; -import static net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule; - -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import net.osmand.StateChangedListener; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.profiles.LocationIcon; -import net.osmand.plus.profiles.NavigationIcon; import net.osmand.plus.profiles.ProfileIconColors; import net.osmand.plus.routing.RouteService; import net.osmand.plus.settings.backend.OsmAndAppCustomization.OsmAndAppCustomizationListener; @@ -30,6 +19,15 @@ import java.util.List; import java.util.Set; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + +import static net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule; + public class ApplicationMode { public static final String CUSTOM_MODE_KEY_SEPARATOR = "_"; @@ -373,17 +371,19 @@ public void setRouteService(RouteService routeService) { } } - public NavigationIcon getNavigationIcon() { + @NonNull + public String getNavigationIcon() { return app.getSettings().NAVIGATION_ICON.getModeValue(this); } - public void setNavigationIcon(NavigationIcon navigationIcon) { - if (navigationIcon != null) { + public void setNavigationIcon(@Nullable String navigationIcon) { + if (!Algorithms.isEmpty(navigationIcon)) { app.getSettings().NAVIGATION_ICON.setModeValue(this, navigationIcon); } } - public LocationIcon getLocationIcon() { + @NonNull + public String getLocationIcon() { return app.getSettings().LOCATION_ICON.getModeValue(this); } @@ -396,8 +396,8 @@ public int getProfileColor(boolean nightMode) { return ContextCompat.getColor(app, getIconColorInfo().getColor(nightMode)); } - public void setLocationIcon(LocationIcon locationIcon) { - if (locationIcon != null) { + public void setLocationIcon(@Nullable String locationIcon) { + if (!Algorithms.isEmpty(locationIcon)) { app.getSettings().LOCATION_ICON.setModeValue(this, locationIcon); } } @@ -676,8 +676,8 @@ public static class ApplicationModeBuilder { private String iconResName; private ProfileIconColors iconColor; private Integer customIconColor; - private LocationIcon locationIcon; - private NavigationIcon navigationIcon; + private String locationIcon; + private String navigationIcon; private int order = -1; private int version = -1; @@ -765,12 +765,12 @@ public ApplicationModeBuilder setVersion(int version) { return this; } - public ApplicationModeBuilder setLocationIcon(LocationIcon locIcon) { + public ApplicationModeBuilder setLocationIcon(String locIcon) { this.locationIcon = locIcon; return this; } - public ApplicationModeBuilder setNavigationIcon(NavigationIcon navIcon) { + public ApplicationModeBuilder setNavigationIcon(String navIcon) { this.navigationIcon = navIcon; return this; } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationModeBean.java b/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationModeBean.java index d538e1a3b1d..4d0b4d8e96e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationModeBean.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ApplicationModeBean.java @@ -33,9 +33,9 @@ public class ApplicationModeBean { @Expose public RouteService routeService = RouteService.OSMAND; @Expose - public LocationIcon locIcon; + public String locIcon; @Expose - public NavigationIcon navIcon; + public String navIcon; @Expose public int order = -1; @Expose diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java index 8dceb7d702c..b1a69b32b37 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java @@ -1207,28 +1207,28 @@ public RouteService getModeValue(ApplicationMode mode) { public final CommonPreference ONLINE_ROUTING_ENGINES = new StringPreference(this, "online_routing_engines", null).makeGlobal().makeShared().storeLastModifiedTime(); - public final CommonPreference NAVIGATION_ICON = new EnumStringPreference<>(this, "navigation_icon", NavigationIcon.DEFAULT, NavigationIcon.values()).makeProfile().cache(); + public final CommonPreference NAVIGATION_ICON = new StringPreference(this, "navigation_icon", NavigationIcon.DEFAULT.name()).makeProfile().cache(); { - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.DEFAULT, NavigationIcon.DEFAULT); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.CAR, NavigationIcon.DEFAULT); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.BICYCLE, NavigationIcon.DEFAULT); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.BOAT, NavigationIcon.NAUTICAL); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.AIRCRAFT, NavigationIcon.DEFAULT); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.SKI, NavigationIcon.DEFAULT); - NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.HORSE, NavigationIcon.DEFAULT); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.DEFAULT, NavigationIcon.DEFAULT.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.CAR, NavigationIcon.DEFAULT.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.BICYCLE, NavigationIcon.DEFAULT.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.BOAT, NavigationIcon.NAUTICAL.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.AIRCRAFT, NavigationIcon.DEFAULT.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.SKI, NavigationIcon.DEFAULT.name()); + NAVIGATION_ICON.setModeDefaultValue(ApplicationMode.HORSE, NavigationIcon.DEFAULT.name()); } - public final CommonPreference LOCATION_ICON = new EnumStringPreference<>(this, "location_icon", LocationIcon.DEFAULT, LocationIcon.values()).makeProfile().cache(); + public final CommonPreference LOCATION_ICON = new StringPreference(this, "location_icon", LocationIcon.DEFAULT.name()).makeProfile().cache(); { - LOCATION_ICON.setModeDefaultValue(ApplicationMode.DEFAULT, LocationIcon.DEFAULT); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.CAR, LocationIcon.CAR); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.BICYCLE, LocationIcon.BICYCLE); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.BOAT, LocationIcon.DEFAULT); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.AIRCRAFT, LocationIcon.CAR); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.SKI, LocationIcon.BICYCLE); - LOCATION_ICON.setModeDefaultValue(ApplicationMode.HORSE, LocationIcon.BICYCLE); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.DEFAULT, LocationIcon.DEFAULT.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.CAR, LocationIcon.CAR.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.BICYCLE, LocationIcon.BICYCLE.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.BOAT, LocationIcon.DEFAULT.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.AIRCRAFT, LocationIcon.CAR.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.SKI, LocationIcon.BICYCLE.name()); + LOCATION_ICON.setModeDefaultValue(ApplicationMode.HORSE, LocationIcon.BICYCLE.name()); } public final CommonPreference APP_MODE_ORDER = new IntPreference(this, "app_mode_order", 0).makeProfile().cache(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index fce552e90dc..af69f4e5c17 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -1,10 +1,5 @@ package net.osmand.plus.settings.fragments; -import static net.osmand.aidlapi.OsmAndCustomizationConstants.DRAWER_SETTINGS_ID; -import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILES_LIST_UPDATED_ARG; -import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG; -import static net.osmand.plus.settings.backend.ApplicationMode.CUSTOM_MODE_KEY_SEPARATOR; - import android.annotation.SuppressLint; import android.app.Activity; import android.app.ProgressDialog; @@ -28,21 +23,6 @@ import android.widget.ImageView; import android.widget.TextView; -import androidx.activity.OnBackPressedCallback; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.drawable.DrawableCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; -import androidx.recyclerview.widget.RecyclerView; - import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.plus.R; @@ -50,6 +30,7 @@ import net.osmand.plus.card.color.palette.main.ColorsPaletteCard; import net.osmand.plus.card.color.palette.main.OnColorsPaletteListener; import net.osmand.plus.card.color.palette.main.data.PaletteColor; +import net.osmand.plus.helpers.Model3dHelper; import net.osmand.plus.profiles.LocationIcon; import net.osmand.plus.profiles.NavigationIcon; import net.osmand.plus.profiles.ProfileIconColors; @@ -77,6 +58,27 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.List; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; +import androidx.recyclerview.widget.RecyclerView; + +import static net.osmand.aidlapi.OsmAndCustomizationConstants.DRAWER_SETTINGS_ID; +import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILES_LIST_UPDATED_ARG; +import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG; +import static net.osmand.plus.settings.backend.ApplicationMode.CUSTOM_MODE_KEY_SEPARATOR; public class ProfileAppearanceFragment extends BaseSettingsFragment implements OnSelectProfileCallback, OnColorsPaletteListener { @@ -290,8 +292,8 @@ private void saveState(Bundle outState) { } outState.putBoolean(IS_NEW_PROFILE_KEY, isNewProfile); outState.putBoolean(IS_BASE_PROFILE_IMPORTED, isBaseProfileImported); - outState.putSerializable(PROFILE_LOCATION_ICON_KEY, changedProfile.locationIcon); - outState.putSerializable(PROFILE_NAVIGATION_ICON_KEY, changedProfile.navigationIcon); + outState.putString(PROFILE_LOCATION_ICON_KEY, changedProfile.locationIcon); + outState.putString(PROFILE_NAVIGATION_ICON_KEY, changedProfile.navigationIcon); } private void restoreState(Bundle savedInstanceState) { @@ -303,8 +305,8 @@ private void restoreState(Bundle savedInstanceState) { String parentStringKey = savedInstanceState.getString(PROFILE_PARENT_KEY); changedProfile.parent = ApplicationMode.valueOfStringKey(parentStringKey, null); isBaseProfileImported = savedInstanceState.getBoolean(IS_BASE_PROFILE_IMPORTED); - changedProfile.locationIcon = AndroidUtils.getSerializable(savedInstanceState, PROFILE_LOCATION_ICON_KEY, LocationIcon.class); - changedProfile.navigationIcon = AndroidUtils.getSerializable(savedInstanceState, PROFILE_NAVIGATION_ICON_KEY, NavigationIcon.class); + changedProfile.locationIcon = savedInstanceState.getString(PROFILE_LOCATION_ICON_KEY); + changedProfile.navigationIcon = savedInstanceState.getString(PROFILE_NAVIGATION_ICON_KEY); isNewProfile = savedInstanceState.getBoolean(IS_NEW_PROFILE_KEY); } @@ -400,7 +402,7 @@ public void afterTextChanged(Editable s) { } else if (LOCATION_ICON_ITEMS.equals(preference.getKey())) { locationIconItems = (FlowLayout) holder.findViewById(R.id.color_items); locationIconItems.removeAllViews(); - for (LocationIcon locationIcon : LocationIcon.values()) { + for (String locationIcon : listLocationIcons()) { View iconItemView = createLocationIconView(locationIcon, locationIconItems); locationIconItems.addView(iconItemView, new FlowLayout.LayoutParams(0, 0)); } @@ -408,7 +410,7 @@ public void afterTextChanged(Editable s) { } else if (NAV_ICON_ITEMS.equals(preference.getKey())) { navIconItems = (FlowLayout) holder.findViewById(R.id.color_items); navIconItems.removeAllViews(); - for (NavigationIcon navigationIcon : NavigationIcon.values()) { + for (String navigationIcon : listNavigationIcons()) { View iconItemView = createNavigationIconView(navigationIcon, navIconItems); navIconItems.addView(iconItemView, new FlowLayout.LayoutParams(0, 0)); } @@ -500,7 +502,9 @@ private void updateIconSelector(int iconRes) { updateProfileButton(); } - private View createLocationIconView(LocationIcon locationIcon, ViewGroup rootView) { + private View createLocationIconView(@NonNull String locationIconName, ViewGroup rootView) { + LocationIcon locationIcon = LocationIcon.fromName(locationIconName); + FrameLayout locationIconView = (FrameLayout) UiUtilities.getInflater(getContext(), isNightMode()) .inflate(R.layout.preference_select_icon_button, rootView, false); int changedProfileColor = changedProfile.getActualColor(); @@ -517,9 +521,9 @@ private View createLocationIconView(LocationIcon locationIcon, ViewGroup rootVie UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, R.drawable.bg_select_icon_button), ColorUtilities.getColorWithAlpha(ContextCompat.getColor(app, R.color.icon_color_default_light), 0.1f))); coloredRect.setOnClickListener(v -> { - if (locationIcon != changedProfile.locationIcon) { + if (!locationIconName.equals(changedProfile.locationIcon)) { setVerticalScrollBarEnabled(false); - updateLocationIconSelector(locationIcon); + updateLocationIconSelector(locationIconName); setVerticalScrollBarEnabled(true); } }); @@ -530,11 +534,11 @@ private View createLocationIconView(LocationIcon locationIcon, ViewGroup rootVie } outlineRect.setImageDrawable(rectContourDrawable); outlineRect.setVisibility(View.GONE); - locationIconView.setTag(locationIcon); + locationIconView.setTag(locationIconName); return locationIconView; } - private void updateLocationIconSelector(LocationIcon locationIcon) { + private void updateLocationIconSelector(@NonNull String locationIcon) { View viewWithTag = locationIconItems.findViewWithTag(changedProfile.locationIcon); viewWithTag.findViewById(R.id.outlineRect).setVisibility(View.GONE); viewWithTag = locationIconItems.findViewWithTag(locationIcon); @@ -542,7 +546,9 @@ private void updateLocationIconSelector(LocationIcon locationIcon) { changedProfile.locationIcon = locationIcon; } - private View createNavigationIconView(NavigationIcon navigationIcon, ViewGroup rootView) { + private View createNavigationIconView(@NonNull String navigationIconName, ViewGroup rootView) { + NavigationIcon navigationIcon = NavigationIcon.fromName(navigationIconName); + LayoutInflater inflater = UiUtilities.getInflater(getContext(), isNightMode()); FrameLayout navigationIconView = (FrameLayout) inflater.inflate(R.layout.preference_select_icon_button, rootView, false); LayerDrawable navigationDrawable = (LayerDrawable) AppCompatResources.getDrawable(app, navigationIcon.getIconId()); @@ -565,9 +571,9 @@ private View createNavigationIconView(NavigationIcon navigationIcon, ViewGroup r ColorUtilities.getColor(app, R.color.icon_color_default_light, 0.1f)); AndroidUtils.setBackground(coloredRect, coloredDrawable); coloredRect.setOnClickListener(v -> { - if (navigationIcon != changedProfile.navigationIcon) { + if (!navigationIconName.equals(changedProfile.navigationIcon)) { setVerticalScrollBarEnabled(false); - updateNavigationIconSelector(navigationIcon); + updateNavigationIconSelector(navigationIconName); setVerticalScrollBarEnabled(true); } }); @@ -579,11 +585,11 @@ private View createNavigationIconView(NavigationIcon navigationIcon, ViewGroup r } outlineRect.setImageDrawable(rectContourDrawable); outlineRect.setVisibility(View.GONE); - navigationIconView.setTag(navigationIcon); + navigationIconView.setTag(navigationIconName); return navigationIconView; } - private void updateNavigationIconSelector(NavigationIcon navigationIcon) { + private void updateNavigationIconSelector(@NonNull String navigationIcon) { View viewWithTag = navIconItems.findViewWithTag(changedProfile.navigationIcon); viewWithTag.findViewById(R.id.outlineRect).setVisibility(View.GONE); viewWithTag = navIconItems.findViewWithTag(navigationIcon); @@ -824,6 +830,25 @@ private void disableSaveButtonWithErrorMessage(String errorMessage) { profileNameOtfb.setError(errorMessage, true); } + @NonNull + private List listLocationIcons() { + List locationIcons = new ArrayList<>(); + locationIcons.add(LocationIcon.DEFAULT.name()); + locationIcons.add(LocationIcon.CAR.name()); + locationIcons.add(LocationIcon.BICYCLE.name()); + locationIcons.addAll(Model3dHelper.listModels(app)); + return locationIcons; + } + + @NonNull List listNavigationIcons() { + List navigationIcons = new ArrayList<>(); + navigationIcons.add(NavigationIcon.DEFAULT.name()); + navigationIcons.add(NavigationIcon.NAUTICAL.name()); + navigationIcons.add(NavigationIcon.CAR.name()); + navigationIcons.addAll(Model3dHelper.listModels(app)); + return navigationIcons; + } + public void showExitDialog() { hideKeyboard(); if (isChanged()) { @@ -931,8 +956,8 @@ class ApplicationProfileObject { int iconRes; String routingProfile; RouteService routeService; - NavigationIcon navigationIcon; - LocationIcon locationIcon; + String navigationIcon; + String locationIcon; @ColorInt public int getActualColor() { @@ -968,8 +993,8 @@ public boolean equals(Object o) { if (routingProfile != null ? !routingProfile.equals(that.routingProfile) : that.routingProfile != null) return false; if (routeService != that.routeService) return false; - if (navigationIcon != that.navigationIcon) return false; - return locationIcon == that.locationIcon; + if (!Algorithms.objectEquals(navigationIcon, that.navigationIcon)) return false; + return Algorithms.objectEquals(locationIcon, that.locationIcon); } @Override diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteLineAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteLineAppearanceFragment.java index c8b5a630a34..cb430621e30 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteLineAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteLineAppearanceFragment.java @@ -29,6 +29,7 @@ import net.osmand.plus.card.color.ColoringStyle; import net.osmand.plus.card.color.palette.main.data.PaletteColor; import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.profiles.NavigationIcon; import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu; import net.osmand.plus.routing.ColoringType; import net.osmand.plus.routing.PreviewRouteLineInfo; @@ -148,7 +149,11 @@ private PreviewRouteLineInfo createPreviewRouteLineInfo() { PreviewRouteLineInfo previewRouteLineInfo = new PreviewRouteLineInfo(colorDay, colorNight, coloringType, routeInfoAttribute, widthKey, showTurnArrows); - previewRouteLineInfo.setIconId(appMode.getNavigationIcon().getIconId()); + String navigationIconName = appMode.getNavigationIcon(); + NavigationIcon navigationIcon = NavigationIcon.isModel(navigationIconName) + ? NavigationIcon.DEFAULT + : NavigationIcon.fromName(navigationIconName); + previewRouteLineInfo.setIconId(navigationIcon.getIconId()); previewRouteLineInfo.setIconColor(appMode.getProfileColor(isNightMode())); return previewRouteLineInfo; diff --git a/OsmAnd/src/net/osmand/plus/views/AnimateMapMarkersThread.java b/OsmAnd/src/net/osmand/plus/views/AnimateMapMarkersThread.java index 0343f81953d..8b92fbeb901 100644 --- a/OsmAnd/src/net/osmand/plus/views/AnimateMapMarkersThread.java +++ b/OsmAnd/src/net/osmand/plus/views/AnimateMapMarkersThread.java @@ -136,6 +136,16 @@ public void animateDirectionTo(@NonNull MapMarker mapMarker, @NonNull SWIGTYPE_p } } + public void animateModel3dDirectionTo(@NonNull MapMarker mapMarker, float direction, long animationDuration) { + MapRendererView mapRenderer = getMapRenderer(); + MapMarkersAnimator animator = getAnimator(); + if (mapRenderer != null && animator != null) { + animator.animateModel3DDirectionTo(mapMarker, (float) Utilities.normalizedAngleDegrees(direction), + animationDuration / 1000f, TimingFunction.Linear); + startThreadAnimating(() -> animatingMapMarkersAnimator(mapRenderer)); + } + } + public void animatePositionAndDirectionTo(@NonNull MapMarker mapMarker, @NonNull PointI target31, long positionAnimationDuration, @NonNull SWIGTYPE_p_void iconKey, float direction, long directionAnimationDuration) { MapRendererView mapRenderer = getMapRenderer(); diff --git a/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java index 652dd177cdc..ee6af6a52b5 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java @@ -29,6 +29,7 @@ import net.osmand.core.jni.MapMarker; import net.osmand.core.jni.MapMarkerBuilder; import net.osmand.core.jni.MapMarkersCollection; +import net.osmand.core.jni.Model3D; import net.osmand.core.jni.PointI; import net.osmand.core.jni.SWIGTYPE_p_void; import net.osmand.core.jni.SwigUtilities; @@ -42,6 +43,9 @@ import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.base.MapViewTrackingUtilities; +import net.osmand.plus.helpers.Model3dHelper; +import net.osmand.plus.profiles.LocationIcon; +import net.osmand.plus.profiles.NavigationIcon; import net.osmand.plus.profiles.ProfileIconColors; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.settings.backend.ApplicationMode; @@ -74,15 +78,21 @@ public class PointLocationLayer extends OsmandMapLayer private float textScale = 1f; @ColorInt private int profileColor; + + private String navigationIconName; + private Model3D navigationModel; private LayerDrawable navigationIcon; - private int navigationIconId; + + private String locationIconName; + private Model3D locationModel; private LayerDrawable locationIcon; - private int locationIconId; private Bitmap headingIcon; private int headingIconId; + private final OsmAndLocationProvider locationProvider; private final MapViewTrackingUtilities mapViewTrackingUtilities; private final OsmandSettings settings; + private final Model3dHelper model3dHelper; private boolean nm; private boolean locationOutdated; private Location prevLocation; @@ -115,8 +125,8 @@ private static class CoreMapMarker { private SWIGTYPE_p_void onSurfaceHeadingIconKey; public static CoreMapMarker createAndAddToCollection(@NonNull Context ctx, @NonNull MapMarkersCollection markersCollection, - int id, int baseOrder, @NonNull LayerDrawable icon, @DrawableRes int headingIconId, - float scale, @ColorInt int profileColor, boolean withHeading) { + int id, int baseOrder, @NonNull LayerDrawable icon, @Nullable Model3D model3D, + @DrawableRes int headingIconId, float scale, @ColorInt int profileColor, boolean withHeading) { CoreMapMarker marker = new CoreMapMarker(); MapMarkerBuilder myLocMarkerBuilder = new MapMarkerBuilder(); myLocMarkerBuilder.setMarkerId(id); @@ -129,17 +139,23 @@ public static CoreMapMarker createAndAddToCollection(@NonNull Context ctx, @NonN int width = (int) (icon.getIntrinsicWidth() * scale); int height = (int) (icon.getIntrinsicHeight() * scale); - int locationX = width / 2; - int locationY = height / 2; - Bitmap markerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(markerBitmap); - AndroidUtils.drawScaledLayerDrawable(canvas, icon, locationX, locationY, scale); + if (model3D != null) { + myLocMarkerBuilder.setModel3D(model3D); + myLocMarkerBuilder.setModel3DMaxSizeInPixels(Math.max(width, height)); + } else { + int locationX = width / 2; + int locationY = height / 2; + + Bitmap markerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(markerBitmap); + AndroidUtils.drawScaledLayerDrawable(canvas, icon, locationX, locationY, scale); - if (markerBitmap != null) { - marker.onSurfaceIconKey = SwigUtilities.getOnSurfaceIconKey(1); - myLocMarkerBuilder.addOnMapSurfaceIcon(marker.onSurfaceIconKey, - NativeUtilities.createSkImageFromBitmap(markerBitmap)); + if (markerBitmap != null) { + marker.onSurfaceIconKey = SwigUtilities.getOnSurfaceIconKey(1); + myLocMarkerBuilder.addOnMapSurfaceIcon(marker.onSurfaceIconKey, + NativeUtilities.createSkImageFromBitmap(markerBitmap)); + } } if (withHeading) { @@ -168,6 +184,7 @@ public PointLocationLayer(@NonNull Context context) { this.mapViewTrackingUtilities = getApplication().getMapViewTrackingUtilities(); this.locationProvider = getApplication().getLocationProvider(); this.settings = getApplication().getSettings(); + this.model3dHelper = getApplication().getModel3dHelper(); } private void initLegacyRenderer() { @@ -241,7 +258,7 @@ private boolean setMarkerState(MarkerState markerState, boolean showHeading, boo } @Nullable - private CoreMapMarker recreateMarker(LayerDrawable icon, int id, @ColorInt int profileColor, boolean withHeading) { + private CoreMapMarker recreateMarker(LayerDrawable icon, Model3D model3d, int id, @ColorInt int profileColor, boolean withHeading) { if (view == null || icon == null) { return null; } @@ -249,7 +266,7 @@ private CoreMapMarker recreateMarker(LayerDrawable icon, int id, @ColorInt int p mapMarkersCollection = new MapMarkersCollection(); } return CoreMapMarker.createAndAddToCollection(getContext(), mapMarkersCollection, id, - getPointsOrder(), icon, headingIconId, getTextScale(), profileColor, withHeading); + getPointsOrder(), icon, model3d, headingIconId, getTextScale(), profileColor, withHeading); } private void setMarkerProvider() { @@ -264,10 +281,10 @@ private boolean recreateMarkerCollection() { return false; } clearMapMarkersCollections(); - locationMarker = recreateMarker(locationIcon, MARKER_ID_MY_LOCATION, profileColor, false); - locationMarkerWithHeading = recreateMarker(locationIcon, MARKER_ID_MY_LOCATION_HEADING, profileColor, true); - navigationMarker = recreateMarker(navigationIcon, MARKER_ID_NAVIGATION, profileColor, false); - navigationMarkerWithHeading = recreateMarker(navigationIcon, MARKER_ID_NAVIGATION_HEADING, profileColor, true); + locationMarker = recreateMarker(locationIcon, locationModel, MARKER_ID_MY_LOCATION, profileColor, false); + locationMarkerWithHeading = recreateMarker(locationIcon, locationModel, MARKER_ID_MY_LOCATION_HEADING, profileColor, true); + navigationMarker = recreateMarker(navigationIcon, navigationModel, MARKER_ID_NAVIGATION, profileColor, false); + navigationMarkerWithHeading = recreateMarker(navigationIcon, navigationModel, MARKER_ID_NAVIGATION_HEADING, profileColor, true); setMarkerProvider(); return true; } @@ -370,13 +387,22 @@ private void updateMarkerBearing(float bearing, boolean animateRotation) { if (mapRenderer != null && view != null && locMarker != null && locMarker.marker != null) { AnimateMapMarkersThread animationThread = view.getAnimatedMapMarkersThread(); animationThread.cancelCurrentAnimation(locMarker.marker, AnimatedValue.Azimuth); - long bearingRotationDuration = animateRotation && locMarker.onSurfaceIconKey != null + boolean hasModel = locMarker.marker.getModel3D() != null; + long bearingRotationDuration = animateRotation && (hasModel || locMarker.onSurfaceIconKey != null) ? ROTATE_ANIMATION_TIME : 0; - if (bearingRotationDuration > 0) { - animationThread.animateDirectionTo(locMarker.marker, locMarker.onSurfaceIconKey, - bearing, bearingRotationDuration); - } else if (locMarker.onSurfaceIconKey != null) { - locMarker.marker.setOnMapSurfaceIconDirection(locMarker.onSurfaceIconKey, bearing); + if (locMarker.marker.getModel3D() != null) { + if (bearingRotationDuration > 0) { + animationThread.animateModel3dDirectionTo(locMarker.marker, bearing, bearingRotationDuration); + } else { + locMarker.marker.setModel3DDirection(bearing); + } + } else { + if (bearingRotationDuration > 0) { + animationThread.animateDirectionTo(locMarker.marker, locMarker.onSurfaceIconKey, + bearing, bearingRotationDuration); + } else if (locMarker.onSurfaceIconKey != null) { + locMarker.marker.setOnMapSurfaceIconDirection(locMarker.onSurfaceIconKey, bearing); + } } } } @@ -608,41 +634,55 @@ private boolean isMovingToMyLocation() { } private void updateParams(ApplicationMode appMode, boolean nighMode, boolean locationOutdated) { + boolean hasMapRenderer = hasMapRenderer(); Context ctx = getContext(); int profileColor = locationOutdated ? ContextCompat.getColor(ctx, ProfileIconColors.getOutdatedLocationColor(nighMode)) : appMode.getProfileColor(nighMode); - int locationIconId = appMode.getLocationIcon().getIconId(); - int navigationIconId = appMode.getNavigationIcon().getIconId(); - int headingIconId = appMode.getLocationIcon().getHeadingIconId(); + String locationIconName = getLocationIconName(appMode); + String navigationIconName = getNavigationIconName(appMode); float textScale = getTextScale(); boolean carView = getApplication().getOsmandMap().getMapView().isCarView(); if (appMode != this.appMode || this.nm != nighMode || this.locationOutdated != locationOutdated || this.profileColor != profileColor - || this.locationIconId != locationIconId - || this.headingIconId != headingIconId - || this.navigationIconId != navigationIconId + || !locationIconName.equals(this.locationIconName) + || !navigationIconName.equals(this.navigationIconName) || this.textScale != textScale || this.carView != carView) { this.appMode = appMode; this.profileColor = profileColor; this.nm = nighMode; this.locationOutdated = locationOutdated; - this.locationIconId = locationIconId; - this.headingIconId = headingIconId; - this.navigationIconId = navigationIconId; + this.locationIconName = locationIconName; + this.navigationIconName = navigationIconName; this.textScale = textScale; this.carView = carView; - navigationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, navigationIconId); - if (navigationIcon != null) { - DrawableCompat.setTint(navigationIcon.getDrawable(1), profileColor); + + if (NavigationIcon.isModel(navigationIconName)) { + navigationModel = model3dHelper.getNavigationModel(); + } else { + int navigationIconId = NavigationIcon.fromName(navigationIconName).getIconId(); + navigationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, navigationIconId); + if (navigationIcon != null) { + DrawableCompat.setTint(navigationIcon.getDrawable(1), profileColor); + } + navigationModel = null; } - headingIcon = getScaledBitmap(headingIconId); - locationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, locationIconId); - if (locationIcon != null) { - DrawableCompat.setTint(DrawableCompat.wrap(locationIcon.getDrawable(1)), profileColor); + + LocationIcon locationIconType = LocationIcon.fromName(locationIconName); + if (LocationIcon.isModel(locationIconName)) { + locationModel = model3dHelper.getLocationModel(); + } else { + locationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, locationIconType.getIconId()); + if (locationIcon != null) { + DrawableCompat.setTint(DrawableCompat.wrap(locationIcon.getDrawable(1)), profileColor); + } + locationModel = null; } - if (!hasMapRenderer()) { + headingIconId = locationIconType.getHeadingIconId(); + headingIcon = getScaledBitmap(headingIconId); + + if (!hasMapRenderer) { headingPaint.setColorFilter(new PorterDuffColorFilter(profileColor, PorterDuff.Mode.SRC_IN)); area.setColor(ColorUtilities.getColorWithAlpha(profileColor, 0.16f)); aroundArea.setColor(profileColor); @@ -696,4 +736,46 @@ private void getMyLocationFromPoint(RotatedTileBox tb, PointF point, List Date: Tue, 16 Apr 2024 21:53:15 +0500 Subject: [PATCH 02/47] Cache all parsed 3D models --- .../osmand/plus/helpers/Model3dHelper.java | 133 +++++++++--------- .../plus/views/layers/PointLocationLayer.java | 8 +- 2 files changed, 67 insertions(+), 74 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java b/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java index 913b83675d4..edee04021f4 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/Model3dHelper.java @@ -17,7 +17,12 @@ import java.io.File; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -27,15 +32,9 @@ public class Model3dHelper { private final OsmandApplication app; private final OsmandSettings settings; - @Nullable - private Model3D locationModel; - @Nullable - private Model3D navigationModel; - - @Nullable - private String locationModelName; - @Nullable - private String navigationModelName; + private final Map modelsCache = new HashMap<>(); + private final Set modelsInProgress = new HashSet<>(); + private final Set failedModels = new HashSet<>(); private final StateChangedListener appModeListener; private final StateChangedListener locationIconListener; @@ -56,23 +55,8 @@ public Model3dHelper(@NonNull OsmandApplication app) { } @Nullable - public Model3D getLocationModel() { - return locationModel; - } - - @Nullable - public Model3D getNavigationModel() { - return navigationModel; - } - - @Nullable - public String getLocationModelName() { - return locationModelName; - } - - @Nullable - public String getNavigationModelName() { - return navigationModelName; + public Model3D getModel(@NonNull String modelName) { + return modelsCache.get(modelName.replace(IndexConstants.MODEL_NAME_PREFIX, "")); } public void parseModels() { @@ -97,39 +81,46 @@ private void parseModelsImpl() { } ApplicationMode appMode = settings.getApplicationMode(); - String locationModelName = appMode.getLocationIcon(); - String navigationModelName = appMode.getNavigationIcon(); - boolean sameIcon = locationModelName.equals(navigationModelName); - - String locationModelFileName = locationModelName.replace(IndexConstants.MODEL_NAME_PREFIX, ""); - String navigationModelFileName = navigationModelName.replace(IndexConstants.MODEL_NAME_PREFIX, ""); - - boolean parsingLocationIcon = false; - if (!locationModelName.equals(this.locationModelName) && isModelExist(app, locationModelFileName)) { - parsingLocationIcon = true; - parseModelInBackground(locationModelFileName, model -> { - this.locationModel = model; - this.locationModelName = locationModelName; - if (sameIcon) { - navigationModel = model; - this.navigationModelName = locationModelName; - } - return true; - }); - } + Set iconsNames = new HashSet<>(); + iconsNames.add(appMode.getLocationIcon()); + iconsNames.add(appMode.getNavigationIcon()); + + Set dirsPaths = new HashSet<>(); + for (String iconName : iconsNames) { + if (!iconName.startsWith(IndexConstants.MODEL_NAME_PREFIX)) { + continue; + } - if ((!sameIcon || parsingLocationIcon) && !navigationModelName.equals(this.navigationModelName) && isModelExist(app, navigationModelFileName)) { - parseModelInBackground(navigationModelFileName, model -> { - this.navigationModel = model; - this.navigationModelName = navigationModelName; - return true; - }); + String modelName = iconName.replace(IndexConstants.MODEL_NAME_PREFIX, ""); + + if (modelsCache.containsKey(modelName) + || modelsInProgress.contains(modelName) + || failedModels.contains(modelName)) { + continue; + } + + File dir = new File(app.getAppPath(IndexConstants.MODEL_3D_DIR), modelName); + if (isModelExist(dir)) { + dirsPaths.add(dir.getAbsolutePath()); + modelsInProgress.add(modelName); + } } - } - private void parseModelInBackground(@NonNull String modelName, @NonNull CallbackWithObject callback) { - String modelDir = new File(app.getAppPath(IndexConstants.MODEL_3D_DIR), modelName).getAbsolutePath(); - new Parse3dModelTask(modelDir, callback).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + new Parse3dModelTask(dirsPaths, result -> { + for (Entry entry : result.entrySet()) { + String modelName = entry.getKey(); + Model3D model = entry.getValue(); + + if (model == null) { + failedModels.add(modelName); + } else { + modelsCache.put(modelName, model); + } + modelsInProgress.remove(modelName); + } + + return true; + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @NonNull @@ -147,10 +138,6 @@ public static List listModels(@NonNull OsmandApplication app) { return modelsDirNames; } - public static boolean isModelExist(@NonNull OsmandApplication app, @NonNull String dirName) { - return isModelExist(new File(app.getAppPath(IndexConstants.MODEL_3D_DIR), dirName)); - } - public static boolean isModelExist(@NonNull File dir) { if (!dir.exists() || !dir.isDirectory()) { return false; @@ -168,25 +155,31 @@ public static boolean isModelExist(@NonNull File dir) { return false; } - private static class Parse3dModelTask extends AsyncTask { + private static class Parse3dModelTask extends AsyncTask> { - private final String modelDirPath; - private final CallbackWithObject callback; + private final Set modelDirPaths; + private final CallbackWithObject< Map > callback; - public Parse3dModelTask(@NonNull String modelDirPath, @NonNull CallbackWithObject callback) { - this.modelDirPath = modelDirPath; + public Parse3dModelTask(@NonNull Set modelDirPaths, @NonNull CallbackWithObject< Map > callback) { + this.modelDirPaths = modelDirPaths; this.callback = callback; } @Override - protected Model3D doInBackground(Void... voids) { - ObjParser parser = new ObjParser(modelDirPath + "/model.obj", modelDirPath + "/" + "mtl"); - return parser.parse(); + protected Map doInBackground(Void... voids) { + Map result = new HashMap<>(); + for (String modelDirPath : modelDirPaths) { + String modelName = new File(modelDirPath).getName(); + ObjParser parser = new ObjParser(modelDirPath + "/model.obj", modelDirPath + "/" + "mtl"); + Model3D model = parser.parse(); + result.put(modelName, model); + } + return result; } @Override - protected void onPostExecute(Model3D model3D) { - callback.processResult(model3D); + protected void onPostExecute(Map result) { + callback.processResult(result); } } } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java index ee6af6a52b5..78f4b37415f 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/PointLocationLayer.java @@ -659,7 +659,7 @@ private void updateParams(ApplicationMode appMode, boolean nighMode, boolean loc this.carView = carView; if (NavigationIcon.isModel(navigationIconName)) { - navigationModel = model3dHelper.getNavigationModel(); + navigationModel = model3dHelper.getModel(navigationIconName); } else { int navigationIconId = NavigationIcon.fromName(navigationIconName).getIconId(); navigationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, navigationIconId); @@ -671,7 +671,7 @@ private void updateParams(ApplicationMode appMode, boolean nighMode, boolean loc LocationIcon locationIconType = LocationIcon.fromName(locationIconName); if (LocationIcon.isModel(locationIconName)) { - locationModel = model3dHelper.getLocationModel(); + locationModel = model3dHelper.getModel(locationIconName); } else { locationIcon = (LayerDrawable) AppCompatResources.getDrawable(ctx, locationIconType.getIconId()); if (locationIcon != null) { @@ -748,7 +748,7 @@ private String getLocationIconName(@NonNull ApplicationMode appMode) { } if (LocationIcon.isModel(newLocationIconName)) { - if (newLocationIconName.equals(oldLocationIconName) || !newLocationIconName.equals(model3dHelper.getLocationModelName())) { + if (model3dHelper.getModel(newLocationIconName) == null) { return oldLocationIconName == null ? LocationIcon.DEFAULT.name() : oldLocationIconName; } else { return newLocationIconName; @@ -769,7 +769,7 @@ private String getNavigationIconName(@NonNull ApplicationMode appMode) { } if (NavigationIcon.isModel(newNavigationIconName)) { - if (newNavigationIconName.equals(oldNavigationIconName) || !newNavigationIconName.equals(model3dHelper.getNavigationModelName())) { + if (model3dHelper.getModel(newNavigationIconName) == null) { return oldNavigationIconName == null ? NavigationIcon.DEFAULT.name() : oldNavigationIconName; } else { return newNavigationIconName; From 86222baf6c03149d959a6e05024a4514c461a3a7 Mon Sep 17 00:00:00 2001 From: 0xRe1nk0 <0xre1nk0@gmail.com> Date: Wed, 17 Apr 2024 10:51:02 +0300 Subject: [PATCH 03/47] Add vertical exaggeration via 3D relief --- OsmAnd/res/layout/fragment_relief_3d.xml | 201 ++++++++++ .../layout/vertical_exaggeration_fragment.xml | 53 +++ OsmAnd/res/values/strings.xml | 4 + .../core/android/MapRendererContext.java | 12 + .../configmap/ConfigureMapOptionFragment.java | 4 +- .../osmand/plus/dashboard/DashboardOnMap.java | 11 +- .../plus/plugins/srtm/Relief3DFragment.java | 352 ++++++++++++++++++ .../osmand/plus/plugins/srtm/SRTMPlugin.java | 76 +++- .../srtm/TerrainVisibilityFragment.java | 6 + .../srtm/TerrainZoomLevelsFragment.java | 6 + .../srtm/VerticalExaggerationFragment.java | 138 +++++++ 11 files changed, 843 insertions(+), 20 deletions(-) create mode 100644 OsmAnd/res/layout/fragment_relief_3d.xml create mode 100644 OsmAnd/res/layout/vertical_exaggeration_fragment.xml create mode 100644 OsmAnd/src/net/osmand/plus/plugins/srtm/Relief3DFragment.java create mode 100644 OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java diff --git a/OsmAnd/res/layout/fragment_relief_3d.xml b/OsmAnd/res/layout/fragment_relief_3d.xml new file mode 100644 index 00000000000..aa82a956df0 --- /dev/null +++ b/OsmAnd/res/layout/fragment_relief_3d.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/vertical_exaggeration_fragment.xml b/OsmAnd/res/layout/vertical_exaggeration_fragment.xml new file mode 100644 index 00000000000..06cf125c71c --- /dev/null +++ b/OsmAnd/res/layout/vertical_exaggeration_fragment.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 9e15f230f25..cbc635ff0e7 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -10,6 +10,10 @@ - For wording and consistency, please note https://docs.osmand.net/docs/technical/contributions/translating-osmand Thx - Hardy --> + Scale + Additional maps are needed to view Terrain on the map. + By changing the scale value, you can emphasise the 3D relief. + Vertical exaggeration Apply only to new Apply changes to existing tracks in the folder or only to new ones? Changes diff --git a/OsmAnd/src/net/osmand/core/android/MapRendererContext.java b/OsmAnd/src/net/osmand/core/android/MapRendererContext.java index fef45b3cc92..73c47f8b419 100644 --- a/OsmAnd/src/net/osmand/core/android/MapRendererContext.java +++ b/OsmAnd/src/net/osmand/core/android/MapRendererContext.java @@ -452,6 +452,7 @@ private void applyCurrentContextToView() { mapRendererView.addSymbolsProvider(providerType.symbolsSectionIndex, obfMapSymbolsProvider); } recreateHeightmapProvider(); + updateVerticalExaggerationScale(); setMapBackgroundColor(); } @@ -470,6 +471,17 @@ public void updateElevationConfiguration() { mapRendererView.setElevationConfiguration(elevationConfiguration); } + public void updateVerticalExaggerationScale() { + MapRendererView mapRendererView = this.mapRendererView; + if (mapRendererView == null) { + return; + } + SRTMPlugin plugin = PluginsHelper.getPlugin(SRTMPlugin.class); + if (plugin != null) { + mapRendererView.setElevationScaleFactor(plugin.getVerticalExaggerationScale()); + } + } + public void updateCachedHeightmapTiles() { GeoTiffCollection geoTiffCollection = getGeoTiffCollection(); for (RasterType rasterType : RasterType.values()) { diff --git a/OsmAnd/src/net/osmand/plus/configmap/ConfigureMapOptionFragment.java b/OsmAnd/src/net/osmand/plus/configmap/ConfigureMapOptionFragment.java index 3af3144e774..773a860abc3 100644 --- a/OsmAnd/src/net/osmand/plus/configmap/ConfigureMapOptionFragment.java +++ b/OsmAnd/src/net/osmand/plus/configmap/ConfigureMapOptionFragment.java @@ -74,11 +74,13 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public void handleOnBackPressed() { activity.getSupportFragmentManager().popBackStack(); - activity.getDashboard().setDashboardVisibility(true, DashboardOnMap.DashboardType.TERRAIN, false); + activity.getDashboard().setDashboardVisibility(true, getBaseDashboardType(), false); } }); } + protected abstract DashboardOnMap.DashboardType getBaseDashboardType(); + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 40c26e5c310..1965b539353 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -11,6 +11,7 @@ import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.NAUTICAL_DEPTH; import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.OSM_NOTES; import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.OVERLAY_MAP; +import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.RELIEF_3D; import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.TERRAIN; import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.TRANSPORT_LINES; import static net.osmand.plus.dashboard.DashboardOnMap.DashboardType.TRAVEL_ROUTES; @@ -91,6 +92,7 @@ import net.osmand.plus.plugins.osmedit.menu.OsmNotesMenu; import net.osmand.plus.plugins.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.plugins.srtm.ContourLinesMenu; +import net.osmand.plus.plugins.srtm.Relief3DFragment; import net.osmand.plus.plugins.srtm.TerrainFragment; import net.osmand.plus.plugins.weather.WeatherBand; import net.osmand.plus.plugins.weather.WeatherPlugin; @@ -200,6 +202,7 @@ public enum DashboardType { OSM_NOTES, WIKIPEDIA, TERRAIN, + RELIEF_3D, CYCLE_ROUTES, HIKING_ROUTES, TRAVEL_ROUTES, @@ -345,6 +348,8 @@ private void updateToolbarActions() { tv.setText(R.string.osm_notes); } else if (isCurrentType(TERRAIN)) { tv.setText(R.string.shared_string_terrain); + }else if (isCurrentType(RELIEF_3D)) { + tv.setText(R.string.relief_3d); } else if (isCurrentType(WIKIPEDIA)) { tv.setText(R.string.shared_string_wikipedia); } else if (isCurrentType(CYCLE_ROUTES)) { @@ -602,6 +607,8 @@ public void setDashboardVisibility(boolean visible, DashboardType type, boolean NauticalDepthContourFragment.showInstance(fragmentManager); } else if (isCurrentType(TERRAIN)) { TerrainFragment.showInstance(fragmentManager); + }else if (isCurrentType(RELIEF_3D)) { + Relief3DFragment.showInstance(fragmentManager); } else if (isCurrentType(WEATHER)) { WeatherMainFragment.showInstance(fragmentManager); } else if (isCurrentType(WEATHER_LAYER)) { @@ -786,6 +793,8 @@ public void refreshContent(boolean force) { refreshFragment(MapillaryFiltersFragment.TAG); } else if (isCurrentType(TERRAIN)) { refreshFragment(TerrainFragment.TAG); + }else if (isCurrentType(RELIEF_3D)) { + refreshFragment(Relief3DFragment.TAG); } else if (isCurrentType(CYCLE_ROUTES)) { refreshFragment(CycleRoutesFragment.TAG); } else if (isCurrentType(HIKING_ROUTES)) { @@ -1037,7 +1046,7 @@ public boolean isNoCurrentType(@NonNull DashboardType... types) { public boolean isCurrentTypeHasIndividualFragment() { return isCurrentType( - CONFIGURE_MAP, MAPILLARY, TERRAIN, CYCLE_ROUTES, HIKING_ROUTES, + CONFIGURE_MAP, MAPILLARY, TERRAIN, RELIEF_3D, CYCLE_ROUTES, HIKING_ROUTES, TRAVEL_ROUTES, TRANSPORT_LINES, WEATHER, WEATHER_LAYER, WEATHER_CONTOURS, NAUTICAL_DEPTH, MTB_ROUTES, DIFFICULTY_CLASSIFICATION ); diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/Relief3DFragment.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/Relief3DFragment.java new file mode 100644 index 00000000000..d642ab1d558 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/Relief3DFragment.java @@ -0,0 +1,352 @@ +package net.osmand.plus.plugins.srtm; + +import static net.osmand.plus.download.DownloadActivityType.GEOTIFF_FILE; +import static net.osmand.plus.download.DownloadActivityType.HILLSHADE_FILE; +import static net.osmand.plus.download.DownloadActivityType.SLOPE_FILE; +import static net.osmand.plus.plugins.srtm.SRTMPlugin.getFormattedScaleValue; +import static net.osmand.plus.plugins.srtm.TerrainMode.HILLSHADE; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; +import androidx.fragment.app.FragmentManager; + +import com.github.ksoichiro.android.observablescrollview.ObservableListView; + +import net.osmand.PlatformUtil; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.download.DownloadActivityType; +import net.osmand.plus.download.DownloadIndexesThread; +import net.osmand.plus.download.DownloadResources; +import net.osmand.plus.download.DownloadValidationManager; +import net.osmand.plus.download.IndexItem; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.plugins.PluginsHelper; +import net.osmand.plus.plugins.development.OsmandDevelopmentPlugin; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.utils.AndroidUtils; +import net.osmand.plus.utils.UiUtilities; +import net.osmand.plus.widgets.ctxmenu.ContextMenuAdapter; +import net.osmand.plus.widgets.ctxmenu.ContextMenuListAdapter; +import net.osmand.plus.widgets.ctxmenu.ViewCreator; +import net.osmand.plus.widgets.ctxmenu.callback.ItemClickListener; +import net.osmand.plus.widgets.ctxmenu.data.ContextMenuItem; + +import org.apache.commons.logging.Log; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; + +public class Relief3DFragment extends BaseOsmAndFragment implements View.OnClickListener, DownloadIndexesThread.DownloadEvents { + + public static final String TAG = Relief3DFragment.class.getSimpleName(); + private static final Log LOG = PlatformUtil.getLog(Relief3DFragment.class.getSimpleName()); + + private SRTMPlugin srtmPlugin; + private boolean relief3DEnabled; + + private int profileColor; + + private TextView exaggerationValueTv; + + private TextView downloadDescriptionTv; + private TextView stateTv; + private SwitchCompat switchCompat; + private ImageView iconIv; + private LinearLayout contentContainer; + private LinearLayout downloadContainer; + private View titleBottomDivider; + private View downloadTopDivider; + private View downloadBottomDivider; + private ObservableListView observableListView; + + private ContextMenuListAdapter listAdapter; + + @Nullable + private MapActivity getMapActivity() { + Activity activity = getActivity(); + if (activity instanceof MapActivity && !activity.isFinishing()) { + return (MapActivity) activity; + } + return null; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + srtmPlugin = PluginsHelper.getPlugin(SRTMPlugin.class); + relief3DEnabled = settings.ENABLE_3D_MAPS.get(); + } + + @Override + protected boolean isUsedOnMap() { + return true; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + updateNightMode(); + View root = themedInflater.inflate(R.layout.fragment_relief_3d, container, false); + profileColor = settings.getApplicationMode().getProfileColor(nightMode); + showHideTopShadow(root); + + exaggerationValueTv = root.findViewById(R.id.exaggeration_value); + downloadDescriptionTv = root.findViewById(R.id.download_description_tv); + titleBottomDivider = root.findViewById(R.id.titleBottomDivider); + contentContainer = root.findViewById(R.id.content_container); + switchCompat = root.findViewById(R.id.switch_compat); + stateTv = root.findViewById(R.id.state_tv); + iconIv = root.findViewById(R.id.icon_iv); + downloadContainer = root.findViewById(R.id.download_container); + downloadTopDivider = root.findViewById(R.id.download_container_top_divider); + downloadBottomDivider = root.findViewById(R.id.download_container_bottom_divider); + observableListView = root.findViewById(R.id.list_view); + + TextView titleTv = root.findViewById(R.id.title_tv); + titleTv.setText(R.string.relief_3d); + + switchCompat.setChecked(relief3DEnabled); + switchCompat.setOnClickListener(this); + UiUtilities.setupCompoundButton(switchCompat, nightMode, UiUtilities.CompoundButtonType.PROFILE_DEPENDENT); + + setupContentCard(root); + updateUiMode(); + return root; + } + + public float getElevationScaleFactor() { + return srtmPlugin.getVerticalExaggerationScale(); + } + + private void setupContentCard(@NonNull View root) { + downloadDescriptionTv.setText(R.string.relief_3d_download_description); + View verticalExaggerationBtn = root.findViewById(R.id.vertical_exaggeration_button); + verticalExaggerationBtn.setOnClickListener(view -> { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + mapActivity.getDashboard().hideDashboard(); + VerticalExaggerationFragment.showInstance(mapActivity.getSupportFragmentManager()); + } + }); + } + + private void showHideTopShadow(@NonNull View view) { + boolean portrait = AndroidUiHelper.isOrientationPortrait(requireActivity()); + AndroidUiHelper.updateVisibility(view.findViewById(R.id.shadow_on_map), portrait); + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (id == R.id.switch_compat) { + onSwitchClick(); + } + } + + private void updateUiMode() { + if (relief3DEnabled) { + iconIv.setImageDrawable(uiUtilities.getPaintedIcon(R.drawable.ic_action_3d_relief, profileColor)); + stateTv.setText(R.string.shared_string_on); + updateDownloadSection(); + } else { + iconIv.setImageDrawable(uiUtilities.getIcon( + R.drawable.ic_action_3d_relief, + nightMode + ? R.color.icon_color_secondary_dark + : R.color.icon_color_secondary_light)); + stateTv.setText(R.string.shared_string_off); + } + exaggerationValueTv.setText(getFormattedScaleValue(app, getElevationScaleFactor())); + adjustGlobalVisibility(); + } + + private void adjustGlobalVisibility() { + titleBottomDivider.setVisibility(relief3DEnabled ? View.GONE : View.VISIBLE); + contentContainer.setVisibility(relief3DEnabled ? View.VISIBLE : View.GONE); + } + + private void onSwitchClick() { + relief3DEnabled = !relief3DEnabled; + switchCompat.setChecked(relief3DEnabled); + settings.ENABLE_3D_MAPS.set(relief3DEnabled); + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + app.runInUIThread(() -> app.getOsmandMap().getMapLayers().getMapInfoLayer().recreateAllControls(mapActivity)); + } + + updateUiMode(); + } + + private void updateDownloadSection() { + ContextMenuAdapter adapter = new ContextMenuAdapter(app); + + MapActivity mapActivity = getMapActivity(); + if (mapActivity == null) { + return; + } + WeakReference mapActivityRef = new WeakReference<>(mapActivity); + + DownloadIndexesThread downloadThread = app.getDownloadThread(); + if (!downloadThread.getIndexes().isDownloadedFromInternet) { + if (settings.isInternetConnectionAvailable()) { + downloadThread.runReloadIndexFiles(); + } + } + + if (downloadThread.shouldDownloadIndexes()) { + adapter.addItem(new ContextMenuItem(null) + .setLayout(R.layout.list_item_icon_and_download) + .setTitleId(R.string.downloading_list_indexes, mapActivity) + .setLoading(true)); + } else { + try { + DownloadActivityType type = getDownloadActivityType(); + IndexItem currentDownloadingItem = downloadThread.getCurrentDownloadingItem(); + int currentDownloadingProgress = (int) downloadThread.getCurrentDownloadProgress(); + List terrainItems = DownloadResources.findIndexItemsAt(app, + mapActivity.getMapLocation(), type, false, -1, true); + if (terrainItems.size() > 0) { + downloadContainer.setVisibility(View.VISIBLE); + downloadTopDivider.setVisibility(View.VISIBLE); + downloadBottomDivider.setVisibility(View.VISIBLE); + for (IndexItem indexItem : terrainItems) { + ContextMenuItem _item = new ContextMenuItem(null) + .setLayout(R.layout.list_item_icon_and_download) + .setTitle(indexItem.getVisibleName(app, app.getRegions(), false)) + .setDescription(type.getString(app) + " • " + indexItem.getSizeDescription(app)) + .setIcon(type.getIconResource()) + .setListener((uiAdapter, view, item, isChecked) -> { + MapActivity mapActivity1 = mapActivityRef.get(); + if (mapActivity1 != null && !mapActivity1.isFinishing()) { + if (downloadThread.isDownloading(indexItem)) { + downloadThread.cancelDownload(indexItem); + item.setProgress(ContextMenuItem.INVALID_ID); + item.setLoading(false); + item.setSecondaryIcon(R.drawable.ic_action_import); + uiAdapter.onDataSetChanged(); + } else { + new DownloadValidationManager(app).startDownload(mapActivity1, indexItem); + item.setProgress(ContextMenuItem.INVALID_ID); + item.setLoading(true); + item.setSecondaryIcon(R.drawable.ic_action_remove_dark); + uiAdapter.onDataSetChanged(); + } + } + return false; + }) + .setProgressListener((progressObject, progress, adptr, itemId, position) -> { + if (progressObject instanceof IndexItem) { + IndexItem progressItem = (IndexItem) progressObject; + if (indexItem.compareTo(progressItem) == 0) { + ContextMenuItem item = adptr.getItem(position); + if (item != null) { + item.setProgress(progress); + item.setLoading(true); + item.setSecondaryIcon(R.drawable.ic_action_remove_dark); + adptr.notifyDataSetChanged(); + } + return true; + } + } + return false; + }); + + if (indexItem == currentDownloadingItem) { + _item.setLoading(true) + .setProgress(currentDownloadingProgress) + .setSecondaryIcon(R.drawable.ic_action_remove_dark); + } else { + _item.setSecondaryIcon(R.drawable.ic_action_import); + } + adapter.addItem(_item); + } + } else { + downloadContainer.setVisibility(View.GONE); + downloadTopDivider.setVisibility(View.GONE); + downloadBottomDivider.setVisibility(View.GONE); + } + } catch (IOException e) { + LOG.error(e); + } + } + + ApplicationMode appMode = settings.getApplicationMode(); + ViewCreator viewCreator = new ViewCreator(mapActivity, nightMode); + viewCreator.setDefaultLayoutId(R.layout.list_item_icon_and_menu); + viewCreator.setCustomControlsColor(appMode.getProfileColor(nightMode)); + + listAdapter = adapter.toListAdapter(mapActivity, viewCreator); + observableListView.setAdapter(listAdapter); + observableListView.setOnItemClickListener((parent, view, position, id) -> { + ContextMenuItem item = adapter.getItem(position); + ItemClickListener click = item.getItemClickListener(); + if (click != null) { + click.onContextMenuClick(listAdapter, view, item, false); + } + }); + } + + @NonNull + private DownloadActivityType getDownloadActivityType() { + OsmandDevelopmentPlugin plugin = PluginsHelper.getPlugin(OsmandDevelopmentPlugin.class); + if (plugin != null && plugin.generateTerrainFrom3DMaps()) { + return GEOTIFF_FILE; + } else { + return srtmPlugin.getTerrainMode() == HILLSHADE ? HILLSHADE_FILE : SLOPE_FILE; + } + } + + @Override + public void onUpdatedIndexesList() { + updateDownloadSection(); + } + + @Override + public void downloadInProgress() { + DownloadIndexesThread downloadThread = app.getDownloadThread(); + IndexItem downloadIndexItem = downloadThread.getCurrentDownloadingItem(); + if (downloadIndexItem != null && listAdapter != null) { + int downloadProgress = (int) downloadThread.getCurrentDownloadProgress(); + ArrayAdapter adapter = listAdapter; + for (int i = 0; i < adapter.getCount(); i++) { + ContextMenuItem item = adapter.getItem(i); + if (item != null && item.getProgressListener() != null) { + item.getProgressListener().onProgressChanged( + downloadIndexItem, downloadProgress, adapter, (int) adapter.getItemId(i), i); + } + } + } + } + + @Override + public void downloadHasFinished() { + updateDownloadSection(); + MapActivity mapActivity = getMapActivity(); + SRTMPlugin plugin = PluginsHelper.getActivePlugin(SRTMPlugin.class); + if (mapActivity != null && plugin != null && plugin.isTerrainLayerEnabled()) { + plugin.registerLayers(mapActivity, mapActivity); + } + } + + public static void showInstance(@NonNull FragmentManager fragmentManager) { + if (AndroidUtils.isFragmentCanBeAdded(fragmentManager, TAG)) { + fragmentManager.beginTransaction() + .replace(R.id.content, new Relief3DFragment(), TAG) + .commitAllowingStateLoss(); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java index 3e5077c0a55..a57b5dc41bb 100644 --- a/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java @@ -58,6 +58,7 @@ import org.jetbrains.annotations.NotNull; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -92,11 +93,14 @@ public class SRTMPlugin extends OsmandPlugin { public final CommonPreference TERRAIN; public final CommonPreference TERRAIN_MODE; + public final CommonPreference VERTICAL_EXAGGERATION_SCALE; + public final CommonPreference CONTOUR_LINES_ZOOM; private final StateChangedListener enable3DMapsListener; private final StateChangedListener terrainListener; private final StateChangedListener terrainModeListener; + private final StateChangedListener verticalExaggerationListener; private TerrainLayer terrainLayer; @@ -116,6 +120,8 @@ public SRTMPlugin(OsmandApplication app) { SLOPE_MAX_ZOOM = registerIntPreference("slope_max_zoom", 17).makeProfile(); SLOPE_TRANSPARENCY = registerIntPreference("slope_transparency", 80).makeProfile(); + VERTICAL_EXAGGERATION_SCALE = registerFloatPreference("vertical_exaggeration_scale", 1).makeProfile(); + TERRAIN = registerBooleanPreference("terrain_layer", true).makeProfile(); TERRAIN_MODE = registerEnumStringPreference("terrain_mode", TerrainMode.HILLSHADE, TerrainMode.values(), TerrainMode.class).makeProfile(); @@ -144,6 +150,13 @@ public SRTMPlugin(OsmandApplication app) { } }); TERRAIN_MODE.addListener(terrainModeListener); + verticalExaggerationListener = scale -> { + MapRendererContext mapContext = NativeCoreContext.getMapRendererContext(); + if (mapContext != null) { + mapContext.updateVerticalExaggerationScale(); + } + }; + VERTICAL_EXAGGERATION_SCALE.addListener(verticalExaggerationListener); } @Override @@ -235,6 +248,7 @@ public void setEnabled(boolean enabled) { if (mapRendererContext != null) { mapRendererContext.updateElevationConfiguration(); mapRendererContext.recreateHeightmapProvider(); + mapRendererContext.updateVerticalExaggerationScale(); } } @@ -295,6 +309,10 @@ public void setTerrainZoomValues(int minZoom, int maxZoom, TerrainMode mode) { } } + public void setVerticalExaggerationScale(float scale){ + VERTICAL_EXAGGERATION_SCALE.set(scale); + } + public int getTerrainTransparency() { switch (getTerrainMode()) { case HILLSHADE: @@ -305,6 +323,10 @@ public int getTerrainTransparency() { return 100; } + public float getVerticalExaggerationScale(){ + return VERTICAL_EXAGGERATION_SCALE.get(); + } + public void resetZoomLevelsToDefault() { switch (getTerrainMode()) { case HILLSHADE: @@ -329,6 +351,10 @@ public void resetTransparencyToDefault() { } } + public void resetVerticalExaggerationToDefault(){ + VERTICAL_EXAGGERATION_SCALE.resetToDefault(); + } + public int getTerrainMinZoom() { int minSupportedZoom = TERRAIN_MIN_SUPPORTED_ZOOM; int minZoom = minSupportedZoom; @@ -405,9 +431,6 @@ protected void registerConfigureMapCategoryActions(@NonNull ContextMenuAdapter a addTerrainDescriptionItem(adapter, mapActivity); } else { createContextMenuItems(adapter, mapActivity); - if (app.useOpenGlRenderer()) { - add3DReliefItem(adapter, mapActivity); - } } NauticalMapsPlugin nauticalPlugin = PluginsHelper.getPlugin(NauticalMapsPlugin.class); if (nauticalPlugin != null) { @@ -447,6 +470,13 @@ public boolean onRowItemClick(@NonNull @NotNull OnDataChangeUiAdapter uiAdapter, } else if (itemId == R.string.shared_string_terrain) { mapActivity.getDashboard().setDashboardVisibility(true, DashboardOnMap.DashboardType.TERRAIN, viewCoordinates); return false; + } else if (itemId == R.string.relief_3d) { + if (InAppPurchaseUtils.is3dMapsAvailable(app)) { + mapActivity.getDashboard().setDashboardVisibility(true, DashboardOnMap.DashboardType.RELIEF_3D, viewCoordinates); + } else { + ChoosePlanFragment.showInstance(mapActivity, OsmAndFeature.RELIEF_3D); + } + return false; } return true; } @@ -485,6 +515,18 @@ public boolean onContextMenuClick(@Nullable OnDataChangeUiAdapter uiAdapter, @Nu updateLayers(mapActivity, mapActivity); mapActivity.refreshMapComplete(); }); + } else if (itemId == R.string.relief_3d) { + if (InAppPurchaseUtils.is3dMapsAvailable(app)) { + settings.ENABLE_3D_MAPS.set(isChecked); + item.setColor(app, isChecked ? R.color.osmand_orange : ContextMenuItem.INVALID_ID); + item.setSelected(isChecked); + item.setDescription(app.getString(isChecked ? R.string.shared_string_on : R.string.shared_string_off)); + uiAdapter.onDataSetChanged(); + + app.runInUIThread(() -> app.getOsmandMap().getMapLayers().getMapInfoLayer().recreateAllControls(mapActivity)); + } else { + ChoosePlanFragment.showInstance(mapActivity, OsmAndFeature.RELIEF_3D); + } } return true; } @@ -520,26 +562,16 @@ public boolean onContextMenuClick(@Nullable OnDataChangeUiAdapter uiAdapter, @Nu .setListener(listener) ); + if (app.useOpenGlRenderer()) { + add3DReliefItem(adapter, mapActivity, listener); + } } - private void add3DReliefItem(@NonNull ContextMenuAdapter adapter, @NonNull MapActivity activity) { + private void add3DReliefItem(@NonNull ContextMenuAdapter adapter, @NonNull MapActivity activity, @NonNull ItemClickListener listener) { ContextMenuItem item = new ContextMenuItem(RELIEF_3D_ID) .setTitleId(R.string.relief_3d, app) .setIcon(R.drawable.ic_action_3d_relief) - .setListener((uiAdapter, view, contextItem, isChecked) -> { - if (InAppPurchaseUtils.is3dMapsAvailable(app)) { - settings.ENABLE_3D_MAPS.set(isChecked); - contextItem.setColor(app, isChecked ? R.color.osmand_orange : ContextMenuItem.INVALID_ID); - contextItem.setSelected(isChecked); - contextItem.setDescription(app.getString(isChecked ? R.string.shared_string_on : R.string.shared_string_off)); - uiAdapter.onDataSetChanged(); - - app.runInUIThread(() -> app.getOsmandMap().getMapLayers().getMapInfoLayer().recreateAllControls(activity)); - } else { - ChoosePlanFragment.showInstance(activity, OsmAndFeature.RELIEF_3D); - } - return false; - }); + .setListener(listener); boolean enabled3DMode = settings.ENABLE_3D_MAPS.get(); if (!InAppPurchaseUtils.is3dMapsAvailable(app)) { @@ -549,11 +581,18 @@ private void add3DReliefItem(@NonNull ContextMenuAdapter adapter, @NonNull MapAc } else { item.setColor(app, enabled3DMode ? R.color.osmand_orange : INVALID_ID); item.setSelected(enabled3DMode); + item.setSecondaryIcon(R.drawable.ic_action_additional_option); item.setDescription(app.getString(enabled3DMode ? R.string.shared_string_on : R.string.shared_string_off)); } adapter.addItem(item); } + static public String getFormattedScaleValue(@NonNull OsmandApplication app, float scale) { + DecimalFormat decimalFormat = new DecimalFormat("#"); + String formattedScale = "x" + (scale % 1 == 0 ? decimalFormat.format(scale) : scale); + return scale == 0 ? app.getString(R.string.shared_string_none) : formattedScale; + } + @Nullable @Override protected String getRenderPropertyPrefix() { @@ -710,5 +749,6 @@ private void updateHeightmap(boolean overwriteExistingFile, @NonNull String file public void updateMapPresentationEnvironment(@NonNull MapRendererContext mapRendererContext) { mapRendererContext.updateElevationConfiguration(); mapRendererContext.recreateHeightmapProvider(); + mapRendererContext.updateVerticalExaggerationScale(); } } diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainVisibilityFragment.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainVisibilityFragment.java index c8f716ea2be..e349f6cebc2 100644 --- a/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainVisibilityFragment.java +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainVisibilityFragment.java @@ -12,6 +12,7 @@ import net.osmand.plus.R; import net.osmand.plus.configmap.ConfigureMapOptionFragment; +import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.plugins.PluginsHelper; import net.osmand.plus.utils.AndroidUtils; import net.osmand.plus.utils.UiUtilities; @@ -37,6 +38,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } } + @Override + protected DashboardOnMap.DashboardType getBaseDashboardType() { + return DashboardOnMap.DashboardType.TERRAIN; + } + @Override public void onDestroy() { srtmPlugin.setTerrainTransparency(originalVisibilityValue, srtmPlugin.getTerrainMode()); diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainZoomLevelsFragment.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainZoomLevelsFragment.java index de0c015a9ee..e37838b0637 100644 --- a/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainZoomLevelsFragment.java +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/TerrainZoomLevelsFragment.java @@ -12,6 +12,7 @@ import net.osmand.plus.R; import net.osmand.plus.configmap.ConfigureMapOptionFragment; +import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.plugins.PluginsHelper; import net.osmand.plus.utils.AndroidUtils; import net.osmand.plus.utils.UiUtilities; @@ -44,6 +45,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } } + @Override + protected DashboardOnMap.DashboardType getBaseDashboardType() { + return DashboardOnMap.DashboardType.TERRAIN; + } + @Override public void onDestroy() { srtmPlugin.setTerrainZoomValues(originalMinZoomValue, originalMaxZoomValue, srtmPlugin.getTerrainMode()); diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java new file mode 100644 index 00000000000..c31c22c8d10 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java @@ -0,0 +1,138 @@ +package net.osmand.plus.plugins.srtm; + +import static net.osmand.plus.plugins.srtm.SRTMPlugin.getFormattedScaleValue; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.slider.Slider; + +import net.osmand.plus.R; +import net.osmand.plus.configmap.ConfigureMapOptionFragment; +import net.osmand.plus.dashboard.DashboardOnMap; +import net.osmand.plus.plugins.PluginsHelper; +import net.osmand.plus.utils.AndroidUtils; +import net.osmand.plus.utils.UiUtilities; + +public class VerticalExaggerationFragment extends ConfigureMapOptionFragment { + + private SRTMPlugin srtmPlugin; + + public static final int MIN_VERTICAL_EXAGGERATION = 0; + public static final int MAX_VERTICAL_EXAGGERATION = 3; + public static final String SCALE = "scale"; + + private TextView scaleTv; + private Slider scaleSlider; + private float originalScaleValue; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + srtmPlugin = PluginsHelper.getPlugin(SRTMPlugin.class); + + if (savedInstanceState != null && savedInstanceState.containsKey(SCALE)) { + originalScaleValue = savedInstanceState.getInt(SCALE); + } else { + originalScaleValue = getElevationScaleFactor(); + } + } + + @Override + protected DashboardOnMap.DashboardType getBaseDashboardType() { + return DashboardOnMap.DashboardType.RELIEF_3D; + } + + public float getElevationScaleFactor() { + return srtmPlugin.getVerticalExaggerationScale(); + } + + public void setElevationScaleFactor(float scale) { + srtmPlugin.setVerticalExaggerationScale(scale); + } + + @Override + public void onDestroy() { + setElevationScaleFactor(originalScaleValue); + super.onDestroy(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putFloat(SCALE, originalScaleValue); + } + + @Override + protected String getToolbarTitle() { + return getString(R.string.vertical_exaggeration); + } + + @Override + protected void onResetToDefault() { + srtmPlugin.resetVerticalExaggerationToDefault(); + updateApplyButton(isChangesMade()); + setupSlider(); + refreshMap(); + } + + @Override + protected void setupMainContent() { + View view = themedInflater.inflate(R.layout.vertical_exaggeration_fragment, null, false); + scaleSlider = view.findViewById(R.id.scale_slider); + scaleTv = view.findViewById(R.id.scale_value_tv); + + setupSlider(); + contentContainer.addView(view); + } + + @Override + protected void onApplyButtonClick() { + originalScaleValue = getElevationScaleFactor(); + } + + private void setupSlider() { + float scaleFactor = getElevationScaleFactor(); + scaleTv.setText(getFormattedScaleValue(app, scaleFactor)); + + scaleSlider.addOnChangeListener(transparencySliderChangeListener); + scaleSlider.setValueTo(MAX_VERTICAL_EXAGGERATION); + scaleSlider.setValueFrom(MIN_VERTICAL_EXAGGERATION); + scaleSlider.setValue(scaleFactor); + scaleSlider.setStepSize(0.1f); + int profileColor = settings.getApplicationMode().getProfileColor(nightMode); + UiUtilities.setupSlider(scaleSlider, nightMode, profileColor); + } + + private final Slider.OnChangeListener transparencySliderChangeListener = new Slider.OnChangeListener() { + @Override + public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { + if (fromUser) { + scaleTv.setText(getFormattedScaleValue(app, value)); + setElevationScaleFactor(value); + updateApplyButton(isChangesMade()); + refreshMap(); + } + } + }; + + + + private boolean isChangesMade() { + return getElevationScaleFactor() != originalScaleValue; + } + + public static void showInstance(@NonNull FragmentManager manager) { + if (AndroidUtils.isFragmentCanBeAdded(manager, TAG)) { + manager.beginTransaction() + .replace(R.id.fragmentContainer, new VerticalExaggerationFragment(), TAG) + .addToBackStack(null) + .commitAllowingStateLoss(); + } + } +} \ No newline at end of file From 07a1f75f5370935ac640a888a27442662c6efb6e Mon Sep 17 00:00:00 2001 From: RZR-UA <90444451+RZR-UA@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:51:25 +0200 Subject: [PATCH 04/47] Reduce MAX_COUNT_REITERATION from 500 to 50 (#19580) --- .../src/main/java/net/osmand/router/HHRouteDataStructure.java | 2 +- OsmAnd-java/src/main/java/net/osmand/router/HHRoutePlanner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java b/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java index 6bbd8b48be0..b84355f3bee 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java @@ -41,7 +41,7 @@ public static class HHRoutingConfig { int FULL_DIJKSTRA_NETWORK_RECALC = 10; int MAX_START_END_REITERATIONS = 50; double MAX_INC_COST_CF = 1.25; - double MAX_COUNT_REITERATION = 500; + double MAX_COUNT_REITERATION = 50; /////////// Double INITIAL_DIRECTION = null; diff --git a/OsmAnd-java/src/main/java/net/osmand/router/HHRoutePlanner.java b/OsmAnd-java/src/main/java/net/osmand/router/HHRoutePlanner.java index 9128083d041..6ce6890c29e 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/HHRoutePlanner.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/HHRoutePlanner.java @@ -217,7 +217,7 @@ public HHNetworkRouteRes runRouting(LatLon start, LatLon end, HHRoutingConfig co hctx.stats.routingTime += time / 1e6; if (recalc) { if (calcCount > hctx.config.MAX_COUNT_REITERATION) { - return new HHNetworkRouteRes(" Too many route recalculations (maps are outdated)."); + return new HHNetworkRouteRes("Too many recalculations (outdated maps or unsupported parameters)."); } hctx.clearVisited(stPoints, endPoints); route = null; From 2563411ab4dcfaa6b10c15a8f834265457a059d9 Mon Sep 17 00:00:00 2001 From: RZR-UA <90444451+RZR-UA@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:56:29 +0200 Subject: [PATCH 05/47] Increase MAX_COUNT_REITERATION = 100 (#19590) --- .../src/main/java/net/osmand/router/HHRouteDataStructure.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java b/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java index 6bbd8b48be0..5925b1d389c 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/HHRouteDataStructure.java @@ -41,7 +41,7 @@ public static class HHRoutingConfig { int FULL_DIJKSTRA_NETWORK_RECALC = 10; int MAX_START_END_REITERATIONS = 50; double MAX_INC_COST_CF = 1.25; - double MAX_COUNT_REITERATION = 500; + int MAX_COUNT_REITERATION = 100; /////////// Double INITIAL_DIRECTION = null; From 8342db917fd7efe9965309ca7103b729b709d99c Mon Sep 17 00:00:00 2001 From: 0xRe1nk0 <0xre1nk0@gmail.com> Date: Wed, 17 Apr 2024 12:03:57 +0300 Subject: [PATCH 06/47] Change minimum scale to 1 for vertical exaggeration --- OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java | 3 ++- .../osmand/plus/plugins/srtm/VerticalExaggerationFragment.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java index a57b5dc41bb..6f6030a9943 100644 --- a/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/SRTMPlugin.java @@ -9,6 +9,7 @@ import static net.osmand.aidlapi.OsmAndCustomizationConstants.TERRAIN_ID; import static net.osmand.aidlapi.OsmAndCustomizationConstants.TERRAIN_PROMO_ID; import static net.osmand.plus.download.DownloadActivityType.GEOTIFF_FILE; +import static net.osmand.plus.plugins.srtm.VerticalExaggerationFragment.MIN_VERTICAL_EXAGGERATION; import static net.osmand.plus.widgets.ctxmenu.data.ContextMenuItem.INVALID_ID; import android.app.Activity; @@ -590,7 +591,7 @@ private void add3DReliefItem(@NonNull ContextMenuAdapter adapter, @NonNull MapAc static public String getFormattedScaleValue(@NonNull OsmandApplication app, float scale) { DecimalFormat decimalFormat = new DecimalFormat("#"); String formattedScale = "x" + (scale % 1 == 0 ? decimalFormat.format(scale) : scale); - return scale == 0 ? app.getString(R.string.shared_string_none) : formattedScale; + return scale == MIN_VERTICAL_EXAGGERATION ? app.getString(R.string.shared_string_none) : formattedScale; } @Nullable diff --git a/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java b/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java index c31c22c8d10..ce2f68c126c 100644 --- a/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java +++ b/OsmAnd/src/net/osmand/plus/plugins/srtm/VerticalExaggerationFragment.java @@ -23,7 +23,7 @@ public class VerticalExaggerationFragment extends ConfigureMapOptionFragment { private SRTMPlugin srtmPlugin; - public static final int MIN_VERTICAL_EXAGGERATION = 0; + public static final int MIN_VERTICAL_EXAGGERATION = 1; public static final int MAX_VERTICAL_EXAGGERATION = 3; public static final String SCALE = "scale"; From 6809103637bf5b18b89b4e812210dd1f91ac122c Mon Sep 17 00:00:00 2001 From: alex-osm Date: Wed, 17 Apr 2024 13:18:43 +0300 Subject: [PATCH 07/47] Up version in SettingsHelper to sync with iOS --- .../net/osmand/plus/settings/backend/backup/SettingsHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java index f3ccd55fa3f..b3f1aeee5de 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -43,7 +43,7 @@ public abstract class SettingsHelper { - public static final int VERSION = 1; + public static final int VERSION = 2; public static final String EXPORT_TYPE_LIST_KEY = "export_type_list_key"; public static final String REPLACE_KEY = "replace"; From 7b0f5e38c787479012fff112e221f92f07857bfc Mon Sep 17 00:00:00 2001 From: Corwin-Kh Date: Wed, 17 Apr 2024 14:01:15 +0300 Subject: [PATCH 08/47] Added test app --- .../layout-v26/simple_map_widget_medium.xml | 1 + ui-test-app/app/.gitignore | 1 + ui-test-app/app/build.gradle.kts | 41 ++++ ui-test-app/app/proguard-rules.pro | 21 ++ ui-test-app/app/src/main/AndroidManifest.xml | 27 +++ .../java/com/corwin/testtrx/MainActivity.java | 52 +++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ .../app/src/main/res/layout/activity_main.xml | 159 +++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../app/src/main/res/values-night/themes.xml | 7 + .../app/src/main/res/values/colors.xml | 5 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 9 + .../app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ ui-test-app/build.gradle.kts | 4 + ui-test-app/gradle.properties | 21 ++ ui-test-app/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ui-test-app/gradlew | 185 ++++++++++++++++++ ui-test-app/gradlew.bat | 89 +++++++++ ui-test-app/local.properties | 10 + ui-test-app/settings.gradle.kts | 18 ++ 35 files changed, 903 insertions(+) create mode 100644 ui-test-app/app/.gitignore create mode 100644 ui-test-app/app/build.gradle.kts create mode 100644 ui-test-app/app/proguard-rules.pro create mode 100644 ui-test-app/app/src/main/AndroidManifest.xml create mode 100644 ui-test-app/app/src/main/java/com/corwin/testtrx/MainActivity.java create mode 100644 ui-test-app/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 ui-test-app/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 ui-test-app/app/src/main/res/layout/activity_main.xml create mode 100644 ui-test-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 ui-test-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 ui-test-app/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 ui-test-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 ui-test-app/app/src/main/res/values-night/themes.xml create mode 100644 ui-test-app/app/src/main/res/values/colors.xml create mode 100644 ui-test-app/app/src/main/res/values/strings.xml create mode 100644 ui-test-app/app/src/main/res/values/themes.xml create mode 100644 ui-test-app/app/src/main/res/xml/backup_rules.xml create mode 100644 ui-test-app/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 ui-test-app/build.gradle.kts create mode 100644 ui-test-app/gradle.properties create mode 100644 ui-test-app/gradle/wrapper/gradle-wrapper.jar create mode 100644 ui-test-app/gradle/wrapper/gradle-wrapper.properties create mode 100755 ui-test-app/gradlew create mode 100644 ui-test-app/gradlew.bat create mode 100644 ui-test-app/local.properties create mode 100644 ui-test-app/settings.gradle.kts diff --git a/OsmAnd/res/layout-v26/simple_map_widget_medium.xml b/OsmAnd/res/layout-v26/simple_map_widget_medium.xml index 08c8ef3d3a3..f743aa1264e 100644 --- a/OsmAnd/res/layout-v26/simple_map_widget_medium.xml +++ b/OsmAnd/res/layout-v26/simple_map_widget_medium.xml @@ -75,6 +75,7 @@ android:autoSizeMinTextSize="@dimen/simple_widget_value_minimum_size" android:autoSizeStepGranularity="2sp" android:autoSizeTextType="uniform" + android:includeFontPadding="false" android:ellipsize="none" android:gravity="center_vertical|start" android:letterSpacing="0.04" diff --git a/ui-test-app/app/.gitignore b/ui-test-app/app/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/ui-test-app/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/ui-test-app/app/build.gradle.kts b/ui-test-app/app/build.gradle.kts new file mode 100644 index 00000000000..24fa65670a2 --- /dev/null +++ b/ui-test-app/app/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "com.corwin.testtrx" + compileSdk = 34 + + defaultConfig { + applicationId = "com.corwin.testtrx" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/ui-test-app/app/proguard-rules.pro b/ui-test-app/app/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/ui-test-app/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/ui-test-app/app/src/main/AndroidManifest.xml b/ui-test-app/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..7bda9411f1e --- /dev/null +++ b/ui-test-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui-test-app/app/src/main/java/com/corwin/testtrx/MainActivity.java b/ui-test-app/app/src/main/java/com/corwin/testtrx/MainActivity.java new file mode 100644 index 00000000000..3c0858f96d9 --- /dev/null +++ b/ui-test-app/app/src/main/java/com/corwin/testtrx/MainActivity.java @@ -0,0 +1,52 @@ +package com.corwin.testtrx; + +import androidx.annotation.Dimension; +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + findViewById(R.id.apply_btn).setOnClickListener(view -> { + boolean includeFontPadding = ((CheckBox)findViewById(R.id.include_font_padding)).isChecked(); + TextView txt1 = (TextView) findViewById(R.id.txt1); + TextView txt2 = (TextView) findViewById(R.id.txt2); + txt1.setIncludeFontPadding(includeFontPadding); + txt2.setIncludeFontPadding(includeFontPadding); + + int fontSize = Integer.parseInt(((EditText)findViewById(R.id.font_size)).getText().toString()); + txt1.setTextSize(Dimension.SP, fontSize); + txt2.setTextSize(Dimension.SP, fontSize); + + int minFontSize = Integer.parseInt(((EditText)findViewById(R.id.font_size_min)).getText().toString()); + int maxFontSize = Integer.parseInt(((EditText)findViewById(R.id.font_size_max)).getText().toString()); + + if(maxFontSize <= minFontSize) { + Toast.makeText(getBaseContext(), "Should be maxFontSize > minFontSize", Toast.LENGTH_LONG).show(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + txt1.setAutoSizeTextTypeUniformWithConfiguration(minFontSize, maxFontSize, 1, Dimension.SP); + } + } + + String value = ((EditText)findViewById(R.id.value)).getText().toString(); + txt1.setText(value); + txt2.setText(value); + + }); + + + + } +} \ No newline at end of file diff --git a/ui-test-app/app/src/main/res/drawable/ic_launcher_background.xml b/ui-test-app/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000000..4e12b433a2a --- /dev/null +++ b/ui-test-app/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-test-app/app/src/main/res/drawable/ic_launcher_foreground.xml b/ui-test-app/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000000..c6aee64ecea --- /dev/null +++ b/ui-test-app/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ui-test-app/app/src/main/res/layout/activity_main.xml b/ui-test-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..352338148aa --- /dev/null +++ b/ui-test-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +