Render themes: tag transform (#587)

This commit is contained in:
Gustl22 2018-09-09 22:44:36 +02:00 committed by Emux
parent 7c2d2d3759
commit 61318bff8a
No known key found for this signature in database
GPG Key ID: 64ED9980896038C3
12 changed files with 208 additions and 49 deletions

View File

@ -301,11 +301,24 @@
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
<!-- tag-transform element -->
<xs:complexType name="tag-transform">
<xs:attribute name="k" type="xs:string" use="required" />
<xs:attribute name="v" type="xs:string" use="optional" />
<xs:attribute name="k-match" type="xs:string" use="required" />
<xs:attribute name="v-match" type="xs:string" use="optional" />
</xs:complexType>
<!-- rendertheme element -->
<xs:complexType name="rendertheme">
<xs:sequence maxOccurs="1" minOccurs="0">
<xs:element name="stylemenu" maxOccurs="1" minOccurs="0" type="tns:stylemenu" />
<!-- tag definitions -->
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="tag-transform" type="tns:tag-transform" />
</xs:choice>
<!-- style definitions -->
<xs:sequence maxOccurs="256" minOccurs="0">
<xs:choice maxOccurs="unbounded" minOccurs="0">

View File

@ -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
}
}

View File

@ -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() {
}

View File

@ -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">
<!--<tag-transform k="kind" v="building" k-match="building" v-match="yes" />-->
<!--<tag-transform k="kind" v="building_part" k-match="building:part" v-match="yes" />-->
<tag-transform k="root_id" k-match="ref" />
<tag-transform k="roof_color" k-match="roof:colour" />
<tag-transform k="roof_direction" k-match="roof:direction" />
<tag-transform k="roof_height" k-match="roof:height" />
<tag-transform k="roof_material" k-match="roof:material" />
<tag-transform k="roof_orientation" k-match="roof:orientation" />
<tag-transform k="roof_shape" k-match="roof:shape" />
<!-- base style for fixed width lines -->
<style-line cap="butt" fix="true" id="fix" width="1.0" />

View File

@ -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">
<tag-transform k="render_height" k-match="height" />
<tag-transform k="render_min_height" k-match="min_height" />
<!--<tag-transform k="layer" v="building" k-match="building" v-match="yes" />-->
<!--<tag-transform k="layer" v="building:part" k-match="building:part" v-match="yes" />-->
<!--###### TEXT styles ######-->
<!--default label-->

View File

@ -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

View File

@ -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";

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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<String, String> mTransformKeyMap;
private final Map<Tag, Tag> mTransformTagMap;
class RenderStyleCache {
final int matchType;
final LRUCache<MatchingCacheKey, RenderStyleItem> 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<String, String> transformKeyMap, Map<Tag, Tag> 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<String, String> transformKeyMap, Map<Tag, Tag> 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)

View File

@ -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<String, String> mTransformKeyMap = new HashMap<>();
private Map<Tag, Tag> 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: "