OsmAnd/DataExtractionOSM/src/net/osmand/render/OsmandRenderingRulesParser.java
Victor Shcherb 1bf926ad69 implement vector rendering menu options
git-svn-id: https://osmand.googlecode.com/svn/trunk@687 e29c36b1-1cfa-d876-8d93-3434fc2bb7b8
2010-11-22 19:38:35 +00:00

537 lines
18 KiB
Java

package net.osmand.render;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import net.osmand.LogUtil;
import org.apache.commons.logging.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class OsmandRenderingRulesParser {
private final static Log log = LogUtil.getLog(OsmandRenderingRulesParser.class);
public static class EffectAttributes {
public int color = 0;
public float strokeWidth = 0;
public String pathEffect = null;
public int shadowColor = 0;
public float shadowRadius = 0;
public String cap = null;
}
public static class TextAttributes {
public int textColor = 0;
public float textSize = 0;
public boolean textBold = false;
public String textShield = null;
public int textMinDistance = 0;
public boolean textOnPath = false;
public int textWrapWidth = 0;
public float textHaloRadius = 0;
public int textDy = 0;
public String ref = null;
}
protected static class SwitchState {
List<FilterState> filters = new ArrayList<FilterState>();
}
public static class FilterState {
public int minzoom = -1;
public int maxzoom = -1;
public String tag = null;
public String val = null;
public int layer = 0;
public int textLength = 0;
public float order = 0;
public int orderType = -1;
public String shader = null;
// point
public String icon = null;
public EffectAttributes main = new EffectAttributes();
public TextAttributes text = null;
public List<EffectAttributes> effectAttributes = new ArrayList<EffectAttributes>(3);
protected EffectAttributes getEffectAttributes(int i) {
i -= 2;
while (i >= effectAttributes.size()) {
effectAttributes.add(new EffectAttributes());
}
return effectAttributes.get(i);
}
}
public interface RenderingRuleVisitor {
/**
* @param state - one of the point, polygon, line, text state
* @param filter
*/
public void visitRule(int state, FilterState filter);
public void rendering(String name, String depends, int defaultColor);
}
public final static int POINT_STATE = 1;
public final static int POLYGON_STATE = 2;
public final static int LINE_STATE = 3;
public final static int TEXT_STATE = 4;
public final static int ORDER_STATE = 5;
public void parseRenderingRules(InputStream is, RenderingRuleVisitor visitor) throws IOException, SAXException {
try {
final SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
saxParser.parse(is, new RenderingRulesHandler(saxParser, visitor));
} catch (ParserConfigurationException e) {
throw new SAXException(e);
}
}
private class RenderingRulesHandler extends DefaultHandler {
private final SAXParser parser;
private final RenderingRuleVisitor visitor;
private int state;
Stack<Object> stack = new Stack<Object>();
public RenderingRulesHandler(SAXParser parser, RenderingRuleVisitor visitor){
this.parser = parser;
this.visitor = visitor;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
name = parser.isNamespaceAware() ? localName : name;
if("filter".equals(name)){ //$NON-NLS-1$
FilterState st = parseFilterAttributes(attributes);
stack.push(st);
} else if("order".equals(name)){ //$NON-NLS-1$
state = ORDER_STATE;
} else if("text".equals(name)){ //$NON-NLS-1$
state = TEXT_STATE;
} else if("point".equals(name)){ //$NON-NLS-1$
state = POINT_STATE;
} else if("line".equals(name)){ //$NON-NLS-1$
state = LINE_STATE;
} else if("polygon".equals(name)){ //$NON-NLS-1$
state = POLYGON_STATE;
} else if("switch".equals(name)){ //$NON-NLS-1$
SwitchState st = new SwitchState();
stack.push(st);
} else if("case".equals(name)){ //$NON-NLS-1$
FilterState st = parseFilterAttributes(attributes);
((SwitchState)stack.peek()).filters.add(st);
} else if("renderer".equals(name)){ //$NON-NLS-1$
String dc = attributes.getValue("defaultColor");
int defaultColor = 0;
if(dc != null && dc.length() > 0){
defaultColor = parseColor(dc);
}
visitor.rendering(attributes.getValue("name"), attributes.getValue("depends"), defaultColor); //$NON-NLS-1$ //$NON-NLS-2$
} else {
log.warn("Unknown tag" + name); //$NON-NLS-1$
}
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
name = parser.isNamespaceAware() ? localName : name;
if ("filter".equals(name)) { //$NON-NLS-1$
List<FilterState> list = popAndAggregateState();
for (FilterState pop : list) {
if (pop.tag != null && (pop.minzoom != -1 || state == ORDER_STATE)) {
visitor.visitRule(state, pop);
}
}
} else if("switch".equals(name)){ //$NON-NLS-1$
stack.pop();
}
}
public List<FilterState> popAndAggregateState() {
FilterState pop = (FilterState) stack.pop();
List<FilterState> res = null;
for (int i = stack.size() - 1; i >= 0; i--) {
Object o = stack.get(i);
if(o instanceof FilterState){
if(res == null){
mergeStateInto((FilterState) o, pop);
} else {
for(FilterState f : res){
mergeStateInto((FilterState) o, f);
}
}
} else {
List<FilterState> filters = ((SwitchState)o).filters;
if (res == null) {
res = new ArrayList<FilterState>();
res.add(pop);
}
int l = res.size();
for (int t = 0; t < filters.size() - 1; t++) {
for (int j = 0; j < l; j++) {
FilterState n = new FilterState();
mergeStateInto(res.get(j), n);
res.add(n);
}
}
for (int j = 0; j < res.size(); j++) {
mergeStateInto(filters.get(j / l), res.get(j));
}
}
}
if(res == null){
return Collections.singletonList(pop);
} else {
return res;
}
}
public void mergeStateInto(FilterState toMerge, FilterState mergeInto){
if(toMerge.maxzoom != -1 && mergeInto.maxzoom == -1){
mergeInto.maxzoom = toMerge.maxzoom;
}
if(toMerge.minzoom != -1 && mergeInto.minzoom == -1){
mergeInto.minzoom = toMerge.minzoom;
}
if(toMerge.icon != null && mergeInto.icon == null){
mergeInto.icon = toMerge.icon;
}
if(toMerge.tag != null && mergeInto.tag == null){
mergeInto.tag = toMerge.tag;
}
if(toMerge.orderType != -1 && mergeInto.orderType == -1){
mergeInto.orderType = toMerge.orderType;
}
if(toMerge.layer != 0 && mergeInto.layer == 0){
mergeInto.layer = toMerge.layer;
}
if(toMerge.order != 0 && mergeInto.order == 0){
mergeInto.order = toMerge.order;
}
if(toMerge.textLength != 0 && mergeInto.textLength == 0){
mergeInto.textLength = toMerge.textLength;
}
if(toMerge.val != null && mergeInto.val == null){
mergeInto.val = toMerge.val;
}
if(toMerge.text != null){
if(mergeInto.text == null){
mergeInto.text = new TextAttributes();
}
if(toMerge.text.textColor != 0 && mergeInto.text.textColor == 0){
mergeInto.text.textColor = toMerge.text.textColor;
}
if(toMerge.text.textSize != 0 && mergeInto.text.textSize == 0){
mergeInto.text.textSize = toMerge.text.textSize;
}
if(toMerge.text.textBold && !mergeInto.text.textBold){
mergeInto.text.textBold = toMerge.text.textBold;
}
if(toMerge.text.textShield != null && mergeInto.text.textShield == null){
mergeInto.text.textShield = toMerge.text.textShield;
}
if(toMerge.text.ref != null && mergeInto.text.ref == null){
mergeInto.text.ref = toMerge.text.ref;
}
if(toMerge.text.textMinDistance != 0 && mergeInto.text.textMinDistance == 0){
mergeInto.text.textMinDistance = toMerge.text.textMinDistance;
}
if(toMerge.text.textDy != 0 && mergeInto.text.textDy == 0){
mergeInto.text.textDy = toMerge.text.textDy;
}
if(toMerge.text.textHaloRadius != 0 && mergeInto.text.textHaloRadius == 0){
mergeInto.text.textHaloRadius = toMerge.text.textHaloRadius;
}
if(toMerge.text.textWrapWidth != 0 && mergeInto.text.textWrapWidth == 0){
mergeInto.text.textWrapWidth = toMerge.text.textWrapWidth;
}
if(toMerge.text.textOnPath && !mergeInto.text.textOnPath){
mergeInto.text.textOnPath = toMerge.text.textOnPath;
}
}
mergeStateInto(toMerge.main, mergeInto.main);
while(mergeInto.effectAttributes.size() < toMerge.effectAttributes.size()){
mergeInto.effectAttributes.add(new EffectAttributes());
}
for(int i=0; i<toMerge.effectAttributes.size(); i++){
mergeStateInto(toMerge.effectAttributes.get(i), mergeInto.effectAttributes.get(i));
}
}
public void mergeStateInto(EffectAttributes toMerge, EffectAttributes mergeInto){
if(toMerge.color != 0 && mergeInto.color == 0){
mergeInto.color = toMerge.color;
}
if(toMerge.strokeWidth != 0 && mergeInto.strokeWidth == 0){
mergeInto.strokeWidth = toMerge.strokeWidth;
}
if(toMerge.pathEffect != null && mergeInto.pathEffect == null){
mergeInto.pathEffect = toMerge.pathEffect;
}
if(toMerge.shadowRadius != 0 && mergeInto.shadowRadius == 0){
mergeInto.shadowRadius = toMerge.shadowRadius;
}
if(toMerge.shadowColor != 0 && mergeInto.shadowColor == 0){
mergeInto.shadowColor = toMerge.shadowColor;
}
if(toMerge.cap != null && mergeInto.cap == null){
mergeInto.cap = toMerge.cap;
}
}
public FilterState parseFilterAttributes(Attributes attributes){
FilterState state = new FilterState();
if(this.state == TEXT_STATE){
state.text = new TextAttributes();
}
for(int i=0; i<attributes.getLength(); i++){
String name = attributes.getLocalName(i);
String val = attributes.getValue(i);
if(name.equals("tag")){ //$NON-NLS-1$
state.tag = val;
} else if(name.equals("value")){ //$NON-NLS-1$
state.val = val;
} else if(name.equals("minzoom")){ //$NON-NLS-1$
state.minzoom = Integer.parseInt(val);
} else if(name.equals("maxzoom")){ //$NON-NLS-1$
state.maxzoom = Integer.parseInt(val);
} else if(name.equals("maxzoom")){ //$NON-NLS-1$
state.maxzoom = Integer.parseInt(val);
} else if(name.equals("layer")){ //$NON-NLS-1$
state.layer = Integer.parseInt(val);
} else if(name.equals("orderType")){ //$NON-NLS-1$
int i1 = val.equals("polygon") ? 3 : (val.equals("line") ? 2 : 1); //$NON-NLS-1$ //$NON-NLS-2$
state.orderType = i1;
} else if(name.equals("order")){ //$NON-NLS-1$
state.order = Float.parseFloat(val);
} else if(name.equals("icon")){ //$NON-NLS-1$
state.icon = val;
} else if(name.equals("color")){ //$NON-NLS-1$
state.main.color = parseColor(val);
} else if(name.startsWith("color_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(6)));
ef.color = parseColor(val);
} else if(name.equals("shader")){ //$NON-NLS-1$
state.shader = val;
} else if(name.equals("strokeWidth")){ //$NON-NLS-1$
state.main.strokeWidth = Float.parseFloat(val);
} else if(name.startsWith("strokeWidth_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(12)));
ef.strokeWidth = Float.parseFloat(val);
} else if(name.equals("pathEffect")){ //$NON-NLS-1$
state.main.pathEffect = val;
} else if(name.startsWith("pathEffect_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(11)));
ef.pathEffect = val;
} else if(name.equals("shadowRadius")){ //$NON-NLS-1$
state.main.shadowRadius = Float.parseFloat(val);
} else if(name.startsWith("shadowRadius_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(14)));
ef.shadowRadius = Float.parseFloat(val);
} else if(name.equals("shadowColor")){ //$NON-NLS-1$
state.main.shadowColor = parseColor(val);
} else if(name.startsWith("shadowColor_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(12)));
ef.shadowColor = parseColor(val);
} else if(name.equals("cap")){ //$NON-NLS-1$
state.main.cap = val;
} else if(name.startsWith("cap_")){ //$NON-NLS-1$
EffectAttributes ef = state.getEffectAttributes(Integer.parseInt(name.substring(4)));
ef.cap = val;
} else if(name.equals("ref")){ //$NON-NLS-1$
state.text.ref = val;
} else if(name.equals("textSize")){ //$NON-NLS-1$
state.text.textSize = Float.parseFloat(val);
} else if(name.equals("textBold")){ //$NON-NLS-1$
state.text.textBold = Boolean.parseBoolean(val);
} else if(name.equals("textColor")){ //$NON-NLS-1$
state.text.textColor = parseColor(val);
} else if(name.equals("textLength")){ //$NON-NLS-1$
state.textLength = Integer.parseInt(val);
} else if(name.equals("textShield")){ //$NON-NLS-1$
state.text.textShield = val;
} else if(name.equals("textMinDistance")){ //$NON-NLS-1$
state.text.textMinDistance = Integer.parseInt(val);
} else if(name.equals("textOnPath")){ //$NON-NLS-1$
state.text.textOnPath = Boolean.parseBoolean(val);
} else if(name.equals("textWrapWidth")){ //$NON-NLS-1$
state.text.textWrapWidth = Integer.parseInt(val);
} else if(name.equals("textDy")){ //$NON-NLS-1$
state.text.textDy = Integer.parseInt(val);
} else if(name.equals("textHaloRadius")){ //$NON-NLS-1$
state.text.textHaloRadius = Float.parseFloat(val);
} else {
log.warn("Unknown attribute " + name); //$NON-NLS-1$
}
}
return state;
}
}
/**
* Parse the color string, and return the corresponding color-int.
* If the string cannot be parsed, throws an IllegalArgumentException
* exception. Supported formats are:
* #RRGGBB
* #AARRGGBB
* 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta',
* 'yellow', 'lightgray', 'darkgray'
*/
public static int parseColor(String colorString) {
if (colorString.charAt(0) == '#') {
// Use a long to avoid rollovers on #ffXXXXXX
long color = Long.parseLong(colorString.substring(1), 16);
if (colorString.length() == 7) {
// Set the alpha value
color |= 0x00000000ff000000;
} else if (colorString.length() != 9) {
throw new IllegalArgumentException("Unknown color" + colorString); //$NON-NLS-1$
}
return (int)color;
}
throw new IllegalArgumentException("Unknown color" + colorString); //$NON-NLS-1$
}
// TEST purpose
public static void main(String[] args) throws IOException, SAXException {
OsmandRenderingRulesParser parser = new OsmandRenderingRulesParser();
parser.parseRenderingRules(OsmandRenderingRulesParser.class.getResourceAsStream("default.render.xml"), //$NON-NLS-1$
new RenderingRuleVisitor() {
@Override
public void rendering(String name, String depends, int defColor) {
System.out.println("Renderer " + name); //$NON-NLS-1$
}
@Override
public void visitRule(int state, FilterState filter) {
String gen = generateAttributes(state, filter);
if (gen != null) {
String res = ""; //$NON-NLS-1$
if (filter.maxzoom != -1) {
res += " zoom : " +filter.minzoom + "-" + filter.maxzoom; //$NON-NLS-1$ //$NON-NLS-2$
} else {
res += " zoom : " +filter.minzoom; //$NON-NLS-1$
}
res += " tag="+filter.tag; //$NON-NLS-1$
res += " val="+filter.val; //$NON-NLS-1$
if(filter.layer != 0){
res += " layer="+filter.layer; //$NON-NLS-1$
}
res += gen;
System.out.println(res);
}
}
});
}
public static String colorToString(int color) {
if ((0xFF000000 & color) == 0xFF000000) {
return "#" + Integer.toHexString(color & 0x00FFFFFF); //$NON-NLS-1$
} else {
return "#" + Integer.toHexString(color); //$NON-NLS-1$
}
}
private static String generateAttributes(int state, FilterState s){
String res = ""; //$NON-NLS-1$
if(s.shader != null){
res+=" shader=" + s.shader; //$NON-NLS-1$
}
if(s.icon != null){
res+= " icon="+s.icon; //$NON-NLS-1$
}
if(s.order != 0){
res+= " order="+s.order; //$NON-NLS-1$
}
if(s.orderType != 0){
res+= " orderType="+s.orderType; //$NON-NLS-1$
}
res = generateAttributes(s.main, res, ""); //$NON-NLS-1$
int p = 2;
for(EffectAttributes ef : s.effectAttributes){
res = generateAttributes(ef, res, "_"+(p++)); //$NON-NLS-1$
}
if(s.text != null){
if(s.text.textSize != 0){
res+= " textSize="+s.text.textSize; //$NON-NLS-1$
}
if(s.text.ref != null){
res+= " ref="+s.text.ref; //$NON-NLS-1$
}
if(s.text.textColor != 0){
res+= " textColor="+colorToString(s.text.textColor); //$NON-NLS-1$
}
if(s.text.textShield != null){
res+= " textShield="+s.text.textShield; //$NON-NLS-1$
}
}
if(state == POLYGON_STATE){
// return res;
} else if(state == LINE_STATE){
// return res;
} else if(state == POINT_STATE){
// return res;
} else if(state == TEXT_STATE){
// return res;
} else if(state == ORDER_STATE){
return res;
}
return null;
}
private static String generateAttributes(EffectAttributes s, String res, String prefix) {
if(s.color != 0){
res +=" color"+prefix+"="+colorToString(s.color); //$NON-NLS-1$ //$NON-NLS-2$
}
if(s.strokeWidth != 0){
res+= " strokeWidth"+prefix+"="+s.strokeWidth; //$NON-NLS-1$ //$NON-NLS-2$
}
if(s.pathEffect != null){
res+= " pathEffect"+prefix+"="+s.pathEffect; //$NON-NLS-1$ //$NON-NLS-2$
}
return res;
}
}