From bcb529906a27a1e528de5bb37ddedbee888d9120 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Mon, 22 Apr 2013 02:19:32 +0200 Subject: [PATCH] - extract interface of RenderTheme - pass MapElement to RenderTheme - refactor: join duplicated code for node and way matching --- src/org/oscim/layers/tile/TileGenerator.java | 61 ++-- src/org/oscim/layers/tile/TileLayer.java | 4 +- src/org/oscim/renderer/GLRenderer.java | 4 +- src/org/oscim/theme/IRenderTheme.java | 66 ++++ src/org/oscim/theme/RenderTheme.java | 335 ++++++------------- src/org/oscim/theme/RenderThemeHandler.java | 11 +- 6 files changed, 216 insertions(+), 265 deletions(-) create mode 100644 src/org/oscim/theme/IRenderTheme.java diff --git a/src/org/oscim/layers/tile/TileGenerator.java b/src/org/oscim/layers/tile/TileGenerator.java index b951c27d..9ea16a39 100644 --- a/src/org/oscim/layers/tile/TileGenerator.java +++ b/src/org/oscim/layers/tile/TileGenerator.java @@ -35,7 +35,7 @@ import org.oscim.renderer.layer.LineTexLayer; import org.oscim.renderer.layer.PolygonLayer; import org.oscim.renderer.layer.TextItem; import org.oscim.theme.IRenderCallback; -import org.oscim.theme.RenderTheme; +import org.oscim.theme.IRenderTheme; import org.oscim.theme.renderinstruction.Area; import org.oscim.theme.renderinstruction.Circle; import org.oscim.theme.renderinstruction.Line; @@ -80,7 +80,7 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { private static DebugSettings debug; - private RenderTheme renderTheme; + private IRenderTheme renderTheme; private int renderLevels; // current MapDatabase used by this TileGenerator @@ -106,7 +106,7 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { private final LineClipper mClipper; - public void setRenderTheme(RenderTheme theme) { + public void setRenderTheme(IRenderTheme theme) { renderTheme = theme; renderLevels = theme.getLevels(); } @@ -182,20 +182,20 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { return false; } - if (debug.drawTileFrames) { - // draw tile coordinate - mTagName = new Tag("name", mTile.toString(), false); - mElement = mDebugPoint; - RenderInstruction[] ri; - ri = renderTheme.matchNode(debugTagWay, (byte) 0); - renderNode(ri); - - // draw tile box - mElement = mDebugWay; - mDrawingLayer = 100 * renderLevels; - ri = renderTheme.matchWay(mDebugWay.tags, (byte) 0, false); - renderWay(ri); - } +// if (debug.drawTileFrames) { +// // draw tile coordinate +// mTagName = new Tag("name", mTile.toString(), false); +// mElement = mDebugPoint; +// RenderInstruction[] ri; +// ri = renderTheme.matchNode(debugTagWay, (byte) 0); +// renderNode(ri); +// +// // draw tile box +// mElement = mDebugWay; +// mDrawingLayer = 100 * renderLevels; +// ri = renderTheme.matchWay(mDebugWay.tags, (byte) 0, false); +// renderWay(ri); +// } mTile = null; return true; @@ -281,7 +281,8 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { filterTags(element.tags); // get render instructions - RenderInstruction[] ri = renderTheme.matchNode(element.tags, mTile.zoomLevel); + //RenderInstruction[] ri = renderTheme.matchNode(element.tags, mTile.zoomLevel); + RenderInstruction[] ri = renderTheme.matchElement(element, mTile.zoomLevel); if (ri != null) renderNode(ri); @@ -297,8 +298,10 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { mDrawingLayer = getValidLayer(element.layer) * renderLevels; // get render instructions - RenderInstruction[] ri = renderTheme.matchWay(element.tags, - (byte) (mTile.zoomLevel + 0), closed); +// RenderInstruction[] ri = renderTheme.matchWay(element.tags, +// (byte) (mTile.zoomLevel + 0), closed); + + RenderInstruction[] ri = renderTheme.matchElement(element, mTile.zoomLevel); renderWay(ri); @@ -314,15 +317,15 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback { private void debugUnmatched(boolean closed, Tag[] tags) { Log.d(TAG, "DBG way not matched: " + closed + " " + Arrays.deepToString(tags)); - - mTagName = new Tag("name", tags[0].key + ":" - + tags[0].value, false); - - RenderInstruction[] ri; - ri = renderTheme.matchWay(closed ? debugTagArea : debugTagWay, - (byte) 0, true); - - renderWay(ri); + mElement = null; +// mTagName = new Tag("name", tags[0].key + ":" +// + tags[0].value, false); +// +// RenderInstruction[] ri; +// ri = renderTheme.matchWay(closed ? debugTagArea : debugTagWay, +// (byte) 0, true); +// +// renderWay(ri); } private void renderWay(RenderInstruction[] ri) { diff --git a/src/org/oscim/layers/tile/TileLayer.java b/src/org/oscim/layers/tile/TileLayer.java index 87b31ee9..4939a189 100644 --- a/src/org/oscim/layers/tile/TileLayer.java +++ b/src/org/oscim/layers/tile/TileLayer.java @@ -32,8 +32,8 @@ import org.oscim.database.MapOptions; import org.oscim.layers.Layer; import org.oscim.renderer.GLRenderer; import org.oscim.theme.ExternalRenderTheme; +import org.oscim.theme.IRenderTheme; import org.oscim.theme.InternalRenderTheme; -import org.oscim.theme.RenderTheme; import org.oscim.theme.RenderThemeHandler; import org.oscim.theme.Theme; import org.oscim.view.MapView; @@ -263,7 +263,7 @@ public class TileLayer extends Layer { InputStream inputStream = null; try { inputStream = theme.getRenderThemeAsStream(); - RenderTheme t = RenderThemeHandler.getRenderTheme(inputStream); + IRenderTheme t = RenderThemeHandler.getRenderTheme(inputStream); t.scaleTextSize(1 + (MapView.dpi / 240 - 1) * 0.5f); // FIXME !!! diff --git a/src/org/oscim/renderer/GLRenderer.java b/src/org/oscim/renderer/GLRenderer.java index 7ca27c6a..2e5ee532 100644 --- a/src/org/oscim/renderer/GLRenderer.java +++ b/src/org/oscim/renderer/GLRenderer.java @@ -32,7 +32,7 @@ import org.oscim.core.Tile; import org.oscim.layers.tile.MapTile; import org.oscim.renderer.layer.Layers; import org.oscim.renderer.overlays.RenderOverlay; -import org.oscim.theme.RenderTheme; +import org.oscim.theme.IRenderTheme; import org.oscim.utils.GlUtils; import org.oscim.utils.Matrix4; import org.oscim.view.MapView; @@ -124,7 +124,7 @@ public class GLRenderer implements GLSurfaceView.Renderer { mFillCoords[7] = min; } - public static void setRenderTheme(RenderTheme t) { + public static void setRenderTheme(IRenderTheme t) { mClearColor = GlUtils.colorToFloat(t.getMapBackground()); mUpdateColor = true; } diff --git a/src/org/oscim/theme/IRenderTheme.java b/src/org/oscim/theme/IRenderTheme.java new file mode 100644 index 00000000..119b1294 --- /dev/null +++ b/src/org/oscim/theme/IRenderTheme.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2013 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.theme; + +import org.oscim.core.MapElement; +import org.oscim.theme.renderinstruction.RenderInstruction; + +public interface IRenderTheme { + + /** + * Matches a MapElement with the given parameters against this RenderTheme. + * + * @param zoomLevel + * the zoom level at which the way should be matched. + * @return matching render instructions + */ + public abstract RenderInstruction[] matchElement(MapElement element, int zoomLevel); + + + /** + * Must be called when this RenderTheme gets destroyed to clean up and free + * resources. + */ + public abstract void destroy(); + + /** + * @return the number of distinct drawing levels required by this + * RenderTheme. + */ + public abstract int getLevels(); + + /** + * @return the map background color of this RenderTheme. + */ + public abstract int getMapBackground(); + + /** + * Scales the stroke width of this RenderTheme by the given factor. + * + * @param scaleFactor + * the factor by which the stroke width should be scaled. + */ + public abstract void scaleStrokeWidth(float scaleFactor); + + /** + * Scales the text size of this RenderTheme by the given factor. + * + * @param scaleFactor + * the factor by which the text size should be scaled. + */ + public abstract void scaleTextSize(float scaleFactor); + +} diff --git a/src/org/oscim/theme/RenderTheme.java b/src/org/oscim/theme/RenderTheme.java index aa31fa20..18c14047 100644 --- a/src/org/oscim/theme/RenderTheme.java +++ b/src/org/oscim/theme/RenderTheme.java @@ -14,10 +14,13 @@ */ package org.oscim.theme; +import static org.oscim.core.MapElement.GEOM_LINE; +import static org.oscim.core.MapElement.GEOM_POLY; + import java.util.ArrayList; import java.util.List; -import org.oscim.core.Tag; +import org.oscim.core.MapElement; import org.oscim.theme.renderinstruction.RenderInstruction; import org.oscim.theme.rule.Closed; import org.oscim.theme.rule.Rule; @@ -25,12 +28,13 @@ import org.oscim.utils.LRUCache; import org.xml.sax.Attributes; import android.graphics.Color; +import android.util.Log; /** - * A RenderTheme defines how ways and nodes are drawn. + * A RenderTheme defines how map elements are drawn. */ -public class RenderTheme { - //private final static String TAG = RenderTheme.class.getName(); +public class RenderTheme implements IRenderTheme { + private final static String TAG = RenderTheme.class.getName(); private static final int MATCHING_CACHE_SIZE = 512; private static final int RENDER_THEME_VERSION = 1; @@ -82,25 +86,26 @@ public class RenderTheme { private final float mBaseStrokeWidth; private final float mBaseTextSize; - private int mLevels; private final int mMapBackground; - private final ArrayList mRulesList; - private final LRUCache mNodesCache; - private final LRUCache mWayCache; - private final LRUCache mAreaCache; + private int mLevels; + private Rule[] mRules; - private final MatchingCacheKey mAreaCacheKey; - private final MatchingCacheKey mWayCacheKey; - private final MatchingCacheKey mNodeCacheKey; + class ElementCache { + final LRUCache cache; + final MatchingCacheKey cacheKey; - private final ArrayList mWayInstructionList; - private final ArrayList mAreaInstructionList; - private final ArrayList mNodeInstructionList; + /* temporary matching instructions list */ + final ArrayList instructionList; - private RenderInstructionItem mPrevAreaItem; - private RenderInstructionItem mPrevWayItem; - private RenderInstructionItem mPrevNodeItem; + RenderInstructionItem prevItem; + + public ElementCache() { + cache = new LRUCache(MATCHING_CACHE_SIZE); + instructionList = new ArrayList(4); + cacheKey = new MatchingCacheKey(); + } + } class RenderInstructionItem { RenderInstructionItem next; @@ -109,65 +114,60 @@ public class RenderTheme { MatchingCacheKey key; } + private final ElementCache[] mElementCache; + RenderTheme(int mapBackground, float baseStrokeWidth, float baseTextSize) { mMapBackground = mapBackground; mBaseStrokeWidth = baseStrokeWidth; mBaseTextSize = baseTextSize; - mRulesList = new ArrayList(); - mNodesCache = new LRUCache(MATCHING_CACHE_SIZE); - mWayCache = new LRUCache(MATCHING_CACHE_SIZE); - mAreaCache = new LRUCache(MATCHING_CACHE_SIZE); + mElementCache = new ElementCache[3]; + for (int i = 0; i < 3; i++) + mElementCache[i] = new ElementCache(); - mWayInstructionList = new ArrayList(4); - mAreaInstructionList = new ArrayList(4); - mNodeInstructionList = new ArrayList(4); - - mAreaCacheKey = new MatchingCacheKey(); - mWayCacheKey = new MatchingCacheKey(); - mNodeCacheKey = new MatchingCacheKey(); } - /** - * Must be called when this RenderTheme gets destroyed to clean up and free - * resources. + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#destroy() */ + @Override public void destroy() { - mNodesCache.clear(); - mAreaCache.clear(); - mWayCache.clear(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).onDestroy(); + for (int i = 0; i < 3; i++) + mElementCache[i].cache.clear(); + + if (mRules != null) { + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].onDestroy(); } } - /** - * @return the number of distinct drawing levels required by this - * RenderTheme. + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#getLevels() */ + @Override public int getLevels() { return mLevels; } - /** - * @return the map background color of this RenderTheme. - * @see Color + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#getMapBackground() */ + @Override public int getMapBackground() { return mMapBackground; } - /** - * ... - * - * @param tags - * ... - * @param zoomLevel - * ... - * @return ... + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#matchWay(org.oscim.core.Tag[], byte, + * boolean) */ - public RenderInstruction[] matchNode(Tag[] tags, byte zoomLevel) { + @Override + public RenderInstruction[] matchElement(MapElement element, int zoomLevel) { // list of renderinsctruction items in cache RenderInstructionItem ris = null; @@ -175,157 +175,32 @@ public class RenderTheme { // the item matching tags and zoomlevel RenderInstructionItem ri = null; - synchronized (mNodesCache) { - int zoomMask = 1 << zoomLevel; - - MatchingCacheKey cacheKey = mNodeCacheKey; - if (mPrevNodeItem == null || (mPrevNodeItem.zoom & zoomMask) == 0) { - // previous instructions zoom does not match - cacheKey.set(tags, null); - } else { - // compare if tags match previous instructions - if (cacheKey.set(tags, mPrevNodeItem.key)) - ri = mPrevNodeItem; - } - - if (ri == null) { - boolean found = mNodesCache.containsKey(cacheKey); - - if (found) { - ris = mNodesCache.get(cacheKey); - - for (ri = ris; ri != null; ri = ri.next) - if ((ri.zoom & zoomMask) != 0) - // cache hit - break; - } - } - - if (ri == null) { - // cache miss - List matches = mNodeInstructionList; - matches.clear(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) - mRulesList.get(i).matchNode(tags, zoomLevel, matches); - - 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) { - ri.next = ris.next; - ri.key = ris.key; - ris.next = ri; - } else { - ri.key = new MatchingCacheKey(cacheKey); - mNodesCache.put(ri.key, ri); - } - } - } + int type = element.geometryType; + if (type < 1 || type > 3) { + Log.d(TAG, "invalid geometry type for RenderTheme " + type); + return null; } - mPrevNodeItem = ri; - - return ri.list; - - } - - /** - * Matches a way with the given parameters against this RenderTheme. - * - * @param tags - * the tags of the way. - * @param zoomLevel - * the zoom level at which the way should be matched. - * @param closed - * way is Closed - * @return currently processed render instructions - */ - public RenderInstruction[] matchWay(Tag[] tags, byte zoomLevel, boolean closed) { - - // list of renderinsctruction items in cache - RenderInstructionItem ris = null; - - RenderInstructionItem prevInstructions = null; - - // the item matching tags and zoomlevel - RenderInstructionItem ri = null; - - // temporary matching instructions list - List matches; - - LRUCache matchingCache; - MatchingCacheKey cacheKey; - - if (closed) - matchingCache = mAreaCache; - else - matchingCache = mWayCache; + ElementCache cache = mElementCache[type - 1]; + // NOTE: maximum zoom level supported is 32 int zoomMask = 1 << zoomLevel; - synchronized (matchingCache) { - if (closed) { - cacheKey = mAreaCacheKey; - matches = mAreaInstructionList; - prevInstructions = mPrevAreaItem; - } else { - cacheKey = mWayCacheKey; - matches = mWayInstructionList; - prevInstructions = mPrevWayItem; - } + synchronized (cache) { - if (prevInstructions == null || (prevInstructions.zoom & zoomMask) == 0) { + if (cache.prevItem == null || (cache.prevItem.zoom & zoomMask) == 0) { // previous instructions zoom does not match - cacheKey.set(tags, null); + cache.cacheKey.set(element.tags, null); } else { // compare if tags match previous instructions - if (cacheKey.set(tags, prevInstructions.key)) { + if (cache.cacheKey.set(element.tags, cache.prevItem.key)) { //Log.d(TAG, "same as previous " + Arrays.deepToString(tags)); - ri = prevInstructions; + ri = cache.prevItem; } } if (ri == null) { - ris = matchingCache.get(cacheKey); + ris = cache.cache.get(cache.cacheKey); for (ri = ris; ri != null; ri = ri.next) if ((ri.zoom & zoomMask) != 0) @@ -337,12 +212,22 @@ public class RenderTheme { // cache miss //Log.d(TAG, missCnt++ + " / " + hitCnt + " Cache Miss"); - int c = (closed ? Closed.YES : Closed.NO); - //List matches = mMatchingList; + List matches = cache.instructionList; matches.clear(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) - mRulesList.get(i).matchWay(tags, zoomLevel, c, matches); + if (type == GEOM_LINE) { + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].matchWay(element.tags, + (byte) zoomLevel, Closed.NO, matches); + } else if (type == GEOM_POLY) { + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].matchWay(element.tags, + (byte) zoomLevel, Closed.YES, matches); + } else { + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].matchNode(element.tags, + (byte) zoomLevel, matches); + } int size = matches.size(); // check if same instructions are used in another level @@ -373,12 +258,13 @@ public class RenderTheme { } if (ri != null) { - // we found a same matchting list on another zoomlevel + // we found a same matchting list on another zoomlevel add + // this zoom level to the existing RenderInstructionItem. ri.zoom |= zoomMask; + //Log.d(TAG, // zoomLevel + " same instructions " + size + " " // + Arrays.deepToString(tags)); - } else { //Log.d(TAG, // zoomLevel + " new instructions " + size + " " @@ -398,56 +284,49 @@ public class RenderTheme { ri.key = ris.key; ris.next = ri; } else { - ri.key = new MatchingCacheKey(cacheKey); - matchingCache.put(ri.key, ri); + ri.key = new MatchingCacheKey(cache.cacheKey); + cache.cache.put(ri.key, ri); } } } - if (closed) - mPrevAreaItem = ri; - else - mPrevWayItem = ri; + + cache.prevItem = ri; } return ri.list; } - void addRule(Rule rule) { - mRulesList.add(rule); - } - void complete() { - mRulesList.trimToSize(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).onComplete(); - } - } - - void setLevels(int levels) { + void complete(List rulesList, int levels) { mLevels = levels; + + mRules = new Rule[rulesList.size()]; + rulesList.toArray(mRules); + + for (int i = 0, n = mRules.length; i < n; i++) { + mRules[i].onComplete(); + } } - /** - * Scales the stroke width of this RenderTheme by the given factor. - * - * @param scaleFactor - * the factor by which the stroke width should be scaled. + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#scaleStrokeWidth(float) */ + @Override public void scaleStrokeWidth(float scaleFactor) { - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).scaleStrokeWidth(scaleFactor * mBaseStrokeWidth); - } + + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].scaleStrokeWidth(scaleFactor * mBaseStrokeWidth); } - /** - * Scales the text size of this RenderTheme by the given factor. - * - * @param scaleFactor - * the factor by which the text size should be scaled. + /* + * (non-Javadoc) + * @see org.oscim.theme.IRenderTheme#scaleTextSize(float) */ + @Override public void scaleTextSize(float scaleFactor) { - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).scaleTextSize(scaleFactor * mBaseTextSize); - } + + for (int i = 0, n = mRules.length; i < n; i++) + mRules[i].scaleTextSize(scaleFactor * mBaseTextSize); } } diff --git a/src/org/oscim/theme/RenderThemeHandler.java b/src/org/oscim/theme/RenderThemeHandler.java index 0196f6b5..34f9d694 100644 --- a/src/org/oscim/theme/RenderThemeHandler.java +++ b/src/org/oscim/theme/RenderThemeHandler.java @@ -17,6 +17,7 @@ package org.oscim.theme; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; @@ -75,7 +76,7 @@ public class RenderThemeHandler extends DefaultHandler { * @throws IOException * if an I/O error occurs while reading from the input stream. */ - public static RenderTheme getRenderTheme(InputStream inputStream) + public static IRenderTheme getRenderTheme(InputStream inputStream) throws SAXException, ParserConfigurationException, IOException { RenderThemeHandler renderThemeHandler = new RenderThemeHandler(); @@ -112,7 +113,9 @@ public class RenderThemeHandler extends DefaultHandler { Log.d(TAG, stringBuilder.toString()); } + private final ArrayList mRulesList = new ArrayList(); private Rule mCurrentRule; + private final Stack mElementStack = new Stack(); private int mLevel; private RenderTheme mRenderTheme; @@ -126,8 +129,8 @@ public class RenderThemeHandler extends DefaultHandler { throw new IllegalArgumentException("missing element: rules"); } - mRenderTheme.setLevels(mLevel); - mRenderTheme.complete(); + mRenderTheme.complete(mRulesList, mLevel); + mRulesList.clear(); tmpStyleHash.clear(); } @@ -138,7 +141,7 @@ public class RenderThemeHandler extends DefaultHandler { if (ELEMENT_NAME_RULE.equals(localName)) { mRuleStack.pop(); if (mRuleStack.empty()) { - mRenderTheme.addRule(mCurrentRule); + mRulesList.add(mCurrentRule); } else { mCurrentRule = mRuleStack.peek(); }