package de.blau.android.filter; import java.util.List; import android.content.Context; import android.support.design.widget.FloatingActionButton; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.TextView; import de.blau.android.Main; import de.blau.android.R; import de.blau.android.osm.Node; import de.blau.android.osm.OsmElement; import de.blau.android.osm.Relation; import de.blau.android.osm.RelationMember; import de.blau.android.osm.Tags; import de.blau.android.osm.Way; import de.blau.android.prefs.Preferences; import de.blau.android.util.Util; /** * Filter plus UI for indoor tagging see https://wiki.openstreetmap.org/wiki/Simple_Indoor_Tagging * NOTE: the relevant ways should be processed before nodes * @author simon * */ public class IndoorFilter extends Filter { /** * */ private static final long serialVersionUID = 4L; private final static String DEBUG_TAG = "IndoorFilter"; /** * Current level */ private int level = 0; private boolean inverted = false; public boolean isInverted() { return inverted; } public void setInverted(boolean inverted) { this.inverted = inverted; } public IndoorFilter() { super(); } @Override public boolean include(Node node, boolean selected) { int level = getLevel(); Include include = cachedNodes.get(node); if (include != null) { return include != Include.DONT; } if (!inverted) { include = (selected || (node.hasTags() && ( contains(node.getTagWithKey(Tags.KEY_LEVEL),level) || contains(node.getTagWithKey(Tags.KEY_REPEAT_ON),level) ) )) ? Include.INCLUDE : Include.DONT; } else { include = (selected || (node.hasTags() && !node.hasTagKey(Tags.KEY_LEVEL) && !node.hasTagKey(Tags.KEY_REPEAT_ON))) ? Include.INCLUDE : Include.DONT; } if (include == Include.DONT) { // check if it is a relation member List<Relation> parents = node.getParentRelations(); if (parents != null) { for (Relation r:parents) { if (include(r, false)) { include = Include.INCLUDE; // inherit include status from relation break; } } } } cachedNodes.put(node,include); return include != Include.DONT; } @Override public boolean include(Way way, boolean selected) { int level = getLevel(); Include include = cachedWays.get(way); if (include != null) { return include != Include.DONT; } if (!inverted) { include = (selected || (way.hasTags() && ( contains(way.getTagWithKey(Tags.KEY_LEVEL),level) || contains(way.getTagWithKey(Tags.KEY_REPEAT_ON),level) || buildingHasLevel(way, level) ) )) ? Include.INCLUDE : Include.DONT; } else { include = (selected || (way.hasTags() && !way.hasTagKey(Tags.KEY_LEVEL) && !way.hasTagKey(Tags.KEY_REPEAT_ON) && !(way.hasTagKey(Tags.KEY_MIN_LEVEL) || way.hasTagKey(Tags.KEY_MAX_LEVEL)))) ? Include.INCLUDE : Include.DONT; } if (include == Include.DONT) { // check if it is a relation member List<Relation> parents = way.getParentRelations(); if (parents != null) { for (Relation r:parents) { if (include(r, false)) { include = Include.INCLUDE; // inherit include status from relation break; } } } } for (Node n:way.getNodes()) { Include includeNode = cachedNodes.get(n); if (includeNode == null || (include != Include.DONT && includeNode == Include.DONT)) { // if not originally included overwrite now if (include == Include.DONT && (n.hasTags() || n.hasParentRelations())) { // no entry yet so we have to check tags and relations include(n,false); continue; } cachedNodes.put(n,include); } } cachedWays.put(way,include); return include != Include.DONT; } @Override public boolean include(Relation relation, boolean selected) { int level = getLevel(); Include include = cachedRelations.get(relation); if (include != null) { return include != Include.DONT; } if (!inverted) { include = (selected || buildingHasLevel(relation, level)) ? Include.INCLUDE : Include.DONT; } else { include = (selected || (relation.hasTags() && !(relation.hasTagKey(Tags.KEY_MIN_LEVEL) || relation.hasTagKey(Tags.KEY_MAX_LEVEL)))) ? Include.INCLUDE : Include.DONT; } cachedRelations.put(relation, include); List<RelationMember> members = relation.getMembers(); if (members != null) { for (RelationMember rm:members) { OsmElement element = rm.getElement(); if (element != null) { if (element instanceof Way) { Way w = (Way)element; Include includeWay = cachedWays.get(w); if (includeWay == null || (include != Include.DONT && includeWay == Include.DONT)) { // if not originally included overwrite now for (Node n:w.getNodes()) { cachedNodes.put(n,include); } cachedWays.put(w,include); } } else if (element instanceof Node) { Node n = (Node)element; Include includeNode = cachedNodes.get(n); if (includeNode == null || (include != Include.DONT && includeNode == Include.DONT)) { // if not originally included overwrite now cachedNodes.put(n,include); } } else if (element instanceof Relation) { // FIXME } } } } return include != Include.DONT; } /** * @param levelSpec either a single integer, a semi-colon separated list, or a range * @param level * @return true if the level is contained in levelSpec */ private boolean contains(String levelSpec, int level) { // Log.d("Indoor","levelSpec " + levelSpec + " level " + level); if (levelSpec == null || "".equals(levelSpec)) { return false; } String[] l = levelSpec.split(";"); if (l.length > 1) { for (String i:l) { //noinspection EmptyCatchBlock try { if (Integer.parseInt(i)==level) { return true; } } catch (NumberFormatException e) { } } return false; } else { int hyphen = levelSpec.indexOf("-",1); if (hyphen > 0) { // needs to be split l = levelSpec.split("-",2); if (l.length==2 && !"".equals(l[0])) { try { return level >= Integer.parseInt(l[0]) && level <= Integer.parseInt(l[1]); } catch (NumberFormatException e) { return false; } } try { return level == Integer.parseInt(levelSpec); } catch (NumberFormatException e) { return false; } } else { try { return level == Integer.parseInt(levelSpec); } catch (NumberFormatException e) { return false; } } } } /** * @param b * @param level * @return true if the building/building:part has a level between (inclusive) min/max */ private static boolean buildingHasLevel(OsmElement b, int level) { if (b.hasTagKey(Tags.KEY_BUILDING) || b.hasTagKey(Tags.KEY_BUILDING_PART)) { String minLevel = b.getTagWithKey(Tags.KEY_MIN_LEVEL); String maxLevel = b.getTagWithKey(Tags.KEY_MAX_LEVEL); if (minLevel != null && maxLevel != null) { try { return level >= Integer.parseInt(minLevel) && level <= Integer.parseInt(maxLevel); } catch (NumberFormatException e) { return false; } } } return false; } /** * @return indoor mode level */ public int getLevel() { return level; } /** * Set level used in indoor mode * @param level */ public void setLevel(int level) { if (level != this.level) { clear(); } this.level = level; } /** * Indoor filter controls */ transient private FloatingActionButton levelUp; transient private FrameLayout levelDisplay; transient private TextView levelText; transient private FloatingActionButton levelTextButton; transient private FloatingActionButton levelDown; private transient ViewGroup parent; private transient RelativeLayout controls; private transient Update update; @Override public void addControls(ViewGroup layout, final Update update) { Log.d(DEBUG_TAG, "adding filter controls"); this.parent = layout; this.update = update; levelUp = (FloatingActionButton)parent.findViewById(R.id.levelUp); levelDisplay = (FrameLayout)parent.findViewById(R.id.level); levelText = (TextView)parent.findViewById(R.id.levelText); levelTextButton = (FloatingActionButton)parent.findViewById(R.id.levelTextButton); levelDown = (FloatingActionButton)parent.findViewById(R.id.levelDown); // we weren't already added ... if (levelUp == null || levelDisplay == null || levelText == null || levelDown == null) { Context context = layout.getContext(); Preferences prefs = new Preferences(context); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); controls = (RelativeLayout)inflater.inflate(prefs.followGPSbuttonPosition().equals("LEFT")?R.layout.indoor_controls_right:R.layout.indoor_controls_left, layout); levelUp = (FloatingActionButton)controls.findViewById(R.id.levelUp); levelDisplay = (FrameLayout)controls.findViewById(R.id.level); levelText = (TextView)controls.findViewById(R.id.levelText); levelTextButton = (FloatingActionButton)controls.findViewById(R.id.levelTextButton); levelDown = (FloatingActionButton)controls.findViewById(R.id.levelDown); } // indoor controls levelUp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int level = getLevel(); Log.d(DEBUG_TAG,"Current level " + level); updateLevel(level+1); update.execute(); } }); Util.setAlpha(levelUp, Main.FABALPHA); levelText.setText(Integer.toString(getLevel())); levelTextButton.setClickable(true); levelTextButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View b) { Log.d(DEBUG_TAG,"Level clicked"); setupControls(true); } }); Util.setAlpha(levelTextButton, Main.FABALPHA); levelDown.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int level = getLevel(); Log.d(DEBUG_TAG,"Current level " + level); updateLevel(level-1); update.execute(); } }); Util.setAlpha(levelDown, Main.FABALPHA); setupControls(false); } private void setupControls(boolean toggle) { if (toggle) { clear(); } inverted = toggle ? !inverted : inverted; if (inverted) { levelText.setText("--"); levelUp.setEnabled(false); levelDown.setEnabled(false); } else { updateLevel(level); levelUp.setEnabled(true); levelDown.setEnabled(true); } update.execute(); } @Override public void removeControls() { if (parent != null && controls != null) { parent.removeView(controls); } } @Override public void hideControls() { //NOTE order is important if(levelDown != null) { levelDown.hide(); } if(levelDisplay != null) { levelDisplay.setVisibility(View.GONE); } if(levelUp != null) { levelUp.hide(); } } @Override public void showControls() { //NOTE order is important if(levelUp != null) { levelUp.show(); } if(levelDisplay != null) { levelDisplay.setVisibility(View.VISIBLE); } if(levelDown != null) { levelDown.show(); } } private void updateLevel(int level) { Log.d(DEBUG_TAG,"setting level to " + level); if (levelText != null) { levelText.setText(Integer.toString(level)); } setLevel(level); } }