diff --git a/src/org/oscim/theme/MatchingCacheKey.java b/src/org/oscim/theme/MatchingCacheKey.java index 741c90b1..85fc6a7d 100644 --- a/src/org/oscim/theme/MatchingCacheKey.java +++ b/src/org/oscim/theme/MatchingCacheKey.java @@ -19,26 +19,22 @@ import org.oscim.core.Tag; class MatchingCacheKey { int mHashCodeValue; Tag[] mTags; - byte mZoomLevel; MatchingCacheKey() { } - MatchingCacheKey(Tag[] tags, byte zoomLevel) { + MatchingCacheKey(Tag[] tags) { mTags = tags; - mZoomLevel = zoomLevel; mHashCodeValue = calculateHashCode(); } MatchingCacheKey(MatchingCacheKey key) { mTags = key.mTags; - mZoomLevel = key.mZoomLevel; mHashCodeValue = key.mHashCodeValue; } - void set(Tag[] tags, byte zoomLevel) { + void set(Tag[] tags) { mTags = tags; - mZoomLevel = zoomLevel; int result = 7; @@ -47,7 +43,7 @@ class MatchingCacheKey { break; result = 31 * result + mTags[i].hashCode(); } - result = 31 * result + mZoomLevel; + result = 31 * result; mHashCodeValue = result; } @@ -57,15 +53,9 @@ class MatchingCacheKey { if (this == obj) { return true; } - // else if (!(obj instanceof MatchingCacheKey)) { - // return false; - // } MatchingCacheKey other = (MatchingCacheKey) obj; - if (mZoomLevel != other.mZoomLevel) - return false; - if (mTags == null) { return (other.mTags == null); } else if (other.mTags == null) @@ -76,10 +66,14 @@ class MatchingCacheKey { return false; } - for (int i = 0; i < length; i++) - if (mTags[i] != other.mTags[i]) - return false; + for (int i = 0; i < length; i++) { + if (mTags[i] == other.mTags[i]) + continue; + if (mTags[i].key == other.mTags[i].key && mTags[i].value == other.mTags[i].value) + continue; + return false; + } return true; } @@ -99,7 +93,7 @@ class MatchingCacheKey { break; result = 31 * result + mTags[i].hashCode(); } - result = 31 * result + mZoomLevel; + result = 31 * result; return result; } diff --git a/src/org/oscim/theme/NegativeRule.java b/src/org/oscim/theme/NegativeRule.java index 6220759d..8388397f 100644 --- a/src/org/oscim/theme/NegativeRule.java +++ b/src/org/oscim/theme/NegativeRule.java @@ -27,17 +27,12 @@ class NegativeRule extends Rule { } @Override - boolean matchesNode(Tag[] tags, byte zoomLevel) { - return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement != Element.WAY) - && mAttributeMatcher.matches(tags); + boolean matchesNode(Tag[] tags) { + return mAttributeMatcher.matches(tags); } @Override - boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { - return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement != Element.NODE) - && (mClosed == closed || mClosed == Closed.ANY) - && mAttributeMatcher.matches(tags); + boolean matchesWay(Tag[] tags) { + return mAttributeMatcher.matches(tags); } } diff --git a/src/org/oscim/theme/PositiveRule.java b/src/org/oscim/theme/PositiveRule.java index f7b7558f..8843e42f 100644 --- a/src/org/oscim/theme/PositiveRule.java +++ b/src/org/oscim/theme/PositiveRule.java @@ -36,21 +36,14 @@ class PositiveRule extends Rule { } @Override - boolean matchesNode(Tag[] tags, byte zoomLevel) { - return (mElement != Element.WAY) - && mZoomMin <= zoomLevel - && mZoomMax >= zoomLevel - && (mKeyMatcher == null || mKeyMatcher.matches(tags)) + boolean matchesNode(Tag[] tags) { + return (mKeyMatcher == null || mKeyMatcher.matches(tags)) && (mValueMatcher == null || mValueMatcher.matches(tags)); } @Override - boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { - return (mElement != Element.NODE) - && mZoomMin <= zoomLevel - && mZoomMax >= zoomLevel - && (mClosed == closed || mClosed == Closed.ANY) - && (mKeyMatcher == null || mKeyMatcher.matches(tags)) - && (mValueMatcher == null || mValueMatcher.matches(tags)); + boolean matchesWay(Tag[] tags) { + return (mKeyMatcher == null || mKeyMatcher.matches(tags)) && + (mValueMatcher == null || mValueMatcher.matches(tags)); } } diff --git a/src/org/oscim/theme/RenderTheme.java b/src/org/oscim/theme/RenderTheme.java index bda3fde1..2fb6ace3 100644 --- a/src/org/oscim/theme/RenderTheme.java +++ b/src/org/oscim/theme/RenderTheme.java @@ -28,7 +28,9 @@ import android.graphics.Color; * A RenderTheme defines how ways and nodes are drawn. */ public class RenderTheme { - private static final int MATCHING_CACHE_SIZE = 1024; + private final static String TAG = RenderTheme.class.getName(); + + private static final int MATCHING_CACHE_SIZE = 512; private static final int RENDER_THEME_VERSION = 1; private static void validate(String elementName, Integer version, @@ -82,9 +84,15 @@ public class RenderTheme { private final int mMapBackground; private final ArrayList mRulesList; - private final LRUCache mMatchingCacheNodes; - private final LRUCache mMatchingCacheWay; - private final LRUCache mMatchingCacheArea; + private final LRUCache mMatchingCacheNodes; + private final LRUCache mMatchingCacheWay; + private final LRUCache mMatchingCacheArea; + + class RenderInstructionItem { + RenderInstructionItem next; + int zoom; + RenderInstruction[] list; + } RenderTheme(int mapBackground, float baseStrokeWidth, float baseTextSize) { mMapBackground = mapBackground; @@ -92,11 +100,11 @@ public class RenderTheme { mBaseTextSize = baseTextSize; mRulesList = new ArrayList(); - mMatchingCacheNodes = new LRUCache( + mMatchingCacheNodes = new LRUCache( MATCHING_CACHE_SIZE); - mMatchingCacheWay = new LRUCache( + mMatchingCacheWay = new LRUCache( MATCHING_CACHE_SIZE); - mMatchingCacheArea = new LRUCache( + mMatchingCacheArea = new LRUCache( MATCHING_CACHE_SIZE); } @@ -130,6 +138,12 @@ public class RenderTheme { return mMapBackground; } + private static void render(IRenderCallback renderCallback, + RenderInstruction[] renderInstructions, Tag[] tags) { + for (int i = 0, n = renderInstructions.length; i < n; i++) + renderInstructions[i].renderNode(renderCallback, tags); + } + /** * @param renderCallback * ... @@ -142,41 +156,92 @@ public class RenderTheme { public synchronized RenderInstruction[] matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel) { - RenderInstruction[] renderInstructions = null; + // list of renderinsctruction items in cache + RenderInstructionItem ris = null; - MatchingCacheKey matchingCacheKey; + // the item matching tags and zoomlevel + RenderInstructionItem ri = null; - matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); + MatchingCacheKey matchingCacheKey = new MatchingCacheKey(tags); boolean found = mMatchingCacheNodes.containsKey(matchingCacheKey); + int zoomMask = 1 << zoomLevel; + if (found) { - renderInstructions = mMatchingCacheNodes.get(matchingCacheKey); - } else { - // cache miss - List matchingList = new ArrayList(4); + ris = mMatchingCacheNodes.get(matchingCacheKey); + + for (ri = ris; ri != null; ri = ri.next) + if ((ri.zoom & zoomMask) != 0) + // cache hit + break; + } + + if (ri == null) { + // cache miss + List matches = mMatchingList; + matches.clear(); for (int i = 0, n = mRulesList.size(); i < n; ++i) - mRulesList.get(i) - .matchNode(renderCallback, tags, zoomLevel, matchingList); + mRulesList.get(i).matchNode(renderCallback, tags, zoomLevel, matches); - int size = matchingList.size(); - if (size > 0) { - renderInstructions = new RenderInstruction[size]; - matchingList.toArray(renderInstructions); + int size = matches.size(); + + // check if same instructions are used in another level + for (ri = ris; ri != null; ri = ri.next) { + if (size == 0) { + if (ri.list != null) + continue; + + // both matchinglists are empty + break; + } + + if (ri.list == null) + continue; + + if (ri.list.length != size) + continue; + + int i = 0; + for (RenderInstruction r : ri.list) { + if (r != matches.get(i)) + break; + i++; + } + if (i == size) + // both matching lists contain the same items + break; + } + + if (ri != null) { + // we found a same matchting list on another zoomlevel + ri.zoom |= zoomMask; + } else { + + ri = new RenderInstructionItem(); + ri.zoom = zoomMask; + + if (size > 0) { + ri.list = new RenderInstruction[size]; + matches.toArray(ri.list); + } + // attach this list to the one found for MatchingKey + if (ris != null) + ris.next = ri; + else + mMatchingCacheNodes.put(matchingCacheKey, ri); } - mMatchingCacheNodes.put(matchingCacheKey, renderInstructions); } - if (renderInstructions != null) { - for (int i = 0, n = renderInstructions.length; i < n; i++) - renderInstructions[i].renderNode(renderCallback, tags); - } + if (ri.list != null) + render(renderCallback, ri.list, tags); - return renderInstructions; + return ri.list; } - // private int missCnt = 0; - // private int hitCnt = 0; + private int missCnt = 0; + private int hitCnt = 0; private MatchingCacheKey mCacheKey = new MatchingCacheKey(); + private ArrayList mMatchingList = new ArrayList(4); /** * Matches a way with the given parameters against this RenderTheme. @@ -196,9 +261,13 @@ public class RenderTheme { public synchronized RenderInstruction[] matchWay(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, boolean closed, boolean render) { - RenderInstruction[] renderInstructions = null; + // list of renderinsctruction items in cache + RenderInstructionItem ris = null; - LRUCache matchingCache; + // the item matching tags and zoomlevel + RenderInstructionItem ri = null; + + LRUCache matchingCache; MatchingCacheKey matchingCacheKey; if (closed) { @@ -206,38 +275,87 @@ public class RenderTheme { } else { matchingCache = mMatchingCacheWay; } + int zoomMask = 1 << zoomLevel; - mCacheKey.set(tags, zoomLevel); - renderInstructions = matchingCache.get(mCacheKey); + mCacheKey.set(tags); + ris = matchingCache.get(mCacheKey); - if (renderInstructions != null) { - // Log.d("RenderTheme", hitCnt++ + "Cache Hit"); - } else if (!matchingCache.containsKey(mCacheKey)) { - matchingCacheKey = new MatchingCacheKey(mCacheKey); + for (ri = ris; ri != null; ri = ri.next) + if ((ri.zoom & zoomMask) != 0) + // cache hit + break; + if (ri == null) { // cache miss - // Log.d("RenderTheme", missCnt++ + " Cache Miss"); + //Log.d(TAG, missCnt++ + " / " + hitCnt + " Cache Miss"); int c = (closed ? Closed.YES : Closed.NO); - List matchingList = new ArrayList(4); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c, - matchingList); - } - int size = matchingList.size(); - if (size > 0) { - renderInstructions = new RenderInstruction[size]; - matchingList.toArray(renderInstructions); - } - matchingCache.put(matchingCacheKey, renderInstructions); - } + List matches = mMatchingList; + matches.clear(); - if (render && renderInstructions != null) { - for (int i = 0, n = renderInstructions.length; i < n; i++) - renderInstructions[i].renderWay(renderCallback, tags); - } + for (int i = 0, n = mRulesList.size(); i < n; ++i) + mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c, matches); - return renderInstructions; + int size = matches.size(); + // check if same instructions are used in another level + for (ri = ris; ri != null; ri = ri.next) { + if (size == 0) { + if (ri.list != null) + continue; + + // both matchinglists are empty + break; + } + + if (ri.list == null) + continue; + + if (ri.list.length != size) + continue; + + int i = 0; + for (RenderInstruction r : ri.list) { + if (r != matches.get(i)) + break; + i++; + } + if (i == size) + // both matching lists contain the same items + break; + } + + if (ri != null) { + // we found a same matchting list on another zoomlevel + ri.zoom |= zoomMask; + //Log.d(TAG, " same instructions " + size + " " + Arrays.deepToString(tags)); + + } else { + //Log.d(TAG, " new instructions " + size + " " + Arrays.deepToString(tags)); + + ri = new RenderInstructionItem(); + ri.zoom = zoomMask; + + if (size > 0) { + ri.list = new RenderInstruction[size]; + matches.toArray(ri.list); + } + + // attach this list to the one found for MatchingKey + if (ris != null) { + ris.next = ri; + } + else { + matchingCacheKey = new MatchingCacheKey(mCacheKey); + matchingCache.put(matchingCacheKey, ri); + } + } + + } + if (render && ri.list != null) { + for (int i = 0, n = ri.list.length; i < n; i++) + ri.list[i].renderWay(renderCallback, tags); + } + return ri.list; } void addRule(Rule rule) { diff --git a/src/org/oscim/theme/Rule.java b/src/org/oscim/theme/Rule.java index 5fd9c32b..aa73e31b 100644 --- a/src/org/oscim/theme/Rule.java +++ b/src/org/oscim/theme/Rule.java @@ -173,8 +173,7 @@ abstract class Rule { final int mElement; final int mClosed; - Rule(int element, int closed, byte zoomMin, - byte zoomMax) { + Rule(int element, int closed, byte zoomMin, byte zoomMax) { mClosed = closed; mElement = element; @@ -193,13 +192,17 @@ abstract class Rule { mSubRules.add(rule); } - abstract boolean matchesNode(Tag[] tags, byte zoomLevel); + abstract boolean matchesNode(Tag[] tags); - abstract boolean matchesWay(Tag[] tags, byte zoomLevel, int closed); + abstract boolean matchesWay(Tag[] tags); void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, List matchingList) { - if (matchesNode(tags, zoomLevel)) { + if ((mElement != Element.WAY) + && mZoomMin <= zoomLevel + && mZoomMax >= zoomLevel + && matchesNode(tags)) { + for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) matchingList.add(mRenderInstructionArray[i]); @@ -210,13 +213,19 @@ abstract class Rule { } void matchWay(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, - int closed, - List matchingList) { + int closed, List matchingList) { - if (matchesWay(tags, zoomLevel, closed)) { + if ((mElement != Element.NODE) + && mZoomMin <= zoomLevel + && mZoomMax >= zoomLevel + && (mClosed == closed || mClosed == Closed.ANY) + && (matchesWay(tags))) { + + // add instructions for this rule for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) matchingList.add(mRenderInstructionArray[i]); + // check subrules for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].matchWay(renderCallback, tags, zoomLevel, closed, matchingList); @@ -230,15 +239,10 @@ abstract class Rule { mRenderInstructionArray = new RenderInstruction[mRenderInstructions.size()]; mRenderInstructions.toArray(mRenderInstructionArray); - // for (int i = 0, n = mRenderInstructions.size(); i < n; i++) - // mRenderInstructionArray[i] = mRenderInstructions.get(i); mSubRuleArray = new Rule[mSubRules.size()]; mSubRules.toArray(mSubRuleArray); - // for (int i = 0, n = mSubRules.size(); i < n; i++) - // mSubRuleArray[i] = mSubRules.get(i); - mRenderInstructions.clear(); mRenderInstructions = null; mSubRules.clear(); diff --git a/src/org/oscim/theme/RuleOptimizer.java b/src/org/oscim/theme/RuleOptimizer.java index 1cc8b3d6..4b1bbde7 100644 --- a/src/org/oscim/theme/RuleOptimizer.java +++ b/src/org/oscim/theme/RuleOptimizer.java @@ -17,7 +17,6 @@ package org.oscim.theme; import java.util.Stack; final class RuleOptimizer { - // private static final Logger LOG = Logger.getLogger(RuleOptimizer.class.getName()); private static AttributeMatcher optimizeKeyMatcher(AttributeMatcher attributeMatcher, Stack ruleStack) { @@ -26,7 +25,7 @@ final class RuleOptimizer { PositiveRule positiveRule = (PositiveRule) ruleStack.get(i); if (positiveRule.mKeyMatcher != null && positiveRule.mKeyMatcher.isCoveredBy(attributeMatcher)) { - return null; // AnyMatcher.getInstance(); + return null; } } } @@ -42,7 +41,7 @@ final class RuleOptimizer { if (positiveRule.mValueMatcher != null && positiveRule.mValueMatcher.isCoveredBy(attributeMatcher)) { - return null; // AnyMatcher.getInstance(); + return null; } } }