/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.util.geo.maps.svg;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.PathIterator;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
import org.apache.batik.parser.AWTPathProducer;
import org.apache.batik.parser.PathParser;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.locationtech.jts.algorithm.LineIntersector;
import org.locationtech.jts.algorithm.RobustLineIntersector;
import de.tud.kom.p2psim.api.scenario.ConfigurationException;
import de.tud.kom.p2psim.impl.topology.obstacles.PolygonObstacle;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisualizationInjector;
import de.tud.kom.p2psim.impl.topology.waypoints.graph.PathEdge;
import de.tud.kom.p2psim.impl.topology.waypoints.graph.Waypoint;
import de.tud.kom.p2psim.impl.topology.waypoints.graph.WeakWaypoint;
import de.tud.kom.p2psim.impl.util.geo.maps.AbstractMap;
/**
* This map generates waypoints and obstacles based on a given SVG file.
*
* The SVG file must adhere to the following conventions: - Everything must be
* contained in a rect element named "map" (id) - Obstacles are implemented
* using rect or path elements. - Their id must start with "obstacle" - Ways for
* the movement of nodes are implemented using path elements. - Their id must
* start with "way"
*
* The size of the map element will be used to determine the map size and to
* scale the map to the configured dimensions. All elements besides rect and
* path elements with the previously named ids are ignored.
*
* Note: The SVGMap will not verify if the obstacles or ways are inside of the
* map rect. All rects or paths with the correct ids will be loaded regardless
* of their positioning.
*
* @author Fabio Zöllner
* @version 1.0, 22.10.2012
*/
public class SVGMap extends AbstractMap {
private Map waypointCache = Maps.newHashMap();
@Override
protected void doLoadMap() {
File osmFile = new File(getFilename());
if (!osmFile.exists())
throw new ConfigurationException("Couldn't find SVG file: "
+ getFilename());
try {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
SAXParser parser;
parser = parserFactory.newSAXParser();
parser.parse(new File(filename), new SVGMapHandler());
postProcessing();
} catch (Exception e) {
e.printStackTrace();
}
}
private void postProcessing() {
waypointCache.clear();
resolveIntersections();
}
private void resolveIntersections() {
List intersections = Lists.newLinkedList();
List worklist = Lists.newLinkedList(getPaths());
Iterator iter = concurrentIterator(worklist);
while (iter.hasNext()) {
PathEdge path = iter.next();
Iterator compareIter = concurrentIterator(worklist);
while (compareIter.hasNext()) {
PathEdge comparePath = compareIter.next();
// Prevent intersection comparison for the same path and
// connected paths
if (path.equals(comparePath))
continue;
if (path.getSource().getPosition()
.equals(comparePath.getSource().getPosition()))
continue;
if (path.getSource().getPosition()
.equals(comparePath.getTarget().getPosition()))
continue;
if (path.getTarget().getPosition()
.equals(comparePath.getSource().getPosition()))
continue;
if (path.getTarget().getPosition()
.equals(comparePath.getTarget().getPosition()))
continue;
PositionVector intersection = intersects(path, comparePath);
if (intersection != null) {
PathEdge[] splitPath1 = splitPath(path, intersection);
PathEdge[] splitPath2 = splitPath(comparePath, intersection);
// Prevent the creation of loops
if (splitPath1[0].getSource().equals(
splitPath1[0].getTarget()))
continue;
if (splitPath1[1].getSource().equals(
splitPath1[1].getTarget()))
continue;
if (splitPath2[0].getSource().equals(
splitPath2[0].getTarget()))
continue;
if (splitPath2[1].getSource().equals(
splitPath2[1].getTarget()))
continue;
worklist.add(splitPath1[0]);
worklist.add(splitPath1[1]);
worklist.add(splitPath2[0]);
worklist.add(splitPath2[1]);
intersections.add(intersection);
compareIter.remove();
iter.remove();
break;
}
}
}
getPaths().clear();
getPaths().addAll(worklist);
VisualizationInjector.injectComponent("New intersections", 1,
new MarkPositions(intersections, this));
}
public Iterator concurrentIterator(final List list) {
return new Iterator() {
int index = -1;
@Override
public boolean hasNext() {
System.err.println("Checking if index " + (index + 1)
+ " is in bounds");
return index + 1 < list.size();
}
@Override
public E next() {
index++;
if (index < list.size()) {
return list.get(index);
} else {
throw new IndexOutOfBoundsException(
"List has "
+ list.size()
+ " entries at the moment. Couldn't retrieve entry at position "
+ index);
}
}
@Override
public void remove() {
if (index < list.size()) {
list.remove(index);
index--;
} else {
throw new IndexOutOfBoundsException(
"List has "
+ list.size()
+ " entries at the moment. Couldn't retrieve entry at position "
+ index);
}
}
};
}
private PathEdge[] splitPath(PathEdge path, PositionVector intersectionPoint) {
PathEdge[] paths = new PathEdge[2];
Waypoint wp1 = path.getSource();
Waypoint wp2 = path.getTarget();
Waypoint iwp = getWaypoint(intersectionPoint);
paths[0] = new PathEdge(wp1, iwp);
paths[1] = new PathEdge(wp2, iwp);
return paths;
}
private PositionVector intersects(PathEdge path1, PathEdge path2) {
LineIntersector inter = new RobustLineIntersector();
inter.computeIntersection(path1.getSource().getPosition()
.asCoordinate(),
path1.getTarget().getPosition().asCoordinate(), path2
.getSource().getPosition().asCoordinate(), path2
.getTarget().getPosition().asCoordinate());
if (inter.getIntersectionNum() == 0) {
return null;
}
return new PositionVector(inter.getIntersection(0).x,
inter.getIntersection(0).y);
}
private class SVGMapHandler extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName,
Attributes attrs) throws SAXException {
if (checkId(attrs, "map")) {
int x = (int) Double.parseDouble(attrs.getValue("x"));
int y = (int) Double.parseDouble(attrs.getValue("y"));
int width = (int) Double.parseDouble(attrs.getValue("width"));
int height = (int) Double.parseDouble(attrs.getValue("height"));
setMinPosition(new PositionVector(x, y));
setMaxPosition(new PositionVector(x + width, y + height));
} else if (checkId(attrs, "obstacle")) {
if (qName.equals("rect")) {
int x = (int) Double.parseDouble(attrs.getValue("x"));
int y = (int) Double.parseDouble(attrs.getValue("y"));
int width = (int) Double.parseDouble(attrs
.getValue("width"));
int height = (int) Double.parseDouble(attrs
.getValue("height"));
addObstacle(new PolygonObstacle(Lists.newArrayList(
pos(x, y), pos(x + width, y),
pos(x + width, y + height), pos(x, y + height))));
} else if (qName.equals("path")) {
List positions = parseObstaclePositions(attrs
.getValue("d"));
addObstacle(new PolygonObstacle(positions));
}
// TODO: Add path
} else if (checkId(attrs, "way")) {
parseWay(attrs.getValue("d"));
}
}
}
private Waypoint getWaypoint(PositionVector pos) {
Waypoint wp = waypointCache.get(pos);
if (wp == null) {
wp = new WeakWaypoint(pos);
waypointCache.put(pos, wp);
}
return wp;
}
void parseWay(String d) {
PathParser parser = new PathParser();
AWTPathProducer producer = new AWTPathProducer();
parser.setPathHandler(producer);
parser.parse(d);
ExtendedGeneralPath path = (ExtendedGeneralPath) producer.getShape();
PathIterator iter = path.getPathIterator(null, 1.0);
Waypoint lastWaypoint = null;
Waypoint lastMoveTo = null;
double[] coords = new double[6];
while (!iter.isDone()) {
int type = iter.currentSegment(coords);
if (type == iter.SEG_MOVETO) {
lastWaypoint = getWaypoint(new PositionVector((int) coords[0],
(int) coords[1]));
lastMoveTo = lastWaypoint;
System.err.println("Moved to " + lastMoveTo);
} else if (type == iter.SEG_LINETO) {
Waypoint targetWaypoint = getWaypoint(new PositionVector(
(int) coords[0], (int) coords[1]));
createPath(lastWaypoint, targetWaypoint);
lastWaypoint = targetWaypoint;
System.err.println("Created a line to " + targetWaypoint);
} else if (type == iter.SEG_CLOSE) {
createPath(lastWaypoint, lastMoveTo);
lastWaypoint = lastMoveTo;
System.err.println("Closed a line to " + lastMoveTo);
}
iter.next();
}
}
@Override
public void createPath(Waypoint wp1, Waypoint wp2) {
if (wp1.equals(wp2))
return; // Prevent loops
super.createPath(wp1, wp2);
}
List parseObstaclePositions(String d) {
PathParser parser = new PathParser();
AWTPathProducer producer = new AWTPathProducer();
parser.setPathHandler(producer);
parser.parse(d);
ExtendedGeneralPath path = (ExtendedGeneralPath) producer.getShape();
PathIterator iter = path.getPathIterator(null, 1.0);
PositionVector firstPosition = null;
List positions = Lists.newLinkedList();
double[] coords = new double[6];
while (!iter.isDone()) {
int type = iter.currentSegment(coords);
PositionVector newPosition = new PositionVector((int) coords[0],
(int) coords[1]);
if (firstPosition == null) {
firstPosition = newPosition;
}
if (type == iter.SEG_MOVETO || type == iter.SEG_LINETO) {
positions.add(newPosition);
} else if (type == iter.SEG_CLOSE) {
positions.add(firstPosition);
} else {
throw new ConfigurationException(
"Cannot handle obstacles with a path more complex than a simple closed path. (e.g. using more than moveto, lineto or close commands)");
}
iter.next();
}
return positions;
}
boolean checkId(Attributes attrs, String value) {
if (attrs.getValue("id") == null)
return false;
return attrs.getValue("id").startsWith(value);
}
private class MarkPositions extends JComponent {
private List positions;
public MarkPositions(List positions, SVGMap map) {
super();
this.positions = positions;
setBounds(0, 0, VisualizationInjector.getWorldX(),
VisualizationInjector.getWorldY());
setOpaque(false);
setVisible(true);
}
protected PositionVector toPixelCoords(PositionVector position,
PositionVector pixelPerMeter) {
PositionVector clonedPosition = position.clone();
PositionVector relativePosition = clonedPosition
.minus(getMinPosition());
relativePosition.multiply(pixelPerMeter);
return relativePosition;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setFont(new Font("SansSerif", Font.PLAIN, 9));
PositionVector lst = null;
for (PositionVector p : positions) {
PositionVector pos = p;
if (ppm != null) {
pos = toPixelCoords(p, ppm);
}
g2.setColor(Color.RED);
g2.drawOval((int) pos.getX() - 10, (int) pos.getY() - 10, 20,
20);
g2.setColor(Color.BLACK);
g2.drawLine((int) pos.getX() - 2, (int) pos.getY() - 2,
(int) pos.getX() + 2, (int) pos.getY() + 2);
g2.drawLine((int) pos.getX() - 2, (int) pos.getY() + 2,
(int) pos.getX() + 2, (int) pos.getY() - 2);
if (lst != null) {
g2.setColor(Color.GREEN);
g2.drawLine((int) lst.getX(), (int) lst.getY(),
(int) pos.getX(), (int) pos.getY());
}
lst = pos;
}
}
}
}