aima.core.util.math.geom.SVGGroupParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aima-core Show documentation
Show all versions of aima-core Show documentation
AIMA-Java Core Algorithms from the book Artificial Intelligence a Modern Approach 3rd Ed.
The newest version!
package aima.core.util.math.geom;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import aima.core.util.math.geom.shapes.Circle2D;
import aima.core.util.math.geom.shapes.Ellipse2D;
import aima.core.util.math.geom.shapes.IGeometric2D;
import aima.core.util.math.geom.shapes.Line2D;
import aima.core.util.math.geom.shapes.Point2D;
import aima.core.util.math.geom.shapes.Polyline2D;
import aima.core.util.math.geom.shapes.Rect2D;
import aima.core.util.math.geom.shapes.TransformMatrix2D;
/**
* This class implements {@link IGroupParser} for a SVG map.
* the "g" element is used to define the group(s) that should be parsed.
*
* The parser only recognizes the following basic shapes:
*
* - rect
* - line
* - circle
* - ellipse
* - polyline
* - polygon
*
* In addition any number of grouping elements are allowed.
* For all elements only the coordinates and the transform attribute are used. This means that rounded corners etc. are ignored.
* Every element/shape can use the transform attribute. The following transformations may be used:
*
* - translate
* - scale
* - rotate
*
* To use the svg map with {@code CartesianPlot2D} it has to contain a "g" element with an id.
* See w3c® SVG standard definition for more information.
*
* During the process of parsing most of the time is spent in the {@link XMLStreamReader}.
* A known issue is a
element in the file. Removing this element can speed up the parsing significantly.
*
* @author Arno von Borries
* @author Jan Phillip Kretzschmar
* @author Andreas Walscheid
*
*/
public class SVGGroupParser implements IGroupParser {
private static final String GROUP_ELEMENT = "g";
private static final String CIRCLE_ELEMENT = "circle";
private static final String ELLIPSE_ELEMENT = "ellipse";
private static final String LINE_ELEMENT = "line";
private static final String POLYLINE_ELEMENT = "polyline";
private static final String POLYGON_ELEMENT = "polygon";
private static final String RECT_ELEMENT = "rect";
private static final String ID_ATTRIBUTE = "id";
private static final String TRANSFORM_ATTRIBUTE = "transform";
private static final String X_ATTRIBUTE = "x";
private static final String Y_ATTRIBUTE = "y";
private static final String CX_ATTRIBUTE = "cx";
private static final String CY_ATTRIBUTE = "cy";
private static final String X1_ATTRIBUTE = "x1";
private static final String Y1_ATTRIBUTE = "y1";
private static final String X2_ATTRIBUTE = "x2";
private static final String Y2_ATTRIBUTE = "y2";
private static final String R_ATTRIBUTE = "r";
private static final String RX_ATTRIBUTE = "rx";
private static final String RY_ATTRIBUTE = "ry";
private static final String WIDTH_ATTRIBUTE = "width";
private static final String HEIGHT_ATTRIBUTE = "height";
private static final String POINTS_ATTRIBUTE = "points";
private static final String TRANSLATE_TRANSFORM = "translate";
private static final String SCALE_TRANSFORM = "scale";
private static final String ROTATE_TRANSFORM = "rotate";
private static final String POINTS_REGEX = "[,\\s]+";
private static final String TRANSFORM_REGEX1 = "[a-zA-Z]*\\([0-9.,Ee\\+\\-\\s]*\\)";
private static final String TRANSFORM_REGEX2 = "([a-zA-Z]+)|([0-9\\.Ee\\+\\-]*[eEmMxXpPiInNcCtT%]*[^\\,\\(\\)\\s]+)";
private static final String NUMBER_REGEX = "([\\+\\-]?[0-9]+\\.?[0-9]*[Ee]?[\\+\\-]?[0-9]*\\.?[0-9]*)|em|ex|px|in|cm|mm|pt|pc|\\%";
private static final Pattern NUMBER_PATTERN = Pattern.compile(NUMBER_REGEX);
private static final Pattern TRANSFORM_PATTERN1 = Pattern.compile(TRANSFORM_REGEX1);
private static final Pattern TRANSFORM_PATTERN2 = Pattern.compile(TRANSFORM_REGEX2);
private static final XMLInputFactory FACTORY = XMLInputFactory.newInstance();
private XMLStreamReader reader;
private ArrayList shapes;
private Stack transformations = new Stack();
private TransformMatrix2D currentMatrix;
/**
* Parses the given {@link InputStream} into a group of geometric shapes.
* @param input the given input stream.
* @param groupID the identifier for the group.
* @throws XMLStreamException if a syntax error is found in the input.
* @return the constructed list of geometric shapes.
*/
@Override
public ArrayList parse(InputStream input, String groupID) throws XMLStreamException {
if(input == null || groupID == null) throw new NullPointerException();
reader = FACTORY.createXMLStreamReader(input);
shapes = new ArrayList();
transformations.clear();
currentMatrix = TransformMatrix2D.UNITY_MATRIX;
while(reader.hasNext()) {
final int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
applyTransform();
if(reader.getLocalName().equalsIgnoreCase(GROUP_ELEMENT)) {
final String element = reader.getAttributeValue(null, ID_ATTRIBUTE);
if(element != null) {
if(element.equalsIgnoreCase(groupID)) {
parseGroup();
break;
}
}
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
applyTransformEnd();
}
}
return shapes;
}
/**
* Parses the specified group.
* @throws XMLStreamException if an syntax error was encountered in the file.
*/
private void parseGroup() throws XMLStreamException {
int groupCounter = 1;
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
applyTransform();
final String elementName = reader.getLocalName();
if(elementName.equalsIgnoreCase(CIRCLE_ELEMENT)) parseCircle();
else if(elementName.equalsIgnoreCase(ELLIPSE_ELEMENT)) parseEllipse();
else if(elementName.equalsIgnoreCase(LINE_ELEMENT)) parseLine();
else if(elementName.equalsIgnoreCase(POLYLINE_ELEMENT)) parsePolyline();
else if(elementName.equalsIgnoreCase(POLYGON_ELEMENT)) parsePolygon();
else if(elementName.equalsIgnoreCase(RECT_ELEMENT)) parseRect();
} else if(event == XMLStreamConstants.END_ELEMENT) {
applyTransformEnd();
if (reader.getLocalName().equalsIgnoreCase(GROUP_ELEMENT)) {
groupCounter--;
if(groupCounter == 0) break;
}
}
}
}
/**
* Checks the current element for a transform attribute and adds that attribute to the current transform matrix.
* Manages the transformation stack.
*/
private void applyTransform() {
String value = reader.getAttributeValue(null, TRANSFORM_ATTRIBUTE);
transformations.push(currentMatrix);
currentMatrix = currentMatrix.multiply(parseTransform(value));
}
/**
* Sets the current transform matrix to the matrix of the underlying element when leaving an element.
* Manages the transformation stack.
*/
private void applyTransformEnd() {
currentMatrix = transformations.pop();
}
/**
* This method parses a transform attribute into a {@link TransformMatrix2D}.
* @param string the string of the transform attribute.
* @return the parsed transform matrix.
*/
private TransformMatrix2D parseTransform(String string) {
TransformMatrix2D result = TransformMatrix2D.UNITY_MATRIX;
if(string != null) {
Matcher matcher1 = TRANSFORM_PATTERN1.matcher(string);
int transformCount1 = 0;
while(matcher1.lookingAt()) transformCount1++;
for(int j=1;j<=transformCount1;j++) {
Matcher matcher2 = TRANSFORM_PATTERN2.matcher(matcher1.group(j));
int transformCount2 = 0;
while(matcher1.lookingAt()) transformCount2++;
for(int i=1;i= 2 && coords.length % 2 == 0) {
//otherwise something is wrong with the points list!
Point2D[] vertexes = new Point2D[coords.length / 2];
for(int i=0; i= 2 && coords.length % 2 == 0) {
//otherwise something is wrong with the points list!
Point2D[] vertexes = new Point2D[coords.length / 2];
for(int i=1; i