vtm/src/org/mapsforge/android/rendertheme/RenderThemeHandler.java
Hannes Janetzek 2fccf0f214 - initial way labeling
- add no-projection option for mapdata
- use depth buffer for line clipping to tile - no more scissor, yay!
- extract line and poly render function from maprenderer
- use one vbo for both polys and lines
- use linear interpolator for fling-scroller
- add 'exclusive' negative matcher to rendertheme
- add some more options to rendertheme, kind of inheritance at least for
  lines, see theme
- add caching for node tags -> renderinstructions
- ...
2013-10-09 01:17:33 +02:00

349 lines
12 KiB
Java

/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.android.rendertheme;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.mapsforge.android.rendertheme.renderinstruction.Area;
import org.mapsforge.android.rendertheme.renderinstruction.Caption;
import org.mapsforge.android.rendertheme.renderinstruction.Circle;
import org.mapsforge.android.rendertheme.renderinstruction.AreaLevel;
import org.mapsforge.android.rendertheme.renderinstruction.Line;
import org.mapsforge.android.rendertheme.renderinstruction.LineSymbol;
import org.mapsforge.android.rendertheme.renderinstruction.PathText;
import org.mapsforge.android.rendertheme.renderinstruction.RenderInstruction;
import org.mapsforge.android.rendertheme.renderinstruction.Symbol;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* SAX2 handler to parse XML render theme files.
*/
public class RenderThemeHandler extends DefaultHandler {
private static final Logger LOG = Logger
.getLogger(RenderThemeHandler.class.getName());
private static enum Element {
RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE;
}
private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme";
private static final String ELEMENT_NAME_RULE = "rule";
private static final String ELEMENT_NAME_STYPE_PATH_TEXT = "style-pathtext";
private static final String ELEMENT_NAME_STYLE_AREA = "style-area";
private static final String ELEMENT_NAME_STYLE_LINE = "style-line";
private static final String ELEMENT_NAME_STYLE_OUTLINE = "style-outline";
private static final String ELEMENT_NAME_USE_STYLE_PATH_TEXT = "use-text";
private static final String ELEMENT_NAME_USE_STYLE_AREA = "use-area";
private static final String ELEMENT_NAME_USE_STYLE_LINE = "use-line";
private static final String ELEMENT_NAME_USE_STYLE_OUTLINE = "use-outline";
private static final String UNEXPECTED_ELEMENT = "unexpected element: ";
/**
* @param inputStream
* an input stream containing valid render theme XML data.
* @return a new RenderTheme which is created by parsing the XML data from the input stream.
* @throws SAXException
* if an error occurs while parsing the render theme XML.
* @throws ParserConfigurationException
* if an error occurs while creating the XML parser.
* @throws IOException
* if an I/O error occurs while reading from the input stream.
*/
public static RenderTheme getRenderTheme(InputStream inputStream)
throws SAXException,
ParserConfigurationException, IOException {
RenderThemeHandler renderThemeHandler = new RenderThemeHandler();
XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser()
.getXMLReader();
xmlReader.setContentHandler(renderThemeHandler);
xmlReader.parse(new InputSource(inputStream));
return renderThemeHandler.mRenderTheme;
}
/**
* Logs the given information about an unknown XML attribute.
*
* @param element
* the XML element name.
* @param name
* the XML attribute name.
* @param value
* the XML attribute value.
* @param attributeIndex
* the XML attribute index position.
*/
public static void logUnknownAttribute(String element, String name, String value,
int attributeIndex) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("unknown attribute in element ");
stringBuilder.append(element);
stringBuilder.append(" (");
stringBuilder.append(attributeIndex);
stringBuilder.append("): ");
stringBuilder.append(name);
stringBuilder.append('=');
stringBuilder.append(value);
LOG.info(stringBuilder.toString());
}
private Rule mCurrentRule;
private final Stack<Element> mElementStack = new Stack<Element>();
private int mLevel;
private RenderTheme mRenderTheme;
private final Stack<Rule> mRuleStack = new Stack<Rule>();
@Override
public void endDocument() {
if (mRenderTheme == null) {
throw new IllegalArgumentException("missing element: rules");
}
mRenderTheme.setLevels(mLevel);
mRenderTheme.complete();
tmpStyleHash.clear();
}
@Override
public void endElement(String uri, String localName, String qName) {
mElementStack.pop();
if (ELEMENT_NAME_RULE.equals(localName)) {
mRuleStack.pop();
if (mRuleStack.empty()) {
mRenderTheme.addRule(mCurrentRule);
} else {
mCurrentRule = mRuleStack.peek();
}
}
}
@Override
public void error(SAXParseException exception) {
LOG.log(Level.SEVERE, null, exception);
}
private static HashMap<String, RenderInstruction> tmpStyleHash = new HashMap<String, RenderInstruction>(
10);
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
try {
if (ELEMENT_NAME_RENDER_THEME.equals(localName)) {
checkState(localName, Element.RENDER_THEME);
mRenderTheme = RenderTheme.create(localName, attributes);
}
else if (ELEMENT_NAME_RULE.equals(localName)) {
checkState(localName, Element.RULE);
Rule rule = Rule.create(localName, attributes, mRuleStack);
if (!mRuleStack.empty()) {
mCurrentRule.addSubRule(rule);
}
mCurrentRule = rule;
mRuleStack.push(mCurrentRule);
}
else if (ELEMENT_NAME_STYPE_PATH_TEXT.equals(localName)) {
checkState(localName, Element.STYLE);
PathText pathText = PathText.create(localName, attributes);
tmpStyleHash.put("t" + pathText.style, pathText);
// System.out.println("add style: " + pathText.style);
}
else if (ELEMENT_NAME_STYLE_AREA.equals(localName)) {
checkState(localName, Element.STYLE);
Area area = Area.create(localName, attributes, 0);
tmpStyleHash.put("a" + area.style, area);
// System.out.println("add style: " + area.style);
}
else if (ELEMENT_NAME_STYLE_LINE.equals(localName)) {
checkState(localName, Element.STYLE);
String style = null;
if ((style = attributes.getValue("from")) != null) {
RenderInstruction ri = tmpStyleHash.get("l" + style);
if (ri instanceof Line) {
Line line = Line.create((Line) ri, localName, attributes, 0,
false);
tmpStyleHash.put("l" + line.style, line);
// System.out.println("add style: " + line.style + " from " + style);
}
else {
// System.out.println("couldnt check the style yo! " + style);
}
} else {
Line line = Line.create(null, localName, attributes, 0, false);
tmpStyleHash.put("l" + line.style, line);
// System.out.println("add style: " + line.style);
}
}
else if (ELEMENT_NAME_STYLE_OUTLINE.equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Line line = Line.create(null, localName, attributes, mLevel++, true);
tmpStyleHash.put("o" + line.style, line);
// outlineLayers.add(line);
}
else if ("area".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Area area = Area.create(localName, attributes, mLevel++);
// mRuleStack.peek().addRenderingInstruction(area);
mCurrentRule.addRenderingInstruction(area);
}
else if ("caption".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Caption caption = Caption.create(localName, attributes);
mCurrentRule.addRenderingInstruction(caption);
}
else if ("circle".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Circle circle = Circle.create(localName, attributes, mLevel++);
mCurrentRule.addRenderingInstruction(circle);
}
else if ("line".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Line line = Line.create(null, localName, attributes, mLevel++, false);
mCurrentRule.addRenderingInstruction(line);
}
else if ("lineSymbol".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
LineSymbol lineSymbol = LineSymbol.create(localName, attributes);
mCurrentRule.addRenderingInstruction(lineSymbol);
}
else if ("pathText".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
PathText pathText = PathText.create(localName, attributes);
mCurrentRule.addRenderingInstruction(pathText);
}
else if ("symbol".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Symbol symbol = Symbol.create(localName, attributes);
mCurrentRule.addRenderingInstruction(symbol);
}
else if (ELEMENT_NAME_USE_STYLE_LINE.equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("name");
if (style != null) {
Line line = (Line) tmpStyleHash.get("l" + style);
if (line != null) {
// System.out.println("found style line : " + line.style);
Line newLine = Line.create(line, localName, attributes,
mLevel++, false);
mCurrentRule.addRenderingInstruction(newLine);
}
}
} else if (ELEMENT_NAME_USE_STYLE_OUTLINE.equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("name");
if (style != null) {
Line line = (Line) tmpStyleHash.get("o" + style);
if (line != null && line.outline)
mCurrentRule.addRenderingInstruction(line);
}
} else if (ELEMENT_NAME_USE_STYLE_AREA.equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("name");
if (style != null) {
Area area = (Area) tmpStyleHash.get("a" + style);
if (area != null)
mCurrentRule.addRenderingInstruction(new AreaLevel(area,
mLevel++));
}
} else if (ELEMENT_NAME_USE_STYLE_PATH_TEXT.equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("name");
if (style != null) {
PathText pt = (PathText) tmpStyleHash.get("t" + style);
if (pt != null)
mCurrentRule.addRenderingInstruction(pt);
}
} else {
throw new SAXException("unknown element: " + localName);
}
} catch (IllegalArgumentException e) {
throw new SAXException(null, e);
} catch (IOException e) {
throw new SAXException(null, e);
}
}
@Override
public void warning(SAXParseException exception) {
LOG.log(Level.SEVERE, null, exception);
}
private void checkElement(String elementName, Element element) throws SAXException {
Element parentElement;
switch (element) {
case RENDER_THEME:
if (!mElementStack.empty()) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case RULE:
parentElement = mElementStack.peek();
if (parentElement != Element.RENDER_THEME
&& parentElement != Element.RULE) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case STYLE:
parentElement = mElementStack.peek();
if (parentElement != Element.RENDER_THEME) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case RENDERING_INSTRUCTION:
if (mElementStack.peek() != Element.RULE) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
}
throw new SAXException("unknown enum value: " + element);
}
private void checkState(String elementName, Element element) throws SAXException {
checkElement(elementName, element);
mElementStack.push(element);
}
}