diff --git a/resources/rendertheme.xsd b/resources/rendertheme.xsd index 9ec1edab..cdfc815c 100644 --- a/resources/rendertheme.xsd +++ b/resources/rendertheme.xsd @@ -301,11 +301,24 @@ + + + + + + + + + + + + + diff --git a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java index 86c7d30d..8264649e 100644 --- a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java +++ b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java @@ -251,6 +251,20 @@ public class RenderTheme implements IRenderTheme { public void scaleTextSize(float scaleFactor) { } + @Override + public String transformKey(String key) { + return null; + } + + @Override + public Tag transformTag(Tag tag) { + return null; + } + + @Override + public void updateStyles() { + } + public static void main(String[] args) { RenderTheme t = new RenderTheme(); @@ -261,11 +275,4 @@ public class RenderTheme implements IRenderTheme { t.matchElement(GeometryType.POLY, e.tags, 16); t.matchElement(GeometryType.POLY, e.tags, 15); } - - @Override - public void updateStyles() { - // TODO Auto-generated method stub - - } - } diff --git a/vtm-playground/src/org/oscim/test/DebugTheme.java b/vtm-playground/src/org/oscim/test/DebugTheme.java index ad1390a2..7845850f 100644 --- a/vtm-playground/src/org/oscim/test/DebugTheme.java +++ b/vtm-playground/src/org/oscim/test/DebugTheme.java @@ -2,6 +2,7 @@ package org.oscim.test; import org.oscim.backend.canvas.Color; import org.oscim.core.GeometryBuffer.GeometryType; +import org.oscim.core.Tag; import org.oscim.core.TagSet; import org.oscim.theme.IRenderTheme; import org.oscim.theme.styles.AreaStyle; @@ -46,6 +47,16 @@ public class DebugTheme implements IRenderTheme { public void scaleTextSize(float scaleFactor) { } + @Override + public String transformKey(String key) { + return null; + } + + @Override + public Tag transformTag(Tag tag) { + return null; + } + @Override public void updateStyles() { } diff --git a/vtm-themes/resources/assets/vtm/mapzen.xml b/vtm-themes/resources/assets/vtm/mapzen.xml index 01c896a5..ec17fc38 100644 --- a/vtm-themes/resources/assets/vtm/mapzen.xml +++ b/vtm-themes/resources/assets/vtm/mapzen.xml @@ -3,6 +3,17 @@ version="1" xmlns="http://opensciencemap.org/rendertheme" xsi:schemaLocation="http://opensciencemap.org/rendertheme https://raw.githubusercontent.com/mapsforge/vtm/master/resources/rendertheme.xsd"> + + + + + + + + + + + diff --git a/vtm-themes/resources/assets/vtm/openmaptiles.xml b/vtm-themes/resources/assets/vtm/openmaptiles.xml index 5453f06d..79282f32 100644 --- a/vtm-themes/resources/assets/vtm/openmaptiles.xml +++ b/vtm-themes/resources/assets/vtm/openmaptiles.xml @@ -3,6 +3,11 @@ version="1" xmlns="http://opensciencemap.org/rendertheme" xsi:schemaLocation="http://opensciencemap.org/rendertheme https://raw.githubusercontent.com/mapsforge/vtm/master/resources/rendertheme.xsd"> + + + + + diff --git a/vtm/src/org/oscim/core/MapElement.java b/vtm/src/org/oscim/core/MapElement.java index b6b69b34..0cbc8ef2 100644 --- a/vtm/src/org/oscim/core/MapElement.java +++ b/vtm/src/org/oscim/core/MapElement.java @@ -19,6 +19,8 @@ */ package org.oscim.core; +import org.oscim.theme.IRenderTheme; + /** * The MapElement class is a reusable container for a geometry * with tags. @@ -63,10 +65,9 @@ public class MapElement extends GeometryBuffer { /** * @return height in meters, if present */ - public Float getHeight() { - String v = tags.getValue(Tag.KEY_HEIGHT); - if (v == null) - v = tags.getValue("render_height"); // OpenMapTiles + public Float getHeight(IRenderTheme theme) { + String res = theme.transformKey(Tag.KEY_HEIGHT); + String v = tags.getValue(res != null ? res : Tag.KEY_HEIGHT); if (v != null) return Float.parseFloat(v); return null; @@ -75,22 +76,21 @@ public class MapElement extends GeometryBuffer { /** * @return minimum height in meters, if present */ - public Float getMinHeight() { - String v = tags.getValue(Tag.KEY_MIN_HEIGHT); - if (v == null) - v = tags.getValue("render_min_height"); // OpenMapTiles + public Float getMinHeight(IRenderTheme theme) { + String res = theme.transformKey(Tag.KEY_MIN_HEIGHT); + String v = tags.getValue(res != null ? res : Tag.KEY_MIN_HEIGHT); if (v != null) return Float.parseFloat(v); return null; } - public boolean isBuilding() { + public boolean isBuilding() { // TODO from themes (with overzoom ref) return tags.containsKey(Tag.KEY_BUILDING) || "building".equals(tags.getValue("kind")) // Mapzen || "building".equals(tags.getValue("layer")); // OpenMapTiles } - public boolean isBuildingPart() { + public boolean isBuildingPart() { // TODO from themes (with overzoom ref) return tags.containsKey(Tag.KEY_BUILDING_PART) || "building_part".equals(tags.getValue("kind")) // Mapzen || "building:part".equals(tags.getValue("layer")); // OpenMapTiles diff --git a/vtm/src/org/oscim/core/Tag.java b/vtm/src/org/oscim/core/Tag.java index 808fd069..03105a0d 100644 --- a/vtm/src/org/oscim/core/Tag.java +++ b/vtm/src/org/oscim/core/Tag.java @@ -65,9 +65,9 @@ public class Tag { public static final String KEY_BUILDING_MATERIAL = "building:material"; public static final String KEY_BUILDING_MIN_LEVEL = "building:min_level"; public static final String KEY_BUILDING_PART = "building:part"; - public static final String KEY_COLOR = "colour"; + //public static final String KEY_COLOR = "colour"; public static final String KEY_HEIGHT = "height"; - public static final String KEY_MATERIAL = "material"; + //public static final String KEY_MATERIAL = "material"; public static final String KEY_MIN_HEIGHT = "min_height"; public static final String KEY_ROOF = "roof"; public static final String KEY_ROOF_ANGLE = "roof:angle"; diff --git a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java index 9e63cffb..985b1f10 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java @@ -31,6 +31,7 @@ import org.oscim.renderer.OffscreenRenderer; import org.oscim.renderer.OffscreenRenderer.Mode; import org.oscim.renderer.bucket.ExtrusionBuckets; import org.oscim.renderer.bucket.RenderBuckets; +import org.oscim.theme.IRenderTheme; import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.RenderStyle; @@ -66,6 +67,8 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim private final ZoomLimiter mZoomLimiter; + protected final IRenderTheme mRenderTheme; + class BuildingElement { MapElement element; ExtrusionStyle style; @@ -103,6 +106,8 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim mRenderer = new BuildingRenderer(tileLayer.tileRenderer(), mZoomLimiter, mesh, TRANSLUCENT); if (POST_AA) mRenderer = new OffscreenRenderer(Mode.SSAO_FXAA, mRenderer); + + mRenderTheme = tileLayer.getTheme(); } @Override @@ -160,22 +165,22 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim int height = 0; // cm int minHeight = 0; // cm - Float f = element.getHeight(); + Float f = element.getHeight(mRenderTheme); if (f != null) height = (int) (f * 100); else { // #TagFromTheme: generalize level/height tags - String v = element.tags.getValue(Tag.KEY_BUILDING_LEVELS); + String v = getValue(element, Tag.KEY_BUILDING_LEVELS); if (v != null) height = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT); } - f = element.getMinHeight(); + f = element.getMinHeight(mRenderTheme); if (f != null) minHeight = (int) (f * 100); else { // #TagFromTheme: level/height tags - String v = element.tags.getValue(Tag.KEY_BUILDING_MIN_LEVEL); + String v = getValue(element, Tag.KEY_BUILDING_MIN_LEVEL); if (v != null) minHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT); } @@ -202,15 +207,14 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim if (!partBuilding.element.isBuildingPart()) continue; - String refId = partBuilding.element.tags.getValue(Tag.KEY_REF); // #TagFromTheme - refId = refId == null ? partBuilding.element.tags.getValue("root_id") : refId; // Mapzen + String refId = getValue(partBuilding.element, Tag.KEY_REF); if (refId == null) continue; // Search buildings which inherit parts for (BuildingElement rootBuilding : tileBuildings) { if (rootBuilding.element.isBuildingPart() - || !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID)))) + || !(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID)))) continue; rootBuildings.add(rootBuilding); @@ -239,6 +243,15 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim return ebs; } + protected String getKeyOrDefault(String key) { + String res = mRenderTheme.transformKey(key); + return res != null ? res : key; + } + + protected String getValue(MapElement element, String key) { + return element.tags.getValue(getKeyOrDefault(key)); + } + @Override public void complete(MapTile tile, boolean success) { if (success) { diff --git a/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java b/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java index ef9e0897..93b0cee5 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java @@ -101,12 +101,12 @@ public class S3DBLayer extends BuildingLayer { int roofHeight = 0; // Get roof height - String v = element.tags.getValue(Tag.KEY_ROOF_HEIGHT); + String v = getValue(element, Tag.KEY_ROOF_HEIGHT); if (v != null) { roofHeight = (int) (Float.parseFloat(v) * 100); - } else if ((v = element.tags.getValue(Tag.KEY_ROOF_LEVELS)) != null) { + } else if ((v = getValue(element, Tag.KEY_ROOF_LEVELS)) != null) { roofHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT); - } else if ((v = element.tags.getValue(Tag.KEY_ROOF_ANGLE)) != null) { + } else if ((v = getValue(element, Tag.KEY_ROOF_ANGLE)) != null) { Box bb = null; for (int k = 0; k < element.index[0]; k += 2) { float p1 = element.points[k]; @@ -122,17 +122,17 @@ public class S3DBLayer extends BuildingLayer { // Angle is simplified, 40 is an estimated constant roofHeight = (int) ((Float.parseFloat(v) / 45.f) * (minSize * 40)); } - } else if ((v = element.tags.getValue(Tag.KEY_ROOF_SHAPE)) != null && !v.equals(Tag.VALUE_FLAT)) { + } else if ((v = getValue(element, Tag.KEY_ROOF_SHAPE)) != null && !v.equals(Tag.VALUE_FLAT)) { roofHeight = (2 * BUILDING_LEVEL_HEIGHT); } // Get building height - v = element.tags.getValue(Tag.KEY_HEIGHT); + v = getValue(element, Tag.KEY_HEIGHT); if (v != null) { maxHeight = (int) (Float.parseFloat(v) * 100); } else { // #TagFromTheme: generalize level/height tags - if ((v = element.tags.getValue(Tag.KEY_BUILDING_LEVELS)) != null) { + if ((v = getValue(element, Tag.KEY_BUILDING_LEVELS)) != null) { maxHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT); maxHeight += roofHeight; } @@ -140,22 +140,22 @@ public class S3DBLayer extends BuildingLayer { if (maxHeight == 0) maxHeight = extrusion.defaultHeight * 100; - v = element.tags.getValue(Tag.KEY_MIN_HEIGHT); + v = getValue(element, Tag.KEY_MIN_HEIGHT); if (v != null) minHeight = (int) (Float.parseFloat(v) * 100); else { // #TagFromTheme: level/height tags - if ((v = element.tags.getValue(Tag.KEY_BUILDING_MIN_LEVEL)) != null) + if ((v = getValue(element, Tag.KEY_BUILDING_MIN_LEVEL)) != null) minHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT); } // Get building color Integer bColor = null; if (mColored) { - if (element.tags.containsKey(Tag.KEY_BUILDING_COLOR)) { - bColor = S3DBUtils.getColor(element.tags.getValue(Tag.KEY_BUILDING_COLOR), false, false); - } else if (element.tags.containsKey(Tag.KEY_BUILDING_MATERIAL)) { - bColor = S3DBUtils.getMaterialColor(element.tags.getValue(Tag.KEY_BUILDING_MATERIAL), false); + if ((v = getValue(element, Tag.KEY_BUILDING_COLOR)) != null) { + bColor = S3DBUtils.getColor(v, false, false); + } else if ((v = getValue(element, Tag.KEY_BUILDING_MATERIAL)) != null) { + bColor = S3DBUtils.getMaterialColor(v, false); } } @@ -190,25 +190,32 @@ public class S3DBLayer extends BuildingLayer { if (!partBuilding.element.isBuildingPart()) continue; - TagSet partTags = partBuilding.element.tags; - String refId = partTags.getValue(Tag.KEY_REF); // #TagFromTheme - refId = refId == null ? partTags.getValue("root_id") : refId; // Mapzen + String refId = getValue(partBuilding.element, Tag.KEY_REF); if (refId == null) continue; + TagSet partTags = partBuilding.element.tags; + // Search buildings which inherit parts for (BuildingElement rootBuilding : tileBuildings) { if (rootBuilding.element.isBuildingPart() || !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID)))) continue; + if ((getValue(rootBuilding.element, Tag.KEY_ROOF_SHAPE) != null) + && (getValue(partBuilding.element, Tag.KEY_ROOF_SHAPE) == null)) { + partBuilding.element.tags.add(rootBuilding.element.tags.get(getKeyOrDefault(Tag.KEY_ROOF_SHAPE))); + } + if (mColored) { TagSet rootTags = rootBuilding.element.tags; + for (int i = 0; i < rootTags.size(); i++) { Tag rTag = rootTags.get(i); - if ((rTag.key.equals(Tag.KEY_COLOR) && !partTags.containsKey(Tag.KEY_MATERIAL) - || rTag.key.equals(Tag.KEY_ROOF_COLOR) && !partTags.containsKey(Tag.KEY_ROOF_MATERIAL) - || rTag.key.equals(Tag.KEY_ROOF_SHAPE)) + if ((rTag.key.equals(getKeyOrDefault(Tag.KEY_BUILDING_COLOR)) + && !partTags.containsKey(getKeyOrDefault(Tag.KEY_BUILDING_MATERIAL)) + || rTag.key.equals(getKeyOrDefault(Tag.KEY_ROOF_COLOR)) + && !partTags.containsKey(getKeyOrDefault(Tag.KEY_ROOF_MATERIAL))) && !partTags.containsKey(rTag.key)) { partTags.add(rTag); } @@ -243,22 +250,22 @@ public class S3DBLayer extends BuildingLayer { String v; if (mColored) { - v = element.tags.getValue(Tag.KEY_ROOF_COLOR); + v = getValue(element, Tag.KEY_ROOF_COLOR); if (v != null) roofColor = S3DBUtils.getColor(v, true, false); - else if ((v = element.tags.getValue(Tag.KEY_ROOF_MATERIAL)) != null) + else if ((v = getValue(element, Tag.KEY_ROOF_MATERIAL)) != null) roofColor = S3DBUtils.getMaterialColor(v, true); } boolean roofOrientationAcross = false; - if ((v = element.tags.getValue(Tag.KEY_ROOF_ORIENTATION)) != null) { + if ((v = getValue(element, Tag.KEY_ROOF_ORIENTATION)) != null) { if (v.equals(Tag.VALUE_ACROSS)) { roofOrientationAcross = true; } } // Calc roof shape - v = element.tags.getValue(Tag.KEY_ROOF_SHAPE); + v = getValue(element, Tag.KEY_ROOF_SHAPE); if (v == null) { v = Tag.VALUE_FLAT; } diff --git a/vtm/src/org/oscim/theme/IRenderTheme.java b/vtm/src/org/oscim/theme/IRenderTheme.java index 5973a984..e7725f5d 100644 --- a/vtm/src/org/oscim/theme/IRenderTheme.java +++ b/vtm/src/org/oscim/theme/IRenderTheme.java @@ -2,6 +2,7 @@ * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2013 Hannes Janetzek * Copyright 2017 devemux86 + * Copyright 2018 Gustl22 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -19,6 +20,7 @@ package org.oscim.theme; import org.oscim.core.GeometryBuffer.GeometryType; +import org.oscim.core.Tag; import org.oscim.core.TagSet; import org.oscim.theme.styles.RenderStyle; @@ -63,6 +65,16 @@ public interface IRenderTheme { */ void scaleTextSize(float scaleFactor); + /** + * @return the transformed tag key of this RenderTheme. + */ + String transformKey(String key); + + /** + * @return the transformed tag of this RenderTheme. + */ + Tag transformTag(Tag tag); + class ThemeException extends IllegalArgumentException { public ThemeException(String string) { super(string); diff --git a/vtm/src/org/oscim/theme/RenderTheme.java b/vtm/src/org/oscim/theme/RenderTheme.java index a3f787da..974f3076 100644 --- a/vtm/src/org/oscim/theme/RenderTheme.java +++ b/vtm/src/org/oscim/theme/RenderTheme.java @@ -2,6 +2,7 @@ * Copyright 2014 Hannes Janetzek * Copyright 2017 Longri * Copyright 2017 devemux86 + * Copyright 2018 Gustl22 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -19,6 +20,7 @@ package org.oscim.theme; import org.oscim.core.GeometryBuffer.GeometryType; +import org.oscim.core.Tag; import org.oscim.core.TagSet; import org.oscim.theme.rule.Rule; import org.oscim.theme.rule.Rule.Element; @@ -31,6 +33,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; public class RenderTheme implements IRenderTheme { static final Logger log = LoggerFactory.getLogger(RenderTheme.class); @@ -44,6 +47,9 @@ public class RenderTheme implements IRenderTheme { private final Rule[] mRules; private final boolean mMapsforgeTheme; + private final Map mTransformKeyMap; + private final Map mTransformTagMap; + class RenderStyleCache { final int matchType; final LRUCache cache; @@ -79,7 +85,17 @@ public class RenderTheme implements IRenderTheme { this(mapBackground, baseTextSize, rules, levels, false); } + public RenderTheme(int mapBackground, float baseTextSize, Rule[] rules, int levels, + Map transformKeyMap, Map transformTagMap) { + this(mapBackground, baseTextSize, rules, levels, transformKeyMap, transformTagMap, false); + } + public RenderTheme(int mapBackground, float baseTextSize, Rule[] rules, int levels, boolean mapsforgeTheme) { + this(mapBackground, baseTextSize, rules, levels, null, null, mapsforgeTheme); + } + + public RenderTheme(int mapBackground, float baseTextSize, Rule[] rules, int levels, + Map transformKeyMap, Map transformTagMap, boolean mapsforgeTheme) { if (rules == null) throw new IllegalArgumentException("rules missing"); @@ -89,6 +105,9 @@ public class RenderTheme implements IRenderTheme { mRules = rules; mMapsforgeTheme = mapsforgeTheme; + mTransformKeyMap = transformKeyMap; + mTransformTagMap = transformTagMap; + mStyleCache = new RenderStyleCache[3]; mStyleCache[0] = new RenderStyleCache(Element.NODE); mStyleCache[1] = new RenderStyleCache(Element.LINE); @@ -272,6 +291,20 @@ public class RenderTheme implements IRenderTheme { rule.scaleTextSize(scaleFactor * mBaseTextSize); } + @Override + public String transformKey(String key) { + if (mTransformKeyMap != null) + return mTransformKeyMap.get(key); + return null; + } + + @Override + public Tag transformTag(Tag tag) { + if (mTransformTagMap != null) + return mTransformTagMap.get(tag); + return null; + } + @Override public void updateStyles() { for (Rule rule : mRules) diff --git a/vtm/src/org/oscim/theme/XmlThemeBuilder.java b/vtm/src/org/oscim/theme/XmlThemeBuilder.java index c5640d34..9681ba53 100644 --- a/vtm/src/org/oscim/theme/XmlThemeBuilder.java +++ b/vtm/src/org/oscim/theme/XmlThemeBuilder.java @@ -4,6 +4,7 @@ * Copyright 2016-2018 devemux86 * Copyright 2016-2017 Longri * Copyright 2016 Andrey Novikov + * Copyright 2018 Gustl22 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -64,6 +65,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.Stack; @@ -168,6 +170,9 @@ public class XmlThemeBuilder extends DefaultHandler { private XmlRenderThemeStyleLayer mCurrentLayer; private XmlRenderThemeStyleMenu mRenderThemeStyleMenu; + private Map mTransformKeyMap = new HashMap<>(); + private Map mTransformTagMap = new HashMap<>(); + public XmlThemeBuilder(ThemeFile theme) { this(theme, null); } @@ -202,7 +207,7 @@ public class XmlThemeBuilder extends DefaultHandler { } RenderTheme createTheme(Rule[] rules) { - return new RenderTheme(mMapBackground, mTextScale, rules, mLevels, mMapsforgeTheme); + return new RenderTheme(mMapBackground, mTextScale, rules, mLevels, mTransformKeyMap, mTransformTagMap, mMapsforgeTheme); } @Override @@ -369,6 +374,10 @@ public class XmlThemeBuilder extends DefaultHandler { mRenderThemeStyleMenu = new XmlRenderThemeStyleMenu(getStringAttribute(attributes, "id"), getStringAttribute(attributes, "defaultlang"), getStringAttribute(attributes, "defaultvalue")); + } else if ("tag-transform".equals(localName)) { + checkState(qName, Element.RENDERING_STYLE); + tagTransform(localName, attributes); + } else { log.error("unknown element: {}", localName); throw new SAXException("unknown element: " + localName); @@ -1239,6 +1248,44 @@ public class XmlThemeBuilder extends DefaultHandler { return dashIntervals; } + private void tagTransform(String localName, Attributes attributes) { + String k, v, matchKey, matchValue; + k = v = matchKey = matchValue = null; + + for (int i = 0; i < attributes.getLength(); i++) { + String name = attributes.getLocalName(i); + String value = attributes.getValue(i); + + switch (name) { + case "k": + k = value; + break; + case "v": + v = value; + break; + case "k-match": + matchKey = value; + break; + case "v-match": + matchValue = value; + break; + default: + logUnknownAttribute(localName, name, value, i); + } + } + + if (k == null || k.isEmpty() || matchKey == null || matchKey.isEmpty()) { + log.debug("empty key in element " + localName); + return; + } + + if (v == null && matchValue == null) { + mTransformKeyMap.put(matchKey, k); + } else { + mTransformTagMap.put(new Tag(matchKey, matchValue), new Tag(k, v)); + } + } + private static void validateNonNegative(String name, float value) { if (value < 0) throw new ThemeException(name + " must not be negative: "