From 410ba797f129ddacf1c677394dde91114bf30fae Mon Sep 17 00:00:00 2001 From: simonpoole Date: Thu, 14 Sep 2023 20:30:37 +0200 Subject: [PATCH 1/3] Remember last GPX layer style preferences This retains the last values for stroke width, label source, label symbol and min. label zoom as defaults for the next layer. Fixes https://github.com/MarcusWolschon/osmeditor4android/issues/2047 --- src/main/java/de/blau/android/Splash.java | 2 + .../java/de/blau/android/dialogs/Layers.java | 1 - .../de/blau/android/layer/gpx/MapOverlay.java | 39 ++++- .../de/blau/android/prefs/Preferences.java | 153 ++++++++++++++---- .../de/blau/android/resources/DataStyle.java | 40 +++-- src/main/res/values/prefkeys.xml | 5 + 6 files changed, 182 insertions(+), 58 deletions(-) diff --git a/src/main/java/de/blau/android/Splash.java b/src/main/java/de/blau/android/Splash.java index f1b3155a87..fd2be352f0 100644 --- a/src/main/java/de/blau/android/Splash.java +++ b/src/main/java/de/blau/android/Splash.java @@ -126,7 +126,9 @@ protected Void doInBackground(Void param) { } // read Presets here to avoid reading them on UI thread on startup of Main Progress.showDialog(Splash.this, Progress.PROGRESS_LOADING_PRESET); + Log.d(DEBUG_TAG, "Initial preset load"); App.getCurrentPresets(Splash.this); + Log.d(DEBUG_TAG, "Preset load finished"); // Intent intent = new Intent(Splash.this, Main.class); intent.putExtra(SHORTCUT_EXTRAS_KEY, shortcutExtras); diff --git a/src/main/java/de/blau/android/dialogs/Layers.java b/src/main/java/de/blau/android/dialogs/Layers.java index 7252e82939..f160fe0dcd 100644 --- a/src/main/java/de/blau/android/dialogs/Layers.java +++ b/src/main/java/de/blau/android/dialogs/Layers.java @@ -436,7 +436,6 @@ private void addStyleableLayerFromUri(@NonNull final FragmentActivity activity, map.setUpLayers(activity); layer = (de.blau.android.layer.StyleableLayer) map.getLayer(type, uriString); if (layer != null) { // if null setUpLayers will have toasted - layer.resetStyling(); LayerStyle.showDialog(activity, layer.getIndex()); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); layer.invalidate(); diff --git a/src/main/java/de/blau/android/layer/gpx/MapOverlay.java b/src/main/java/de/blau/android/layer/gpx/MapOverlay.java index 46c0fbb8c7..b0e7c57e10 100644 --- a/src/main/java/de/blau/android/layer/gpx/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/gpx/MapOverlay.java @@ -49,6 +49,7 @@ import de.blau.android.osm.BoundingBox; import de.blau.android.osm.Server; import de.blau.android.osm.ViewBox; +import de.blau.android.prefs.Preferences; import de.blau.android.resources.DataStyle; import de.blau.android.resources.DataStyle.FeatureStyle; import de.blau.android.resources.symbols.TriangleDown; @@ -114,7 +115,8 @@ public class MapOverlay extends StyleableLayer public MapOverlay(@NonNull final Map map, @NonNull String contentId) { this.map = map; this.contentId = contentId; - resetStyling(); + final Preferences prefs = map.getPrefs(); + initStyling(prefs.getGpxStrokeWidth(), prefs.getGpxLabelSource(), prefs.getGpxLabelMinZoom(), prefs.getGpxSynbol()); // the following can only be changed in the DataStyle FeatureStyle fs = DataStyle.getInternal(DataStyle.LABELTEXT_NORMAL); fontPaint = fs.getPaint(); @@ -122,8 +124,7 @@ public MapOverlay(@NonNull final Map map, @NonNull String contentId) { labelBackground = DataStyle.getInternal(DataStyle.LABELTEXT_BACKGROUND).getPaint(); yOffset = 2 * fontPaint.getStrokeWidth() + iconRadius; Context context = map.getContext(); - labelKey = context.getString(R.string.gpx_automatic); // default, don't have a context in resetStyling - labelList = Arrays.asList(labelKey, context.getString(R.string.gpx_name), context.getString(R.string.gpx_description), + labelList = Arrays.asList(context.getString(R.string.gpx_automatic), context.getString(R.string.gpx_name), context.getString(R.string.gpx_description), context.getString(R.string.gpx_type)); int threadPoolSize = Util.usableProcessors(); @@ -376,24 +377,39 @@ public void setColor(int color) { public void setStrokeWidth(float width) { super.setStrokeWidth(width); wayPointPaint.setStrokeWidth(width); + map.getPrefs().setGpxStrokeWidth(width); } @Override public void resetStyling() { + initStyling(DataStyle.DEFAULT_GPX_STROKE_WIDTH, labelList.get(0), Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); + } + + /** + * Set the styling to the provided values + * + * @param strokeWidth the stroke width + * @param labelKey the source of the label + * @param labelMinZoom min. zoom from on we show the label + * @param symbolName the name of the point symbol + */ + private void initStyling(float strokeWidth, @NonNull String labelKey, int labelMinZoom, @NonNull String symbolName) { paint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GPS_TRACK).getPaint()); wayPointPaint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GPS_POS_FOLLOW).getPaint()); + + paint.setStrokeWidth(strokeWidth); // currently styling always sets the waypoint stroke width to the same as the track - wayPointPaint.setStrokeWidth(paint.getStrokeWidth()); - labelKey = ""; - labelMinZoom = Map.SHOW_LABEL_LIMIT; + wayPointPaint.setStrokeWidth(strokeWidth); + setLabel(labelKey); + setLabelMinZoom(labelMinZoom); iconRadius = map.getIconRadius(); - symbolName = TriangleDown.NAME; - symbolPath = DataStyle.getCurrent().getSymbol(TriangleDown.NAME); + setPointSymbol(symbolName); } @Override public void setLabel(String key) { labelKey = key; + map.getPrefs().setGpxLabelSource(key); } @Override @@ -409,6 +425,7 @@ public String getLabel() { @Override public void setLabelMinZoom(int minZoom) { labelMinZoom = minZoom; + map.getPrefs().setGpxLabelMinZoom(minZoom); } @Override @@ -416,6 +433,12 @@ public int getLabelMinZoom() { return labelMinZoom; } + @Override + public void setPointSymbol(@NonNull String symbol) { + super.setPointSymbol(symbol); + map.getPrefs().setGpxSymbol(symbol); + } + /** * Read a file in GPX format from device * diff --git a/src/main/java/de/blau/android/prefs/Preferences.java b/src/main/java/de/blau/android/prefs/Preferences.java index a6a5f08be2..0379c7ba2a 100755 --- a/src/main/java/de/blau/android/prefs/Preferences.java +++ b/src/main/java/de/blau/android/prefs/Preferences.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; import de.blau.android.App; +import de.blau.android.Map; import de.blau.android.R; import de.blau.android.contract.Urls; import de.blau.android.osm.Capabilities; @@ -23,6 +24,7 @@ import de.blau.android.presets.Preset; import de.blau.android.resources.DataStyle; import de.blau.android.resources.TileLayerSource.Category; +import de.blau.android.resources.symbols.TriangleDown; import de.blau.android.util.BrokenAndroid; import de.blau.android.util.Sound; @@ -124,6 +126,10 @@ public class Preferences { private final boolean useImperialUnits; private final boolean supportPresetLabels; private final int longStringLimit; + private String gpxLabelSource; + private String gpxSymbol; + private int gpxLabelMinZoom; + private float gpxStrokeWidth; private static final String DEFAULT_MAP_PROFILE = "Color Round Nodes"; @@ -192,6 +198,11 @@ public Preferences(@NonNull Context ctx) { gpsTcpSource = prefs.getString(r.getString(R.string.config_gps_source_tcp_key), "127.0.0.1:1958"); gpsDistance = getIntPref(R.string.config_gps_distance_key, 2); gpsInterval = getIntPref(R.string.config_gps_interval_key, 1000); + gpxLabelSource = prefs.getString(r.getString(R.string.config_gpx_label_source_key), r.getString(R.string.gpx_automatic)); + gpxSymbol = prefs.getString(r.getString(R.string.config_gpx_symbol_key), TriangleDown.NAME); + gpxLabelMinZoom = getIntPref(R.string.config_gpx_label_min_zoom_key, Map.SHOW_LABEL_LIMIT); + // DataStyle may not be available here, so use a constant for the default + gpxStrokeWidth = prefs.getFloat(r.getString(R.string.config_gpx_stroke_width_key), DataStyle.DEFAULT_GPX_STROKE_WIDTH); forceContextMenu = prefs.getBoolean(r.getString(R.string.config_forceContextMenu_key), false); @@ -296,39 +307,6 @@ public Preferences(@NonNull Context ctx) { longStringLimit = getIntPref(R.string.config_longStringLimit_key, Capabilities.DEFAULT_MAX_STRING_LENGTH); } - /** - * Get an integer valued preference from a string pref - * - * @param keyResId the res id - * @param def default value - * @return the stored preference or the default if none found - */ - private int getIntFromStringPref(int keyResId, int def) { - try { - String temp = prefs.getString(r.getString(keyResId), Integer.toString(def)); - return Integer.parseInt(temp); - } catch (ClassCastException | NumberFormatException e) { - return def; - } - } - - /** - * Get an integer valued preference - * - * @param keyResId the res id - * @param def default value - * @return the stored preference or the default if none found - */ - int getIntPref(int keyResId, int def) { - String key = r.getString(keyResId); - try { - return prefs.getInt(r.getString(keyResId), def); - } catch (ClassCastException e) { - Log.w(DEBUG_TAG, "error retrieving pref for " + key); - return def; - } - } - /** * @return the maximum width of a stroke */ @@ -1671,6 +1649,115 @@ public int getLongStringLimit() { return longStringLimit; } + /** + * Get the current default GPX label source + * + * @return a String with the default source name + */ + public String getGpxLabelSource() { + return gpxLabelSource; + } + + /** + * Set the current default GPX label source + * + * @param labelSource the default label source + */ + public void setGpxLabelSource(@NonNull String labelSource) { + gpxLabelSource = labelSource; + putString(R.string.config_gpx_label_source_key, labelSource); + } + + /** + * Get the current default GPX waypoint symbol + * + * @return a String with the default symbol name + */ + public String getGpxSynbol() { + return gpxSymbol; + } + + /** + * Set the current GPX waypoint symbol + * + * @param symbol the symbol name + */ + public void setGpxSymbol(@NonNull String symbol) { + gpxSymbol = symbol; + putString(R.string.config_gpx_symbol_key, gpxSymbol); + } + + /** + * Get the current default GPX min. zoom level to display labels at + * + * @param the default GPX min. zoom level to display labels at + */ + public int getGpxLabelMinZoom() { + return gpxLabelMinZoom; + } + + /** + * Set the current default GPX min. zoom level to display labels at + * + * @param the deault GPX min. zoom level to display labels at + */ + public void setGpxLabelMinZoom(int zoom) { + gpxLabelMinZoom = zoom; + prefs.edit().putInt(r.getString(R.string.config_gpx_label_min_zoom_key), gpxLabelMinZoom).commit(); + } + + /** + * Get the current default GPX stroke width + * + * @param the GPX default stroke width + */ + public float getGpxStrokeWidth() { + return gpxStrokeWidth; + } + + /** + * Set the current default GPX stroke width + * + * @param the default GPX stroke width + */ + public void setGpxStrokeWidth(float width) { + gpxStrokeWidth = width; + prefs.edit().putFloat(r.getString(R.string.config_gpx_stroke_width_key), gpxStrokeWidth).commit(); + } + + /** + * Get an integer valued preference from a string pref + * + * @param keyResId the res id + * @param def default value + * @return the stored preference or the default if none found + */ + private int getIntFromStringPref(int keyResId, int def) { + try { + String temp = prefs.getString(r.getString(keyResId), Integer.toString(def)); + return Integer.parseInt(temp); + } catch (ClassCastException | NumberFormatException e) { + return def; + } + } + + /** + * Get an integer valued preference + * + * @param keyResId the res id + * @param def default value + * @return the stored preference or the default if none found + */ + int getIntPref(int keyResId, int def) { + String key = r.getString(keyResId); + try { + return prefs.getInt(r.getString(keyResId), def); + } catch (ClassCastException e) { + Log.w(DEBUG_TAG, "error retrieving pref for " + key); + return def; + } + } + /** * Get a string from shared preferences * diff --git a/src/main/java/de/blau/android/resources/DataStyle.java b/src/main/java/de/blau/android/resources/DataStyle.java index c4c88a72e2..fef8449fad 100644 --- a/src/main/java/de/blau/android/resources/DataStyle.java +++ b/src/main/java/de/blau/android/resources/DataStyle.java @@ -61,13 +61,12 @@ import de.blau.android.resources.symbols.Symbols; import de.blau.android.util.Density; import de.blau.android.util.FileUtil; -import de.blau.android.util.SavingHelper; import de.blau.android.util.Snack; import de.blau.android.util.Version; import de.blau.android.util.XmlFileFilter; public final class DataStyle extends DefaultHandler { - private static final String DEBUG_TAG = "DataStyle"; + private static final String DEBUG_TAG = DataStyle.class.getSimpleName(); private static final String I18N_DATASTYLE = "i18n/datastyle_"; @@ -172,7 +171,8 @@ public final class DataStyle extends DefaultHandler { private static final String OFFSET_ATTR = "offset"; private static final String TEXT_COLOR_ATTR = "textColor"; - private static final int DEFAULT_MIN_VISIBLE_ZOOM = 15; + private static final int DEFAULT_MIN_VISIBLE_ZOOM = 15; + public static final float DEFAULT_GPX_STROKE_WIDTH = 4.0f; public class FeatureStyle { @@ -980,7 +980,7 @@ private void init() { fp = new FeatureStyle(GPS_POS, internalStyles.get(GPS_TRACK)); fp.getPaint().setStyle(Style.FILL); - fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, 4.0f)); + fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, DEFAULT_GPX_STROKE_WIDTH)); fp.setUpdateWidth(false); internalStyles.put(GPS_POS, fp); @@ -990,7 +990,7 @@ private void init() { fp = new FeatureStyle(GPS_POS_STALE, baseWayStyle); fp.getPaint().setStyle(Style.FILL); - fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, 4.0f)); + fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, DEFAULT_GPX_STROKE_WIDTH)); fp.setUpdateWidth(false); internalStyles.put(GPS_POS_STALE, fp); @@ -1429,15 +1429,7 @@ public static String[] getStyleList(@NonNull Context context) { @NonNull public static String[] getStyleListTranslated(@NonNull Context context, @NonNull String[] styleNames) { Locale locale = Locale.getDefault(); - String language = locale.getLanguage(); - InputStream poFileStream = null; - try { - AssetManager assetManager = context.getAssets(); - try { - poFileStream = assetManager.open(I18N_DATASTYLE + locale + "." + FileExtensions.PO); - } catch (IOException ioex) { - poFileStream = assetManager.open(I18N_DATASTYLE + language + "." + FileExtensions.PO); - } + try (InputStream poFileStream = getPoFileStream(context, locale)) { Po po = de.blau.android.util.Util.parsePoFile(poFileStream); if (po != null) { int len = styleNames.length; @@ -1453,8 +1445,24 @@ public static String[] getStyleListTranslated(@NonNull Context context, @NonNull } catch (IOException ioex) { Log.w(DEBUG_TAG, "No translations found for " + locale); return styleNames; - } finally { - SavingHelper.close(poFileStream); + } + } + + /** + * Get a stream for the translation + * + * @param context an Android Context + * @param locale the relevant Locale + * @return an InputStream + * @throws IOException if no file could be opened or read + */ + @NonNull + private static InputStream getPoFileStream(Context context, Locale locale) throws IOException { + AssetManager assetManager = context.getAssets(); + try { + return assetManager.open(I18N_DATASTYLE + locale + "." + FileExtensions.PO); + } catch (IOException ioex) { + return assetManager.open(I18N_DATASTYLE + locale.getLanguage() + "." + FileExtensions.PO); } } diff --git a/src/main/res/values/prefkeys.xml b/src/main/res/values/prefkeys.xml index e1ce79bc66..c5239b57af 100644 --- a/src/main/res/values/prefkeys.xml +++ b/src/main/res/values/prefkeys.xml @@ -10,6 +10,11 @@ backgroundCategory overlayCategory waySnap + gpxLabelSource + gpxSymbol + gpxLabelMinZoom + gpxStrokeWidth + showStats showTolerance From 5c85dad729b623170d71fd72356663e16e3e99c3 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Fri, 15 Sep 2023 15:23:14 +0200 Subject: [PATCH 2/3] Remember last GeoJSON layer style preferences This retains the last values for stroke width, label source, label symbol and min. label zoom as defaults for the next layer. --- .../android/layer/geojson/MapOverlay.java | 72 +++++++++++----- .../de/blau/android/prefs/Preferences.java | 85 +++++++++++++++++++ .../de/blau/android/resources/DataStyle.java | 7 +- src/main/res/values/prefkeys.xml | 4 + 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/src/main/java/de/blau/android/layer/geojson/MapOverlay.java b/src/main/java/de/blau/android/layer/geojson/MapOverlay.java index de5f84f8df..51deb22b55 100644 --- a/src/main/java/de/blau/android/layer/geojson/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/geojson/MapOverlay.java @@ -60,6 +60,7 @@ import de.blau.android.osm.OsmXml; import de.blau.android.osm.Server; import de.blau.android.osm.ViewBox; +import de.blau.android.prefs.Preferences; import de.blau.android.resources.DataStyle; import de.blau.android.resources.DataStyle.FeatureStyle; import de.blau.android.resources.symbols.TriangleDown; @@ -194,7 +195,8 @@ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassN */ public MapOverlay(final Map map) { this.map = map; - resetStyling(); + final Preferences prefs = map.getPrefs(); + initStyling(prefs.getGeoJsonStrokeWidth(), prefs.getGeoJsonLabelSource(), prefs.getGeoJsonLabelMinZoom(), prefs.getGeoJsonSynbol()); } @Override @@ -707,41 +709,62 @@ public List getFeatures() { @Override public void resetStyling() { + initStyling(DataStyle.DEFAULT_GEOJSON_STROKE_WIDTH, "", Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); + } + + /** + * Init the styling to the provided values + * + * @param strokeWidth the stroke width + * @param labelKey the source of the label + * @param labelMinZoom min. zoom from on we show the label + * @param symbolName the name of the point symbol + */ + private void initStyling(float strokeWidth, @NonNull String labelKey, int labelMinZoom, String symbolName) { paint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GEOJSON_DEFAULT).getPaint()); - labelKey = ""; - labelMinZoom = Map.SHOW_LABEL_LIMIT; + setStrokeWidth(strokeWidth); + setLabel(labelKey); + setLabelMinZoom(labelMinZoom); iconRadius = map.getIconRadius(); + setPointSymbol(symbolName); marker = DataStyle.getCurrent().getSymbol(TriangleDown.NAME); } @Override public List getLabelList() { - if (data != null) { - Collection queryResult = new ArrayList<>(); - data.query(queryResult); - Set result = new TreeSet<>(); - for (BoundedFeature bf : queryResult) { - Feature feature = bf.getFeature(); - if (feature != null) { - JsonObject properties = feature.properties(); - if (properties != null) { - for (String key : properties.keySet()) { - JsonElement e = properties.get(key); - if (e != null && e.isJsonPrimitive()) { - result.add(key); - } - } - } + if (data == null) { + return super.getLabelList(); + } + Collection queryResult = new ArrayList<>(); + data.query(queryResult); + Set result = new TreeSet<>(); + for (BoundedFeature bf : queryResult) { + Feature feature = bf.getFeature(); + JsonObject properties = feature != null ? feature.properties() : null; + if (properties == null) { + continue; + } + for (String key : properties.keySet()) { + JsonElement e = properties.get(key); + if (e != null && e.isJsonPrimitive()) { + result.add(key); } } - return new ArrayList<>(result); + } - return super.getLabelList(); + return new ArrayList<>(result); + } + + @Override + public void setStrokeWidth(float width) { + super.setStrokeWidth(width); + map.getPrefs().setGeoJsonStrokeWidth(width); } @Override public void setLabel(String key) { labelKey = key; + map.getPrefs().setGeoJsonLabelSource(key); } @Override @@ -752,6 +775,7 @@ public String getLabel() { @Override public void setLabelMinZoom(int minZoom) { labelMinZoom = minZoom; + map.getPrefs().setGeoJsonLabelMinZoom(minZoom); } @Override @@ -759,6 +783,12 @@ public int getLabelMinZoom() { return labelMinZoom; } + @Override + public void setPointSymbol(@NonNull String symbol) { + super.setPointSymbol(symbol); + map.getPrefs().setGeoJsonSymbol(symbol); + } + /** * Get the label value for this Feature * diff --git a/src/main/java/de/blau/android/prefs/Preferences.java b/src/main/java/de/blau/android/prefs/Preferences.java index 0379c7ba2a..cd785ee09a 100755 --- a/src/main/java/de/blau/android/prefs/Preferences.java +++ b/src/main/java/de/blau/android/prefs/Preferences.java @@ -130,6 +130,10 @@ public class Preferences { private String gpxSymbol; private int gpxLabelMinZoom; private float gpxStrokeWidth; + private String geoJsonSymbol; + private String geoJsonLabelSource; + private int geoJsonLabelMinZoom; + private float geoJsonStrokeWidth; private static final String DEFAULT_MAP_PROFILE = "Color Round Nodes"; @@ -203,6 +207,11 @@ public Preferences(@NonNull Context ctx) { gpxLabelMinZoom = getIntPref(R.string.config_gpx_label_min_zoom_key, Map.SHOW_LABEL_LIMIT); // DataStyle may not be available here, so use a constant for the default gpxStrokeWidth = prefs.getFloat(r.getString(R.string.config_gpx_stroke_width_key), DataStyle.DEFAULT_GPX_STROKE_WIDTH); + geoJsonLabelSource = prefs.getString(r.getString(R.string.config_geojson_label_source_key), ""); + geoJsonSymbol = prefs.getString(r.getString(R.string.config_geojson_symbol_key), TriangleDown.NAME); + geoJsonLabelMinZoom = getIntPref(R.string.config_geojson_label_min_zoom_key, Map.SHOW_LABEL_LIMIT); + // DataStyle may not be available here, so use a constant for the default + geoJsonStrokeWidth = prefs.getFloat(r.getString(R.string.config_geojson_stroke_width_key), DataStyle.DEFAULT_GEOJSON_STROKE_WIDTH); forceContextMenu = prefs.getBoolean(r.getString(R.string.config_forceContextMenu_key), false); @@ -1725,6 +1734,82 @@ public void setGpxStrokeWidth(float width) { prefs.edit().putFloat(r.getString(R.string.config_gpx_stroke_width_key), gpxStrokeWidth).commit(); } + /** + * Get the current default GeoJSON label source + * + * @return a String with the default source name + */ + public String getGeoJsonLabelSource() { + return geoJsonLabelSource; + } + + /** + * Set the current default GeoJSON label source + * + * @param labelSource the default label source + */ + public void setGeoJsonLabelSource(@NonNull String labelSource) { + geoJsonLabelSource = labelSource; + putString(R.string.config_geojson_label_source_key, labelSource); + } + + /** + * Get the current default GeoJSON point symbol + * + * @return a String with the default symbol name + */ + public String getGeoJsonSynbol() { + return geoJsonSymbol; + } + + /** + * Set the current GeoJSON point symbol + * + * @param symbol the symbol name + */ + public void setGeoJsonSymbol(@NonNull String symbol) { + geoJsonSymbol = symbol; + putString(R.string.config_geojson_symbol_key, symbol); + } + + /** + * Get the current default GeoJSON min. zoom level to display labels at + * + * @param the default GeoJSON min. zoom level to display labels at + */ + public int getGeoJsonLabelMinZoom() { + return geoJsonLabelMinZoom; + } + + /** + * Set the current default GeoJSON min. zoom level to display labels at + * + * @param the deault GeoJSON min. zoom level to display labels at + */ + public void setGeoJsonLabelMinZoom(int zoom) { + geoJsonLabelMinZoom = zoom; + prefs.edit().putInt(r.getString(R.string.config_geojson_label_min_zoom_key), zoom).commit(); + } + + /** + * Get the current default GeoJSON stroke width + * + * @param the GeoJSON default stroke width + */ + public float getGeoJsonStrokeWidth() { + return geoJsonStrokeWidth; + } + + /** + * Set the current default GeoJSON stroke width + * + * @param the default GeoJSON stroke width + */ + public void setGeoJsonStrokeWidth(float width) { + geoJsonStrokeWidth = width; + prefs.edit().putFloat(r.getString(R.string.config_geojson_stroke_width_key), width).commit(); + } + /** * Get an integer valued preference from a string pref * diff --git a/src/main/java/de/blau/android/resources/DataStyle.java b/src/main/java/de/blau/android/resources/DataStyle.java index fef8449fad..7a37c7b299 100644 --- a/src/main/java/de/blau/android/resources/DataStyle.java +++ b/src/main/java/de/blau/android/resources/DataStyle.java @@ -171,8 +171,9 @@ public final class DataStyle extends DefaultHandler { private static final String OFFSET_ATTR = "offset"; private static final String TEXT_COLOR_ATTR = "textColor"; - private static final int DEFAULT_MIN_VISIBLE_ZOOM = 15; - public static final float DEFAULT_GPX_STROKE_WIDTH = 4.0f; + private static final int DEFAULT_MIN_VISIBLE_ZOOM = 15; + public static final float DEFAULT_GPX_STROKE_WIDTH = 4.0f; + public static final float DEFAULT_GEOJSON_STROKE_WIDTH = 3.0f; public class FeatureStyle { @@ -1158,7 +1159,7 @@ private void init() { fp.getPaint().setStyle(Style.STROKE); fp.setColor(0x9d00ff00); fp.setWidthFactor(2f); - fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, 3.0f)); + fp.getPaint().setStrokeWidth(Density.dpToPx(ctx, DEFAULT_GEOJSON_STROKE_WIDTH)); fp.setUpdateWidth(false); internalStyles.put(GEOJSON_DEFAULT, fp); diff --git a/src/main/res/values/prefkeys.xml b/src/main/res/values/prefkeys.xml index c5239b57af..351846b6f5 100644 --- a/src/main/res/values/prefkeys.xml +++ b/src/main/res/values/prefkeys.xml @@ -14,6 +14,10 @@ gpxSymbol gpxLabelMinZoom gpxStrokeWidth + geoJsonLabelSource + geoJsonSymbol + geoJsonLabelMinZoom + geoJsonStrokeWidth showStats From 8eb68d8df08a71bace557410a404fa7375c221f7 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Sat, 16 Sep 2023 16:21:10 +0200 Subject: [PATCH 3/3] Automatically choose a new color for additional layers of the same type Fixes https://github.com/MarcusWolschon/osmeditor4android/issues/2197 --- src/main/java/de/blau/android/Map.java | 20 ++++++- .../android/layer/StyleableFileLayer.java | 59 +++++++++++++++++++ .../android/layer/geojson/MapOverlay.java | 47 +++++++-------- .../de/blau/android/layer/gpx/MapOverlay.java | 57 ++++++++---------- .../java/de/blau/android/util/ColorUtil.java | 16 +++++ 5 files changed, 139 insertions(+), 60 deletions(-) create mode 100644 src/main/java/de/blau/android/layer/StyleableFileLayer.java diff --git a/src/main/java/de/blau/android/Map.java b/src/main/java/de/blau/android/Map.java index be0943ed80..4739ce0fc1 100755 --- a/src/main/java/de/blau/android/Map.java +++ b/src/main/java/de/blau/android/Map.java @@ -266,7 +266,7 @@ public void setUpLayers(@NonNull Context ctx) { layer = new de.blau.android.layer.tasks.MapOverlay(this); break; case GEOJSON: - layer = new de.blau.android.layer.geojson.MapOverlay(this); + layer = new de.blau.android.layer.geojson.MapOverlay(this, contentId); if (!((de.blau.android.layer.geojson.MapOverlay) layer).loadGeoJsonFile(ctx, Uri.parse(contentId), true)) { // other error, has already been toasted layer = null; // this will delete the layer @@ -376,6 +376,24 @@ private List getLayers(@NonNull LayerType type, @Nullable String c return result; } + /** + * Get a count of layers of a specific type + * + * @param type the LayerType + * @return a count + */ + public int getLayerTypeCount(@NonNull LayerType type) { + int count = 0; + synchronized (mLayers) { + for (MapViewLayer l : mLayers) { + if (l.getType().equals(type)) { + count++; + } + } + } + return count; + } + /** * Get the list of configured layers * diff --git a/src/main/java/de/blau/android/layer/StyleableFileLayer.java b/src/main/java/de/blau/android/layer/StyleableFileLayer.java new file mode 100644 index 0000000000..00487050f9 --- /dev/null +++ b/src/main/java/de/blau/android/layer/StyleableFileLayer.java @@ -0,0 +1,59 @@ +package de.blau.android.layer; + +import java.io.InputStream; + +import android.content.Context; +import android.net.Uri; +import androidx.annotation.NonNull; +import de.blau.android.contract.FileExtensions; +import de.blau.android.util.Hash; + +/** + * StyleableLayer that is loaded from a file + * + * @author simon + * + */ +public abstract class StyleableFileLayer extends StyleableLayer { + + private static final long serialVersionUID = 1L; + + /** + * State file file name + */ + protected String stateFileName; + + protected String contentId; // could potentially be transient + + protected StyleableFileLayer(@NonNull String contentId, String defaultStateFileName) { + this.contentId = contentId; + this.stateFileName = defaultStateFileName; + } + + /** + * Check if we have a state file + * + * @param context an Android Context + * @return true if a state file exists + */ + protected boolean hasStateFile(@NonNull Context context) { + setStateFileName(Uri.parse(contentId).getEncodedPath()); + try (InputStream stream = context.openFileInput(stateFileName)) { + return true; + } catch (Exception ex) { + return false; + } + } + + /** + * Set the name of the state file + * + * This needs to be unique across all instances so best an encoded uri, to avoid filename length issues we use the + * SHA-256 hash + * + * @param baseName the base name for this specific instance + */ + protected void setStateFileName(@NonNull String baseName) { + stateFileName = Hash.sha256(baseName) + "." + FileExtensions.RES; + } +} diff --git a/src/main/java/de/blau/android/layer/geojson/MapOverlay.java b/src/main/java/de/blau/android/layer/geojson/MapOverlay.java index 51deb22b55..3b5b6bd2d0 100644 --- a/src/main/java/de/blau/android/layer/geojson/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/geojson/MapOverlay.java @@ -55,6 +55,7 @@ import de.blau.android.layer.LabelMinZoomInterface; import de.blau.android.layer.LayerInfoInterface; import de.blau.android.layer.LayerType; +import de.blau.android.layer.StyleableFileLayer; import de.blau.android.layer.StyleableLayer; import de.blau.android.osm.BoundingBox; import de.blau.android.osm.OsmXml; @@ -64,13 +65,13 @@ import de.blau.android.resources.DataStyle; import de.blau.android.resources.DataStyle.FeatureStyle; import de.blau.android.resources.symbols.TriangleDown; +import de.blau.android.util.ColorUtil; import de.blau.android.util.ContentResolverUtil; import de.blau.android.util.ExecutorTask; import de.blau.android.util.FileUtil; import de.blau.android.util.GeoJSONConstants; import de.blau.android.util.GeoJson; import de.blau.android.util.GeoMath; -import de.blau.android.util.Hash; import de.blau.android.util.SavingHelper; import de.blau.android.util.SerializableTextPaint; import de.blau.android.util.Snack; @@ -79,7 +80,7 @@ import de.blau.android.util.rtree.RTree; import de.blau.android.views.IMapView; -public class MapOverlay extends StyleableLayer +public class MapOverlay extends StyleableFileLayer implements Serializable, ExtentInterface, DiscardInterface, ClickableInterface, LayerInfoInterface, LabelMinZoomInterface { private static final long serialVersionUID = 4L; @@ -183,20 +184,20 @@ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassN */ private String uri; - /** - * State file file name - */ - private String stateFileName = FILENAME; - /** * Construct this layer * * @param map the Map object we are displayed on + * @param contentId the id for the current contents */ - public MapOverlay(final Map map) { + public MapOverlay(@NonNull final Map map, @NonNull String contentId) { + super(contentId, FILENAME); this.map = map; final Preferences prefs = map.getPrefs(); - initStyling(prefs.getGeoJsonStrokeWidth(), prefs.getGeoJsonLabelSource(), prefs.getGeoJsonLabelMinZoom(), prefs.getGeoJsonSynbol()); + initStyling(!hasStateFile(map.getContext()), prefs.getGeoJsonStrokeWidth(), prefs.getGeoJsonLabelSource(), prefs.getGeoJsonLabelMinZoom(), + prefs.getGeoJsonSynbol()); + paint.setColor( + ColorUtil.generateColor(map.getLayerTypeCount(LayerType.GEOJSON), 9, DataStyle.getInternal(DataStyle.GEOJSON_DEFAULT).getPaint().getColor())); } @Override @@ -420,7 +421,7 @@ protected Boolean doInBackground(Void arg) { if (name == null) { name = uri.getLastPathSegment(); } - setFileName(uri.getEncodedPath()); + setStateFileName(uri.getEncodedPath()); MapOverlay.this.uri = uri.toString(); return loadGeoJsonFile(ctx, is, fromState); } catch (SecurityException sex) { @@ -444,17 +445,6 @@ protected Boolean doInBackground(Void arg) { } } - /** - * Set the name of the state file - * - * This needs to be unique across all instances so best an encoded uri - * - * @param baseName the base name for this specific instance - */ - private void setFileName(@NonNull String baseName) { - stateFileName = Hash.sha256(baseName) + "." + FileExtensions.RES; - } - /** * Read an InputStream containing GeoJSON data in to the layer, replacing any existing data * @@ -709,25 +699,28 @@ public List getFeatures() { @Override public void resetStyling() { - initStyling(DataStyle.DEFAULT_GEOJSON_STROKE_WIDTH, "", Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); + initStyling(true, DataStyle.DEFAULT_GEOJSON_STROKE_WIDTH, "", Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); } /** * Init the styling to the provided values * + * @param style if true set styling * @param strokeWidth the stroke width * @param labelKey the source of the label * @param labelMinZoom min. zoom from on we show the label * @param symbolName the name of the point symbol */ - private void initStyling(float strokeWidth, @NonNull String labelKey, int labelMinZoom, String symbolName) { + private void initStyling(boolean style, float strokeWidth, @NonNull String labelKey, int labelMinZoom, String symbolName) { paint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GEOJSON_DEFAULT).getPaint()); - setStrokeWidth(strokeWidth); - setLabel(labelKey); - setLabelMinZoom(labelMinZoom); iconRadius = map.getIconRadius(); - setPointSymbol(symbolName); marker = DataStyle.getCurrent().getSymbol(TriangleDown.NAME); + if (style) { + setStrokeWidth(strokeWidth); + setLabel(labelKey); + setLabelMinZoom(labelMinZoom); + setPointSymbol(symbolName); + } } @Override diff --git a/src/main/java/de/blau/android/layer/gpx/MapOverlay.java b/src/main/java/de/blau/android/layer/gpx/MapOverlay.java index b0e7c57e10..b3c70ebee3 100644 --- a/src/main/java/de/blau/android/layer/gpx/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/gpx/MapOverlay.java @@ -45,6 +45,7 @@ import de.blau.android.layer.LabelMinZoomInterface; import de.blau.android.layer.LayerInfoInterface; import de.blau.android.layer.LayerType; +import de.blau.android.layer.StyleableFileLayer; import de.blau.android.layer.StyleableLayer; import de.blau.android.osm.BoundingBox; import de.blau.android.osm.Server; @@ -54,10 +55,10 @@ import de.blau.android.resources.DataStyle.FeatureStyle; import de.blau.android.resources.symbols.TriangleDown; import de.blau.android.services.TrackerService; +import de.blau.android.util.ColorUtil; import de.blau.android.util.ContentResolverUtil; import de.blau.android.util.ExecutorTask; import de.blau.android.util.GeoMath; -import de.blau.android.util.Hash; import de.blau.android.util.PlaybackTask; import de.blau.android.util.SavingHelper; import de.blau.android.util.SerializableTextPaint; @@ -66,7 +67,7 @@ import de.blau.android.util.collections.FloatPrimitiveList; import de.blau.android.views.IMapView; -public class MapOverlay extends StyleableLayer +public class MapOverlay extends StyleableFileLayer implements Serializable, ExtentInterface, ClickableInterface, LayerInfoInterface, LabelMinZoomInterface { private static final long serialVersionUID = 5L; // note that this can't actually be serialized as the transient @@ -91,7 +92,6 @@ public class MapOverlay extends StyleableLayer private SerializableTextPaint wayPointPaint; private String labelKey; private int labelMinZoom; - private String contentId; // could potentially be transient private TrackPoint pausedPoint; // way point label styling @@ -101,11 +101,6 @@ public class MapOverlay extends StyleableLayer private final transient Paint fontPaint; private final transient List labelList; - /** - * State file file name - */ - private String stateFileName = FILENAME; - /** * Construct a new GPX layer * @@ -113,17 +108,21 @@ public class MapOverlay extends StyleableLayer * @param contentId the id for the current contents */ public MapOverlay(@NonNull final Map map, @NonNull String contentId) { + super(contentId, FILENAME); this.map = map; - this.contentId = contentId; + Context context = map.getContext(); final Preferences prefs = map.getPrefs(); - initStyling(prefs.getGpxStrokeWidth(), prefs.getGpxLabelSource(), prefs.getGpxLabelMinZoom(), prefs.getGpxSynbol()); + // this is slightly annoying as we need to protect against overwriting already saved state + initStyling(!hasStateFile(context), prefs.getGpxStrokeWidth(), prefs.getGpxLabelSource(), prefs.getGpxLabelMinZoom(), prefs.getGpxSynbol()); + paint.setColor(ColorUtil.generateColor(map.getLayerTypeCount(LayerType.GPX), 9, DataStyle.getInternal(DataStyle.GPS_TRACK).getPaint().getColor())); + // the following can only be changed in the DataStyle FeatureStyle fs = DataStyle.getInternal(DataStyle.LABELTEXT_NORMAL); fontPaint = fs.getPaint(); fm = fs.getFontMetrics(); labelBackground = DataStyle.getInternal(DataStyle.LABELTEXT_BACKGROUND).getPaint(); yOffset = 2 * fontPaint.getStrokeWidth() + iconRadius; - Context context = map.getContext(); + labelList = Arrays.asList(context.getString(R.string.gpx_automatic), context.getString(R.string.gpx_name), context.getString(R.string.gpx_description), context.getString(R.string.gpx_type)); @@ -382,32 +381,35 @@ public void setStrokeWidth(float width) { @Override public void resetStyling() { - initStyling(DataStyle.DEFAULT_GPX_STROKE_WIDTH, labelList.get(0), Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); + initStyling(true, DataStyle.DEFAULT_GPX_STROKE_WIDTH, labelList.get(0), Map.SHOW_LABEL_LIMIT, TriangleDown.NAME); } /** * Set the styling to the provided values * + * @param style if true set styling * @param strokeWidth the stroke width * @param labelKey the source of the label * @param labelMinZoom min. zoom from on we show the label * @param symbolName the name of the point symbol */ - private void initStyling(float strokeWidth, @NonNull String labelKey, int labelMinZoom, @NonNull String symbolName) { + private void initStyling(boolean style, float strokeWidth, @NonNull String labelKey, int labelMinZoom, @NonNull String symbolName) { paint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GPS_TRACK).getPaint()); wayPointPaint = new SerializableTextPaint(DataStyle.getInternal(DataStyle.GPS_POS_FOLLOW).getPaint()); - - paint.setStrokeWidth(strokeWidth); - // currently styling always sets the waypoint stroke width to the same as the track - wayPointPaint.setStrokeWidth(strokeWidth); - setLabel(labelKey); - setLabelMinZoom(labelMinZoom); iconRadius = map.getIconRadius(); - setPointSymbol(symbolName); + if (style) { + paint.setStrokeWidth(strokeWidth); + // currently styling always sets the waypoint stroke width to the same as the track + wayPointPaint.setStrokeWidth(strokeWidth); + setLabel(labelKey); + setLabelMinZoom(labelMinZoom); + setPointSymbol(symbolName); + } } @Override public void setLabel(String key) { + dirty(); labelKey = key; map.getPrefs().setGpxLabelSource(key); } @@ -424,6 +426,7 @@ public String getLabel() { @Override public void setLabelMinZoom(int minZoom) { + dirty(); labelMinZoom = minZoom; map.getPrefs().setGpxLabelMinZoom(minZoom); } @@ -487,20 +490,9 @@ protected Integer doInBackground(Void arg) { } } - /** - * Set the name of the state file - * - * This needs to be unique across all instances so best an encoded uri, to avoid filename length issues we use the - * SHA-256 hash - * - * @param baseName the base name for this specific instance - */ - private void setStateFileName(@NonNull String baseName) { - stateFileName = Hash.sha256(baseName) + "." + FileExtensions.RES; - } - @Override public synchronized boolean save(@NonNull Context context) throws IOException { + Log.d(DEBUG_TAG, "Saving state to " + stateFileName); if (playbackTask != null) { playbackTask.pause(); pausedPoint = playbackTask.getPausedPoint(); @@ -511,6 +503,7 @@ public synchronized boolean save(@NonNull Context context) throws IOException { @Override public synchronized StyleableLayer load(@NonNull Context context) { + Log.d(DEBUG_TAG, "Loading state from " + stateFileName); MapOverlay restoredOverlay = savingHelper.load(context, stateFileName, true); if (restoredOverlay != null) { Log.d(DEBUG_TAG, "read saved state"); diff --git a/src/main/java/de/blau/android/util/ColorUtil.java b/src/main/java/de/blau/android/util/ColorUtil.java index 0381986fea..f4e2d79857 100644 --- a/src/main/java/de/blau/android/util/ColorUtil.java +++ b/src/main/java/de/blau/android/util/ColorUtil.java @@ -1,5 +1,7 @@ package de.blau.android.util; +import androidx.core.graphics.ColorUtils; + public final class ColorUtil { /** @@ -38,4 +40,18 @@ public static int argb(int a, int r, int g, int b) { public static int argb(float alpha, float r, float g, float b) { return argb(Math.round(alpha * 255), Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)); } + + /** + * Generate a color + * + * @param index index of the color to create + * @param steps the number of steps in HSL space + * @param seed the initial color (index == 0 will return this) + * @return a color value + */ + public static int generateColor(int index, int steps, int seed) { + float[] hsl = new float[3]; + ColorUtils.colorToHSL(seed, hsl); + return ColorUtils.HSLToColor(new float[] { (hsl[0] + (index * 360f / steps)) % 360f, hsl[1], hsl[2] }); + } }