From fa609b1bac7edf33aa3640efb3ccc6ad7acbc980 Mon Sep 17 00:00:00 2001 From: Simon Poole Date: Sat, 30 Sep 2023 17:22:55 +0200 Subject: [PATCH] If a custom icon is specified include preset icons in search --- .../blau/android/layer/data/MapOverlay.java | 311 ++++++++++-------- .../java/de/blau/android/presets/Preset.java | 199 ++++++----- .../de/blau/android/resources/DataStyle.java | 10 + 3 files changed, 276 insertions(+), 244 deletions(-) diff --git a/src/main/java/de/blau/android/layer/data/MapOverlay.java b/src/main/java/de/blau/android/layer/data/MapOverlay.java index 29c92963cf..688943ead9 100644 --- a/src/main/java/de/blau/android/layer/data/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/data/MapOverlay.java @@ -101,222 +101,225 @@ public class MapOverlay extends MapViewLayer implements ExtentInterface, ConfigureInterface, LayerInfoInterface, PruneableInterface, UpdateInterface { - private static final String DEBUG_TAG = MapOverlay.class.getName(); + private static final String DEBUG_TAG = MapOverlay.class.getName(); - private static final int THREAD_POOL_SIZE = 2; + private static final int THREAD_POOL_SIZE = 2; - public static final int ICON_SIZE_DP = 20; - private static final int HOUSE_NUMBER_RADIUS = 10; - private static final int ICON_SELECTED_BORDER = 2; - private static final int LABEL_EXTRA = 40; + public static final int ICON_SIZE_DP = 20; + private static final int HOUSE_NUMBER_RADIUS = 10; + private static final int ICON_SELECTED_BORDER = 2; + private static final int LABEL_EXTRA = 40; - private static final long AUTOPRUNE_MIN_INTERVAL = 10000; // milli-seconds between autoprunes - public static final int DEFAULT_AUTOPRUNE_NODE_LIMIT = 5000; - public static final int PAN_AND_ZOOM_LIMIT = 17; - private static final int MP_SIZE_LIMIT = 1000; // max size of MP to render as MP + private static final long AUTOPRUNE_MIN_INTERVAL = 10000; // milli-seconds between + // autoprunes + public static final int DEFAULT_AUTOPRUNE_NODE_LIMIT = 5000; + public static final int PAN_AND_ZOOM_LIMIT = 17; + private static final int MP_SIZE_LIMIT = 1000; // max size of MP to render as + // MP /** half the width/height of a node icon in px */ - private final int iconRadius; - private final int iconSelectedBorder; - private final int houseNumberRadius; - private final int verticalNumberOffset; - private float maxDownloadSpeed; - private int autoPruneNodeLimit = DEFAULT_AUTOPRUNE_NODE_LIMIT; // node count for autoprune - private int panAndZoomLimit = PAN_AND_ZOOM_LIMIT; - - private final StorageDelegator delegator; - private final Context context; - private final Validator validator; - private final Map map; + private final int iconRadius; + private final int iconSelectedBorder; + private final int houseNumberRadius; + private final int verticalNumberOffset; + private float maxDownloadSpeed; + private int autoPruneNodeLimit = DEFAULT_AUTOPRUNE_NODE_LIMIT; // node count for autoprune + private int panAndZoomLimit = PAN_AND_ZOOM_LIMIT; + + private final StorageDelegator delegator; + private final Context context; + private final Validator validator; + private final Map map; /** * Preference related fields */ - private Preferences prefs; + private Preferences prefs; /** * show icons for POIs (in a wide sense of the word) */ - private boolean showIcons = false; + private boolean showIcons = false; /** * show icons for POIs tagged on (closed) ways */ - private boolean showWayIcons = false; + private boolean showWayIcons = false; /** * show tolerance area */ - private boolean showTolerance = true; + private boolean showTolerance = true; /** * Download on pan and zoom */ - private boolean panAndZoomDownLoad = false; + private boolean panAndZoomDownLoad = false; /** * Minimum side length for auto-download boxes */ - private int minDownloadSize = 50; + private int minDownloadSize = 50; /** * Stores icons that apply to a certain "thing". This can be e.g. a node or a SortedMap of tags. */ - private final WeakHashMap, Bitmap> iconCache = new WeakHashMap<>(); + private final WeakHashMap, Bitmap> iconCache = new WeakHashMap<>(); /** - * Stores icons that apply to a certain "thing". This can be e.g. a node or a SortedMap of tags. This stores icons - * for areas + * Stores icons that apply to a certain "thing". This can be e.g. a node or a SortedMap of tags. This stores icons for areas */ - private final WeakHashMap, Bitmap> areaIconCache = new WeakHashMap<>(); + private final WeakHashMap, Bitmap> areaIconCache = new WeakHashMap<>(); /** * Stores strings that apply to a certain "thing". This can be e.g. a node or a SortedMap of tags. */ - private final WeakHashMap, String> labelCache = new WeakHashMap<>(); + private final WeakHashMap, String> labelCache = new WeakHashMap<>(); /** * Stores custom icons */ - private final HashMap customIconCache = new HashMap<>(); + private final HashMap customIconCache = new HashMap<>(); /** Caches if the map is zoomed into edit range during one onDraw pass */ - private boolean tmpDrawingInEditRange; + private boolean tmpDrawingInEditRange; /** Caches the edit mode during one onDraw pass */ - private Mode tmpDrawingEditMode; + private Mode tmpDrawingEditMode; /** Caches the currently selected nodes during one onDraw pass */ - private List tmpDrawingSelectedNodes; + private List tmpDrawingSelectedNodes; /** Caches the currently selected ways during one onDraw pass */ - private List tmpDrawingSelectedWays; + private List tmpDrawingSelectedWays; /** Caches the current "clickable elements" set during one onDraw pass */ - private Set tmpClickableElements; + private Set tmpClickableElements; /** used for highlighting relation members */ - private List tmpDrawingSelectedRelationWays; - private List tmpDrawingSelectedRelationNodes; + private List tmpDrawingSelectedRelationWays; + private List tmpDrawingSelectedRelationNodes; /** * Locked or not */ - private boolean tmpLocked; + private boolean tmpLocked; /** * */ - private ArrayList tmpStyledWays = new ArrayList<>(); - private ArrayList tmpHiddenWays = new ArrayList<>(); + private ArrayList tmpStyledWays = new ArrayList<>(); + private ArrayList tmpHiddenWays = new ArrayList<>(); /** Caches the preset during one onDraw pass */ - private Preset[] tmpPresets; + private Preset[] tmpPresets; /** Last validation color that we used */ - int nodeValidationColor = 0; + int nodeValidationColor = 0; /** Caches the Paint used for node tolerance */ - private Paint nodeTolerancePaint; - private Paint nodeTolerancePaint2; + private Paint nodeTolerancePaint; + private Paint nodeTolerancePaint2; /** Caches the Paint used for way tolerance */ - private Paint wayTolerancePaint; - private Paint wayTolerancePaint2; + private Paint wayTolerancePaint; + private Paint wayTolerancePaint2; - private Paint nodeDragRadiusPaint; + private Paint nodeDragRadiusPaint; /** Cached Node FeatureStyles */ - private FeatureStyle nodeFeatureStyle; - private FeatureStyle nodeFeatureStyleThin; - private FeatureStyle nodeFeatureStyleTagged; - private FeatureStyle nodeFeatureStyleSelected; - private FeatureStyle nodeFeatureStyleThinSelected; - private FeatureStyle nodeFeatureStyleTaggedSelected; - private FeatureStyle nodeFeatureStyleRelation; - private FeatureStyle nodeFeatureStyleThinRelation; - private FeatureStyle nodeFeatureStyleTaggedRelation; - private FeatureStyle nodeFeatureStyleFontRelation; - private FeatureStyle nodeFeatureStyleFontSmallRelation; - private FeatureStyle nodeFeatureStyleProblem; - private FeatureStyle nodeFeatureStyleThinProblem; - private FeatureStyle nodeFeatureStyleTaggedProblem; - private FeatureStyle nodeFeatureStyleFontProblem; - private FeatureStyle nodeFeatureStyleFontSmallProblem; - private FeatureStyle nodeFeatureStyleHidden; - - private float nodeToleranceRadius; + private FeatureStyle nodeFeatureStyle; + private FeatureStyle nodeFeatureStyleThin; + private FeatureStyle nodeFeatureStyleTagged; + private FeatureStyle nodeFeatureStyleSelected; + private FeatureStyle nodeFeatureStyleThinSelected; + private FeatureStyle nodeFeatureStyleTaggedSelected; + private FeatureStyle nodeFeatureStyleRelation; + private FeatureStyle nodeFeatureStyleThinRelation; + private FeatureStyle nodeFeatureStyleTaggedRelation; + private FeatureStyle nodeFeatureStyleFontRelation; + private FeatureStyle nodeFeatureStyleFontSmallRelation; + private FeatureStyle nodeFeatureStyleProblem; + private FeatureStyle nodeFeatureStyleThinProblem; + private FeatureStyle nodeFeatureStyleTaggedProblem; + private FeatureStyle nodeFeatureStyleFontProblem; + private FeatureStyle nodeFeatureStyleFontSmallProblem; + private FeatureStyle nodeFeatureStyleHidden; + + private float nodeToleranceRadius; /** Cached Way FeatureStyles and Paints */ - private FeatureStyle selectedWayStyle; - private Paint wayDirectionPaint; - private FeatureStyle wayFeatureStyleHidden; - private FeatureStyle wayFeatureStyleRelation; - private Paint handlePaint; - private FeatureStyle dontRenderWay; + private FeatureStyle selectedWayStyle; + private Paint wayDirectionPaint; + private FeatureStyle wayFeatureStyleHidden; + private FeatureStyle wayFeatureStyleRelation; + private Paint handlePaint; + private FeatureStyle dontRenderWay; /** Cached label FeatureStyles */ - private FeatureStyle labelTextStyleNormal; - private FeatureStyle labelTextStyleSmall; - private FeatureStyle labelTextStyleNormalSelected; - private FeatureStyle labelTextStyleSmallSelected; + private FeatureStyle labelTextStyleNormal; + private FeatureStyle labelTextStyleSmall; + private FeatureStyle labelTextStyleNormalSelected; + private FeatureStyle labelTextStyleSmallSelected; // other styling params - private int showIconsLimit; - private int showIconLabelZoomLimit; + private int showIconsLimit; + private int showIconLabelZoomLimit; /** cached zoom level, calculated once per onDraw pass **/ - private int zoomLevel = 0; + private int zoomLevel = 0; /** Cache the current filter **/ - private Filter tmpFilter = null; + private Filter tmpFilter = null; /** */ - private boolean inNodeIconZoomRange = false; + private boolean inNodeIconZoomRange = false; /** * We just need one path object */ - private final Path path = new Path(); - private final Path casingPath = new Path(); - private final PathMeasure pm = new PathMeasure(); + private final Path path = new Path(); + private final Path casingPath = new Path(); + private final PathMeasure pm = new PathMeasure(); - private final LongHashSet handles = new LongHashSet(); + private final LongHashSet handles = new LongHashSet(); - private Paint labelBackground; + private Paint labelBackground; - private float[][] coord = null; + private float[][] coord = null; - private final FloatPrimitiveList points = new FloatPrimitiveList(); // allocate these just once - private float[] offsettedCasing = new float[100]; - private final List nodesResult = new LowAllocArrayList<>(1000); - private final List waysResult = new LowAllocArrayList<>(1000); - private final List downloadedBoxes = new LowAllocArrayList<>(); - private final ViewBox viewBox = new ViewBox(); + private final FloatPrimitiveList points = new FloatPrimitiveList(); // allocate these just once + private float[] offsettedCasing = new float[100]; + private final List nodesResult = new LowAllocArrayList<>(1000); + private final List waysResult = new LowAllocArrayList<>(1000); + private final List downloadedBoxes = new LowAllocArrayList<>(); + private final ViewBox viewBox = new ViewBox(); /** * Stuff for multipolygon support Instantiate these objects just once */ - private final List waysOnly = new LowAllocArrayList<>(100); - private final LinkedList tenpMultiPolygonSort = new LinkedList<>(); - private final List> outerRings = new LowAllocArrayList<>(); - private final List> innerRings = new LowAllocArrayList<>(); - private final List> unknownRings = new LowAllocArrayList<>(); - private final SimplePool> ringPool = new SimplePool<>(100); - private final List areaNodes = new LowAllocArrayList<>(); // reversing winding - // and + private final List waysOnly = new LowAllocArrayList<>(100); + private final LinkedList tenpMultiPolygonSort = new LinkedList<>(); + private final List> outerRings = new LowAllocArrayList<>(); + private final List> innerRings = new LowAllocArrayList<>(); + private final List> unknownRings = new LowAllocArrayList<>(); + private final SimplePool> ringPool = new SimplePool<>(100); + private final List areaNodes = new LowAllocArrayList<>(); // reversing winding + // and // assembling - private final Set paintRelations = new HashSet<>(); + private final Set paintRelations = new HashSet<>(); - private OnUpdateListener onUpdateListener; + private OnUpdateListener onUpdateListener; /** * Runnable for downloading data */ - private final Downloader download; + private final Downloader download; - private final ThreadPoolExecutor dataThreadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_POOL_SIZE); - private final ThreadPoolExecutor iconThreadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_POOL_SIZE); + private final ThreadPoolExecutor dataThreadPoolExecutor = (ThreadPoolExecutor) Executors + .newFixedThreadPool(THREAD_POOL_SIZE); + private final ThreadPoolExecutor iconThreadPoolExecutor = (ThreadPoolExecutor) Executors + .newFixedThreadPool(THREAD_POOL_SIZE); /** * Construct a new OSM data layer @@ -391,7 +394,7 @@ protected void download() { box.scale(1.2); // make sides 20% larger box.ensureMinumumSize(minDownloadSize); // enforce a minimum size List bboxes = BoundingBox.newBoxes(bbList, box); - for (BoundingBox b : bboxes) { + for (BoundingBox b:bboxes) { if (b.getWidth() <= 1 || b.getHeight() <= 1) { Log.w(DEBUG_TAG, "getNextCenter very small bb " + b.toString()); continue; @@ -449,7 +452,7 @@ protected void onDraw(Canvas canvas, IMapView osmv) { downloadedBoxes.clear(); viewBox.set(map.getViewBox()); - for (BoundingBox box : delegator.getCurrentStorage().getBoundingBoxes()) { + for (BoundingBox box:delegator.getCurrentStorage().getBoundingBoxes()) { if (box.intersects(viewBox)) { downloadedBoxes.add(box); } @@ -491,7 +494,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { // the following should guarantee that if the selected node is off screen but the handle not, the handle gets // drawn, this isn't perfect because touch areas of other nodes just outside the screen still won't get drawn if (tmpDrawingSelectedNodes != null) { - for (Node n : tmpDrawingSelectedNodes) { + for (Node n:tmpDrawingSelectedNodes) { if (!paintNodes.contains(n)) { paintNodes.add(n); } @@ -512,16 +515,15 @@ private void paintOsmData(@NonNull final Canvas canvas) { List waysToDraw = ways; if (filterMode) { // initial filtering need to happen before relations are processed - for (Node n : paintNodes) { + for (Node n:paintNodes) { tmpFilter.include(n, false); } /* - * Split the ways in to those that we are going to show and those that we hide, rendering is far simpler for - * the later + * Split the ways in to those that we are going to show and those that we hide, rendering is far simpler for the later */ tmpHiddenWays.clear(); tmpStyledWays.clear(); - for (Way w : ways) { + for (Way w:ways) { if (tmpFilter.include(w, tmpDrawingInEditRange && tmpDrawingSelectedWays != null && tmpDrawingSelectedWays.contains(w))) { tmpStyledWays.add(w); } else { @@ -529,7 +531,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { } } // draw hidden ways first - for (Way w : tmpHiddenWays) { + for (Way w:tmpHiddenWays) { paintHiddenWay(canvas, w); } waysToDraw = tmpStyledWays; @@ -537,15 +539,15 @@ private void paintOsmData(@NonNull final Canvas canvas) { // get relations for all nodes and ways paintRelations.clear(); - for (Node n : paintNodes) { + for (Node n:paintNodes) { addRelations(filterMode, n.getParentRelations(), paintRelations); } - for (Way w : ways) { + for (Way w:ways) { addRelations(filterMode, w.getParentRelations(), paintRelations); } // draw MPs first - for (Relation rel : paintRelations) { + for (Relation rel:paintRelations) { String relType = rel.getTagWithKey(Tags.KEY_TYPE); if (Tags.VALUE_MULTIPOLYGON.equals(relType) || Tags.VALUE_BOUNDARY.equals(relType)) { paintMultiPolygon(canvas, viewBox, rel); @@ -558,7 +560,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { Collections.sort(waysToDraw, layerComparator); // ways now - for (Way w : waysToDraw) { + for (Way w:waysToDraw) { paintWay(canvas, w, displayHandles, drawTolerance); } @@ -569,7 +571,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { if (drawTolerance && (coord == null || coord.length < paintNodes.size())) { coord = new float[paintNodes.size()][2]; } - for (Node n : paintNodes) { + for (Node n:paintNodes) { boolean noTolerance = false; int lat = n.getLat(); float y = GeoMath.latE7ToY(screenHeight, screenWidth, viewBox, lat); @@ -601,7 +603,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { } // turn restrictions if (inNodeIconZoomRange && showIcons) { - for (Relation rel : paintRelations) { + for (Relation rel:paintRelations) { if (Tags.VALUE_RESTRICTION.equals(rel.getTagWithKey(Tags.KEY_TYPE))) { paintRestriction(canvas, screenWidth, screenHeight, viewBox, rel); } @@ -622,7 +624,7 @@ private void paintOsmData(@NonNull final Canvas canvas) { * @return true if the coordinates are in one of the downloaded areas */ private boolean isInDownload(int lonE7, int latE7) { - for (BoundingBox bb : downloadedBoxes) { + for (BoundingBox bb:downloadedBoxes) { if (bb.isIn(lonE7, latE7)) { return true; } @@ -642,7 +644,7 @@ private void addRelations(final boolean filterMode, @Nullable final List r : outerRings) { + for (List r:outerRings) { map.pointListToLinePointsArray(points, r); float[] linePoints = points.getArray(); int pointsSize = points.size(); @@ -867,7 +869,7 @@ private void addRing(@NonNull String role, @NonNull List ring) { */ private void paintRestriction(@NonNull final Canvas canvas, int screenWidth, int screenHeight, @NonNull ViewBox viewBox, @NonNull Relation restriction) { List vias = restriction.getMembersWithRole(Tags.ROLE_VIA); - for (RelationMember via : vias) { + for (RelationMember via:vias) { OsmElement v = via.getElement(); if (v instanceof Node) { int lat = ((Node) v).getLat(); @@ -1209,15 +1211,7 @@ private void retrieveIcon(@NonNull OsmElement element, boolean isWay, @NonNull W boolean usePresetIcon = style.usePresetIcon(); if (iconPath != null && !usePresetIcon) { - iconDrawable = customIconCache.get(iconPath); - if (iconDrawable == null && !customIconCache.containsKey(iconPath)) { - try (FileInputStream stream = new FileInputStream(iconPath)) { - iconDrawable = PresetIconManager.bitmapDrawableFromStream(context, ICON_SIZE_DP, stream, PresetIconManager.isSvg(iconPath)); - customIconCache.put(iconPath, iconDrawable); - } catch (IOException e) { - Log.e(DEBUG_TAG, "Icon " + iconPath + " not found"); - } - } + iconDrawable = retrieveCustomIcon(iconPath); } else if (tmpPresets != null) { SortedMap tags = element.getTags(); PresetItem match = null; @@ -1246,6 +1240,45 @@ private void retrieveIcon(@NonNull OsmElement element, boolean isWay, @NonNull W map.postInvalidate(); } + /** + * Get a custom icon + * + * @param iconPath configured icon path + */ + @Nullable + public BitmapDrawable retrieveCustomIcon(String iconPath) { + BitmapDrawable iconDrawable = customIconCache.get(iconPath); + if (iconDrawable != null || customIconCache.containsKey(iconPath)) { + return iconDrawable; + } + String iconDirPath = DataStyle.getCurrent().getIconDirPath(); + if (iconDirPath != null) { + try (FileInputStream stream = new FileInputStream(iconPath)) { + iconDrawable = PresetIconManager.bitmapDrawableFromStream(context, ICON_SIZE_DP, stream, PresetIconManager.isSvg(iconPath)); + customIconCache.put(iconPath, iconDrawable); + return iconDrawable; + } catch (IOException e) { + Log.w(DEBUG_TAG, "Icon " + iconPath + " not found"); + } + } + // search in all presets + for (Preset preset:App.getCurrentPresets(context)) { + if (preset != null) { + PresetIconManager iconManager = preset.getIconManager(context); + iconDrawable = iconManager.getDrawable(iconPath, ICON_SIZE_DP); + if (iconDrawable != null) { + customIconCache.put(iconPath, iconDrawable); + break; + } + } + } + if (iconDrawable == null) { + Log.w(DEBUG_TAG, "Icon " + iconPath + " not found"); + customIconCache.put(iconPath, null); + } + return iconDrawable; + } + /** * Remove everything from the iconCache */ @@ -1559,7 +1592,7 @@ private void paintHandles(@NonNull Canvas canvas) { canvas.save(); float lastX = 0; float lastY = 0; - for (long l : handles.values()) { + for (long l:handles.values()) { // draw handle float x = Float.intBitsToFloat((int) (l >>> 32)); float y = Float.intBitsToFloat((int) (l)); diff --git a/src/main/java/de/blau/android/presets/Preset.java b/src/main/java/de/blau/android/presets/Preset.java index 6e285e3f96..5577b2f18c 100644 --- a/src/main/java/de/blau/android/presets/Preset.java +++ b/src/main/java/de/blau/android/presets/Preset.java @@ -57,108 +57,104 @@ /** * This class loads and represents JOSM preset files. * - * Presets can come from one of three sources: a) the default preset, which is loaded from the default asset locations - * (see below) b) an APK-based preset, which is loaded from an APK c) a downloaded preset, which is downloaded to local - * storage by {@link PresetEditorActivity} + * Presets can come from one of three sources: a) the default preset, which is loaded from the default asset locations (see below) b) an APK-based preset, which + * is loaded from an APK c) a downloaded preset, which is downloaded to local storage by {@link PresetEditorActivity} * - * For APK-based presets, the APK must have a "preset.xml" file in the asset directory, and may have images in the - * "images" subdirectory in the asset directory. A preset is considered APK-based if the constructor receives a package - * name. In the preset editor, use the package name prefixed by the {@link APKPRESET_URLPREFIX} to specify an APK - * preset. + * For APK-based presets, the APK must have a "preset.xml" file in the asset directory, and may have images in the "images" subdirectory in the asset directory. + * A preset is considered APK-based if the constructor receives a package name. In the preset editor, use the package name prefixed by the + * {@link APKPRESET_URLPREFIX} to specify an APK preset. * - * The preset.xml is loaded from the following sources: a) for the default preset, "preset.xml" in the default asset - * locations b) for APK-based presets, "preset.xml" in the APK asset directory c) for downloaded presets, "preset.xml" - * in the preset data directory + * The preset.xml is loaded from the following sources: a) for the default preset, "preset.xml" in the default asset locations b) for APK-based presets, + * "preset.xml" in the APK asset directory c) for downloaded presets, "preset.xml" in the preset data directory * - * Icons referenced in the XML preset definition by relative URL are loaded from the following locations: 1. If a - * package name is given and the APK contains a matching asset, from the asset ("images/" is prepended to the path) 2. - * Otherwise, from the default asset location (see below, "images/" is prepended to the path) + * Icons referenced in the XML preset definition by relative URL are loaded from the following locations: 1. If a package name is given and the APK contains a + * matching asset, from the asset ("images/" is prepended to the path) 2. Otherwise, from the default asset location (see below, "images/" is prepended to the + * path) * - * Icons referenced in the XML preset by a http or https URL are loaded from the presets data directory, where they - * should be placed under a name derived from the URL hash by {@link PresetEditorActivity}. Default and APK presets - * cannot have http/https icons. + * Icons referenced in the XML preset by a http or https URL are loaded from the presets data directory, where they should be placed under a name derived from + * the URL hash by {@link PresetEditorActivity}. Default and APK presets cannot have http/https icons. * - * If an asset needs to be loaded from the default asset locations, the loader checks for the existence of an APK with - * the package name specified in {@link PresetIconManager#EXTERNAL_DEFAULT_ASSETS_PACKAGE}. If this package exists and - * contains a matching asset, it is loaded from there. Otherwise, it is loaded from the Vespucci asset directory. The - * external default assets package just needs an asset directory that can contain a preset.xml and/or image directory. + * If an asset needs to be loaded from the default asset locations, the loader checks for the existence of an APK with the package name specified in + * {@link PresetIconManager#EXTERNAL_DEFAULT_ASSETS_PACKAGE}. If this package exists and contains a matching asset, it is loaded from there. Otherwise, it is + * loaded from the Vespucci asset directory. The external default assets package just needs an asset directory that can contain a preset.xml and/or image + * directory. * * @author Jan Schejbal */ public class Preset implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - static final String COMBO_DELIMITER = ","; - static final String MULTISELECT_DELIMITER = ";"; + static final String COMBO_DELIMITER = ","; + static final String MULTISELECT_DELIMITER = ";"; - private static final String DEFAULT_PRESET_TRANSLATION = "preset_"; + private static final String DEFAULT_PRESET_TRANSLATION = "preset_"; /** name of the preset XML file in a preset directory */ - public static final String PRESETXML = "preset.xml"; - public static final String APKPRESET_URLPREFIX = "apk:"; + public static final String PRESETXML = "preset.xml"; + public static final String APKPRESET_URLPREFIX = "apk:"; // hardwired layout stuff - public static final int SPACING = 5; + public static final int SPACING = 5; // - private static final String DEBUG_TAG = Preset.class.getName(); + private static final String DEBUG_TAG = Preset.class.getName(); /** The directory containing all data (xml, MRU data, images) about this preset */ - private File directory; + private File directory; /** version of the preset */ - private String version; + private String version; /** the short description which is essentially the "name" */ - private String shortDescription; + private String shortDescription; /** the description of the content */ - private String description; + private String description; /** - * Lists items having a tag. The map key is tagkey+"\t"+tagvalue. tagItems.get(tagkey+"\t"+tagvalue) will give you - * all items that have the tag tagkey=tagvalue + * Lists items having a tag. The map key is tagkey+"\t"+tagvalue. tagItems.get(tagkey+"\t"+tagvalue) will give you all items that have the tag + * tagkey=tagvalue */ - private final MultiHashMap tagItems = new MultiHashMap<>(); + private final MultiHashMap tagItems = new MultiHashMap<>(); /** * Lists items that define objects */ - private final MultiHashMap objectItems = new MultiHashMap<>(); + private final MultiHashMap objectItems = new MultiHashMap<>(); /** The root group of the preset, containing all top-level groups and items */ - private PresetGroup rootGroup; + private PresetGroup rootGroup; /** {@link PresetIconManager} used for icon loading */ - private PresetIconManager iconManager; + private PresetIconManager iconManager; /** List of all top level object tags used by this preset */ - private List objectKeys = new ArrayList<>(); + private List objectKeys = new ArrayList<>(); /** Maps all possible keys to the respective values for autosuggest (only key/values applying to nodes) */ - private final MultiHashMap autosuggestNodes = new MultiHashMap<>(true); + private final MultiHashMap autosuggestNodes = new MultiHashMap<>(true); /** Maps all possible keys to the respective values for autosuggest (only key/values applying to ways) */ - private final MultiHashMap autosuggestWays = new MultiHashMap<>(true); + private final MultiHashMap autosuggestWays = new MultiHashMap<>(true); /** Maps all possible keys to the respective values for autosuggest (only key/values applying to closed ways) */ - private final MultiHashMap autosuggestClosedways = new MultiHashMap<>(true); + private final MultiHashMap autosuggestClosedways = new MultiHashMap<>(true); /** Maps all possible keys to the respective values for autosuggest (only key/values applying to areas (MPs)) */ - private final MultiHashMap autosuggestAreas = new MultiHashMap<>(true); + private final MultiHashMap autosuggestAreas = new MultiHashMap<>(true); /** Maps all possible keys to the respective values for autosuggest (only key/values applying to closed ways) */ - private final MultiHashMap autosuggestRelations = new MultiHashMap<>(true); + private final MultiHashMap autosuggestRelations = new MultiHashMap<>(true); /** for search support */ - private final MultiHashMap searchIndex = new MultiHashMap<>(); - private final MultiHashMap translatedSearchIndex = new MultiHashMap<>(); + private final MultiHashMap searchIndex = new MultiHashMap<>(); + private final MultiHashMap translatedSearchIndex = new MultiHashMap<>(); - private Po po = null; + private Po po = null; - private final PresetMRUInfo mru; - private String externalPackage; - private final boolean isDefault; + private final PresetMRUInfo mru; + private String externalPackage; + private final boolean isDefault; - private static final FilenameFilter presetFileFilter = (File dir, String name) -> name.endsWith(".xml"); - private static final FileFilter directoryFilter = File::isDirectory; + private static final FilenameFilter presetFileFilter = (File dir, String name) -> name.endsWith(".xml"); + private static final FileFilter directoryFilter = File::isDirectory; /** * create a dummy preset @@ -329,7 +325,7 @@ public Preset(@NonNull List elements) { * @return the PresetIconManager instance */ @NonNull - PresetIconManager getIconManager(@NonNull Context ctx) { + public PresetIconManager getIconManager(@NonNull Context ctx) { if (iconManager == null) { if (directory != null) { if (directory.getName().equals(AdvancedPrefDatabase.ID_DEFAULT)) { @@ -402,7 +398,7 @@ public void setDescription(String description) { * @param elements list of PresetElements */ private void addElementsToIndex(PresetGroup group, List elements) { - for (PresetElement e : elements) { + for (PresetElement e:elements) { if (e instanceof PresetGroup) { addElementsToIndex(group, ((PresetGroup) e).getElements()); } else if (e instanceof PresetItem) { @@ -478,8 +474,7 @@ private void addToObjectItems(@NonNull String key, @NonNull PresetItem item) { } /** - * Add a name, any translation and the individual words to the index. Currently we assume that all words are - * significant + * Add a name, any translation and the individual words to the index. Currently we assume that all words are significant * * @param term search key to add * @param translationContext the translation context if any @@ -492,7 +487,7 @@ void addToSearchIndex(@Nullable String term, @Nullable String translationContext searchIndex.add(normalizedName, item); String[] words = normalizedName.split(" "); if (words.length > 1) { - for (String w : words) { + for (String w:words) { searchIndex.add(w, item); } } @@ -501,7 +496,7 @@ void addToSearchIndex(@Nullable String term, @Nullable String translationContext translatedSearchIndex.add(normalizedTranslatedName, item); String[] translastedWords = normalizedName.split(" "); if (translastedWords.length > 1) { - for (String w : translastedWords) { + for (String w:translastedWords) { translatedSearchIndex.add(w, item); } } @@ -552,11 +547,11 @@ private void addToAutosuggest(@NonNull PresetItem item, @NonNull String key, Str */ void addToIndices(@NonNull PresetItem currentItem) { final StringWithDescription dummy = new StringWithDescription(""); - for (Entry e : currentItem.getFields().entrySet()) { + for (Entry e:currentItem.getFields().entrySet()) { PresetField field = e.getValue(); String key = e.getKey(); if (field instanceof PresetCheckGroupField) { - for (PresetCheckField check : ((PresetCheckGroupField) field).getCheckFields()) { + for (PresetCheckField check:((PresetCheckGroupField) field).getCheckFields()) { String checkKey = check.getKey(); addToTagItems(checkKey, currentItem); if (isObjectKey(checkKey)) { @@ -578,7 +573,7 @@ void addToIndices(@NonNull PresetItem currentItem) { if (field instanceof PresetComboField) { StringWithDescription[] values = ((PresetComboField) field).getValues(); if (values != null) { - for (StringWithDescription v : values) { + for (StringWithDescription v:values) { String value = ""; if (v != null && v.getValue() != null) { value = v.getValue(); @@ -604,16 +599,16 @@ void addToIndices(@NonNull PresetItem currentItem) { * @param item the PresetItem */ public void deleteItem(@NonNull PresetItem item) { - for (String key : searchIndex.getKeys()) { + for (String key:searchIndex.getKeys()) { searchIndex.removeItem(key, item); } - for (String key : translatedSearchIndex.getKeys()) { + for (String key:translatedSearchIndex.getKeys()) { translatedSearchIndex.removeItem(key, item); } - for (String key : tagItems.getKeys()) { + for (String key:tagItems.getKeys()) { tagItems.removeItem(key, item); } - for (String key : objectItems.getKeys()) { + for (String key:objectItems.getKeys()) { objectItems.removeItem(key, item); } removeRecentlyUsed(item); @@ -645,14 +640,13 @@ public String translate(@NonNull String text, @Nullable String context) { } /** - * Translate all relevant parts of the PresetField of a PresetItem Note this needs to be done post building the - * search index + * Translate all relevant parts of the PresetField of a PresetItem Note this needs to be done post building the search index * * @param item the PresetItem */ void translateItem(@NonNull PresetItem item) { if (po != null) { - for (PresetField field : item.getFields().values()) { + for (PresetField field:item.getFields().values()) { field.translate(po); } } @@ -674,7 +668,7 @@ static String getPresetFileName(@NonNull File presetDir) { } else { list = presetDir.listFiles(directoryFilter); if (list != null) { - for (File f : list) { + for (File f:list) { String fileName = getPresetFileName(f); if (fileName != null) { return f.getName() + Paths.DELIMITER + fileName; @@ -738,7 +732,7 @@ public boolean contains(@Nullable PresetItem pi) { * @return true if found */ private boolean contains(@NonNull PresetGroup group, @NonNull PresetItem item) { - for (PresetElement element : group.getElements()) { + for (PresetElement element:group.getElements()) { if (element.equals(item)) { return true; } else if (element instanceof PresetGroup) { @@ -771,10 +765,10 @@ public Set getItemByTag(@NonNull String tag) { * @param tag the tag for which we want to remove the entry */ static void removeItem(@NonNull Context ctx, @NonNull String tag) { - for (Preset preset : App.getCurrentPresets(ctx)) { + for (Preset preset:App.getCurrentPresets(ctx)) { if (preset != null) { Set items = preset.tagItems.get(tag); - for (PresetItem item : items) { + for (PresetItem item:items) { preset.deleteItem(item); } } @@ -818,7 +812,7 @@ public PresetItem getItemByName(@NonNull String name, @Nullable String region, b @Nullable private PresetItem getElementByName(@NonNull PresetGroup group, @NonNull String name, @Nullable String region, boolean deprecated) { List elements = region == null ? group.getElements() : PresetElement.filterElementsByRegion(group.getElements(), region); - for (PresetElement element : elements) { + for (PresetElement element:elements) { final boolean isDeprecated = element.isDeprecated(); if (element instanceof PresetItem && name.equals(((PresetItem) element).getName()) && !(isDeprecated ^ deprecated)) { return (PresetItem) element; @@ -871,7 +865,7 @@ public PresetGroup getGroupByName(@NonNull String name) { */ @Nullable private PresetGroup getGroupByName(@NonNull PresetGroup group, @NonNull String name) { - for (PresetElement e : group.getElements()) { + for (PresetElement e:group.getElements()) { if (e instanceof PresetGroup) { if (name.equals(e.getName())) { return (PresetGroup) e; @@ -893,7 +887,7 @@ private PresetGroup getGroupByName(@NonNull PresetGroup group, @NonNull String n * @param handler PresetElementHandler to execute */ private static void processElements(@NonNull PresetGroup group, @NonNull PresetElementHandler handler) { - for (PresetElement e : group.getElements()) { + for (PresetElement e:group.getElements()) { handler.handle(e); if (e instanceof PresetGroup) { processElements((PresetGroup) e, handler); @@ -935,7 +929,7 @@ public static PresetElement getElementByPath(@NonNull PresetGroup group, @NonNul if (size > 0) { String segment = path.getPath().get(0); List elements = region == null ? group.getElements() : PresetElement.filterElementsByRegion(group.getElements(), region); - for (PresetElement e : elements) { + for (PresetElement e:elements) { if (segment.equals(e.getName())) { final boolean isDeprecated = e.isDeprecated(); if (size == 1 && !(isDeprecated ^ deprecated)) { @@ -1042,9 +1036,9 @@ public void addToRootGroup(@NonNull Preset[] presets) { // a bit of a hack ... this adds the elements from other presets to the dummy root group List rootElements = rootGroup.getElements(); rootElements.clear(); - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { - for (PresetElement e : p.getRootGroup().getElements()) { + for (PresetElement e:p.getRootGroup().getElements()) { if (!rootElements.contains(e)) { // only do this if not already present rootGroup.addElement(e, true); } @@ -1074,12 +1068,10 @@ private String toJSON() { } /** - * Finds the preset item best matching a certain tag set, or null if no preset item matches. To match, all - * (mandatory) tags of the preset item need to be in the tag set. The preset item does NOT need to have all tags in - * the tag set, but the tag set needs to have all (mandatory) tags of the preset item. + * Finds the preset item best matching a certain tag set, or null if no preset item matches. To match, all (mandatory) tags of the preset item need to be in + * the tag set. The preset item does NOT need to have all tags in the tag set, but the tag set needs to have all (mandatory) tags of the preset item. * - * If multiple items match, the most specific one (i.e. having most tags) wins. If there is a draw, no guarantees - * are made. + * If multiple items match, the most specific one (i.e. having most tags) wins. If there is a draw, no guarantees are made. * * @param presets presets to match against * @param tags tags to check against (i.e. tags of a map element) @@ -1093,12 +1085,10 @@ public static PresetItem findBestMatch(@Nullable Preset[] presets, @Nullable Map } /** - * Finds the preset item best matching a certain tag set, or null if no preset item matches. To match, all - * (mandatory) tags of the preset item need to be in the tag set. The preset item does NOT need to have all tags in - * the tag set, but the tag set needs to have all (mandatory) tags of the preset item. + * Finds the preset item best matching a certain tag set, or null if no preset item matches. To match, all (mandatory) tags of the preset item need to be in + * the tag set. The preset item does NOT need to have all tags in the tag set, but the tag set needs to have all (mandatory) tags of the preset item. * - * If multiple items match, the most specific one (i.e. having most tags) wins. If there is a draw, no guarantees - * are made. + * If multiple items match, the most specific one (i.e. having most tags) wins. If there is a draw, no guarantees are made. * * @param presets presets presets to match against * @param tags tags to check against (i.e. tags of a map element) @@ -1128,7 +1118,7 @@ public static PresetItem findBestMatch(@Nullable Preset[] presets, @Nullable Map } // Find best final int FIXED_WEIGHT = 1000; // always prioritize presets with fixed keys - for (PresetItem possibleMatch : possibleMatches) { + for (PresetItem possibleMatch:possibleMatches) { int fixedTagCount = possibleMatch.getFixedTagCount() * FIXED_WEIGHT; int recommendedTagCount = possibleMatch.getRecommendedKeyCount(); if (fixedTagCount + recommendedTagCount >= bestMatchStrength) { @@ -1177,7 +1167,7 @@ public static PresetItem findMatch(@NonNull Preset[] presets, @NonNull Map possibleMatches = buildPossibleMatches(new LinkedHashSet<>(), presets, tags, false, null); // Find match - for (PresetItem possibleMatch : possibleMatches) { + for (PresetItem possibleMatch:possibleMatches) { if (possibleMatch.getFixedTagCount() > 0) { // has required tags if (possibleMatch.matches(tags)) { return possibleMatch; @@ -1202,11 +1192,11 @@ public static PresetItem findMatch(@NonNull Preset[] presets, @NonNull Map buildPossibleMatches(@NonNull Set possibleMatches, @NonNull Preset[] presets, @NonNull Map tags, boolean useAddressKeys, @Nullable Map ignoreTags) { - for (Preset p : presets) { + for (Preset p:presets) { if (p == null) { continue; } - for (Entry tag : tags.entrySet()) { + for (Entry tag:tags.entrySet()) { final String key = tag.getKey(); final String value = tag.getValue(); final String ignoreValue = ignoreTags != null ? ignoreTags.get(key) : null; @@ -1232,7 +1222,7 @@ private static Set buildPossibleMatches(@NonNull Set pos @NonNull static List filterElements(@NonNull List originalElements, @NonNull ElementType type) { List filteredElements = new ArrayList<>(); - for (PresetElement e : originalElements) { + for (PresetElement e:originalElements) { if (!e.isDeprecated() && (e.appliesTo(type) || ((e instanceof PresetSeparator) && !filteredElements.isEmpty() && !(filteredElements.get(filteredElements.size() - 1) instanceof PresetSeparator)))) { // only add separators if there is a non-separator element above them @@ -1251,9 +1241,9 @@ static List filterElements(@NonNull List originalE */ @Nullable public static String getObjectTag(@NonNull Preset[] presets, @NonNull Map tags) { - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { - for (Entry tag : tags.entrySet()) { + for (Entry tag:tags.entrySet()) { String key = tag.getKey(); if (p.isObjectKey(key)) { return key + "=" + tag.getValue(); @@ -1265,8 +1255,7 @@ public static String getObjectTag(@NonNull Preset[] presets, @NonNull Map 0) { - for (StringWithDescription v : swdArray) { + for (StringWithDescription v:swdArray) { if ("".equals(value) || v.getValue() == null || v.equals(value) || "".equals(v.getValue())) { return true; } @@ -1324,7 +1313,7 @@ public static boolean hasKeyValue(@Nullable PresetTagField field, @Nullable Stri @NonNull public static Collection getAutocompleteKeys(@NonNull Preset[] presets, @NonNull ElementType type) { Collection result = new LinkedHashSet<>(); - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { switch (type) { case NODE: @@ -1361,7 +1350,7 @@ public static Collection getAutocompleteKeys(@NonNull Preset[] presets, @NonNull public static Collection getAutocompleteValues(@NonNull Preset[] presets, @Nullable ElementType type, @NonNull String key) { Collection result = new LinkedHashSet<>(); - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { if (type == null) { result.addAll(p.autosuggestNodes.get(key)); @@ -1405,7 +1394,7 @@ public static Collection getAutocompleteValues(@NonNull P */ public static MultiHashMap getSearchIndex(Preset[] presets) { MultiHashMap result = new MultiHashMap<>(); - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { result.addAll(p.searchIndex); } @@ -1421,7 +1410,7 @@ public static MultiHashMap getSearchIndex(Preset[] presets) */ public static MultiHashMap getTranslatedSearchIndex(Preset[] presets) { MultiHashMap result = new MultiHashMap<>(); - for (Preset p : presets) { + for (Preset p:presets) { if (p != null) { result.addAll(p.translatedSearchIndex); } @@ -1485,11 +1474,11 @@ public static List splitValues(@Nullable List values, char delim return null; } String d = Pattern.quote(String.valueOf(delimiter)); - for (String v : values) { + for (String v:values) { if (v == null) { continue; } - for (String s : v.split(d)) { + for (String s:v.split(d)) { result.add(s.trim()); } } @@ -1560,7 +1549,7 @@ public static boolean generateTaginfoJson(@NonNull Context ctx, @NonNull File ou public void toXml(XmlSerializer s) throws IllegalArgumentException, IllegalStateException, IOException { s.startDocument(OsmXml.UTF_8, null); s.startTag("", PresetParser.PRESETS); - for (PresetElement e : getRootGroup().getElements()) { + for (PresetElement e:getRootGroup().getElements()) { e.toXml(s); } s.endTag("", PresetParser.PRESETS); diff --git a/src/main/java/de/blau/android/resources/DataStyle.java b/src/main/java/de/blau/android/resources/DataStyle.java index ff66881845..475ed61da1 100644 --- a/src/main/java/de/blau/android/resources/DataStyle.java +++ b/src/main/java/de/blau/android/resources/DataStyle.java @@ -1999,6 +1999,16 @@ public Path getCrosshairsPath() { public Path getXPath() { return xPath; } + + /** + * If a directory is set for custom icons return it + * + * @return the directory path or null + */ + @Nullable + public String getIconDirPath() { + return iconDirPath; + } /** * @return the name