Unverified Commit 342fea6f authored by Juon Kawakami's avatar Juon Kawakami 🥗
Browse files

init

parent 54f6cedf
package maps.osm;
import java.util.List;
/**
A building in OSM space.
*/
public class OSMBuilding extends OSMWay {
/**
Construct an OSMBuilding.
@param id The ID of the building.
@param ids The IDs of the apex nodes of the building.
*/
public OSMBuilding(Long id, List<Long> ids) {
super(id, ids);
}
@Override
public String toString() {
return "OSMBuilding: id " + getID();
}
}
package maps.osm;
/**
Exceptions related to OpenStreetMap.
*/
public class OSMException extends Exception {
/**
Construct an OSMException with no error message.
*/
public OSMException() {
super();
}
/**
Construct an OSMException with an error message.
@param msg The error message.
*/
public OSMException(String msg) {
super(msg);
}
/**
Construct an OSMException with an underlying cause.
@param cause The cause.
*/
public OSMException(Throwable cause) {
super(cause);
}
/**
Construct an OSMException with an error message and underlying cause.
@param msg The error message.
@param cause The cause.
*/
public OSMException(String msg, Throwable cause) {
super(msg, cause);
}
}
package maps.osm;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
/**
An OpenStreetMap map.
*/
public class OSMMap {
private static final Collection<String> ROAD_MARKERS = new HashSet<String>();
static {
ROAD_MARKERS.add("motorway");
ROAD_MARKERS.add("motorway_link");
ROAD_MARKERS.add("trunk");
ROAD_MARKERS.add("trunk_link");
ROAD_MARKERS.add("primary");
ROAD_MARKERS.add("primary_link");
ROAD_MARKERS.add("secondary");
ROAD_MARKERS.add("secondary_link");
ROAD_MARKERS.add("tertiary");
ROAD_MARKERS.add("unclassified");
ROAD_MARKERS.add("road");
ROAD_MARKERS.add("residential");
ROAD_MARKERS.add("living_street");
ROAD_MARKERS.add("service");
ROAD_MARKERS.add("track");
ROAD_MARKERS.add("services");
ROAD_MARKERS.add("pedestrian");
}
private Map<Long, OSMNode> nodes;
private Map<Long, OSMRoad> roads;
private Map<Long, OSMBuilding> buildings;
private boolean boundsCalculated;
private double minLat;
private double maxLat;
private double minLon;
private double maxLon;
/**
Construct an empty map.
*/
public OSMMap() {
boundsCalculated = false;
nodes = new HashMap<Long, OSMNode>();
roads = new HashMap<Long, OSMRoad>();
buildings = new HashMap<Long, OSMBuilding>();
}
/**
Construct a map from an XML document.
@param doc The document to read.
*/
public OSMMap(Document doc) throws OSMException {
this();
read(doc);
}
/**
Construct a map from an XML file.
@param file The file to read.
*/
public OSMMap(File file) throws OSMException, DocumentException, IOException {
this();
SAXReader reader = new SAXReader();
Document doc = reader.read(file);
read(doc);
}
/**
Construct a copy of an OSMMap over a bounded area.
@param other The map to copy.
@param minLat The minimum latitude of the new map.
@param minLon The minimum longitude of the new map.
@param maxLat The maximum latitude of the new map.
@param maxLon The maximum longitude of the new map.
*/
public OSMMap(OSMMap other, double minLat, double minLon, double maxLat, double maxLon) {
this.minLat = minLat;
this.minLon = minLon;
this.maxLat = maxLat;
this.maxLon = maxLon;
boundsCalculated = true;
nodes = new HashMap<Long, OSMNode>();
roads = new HashMap<Long, OSMRoad>();
buildings = new HashMap<Long, OSMBuilding>();
// Copy all nodes inside the bounds
for (OSMNode next : other.nodes.values()) {
double lat = next.getLatitude();
double lon = next.getLongitude();
long id = next.getID();
if (lat >= minLat && lat <= maxLat && lon >= minLon && lon <= maxLon) {
this.nodes.put(id, new OSMNode(id, lat, lon));
}
}
// Now copy the bits of roads and buildings that do not have missing nodes
for (OSMRoad next : other.roads.values()) {
List<Long> ids = new ArrayList<Long>(next.getNodeIDs());
for (Iterator<Long> it = ids.iterator(); it.hasNext();) {
Long nextID = it.next();
if (!nodes.containsKey(nextID)) {
it.remove();
}
}
if (!ids.isEmpty()) {
roads.put(next.getID(), new OSMRoad(next.getID(), ids));
}
}
for (OSMBuilding next : other.buildings.values()) {
boolean allFound = true;
for (Long nextID : next.getNodeIDs()) {
if (!nodes.containsKey(nextID)) {
allFound = false;
}
}
if (allFound) {
buildings.put(next.getID(), new OSMBuilding(next.getID(), new ArrayList<Long>(next.getNodeIDs())));
}
}
}
/**
Read an XML document and populate this map.
@param doc The document to read.
*/
public void read(Document doc) throws OSMException {
boundsCalculated = false;
nodes = new HashMap<Long, OSMNode>();
roads = new HashMap<Long, OSMRoad>();
buildings = new HashMap<Long, OSMBuilding>();
Element root = doc.getRootElement();
if (!"osm".equals(root.getName())) {
throw new OSMException("Invalid map file: root element must be 'osm', not " + root.getName());
}
for (Object next : root.elements("node")) {
Element e = (Element)next;
OSMNode node = processNode(e);
}
for (Object next : root.elements("way")) {
Element e = (Element)next;
processWay(e);
}
}
/**
Turn this map into XML.
@return A new XML document.
*/
public Document toXML() {
Element root = DocumentHelper.createElement("osm");
Element bounds = root.addElement("bounds");
calculateBounds();
bounds.addAttribute("minlat", String.valueOf(minLat));
bounds.addAttribute("maxlat", String.valueOf(maxLat));
bounds.addAttribute("minlon", String.valueOf(minLon));
bounds.addAttribute("maxlon", String.valueOf(maxLon));
for (OSMNode next : nodes.values()) {
Element node = root.addElement("node");
node.addAttribute("id", String.valueOf(next.getID()));
node.addAttribute("lat", String.valueOf(next.getLatitude()));
node.addAttribute("lon", String.valueOf(next.getLongitude()));
}
for (OSMRoad next : roads.values()) {
Element node = root.addElement("way");
node.addAttribute("id", String.valueOf(next.getID()));
for (Long nextID : next.getNodeIDs()) {
node.addElement("nd").addAttribute("ref", String.valueOf(nextID));
}
node.addElement("tag").addAttribute("k", "highway").addAttribute("v", "primary");
}
for (OSMBuilding next : buildings.values()) {
Element node = root.addElement("way");
node.addAttribute("id", String.valueOf(next.getID()));
for (Long nextID : next.getNodeIDs()) {
node.addElement("nd").addAttribute("ref", String.valueOf(nextID));
}
node.addElement("tag").addAttribute("k", "building").addAttribute("v", "yes");
}
return DocumentHelper.createDocument(root);
}
/**
Get the minimum longitude in this map.
@return The minimum longitude.
*/
public double getMinLongitude() {
calculateBounds();
return minLon;
}
/**
Get the maximum longitude in this map.
@return The maximum longitude.
*/
public double getMaxLongitude() {
calculateBounds();
return maxLon;
}
/**
Get the centre longitude in this map.
@return The centre longitude.
*/
public double getCentreLongitude() {
calculateBounds();
return (maxLon + minLon) / 2;
}
/**
Get the minimum latitude in this map.
@return The minimum latitude.
*/
public double getMinLatitude() {
calculateBounds();
return minLat;
}
/**
Get the maximum latitude in this map.
@return The maximum latitude.
*/
public double getMaxLatitude() {
calculateBounds();
return maxLat;
}
/**
Get the centre latitude in this map.
@return The centre latitude.
*/
public double getCentreLatitude() {
calculateBounds();
return (maxLat + minLat) / 2;
}
/**
Get all nodes in the map.
@return All nodes.
*/
public Collection<OSMNode> getNodes() {
return new HashSet<OSMNode>(nodes.values());
}
/**
Remove a node.
@param node The node to remove.
*/
public void removeNode(OSMNode node) {
nodes.remove(node.getID());
}
/**
Get a node by ID.
@param id The ID of the node.
@return The node with the given ID or null.
*/
public OSMNode getNode(Long id) {
return nodes.get(id);
}
/**
Get the nearest node to a point.
@param lat The latitude of the point.
@param lon The longitude of the point.
@return The nearest node.
*/
public OSMNode getNearestNode(double lat, double lon) {
double smallest = Double.MAX_VALUE;
OSMNode best = null;
for (OSMNode next : nodes.values()) {
double d1 = next.getLatitude() - lat;
double d2 = next.getLongitude() - lon;
double d = (d1 * d1) + (d2 * d2);
if (d < smallest) {
best = next;
smallest = d;
}
}
return best;
}
/**
Replace a node and update all references.
@param old The node to replace.
@param replacement The replacement node.
*/
public void replaceNode(OSMNode old, OSMNode replacement) {
for (OSMRoad r : roads.values()) {
r.replace(old.getID(), replacement.getID());
}
for (OSMBuilding b : buildings.values()) {
b.replace(old.getID(), replacement.getID());
}
removeNode(old);
}
/**
Get all roads.
@return All roads.
*/
public Collection<OSMRoad> getRoads() {
return new HashSet<OSMRoad>(roads.values());
}
/**
Remove a road.
@param road The road to remove.
*/
public void removeRoad(OSMRoad road) {
roads.remove(road.getID());
}
/**
Get all buildings.
@return All buildings.
*/
public Collection<OSMBuilding> getBuildings() {
return new HashSet<OSMBuilding>(buildings.values());
}
/**
Remove a building.
@param building The building to remove.
*/
public void removeBuilding(OSMBuilding building) {
buildings.remove(building.getID());
}
private void calculateBounds() {
if (boundsCalculated) {
return;
}
minLat = Double.POSITIVE_INFINITY;
maxLat = Double.NEGATIVE_INFINITY;
minLon = Double.POSITIVE_INFINITY;
maxLon = Double.NEGATIVE_INFINITY;
for (OSMNode node : nodes.values()) {
minLat = Math.min(minLat, node.getLatitude());
maxLat = Math.max(maxLat, node.getLatitude());
minLon = Math.min(minLon, node.getLongitude());
maxLon = Math.max(maxLon, node.getLongitude());
}
boundsCalculated = true;
}
private OSMNode processNode(Element e) {
long id = Long.parseLong(e.attributeValue("id"));
double lat = Double.parseDouble(e.attributeValue("lat"));
double lon = Double.parseDouble(e.attributeValue("lon"));
OSMNode node = new OSMNode(id, lat, lon);
nodes.put(id, node);
return node;
}
private void processWay(Element e) {
long id = Long.parseLong(e.attributeValue("id"));
List<Long> ids = new ArrayList<Long>();
for (Object next : e.elements("nd")) {
Element nd = (Element)next;
Long nextID = Long.parseLong(nd.attributeValue("ref"));
ids.add(nextID);
}
// Is this way a road or a building?
boolean road = false;
boolean building = false;
for (Object next : e.elements("tag")) {
Element tag = (Element)next;
building = building || tagSignifiesBuilding(tag);
road = road || tagSignifiesRoad(tag);
}
if (building) {
buildings.put(id, new OSMBuilding(id, ids));
}
else if (road) {
roads.put(id, new OSMRoad(id, ids));
}
}
private boolean tagSignifiesRoad(Element tag) {
String key = tag.attributeValue("k");
String value = tag.attributeValue("v");
if (!"highway".equals(key)) {
return false;
}
return ROAD_MARKERS.contains(value);
}
private boolean tagSignifiesBuilding(Element tag) {
String key = tag.attributeValue("k");
String value = tag.attributeValue("v");
if ("building".equals(key)) {
return "yes".equals(value);
}
if ("rcr:building".equals(key)) {
return "1".equals(value);
}
return false;
}
}
package maps.osm;
import javax.swing.JFrame;
import javax.swing.JComponent;
import java.awt.Point;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.Writer;
import java.io.File;
import java.io.FileWriter;
import org.dom4j.Document;
import org.dom4j.io.XMLWriter;
import org.dom4j.io.OutputFormat;
/**
This class extracts a portion of an OSMMap.
*/
public class OSMMapExtractor extends MouseAdapter {
private static final int VIEWER_SIZE = 500;
private static final Color DRAG_COLOUR = new Color(128, 128, 128, 64);
private JComponent glass;
private Point press;
private Point drag;
private Point release;
private OSMMap map;
private OSMMapViewer viewer;
private Writer out;
/**
Construct an OSMMapExtractor.
@param map The map.
@param viewer The viewer.
@param out The writer to write extracted data to.
*/
public OSMMapExtractor(OSMMap map, OSMMapViewer viewer, Writer out) {
this.map = map;
this.viewer = viewer;
this.out = out;
this.glass = new DragGlass();
}
/**
Start the OSMMapExtractor.
@param args Command line arguments: source target.
*/
public static void main(String[] args) {
try {
OSMMap map = new OSMMap(new File(args[0]));
Writer out = new FileWriter(new File(args[1]));
OSMMapViewer viewer = new OSMMapViewer(map);
OSMMapExtractor extractor = new OSMMapExtractor(map, viewer, out);
viewer.addMouseListener(extractor);
viewer.setPreferredSize(new Dimension(VIEWER_SIZE, VIEWER_SIZE));
JFrame frame = new JFrame();
frame.setGlassPane(extractor.getGlass());
frame.setContentPane(viewer);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// CHECKSTYLE:OFF:IllegalCatch
catch (Exception e) {
e.printStackTrace();
}
// CHECKSTYLE:ON:IllegalCatch
}
/**
Get a glass component for drawing the selection overlay.
@return A glass component.
*/
public JComponent getGlass() {
return glass;
}
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
Point p = e.getPoint();
Insets insets = viewer.getInsets();
p.translate(-insets.left, -insets.top);
press = new Point(p);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
Point p = e.getPoint();
Insets insets = viewer.getInsets();
p.translate(-insets.left, -insets.top);
drag = new Point(p);
glass.repaint();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
Point p = e.getPoint();
Insets insets = viewer.getInsets();
p.translate(-insets.left, -insets.top);
release = new Point(p);
drag = null;
write();
}
}
private void write() {
double pressLat = viewer.getLatitude(press.y);
double pressLon = viewer.getLongitude(press.x);
double releaseLat = viewer.getLatitude(release.y);
double releaseLon = viewer.getLongitude(release.x);
try {
OSMMap newMap = new OSMMap(map,
Math.min(pressLat, releaseLat),
Math.min(pressLon, releaseLon),
Math.max(pressLat, releaseLat),
Math.max(pressLon, releaseLon));
Document d = newMap.toXML();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(d);
writer.flush();
writer.close();
System.out.println("Wrote map");
}
// CHECKSTYLE:OFF:IllegalCatch
catch (Exception ex) {
ex.printStackTrace();
}
// CHECKSTYLE:ON:IllegalCatch
}
private class DragGlass extends JComponent {
public void paintComponent(Graphics g) {
if (drag == null) {
return;
}
g.setColor(DRAG_COLOUR);
int x = Math.min(press.x, drag.x);
int y = Math.max(press.y, drag.y);
int width = (int)Math.abs(press.x - drag.x);
int height = (int)Math.abs(press.y - drag.y);
g.fillRect(x, y, width, height);
}
}
}
package maps.osm;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import rescuecore2.misc.gui.ScreenTransform;
import rescuecore2.misc.gui.PanZoomListener;
/**
A component for viewing OSM maps.
*/
public class OSMMapViewer extends JComponent {
private OSMMap map;
private ScreenTransform transform;
private PanZoomListener panZoom;
/**
Create an OSMMapViewer.
*/
public OSMMapViewer() {
this(null);
}
/**
Create an OSMMapViewer.
@param map The map to view.
*/
public OSMMapViewer(final OSMMap map) {
panZoom = new PanZoomListener(this);
setMap(map);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
Point p = e.getPoint();
double lon = transform.screenToX(p.x);
double lat = transform.screenToY(p.y);
OSMNode node = map.getNearestNode(lat, lon);
System.out.println("Click at " + lat + ", " + lon);
System.out.println("Nearest node: " + node);
}
}
});
}
/**
Set the map.
@param map The new map to view.
*/
public void setMap(OSMMap map) {
this.map = map;
transform = null;
if (map != null) {
transform = new ScreenTransform(map.getMinLongitude(), map.getMinLatitude(), map.getMaxLongitude(), map.getMaxLatitude());
}
panZoom.setScreenTransform(transform);
}
/**
Get the latitude of a screen coordinate.
@param y The screen coordinate.
@return The latitude at that coordinate.
*/
public double getLatitude(int y) {
return transform.screenToY(y);
}
/**
Get the longitude of a screen coordinate.
@param x The screen coordinate.
@return The longitude at that coordinate.
*/
public double getLongitude(int x) {
return transform.screenToX(x);
}
@Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (map == null) {
return;
}
Insets insets = getInsets();
int width = getWidth() - insets.left - insets.right;
int height = getHeight() - insets.top - insets.bottom;
Graphics2D g = (Graphics2D)graphics.create(insets.left, insets.top, width + 1 , height + 1);
transform.rescale(width, height);
g.setColor(Color.black);
for (OSMNode next : map.getNodes()) {
int x = transform.xToScreen(next.getLongitude());
int y = transform.yToScreen(next.getLatitude());
g.drawLine(x - 1, y - 1, x + 1, y + 1);
g.drawLine(x + 1, y - 1, x - 1, y + 1);
}
for (OSMRoad next : map.getRoads()) {
int lastX = -1;
int lastY = -1;
for (Long nodeID : next.getNodeIDs()) {
OSMNode node = map.getNode(nodeID);
int x = transform.xToScreen(node.getLongitude());
int y = transform.yToScreen(node.getLatitude());
if (lastX != -1) {
g.drawLine(lastX, lastY, x, y);
}
lastX = x;
lastY = y;
}
}
g.setColor(Color.blue);
for (OSMBuilding next : map.getBuildings()) {
int lastX = -1;
int lastY = -1;
for (Long nodeID : next.getNodeIDs()) {
OSMNode node = map.getNode(nodeID);
int x = transform.xToScreen(node.getLongitude());
int y = transform.yToScreen(node.getLatitude());
if (lastX != -1) {
g.drawLine(lastX, lastY, x, y);
}
lastX = x;
lastY = y;
}
}
}
}
package maps.osm;
/**
An OpenStreetMap node.
*/
public class OSMNode extends OSMObject {
private double lat;
private double lon;
/**
Construct an OSMNode.
@param id The ID of the node.
@param lat The latitude of the node.
@param lon The longitude of the node.
*/
public OSMNode(long id, double lat, double lon) {
super(id);
this.lat = lat;
this.lon = lon;
}
/**
Get the latitude of this node in degrees.
@return The latitude in degrees.
*/
public double getLatitude() {
return lat;
}
/**
Get the longitude of this node in degrees.
@return The longitude in degrees.
*/
public double getLongitude() {
return lon;
}
@Override
public String toString() {
return "OSMNode (" + getID() + ") at lat " + lat + ", lon " + lon;
}
}
package maps.osm;
/**
Abstract base class for OpenStreetMap objects.
*/
public abstract class OSMObject {
private long id;
/**
Construct an OSMObject.
@param id The ID of the object.
*/
public OSMObject(long id) {
this.id = id;
}
/**
Get the ID of this object.
@return The ID of the object.
*/
public long getID() {
return id;
}
}
package maps.osm;
import java.util.List;
/**
An OpenStreetMap road.
*/
public class OSMRoad extends OSMWay {
/**
Construct an OSMRoad.
@param id The ID of the road.
@param ids The IDs of the apex nodes of the road.
*/
public OSMRoad(Long id, List<Long> ids) {
super(id, ids);
}
}
package maps.osm;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
/**
An OSM way.
*/
public abstract class OSMWay extends OSMObject {
private List<Long> ids;
/**
Construct an OSMWay.
@param id The ID of the way.
@param ids The IDs of the nodes of the way.
*/
public OSMWay(Long id, List<Long> ids) {
super(id);
this.ids = ids;
}
/**
Get the IDs of the way nodes.
@return The IDs of the nodes of this way.
*/
public List<Long> getNodeIDs() {
return new ArrayList<Long>(ids);
}
/**
Set the IDs of the way nodes.
@param newIDs The new IDs of the nodes of this way.
*/
public void setNodeIDs(List<Long> newIDs) {
ids = newIDs;
}
/**
Replace a node ID in this way.
@param oldID The old node ID.
@param newID The new node ID.
*/
public void replace(Long oldID, Long newID) {
Collections.replaceAll(ids, oldID, newID);
}
}
package maps.osm.debug;
import java.awt.Color;
import java.awt.Shape;
import java.awt.Polygon;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import rescuecore2.misc.gui.ScreenTransform;
import rescuecore2.misc.gui.ShapeDebugFrame;
import rescuecore2.misc.gui.DrawingTools;
import maps.osm.OSMWay;
import maps.osm.OSMMap;
import maps.osm.OSMNode;
/**
A ShapeInfo that knows how to draw OSMWays.
*/
public class OSMWayShapeInfo extends ShapeDebugFrame.ShapeInfo {
private OSMWay way;
private OSMMap map;
private Color colour;
private boolean drawEdgeDirections;
private Rectangle2D bounds;
/**
Create a new OSMWayShapeInfo.
@param way The way to draw.
@param map The map the way is part of.
@param name The name of the way.
@param colour The colour to draw the way.
@param drawEdgeDirections Whether to draw edge directions or not.
*/
public OSMWayShapeInfo(OSMWay way, OSMMap map, String name, Color colour, boolean drawEdgeDirections) {
super(way, name);
this.way = way;
this.map = map;
this.colour = colour;
this.drawEdgeDirections = drawEdgeDirections;
if (way != null) {
bounds = findBounds();
}
}
@Override
public Shape paint(Graphics2D g, ScreenTransform transform) {
if (way == null) {
return null;
}
List<Long> points = way.getNodeIDs();
int n = points.size();
int[] xs = new int[n];
int[] ys = new int[n];
int i = 0;
for (long next : points) {
xs[i] = transform.xToScreen(map.getNode(next).getLongitude());
ys[i] = transform.yToScreen(map.getNode(next).getLatitude());
++i;
}
Polygon p = new Polygon(xs, ys, n);
if (colour != null) {
g.setColor(colour);
g.draw(p);
if (drawEdgeDirections) {
for (i = 1; i < n; ++i) {
DrawingTools.drawArrowHeads(xs[i - 1], ys[i - 1], xs[i], ys[i], g);
}
}
}
return p;
}
@Override
public void paintLegend(Graphics2D g, int width, int height) {
if (colour != null) {
g.setColor(colour);
g.drawRect(0, 0, width - 1, height - 1);
}
}
@Override
public Rectangle2D getBoundsShape() {
return bounds;
}
@Override
public java.awt.geom.Point2D getBoundsPoint() {
return null;
}
private Rectangle2D findBounds() {
double xMin = Double.POSITIVE_INFINITY;
double xMax = Double.NEGATIVE_INFINITY;
double yMin = Double.POSITIVE_INFINITY;
double yMax = Double.NEGATIVE_INFINITY;
for (long next : way.getNodeIDs()) {
OSMNode n = map.getNode(next);
xMin = Math.min(xMin, n.getLongitude());
xMax = Math.max(xMax, n.getLongitude());
yMin = Math.min(yMin, n.getLatitude());
yMax = Math.max(yMax, n.getLatitude());
}
return new Rectangle2D.Double(xMin, yMin, xMax - xMin, yMax - yMin);
}
}
package maps.validate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import maps.gml.GMLBuilding;
import maps.gml.GMLDirectedEdge;
import maps.gml.GMLMap;
import maps.gml.GMLRoad;
import maps.gml.GMLShape;
/**
* Checks if a GML map is fully connected and if all individual shaped are
* correctly connected to each other (i.e. there are no dangling or no one-way
* connections).
*/
public class GMLConnectivityValidator implements MapValidator<GMLMap> {
private GMLMap map = null;
@Override
public Collection<ValidationError> validate(GMLMap mmap) {
this.map = mmap;
List<ValidationError> errors = new LinkedList<ValidationError>();
// Check if all shapes are connected correctly (no dangling connections,
// etc...)
Set<GMLShape> toBeChecked = new HashSet<GMLShape>();
for (GMLShape shape : map.getAllShapes()) {
errors.addAll(validateShape(shape));
if (shape instanceof GMLBuilding || shape instanceof GMLRoad) {
toBeChecked.add(shape);
}
}
Queue<GMLShape> open = new LinkedList<GMLShape>();
GMLShape first = toBeChecked.iterator().next();
open.add(first);
// check for connectivity (only simple connectivity needs to be checked,
// as
// we made sure that there are no one-way connections
while (!open.isEmpty()) {
GMLShape next = open.remove();
toBeChecked.remove(next);
for (GMLShape n : getNeigbours(next)) {
if (toBeChecked.contains(n)) {
open.add(n);
}
if (!(n instanceof GMLBuilding || n instanceof GMLRoad)) {
String message = "Can reach non-building, non-road shape "
+ n.getID();
errors.add(new ValidationError(next.getID(), message));
}
}
}
if (!toBeChecked.isEmpty()) {
for (GMLShape unreachable : toBeChecked) {
String message = "The map is not fully connected. Shape cannot be reached from "
+ first.getID();
errors.add(new ValidationError(unreachable.getID(), message));
}
}
return errors;
}
/**
* Check if all connections to neighbours are reflexive.
*
* @param shape
* @return
*/
private Collection<ValidationError> validateShape(GMLShape shape) {
List<ValidationError> errors = new LinkedList<ValidationError>();
for (GMLDirectedEdge e : shape.getEdges()) {
if (shape.hasNeighbour(e)) {
int nId = shape.getNeighbour(e);
GMLShape neighbour = map.getShape(nId);
if (neighbour == null) {
String message = "Connection to nonexisting id " + nId
+ " via Edge " + e.getEdge().getID();
errors.add(new ValidationError(shape.getID(), message));
}
else if (neighbour == shape) {
String message = "Shape is connected to itself via Edge"
+ e.getEdge().getID();
errors.add(new ValidationError(shape.getID(), message));
}
else {
GMLShape backRef = null;
try {
if (neighbour.hasNeighbour(e.getEdge())) {
backRef = map.getShape(neighbour.getNeighbour(e
.getEdge()));
}
if (backRef != shape) {
String message = "Connection to " + neighbour.getID()
+ " via Edge " + e.getEdge().getID()
+ " is not reflexive.";
errors.add(new ValidationError(shape.getID(), message));
}
}
catch (IllegalArgumentException ex) {
String message = "Neigbour " + neighbour.getID()
+ " does not share Edge " + e.getEdge().getID();
errors.add(new ValidationError(shape.getID(), message));
}
}
}
}
return errors;
}
/**
* Get all shapes that a shape is connected to.
* @param shape
* @return
*/
private Collection<GMLShape> getNeigbours(GMLShape shape) {
Collection<GMLShape> result = new ArrayList<GMLShape>();
for (GMLDirectedEdge edge : shape.getEdges()) {
if (shape.hasNeighbour(edge)) {
GMLShape n = map.getShape(shape.getNeighbour(edge));
if (n != null) {
result.add(n);
}
}
}
return result;
}
}
package maps.validate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import maps.MapException;
import maps.MapReader;
import maps.gml.GMLMap;
/**
* Load a map and check it for errors.
*
*/
public final class GMLMapValidator {
private static List<MapValidator<GMLMap>> validators;
static {
validators = new ArrayList<MapValidator<GMLMap>>();
validators.add(new GMLConnectivityValidator());
validators.add(new GMLShapeValidator());
validators.add(new GMLTraversabilityValidator());
}
private GMLMapValidator() {}
/**
* Returns a list of default MapValidators to use for GML maps.
@return List of default MapValidators.
*/
public static List<MapValidator<GMLMap>> getDefaultValidators() {
return new ArrayList<MapValidator<GMLMap>>(validators);
}
/**
* @param args The command line arguments.
*/
public static void main(String[] args) {
try {
GMLMap map = (GMLMap)MapReader.readMap(args[0]);
boolean hasErrors = true;
for (MapValidator<GMLMap> val : validators) {
Collection<ValidationError> errors = val.validate(map);
for (ValidationError e : errors) {
System.err.println(e);
hasErrors = true;
}
}
if (!hasErrors) {
System.out.println("No errors have been found.");
}
}
catch (MapException e) {
e.printStackTrace();
}
}
}
package maps.validate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import maps.gml.GMLMap;
import maps.gml.GMLShape;
import com.vividsolutions.jts.geom.Geometry;
/**
Validate the correctness of basic shape properties.
@author goebelbe
*/
public class GMLShapeValidator implements MapValidator<GMLMap> {
@Override
public Collection<ValidationError> validate(GMLMap map) {
List<ValidationError> errors = new ArrayList<ValidationError>();
List<Geometry> polygons = new ArrayList<Geometry>();
List<GMLShape> shapes = new ArrayList<GMLShape>(map.getAllShapes());
for (GMLShape shape : shapes) {
try {
Geometry polygon = checkShape(shape);
polygons.add(polygon);
}
catch (ValidationException e) {
errors.add(e.getError());
polygons.add(null);
}
}
for (int i = 0; i < polygons.size(); i++) {
Geometry s1 = polygons.get(i);
if (s1 == null) {
continue;
}
for (int j = i + 1; j < polygons.size(); j++) {
Geometry s2 = polygons.get(j);
if (s2 != null && s1.intersects(s2) && !s1.touches(s2)) {
int s1Id = shapes.get(i).getID();
int s2Id = shapes.get(j).getID();
String message = " Shape overlaps with shape " + s2Id;
errors.add(new ValidationError(s1Id, message));
}
}
}
return errors;
}
/**
Check if the given shape is correct.
@param shape
@return
*/
private static Geometry checkShape(GMLShape shape) throws ValidationException {
Geometry polygon = JTSTools.shapeToPolygon(shape);
if (!polygon.isValid()) {
throw new ValidationException(shape.getID(), "invalid shape");
}
if (!polygon.contains(polygon.getCentroid())) {
throw new ValidationException(shape.getID(), "Shape doesn't contain centroid.");
}
return polygon;
}
}
package maps.validate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import maps.gml.GMLDirectedEdge;
import maps.gml.GMLMap;
import maps.gml.GMLRoad;
import maps.gml.GMLShape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.operation.linemerge.LineSequencer;
/**
* Validator to check if the shapes of the map are traversable.
*
* For all shaped we check if they can be entered via their entrances.
*
* For road we also check if each entrance can reach the other ones via this
* road.
*
*/
public class GMLTraversabilityValidator implements MapValidator<GMLMap> {
private static final double MIN_ROAD_WIDTH = 1.0;
private static final double SHAPE_PADDING = 0.01;
@Override
public Collection<ValidationError> validate(GMLMap map) {
Collection<ValidationError> errors = new ArrayList<ValidationError>();
for (GMLShape shape : map.getRoads()) {
ValidationError error = checkTraversability(shape, MIN_ROAD_WIDTH);
if (error != null) {
errors.add(error);
}
}
for (GMLShape shape : map.getBuildings()) {
ValidationError error = checkTraversability(shape, MIN_ROAD_WIDTH);
if (error != null) {
errors.add(error);
}
}
return errors;
}
/**
* Check if this shape can be traversed by an agent of width
* <tt>minWidth</tt>.
*
* @param shape
* @param agentWidth
* @return
*/
private ValidationError checkTraversability(GMLShape shape, double minWidth) {
// To check for traversability, we shrink the non-traversable edges
// of the shape by the radius of the agent.
// We then check, if all entrance edges are part of the same part
// of the resulting polygon
try {
Geometry polygon = JTSTools.shapeToPolygon(shape);
if (!polygon.isValid()) {
return new ValidationError(shape.getID(), "invalid shape");
}
Geometry boundary = impassableLines(shape);
Geometry buffer = boundary.buffer(((double) minWidth) / 2);
Geometry result = polygon.difference(buffer);
// make sure the intersection tests succeed
result = result.buffer(SHAPE_PADDING);
Coordinate centroid = JTSTools.pointToCoordinate(shape.getCentroid());
// Build list of adjacent entrance edges
List<GMLDirectedEdge> edges = shape.getEdges();
List<List<GMLDirectedEdge>> entrances = new ArrayList<List<GMLDirectedEdge>>();
List<GMLDirectedEdge> entrance = new ArrayList<GMLDirectedEdge>();
for (GMLDirectedEdge e : edges) {
if (shape.hasNeighbour(e)) {
entrance.add(e);
//Check if we have a line of sight to the centroid
LineString edge = JTSTools.edgeToLine(e);
Coordinate edgeCenter = edge.getCentroid().getCoordinate();
Coordinate[] coords = new Coordinate[]{centroid, edgeCenter};
LineString lineOfSight = JTSTools.getFactory().createLineString(coords);
if (lineOfSight.intersects(boundary)) {
String message = "Edge " + e.getEdge().getID()
+ " has no line of sight to shape center.";
return new ValidationError(shape.getID(), message);
}
}
else {
if (!entrance.isEmpty()) {
entrances.add(entrance);
}
entrance = new ArrayList<GMLDirectedEdge>();
}
}
if (!entrance.isEmpty()) {
// Merge first and last sequences if neccessary
if (shape.hasNeighbour(edges.get(0)) && !entrances.isEmpty()) {
entrances.get(0).addAll(entrance);
}
else {
entrances.add(entrance);
}
}
// Check in which part of the polygon the entrances lie
GMLDirectedEdge firstEdge = null;
int firstPolygon = -1;
for (List<GMLDirectedEdge> etr : entrances) {
int polyIndex = -1;
for (GMLDirectedEdge e : etr) {
polyIndex = findPolygonPartOfEdge(e, result);
if (polyIndex != -1) {
break;
}
}
if (polyIndex == -1) {
// Entrance edge no longer in polygon
String message = "Edge is too narrow to pass through.";
return new ValidationError(etr.get(0).getEdge().getID(),
message);
}
if (firstEdge == null) {
firstEdge = etr.get(0);
firstPolygon = polyIndex;
}
else if (firstPolygon != polyIndex
&& (shape instanceof GMLRoad)) {
// Only check traversability for roads
String message = "Can't reach edge "
+ firstEdge.getEdge().getID() + " from "
+ etr.get(0).getEdge().getID();
return new ValidationError(shape.getID(), message);
}
}
return null;
}
catch (ValidationException e) {
return e.getError();
}
}
/**
* Find the index of the subgeometry the given edge is part of. Return -1 if
* the edge is not contained in the geometry at all.
* @param edge
* @param geom
* @return
*/
private static int findPolygonPartOfEdge(GMLDirectedEdge edge, Geometry geom) {
for (int i = 0; i < geom.getNumGeometries(); i++) {
if (edgePartOfPolygon(edge, geom.getGeometryN(i))) {
return i;
}
}
return -1;
}
/**
* Checks if an edge is part (i.e intersects) of a given polygon.
* @param edge
* @param polygon
* @return
*/
private static boolean edgePartOfPolygon(GMLDirectedEdge edge,
Geometry polygon) {
// No idea if this works...
return polygon.intersects(JTSTools.edgeToLine(edge));
}
/**
* Return a LineString or MultiLineString of the impassable edges of a
* shape.
* @param shape
* @return
*/
private static Geometry impassableLines(GMLShape shape) {
LineSequencer seq = new LineSequencer();
for (GMLDirectedEdge e : shape.getEdges()) {
if (!shape.hasNeighbour(e)) {
Coordinate[] coord = new Coordinate[2];
coord[0] = JTSTools.nodeToCoordinate(e.getStartNode());
coord[1] = JTSTools.nodeToCoordinate(e.getEndNode());
seq.add(JTSTools.getFactory().createLineString(coord));
}
}
return seq.getSequencedLineStrings();
}
}
package maps.validate;
import maps.gml.GMLDirectedEdge;
import maps.gml.GMLNode;
import maps.gml.GMLShape;
import rescuecore2.misc.geometry.Point2D;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.operation.linemerge.LineSequencer;
import com.vividsolutions.jts.util.AssertionFailedException;
/**
* This class provides some conversion functions from GML map classes to JTS
* Geometry classes.
*/
public final class JTSTools {
private static GeometryFactory geomFactory = new GeometryFactory();
private JTSTools() {
};
/**
* Create a LineString from a GMLDirectedEdge.
* @param edge
* The edge to convert.
* @return LineString or MultLineString.
*/
public static LineString edgeToLine(GMLDirectedEdge edge) {
Coordinate[] coord = new Coordinate[2];
coord[0] = nodeToCoordinate(edge.getStartNode());
coord[1] = nodeToCoordinate(edge.getEndNode());
return geomFactory.createLineString(coord);
}
/**
* Create a JTS Polygon form a GMLShape.
* @param shape
* The shape to convert.
* @return Polygon geometry.
* @throws ValidationException
*/
public static Geometry shapeToPolygon(GMLShape shape)
throws ValidationException {
LineSequencer seq = new LineSequencer();
for (GMLDirectedEdge e : shape.getEdges()) {
Coordinate[] coord = new Coordinate[2];
coord[0] = nodeToCoordinate(e.getStartNode());
coord[1] = nodeToCoordinate(e.getEndNode());
if (coord[0].equals(coord[1])) {
throw new ValidationException(e.getEdge().getID(),
"Zero length edge.");
}
seq.add(geomFactory.createLineString(coord));
}
try {
if (!seq.isSequenceable()) {
throw new ValidationException(shape.getID(),
"Outline is not a single line.");
}
}
catch (AssertionFailedException e) {
throw new ValidationException(shape.getID(),
"Could not get outline: " + e.getMessage());
}
Geometry line = seq.getSequencedLineStrings();
CoordinateList coord = new CoordinateList(line.getCoordinates());
coord.closeRing();
// CHECKSTYLE:OFF:MagicNumber
if (coord.size() < 4) {
// CHECKSTYLE:ON:MagicNumber
throw new ValidationException(shape.getID(), "Degenerate Shape");
}
Geometry ring = geomFactory.createLinearRing(coord.toCoordinateArray());
return geomFactory.createPolygon((LinearRing) ring, null);
}
/**
* Create a Coordinate from a GMLNode.
* @param node
* Node to convert.
* @return Coordinate object.
*/
public static Coordinate nodeToCoordinate(GMLNode node) {
return new Coordinate(node.getX(), node.getY());
}
/**
* Create a Coordinate from a Point2D.
* @param point
* Point to convert.
* @return Coordinate object.
*/
public static Coordinate pointToCoordinate(Point2D point) {
return new Coordinate(point.getX(), point.getY());
}
/**
* Get the default GeometryFactory.
* @return The default GeometryFactory.
*/
public static GeometryFactory getFactory() {
return geomFactory;
}
}
package maps.validate;
import java.util.Collection;
import maps.Map;
/**
* Interface for classes that can validate maps.
*
* @param <T>
*/
public interface MapValidator<T extends Map> {
/**
* Check if the given map is valid. If not, return a collection of errors.
* @param map Map that should be checked.
* @return Collection of errors.
*/
Collection<ValidationError> validate(T map);
}
package maps.validate;
/**
* This class encapsulates a validation error. It contains the object id and an
* error message.
*/
public class ValidationError {
private int id;
private String message;
/**
* Create a new ValidationError object.
* @param id The id of the GMLObject containing the error.
* @param message The error message.
*/
public ValidationError(int id, String message) {
this.id = id;
this.message = message;
}
/**
* Get the id of the object this error refers to.
* @return The id of the GMLObject containing the error.
*/
public int getId() {
return id;
}
/**
* Get the error message of this ValidationError.
* @return The error message.
*/
public String getMessage() {
return message;
}
@Override
public String toString() {
return "Error in object " + id + ": " + message;
}
}
package maps.validate;
/**
* Exception that is raised when an error in a map is detected during
* validation.
*
*/
public class ValidationException extends Exception {
private ValidationError error;
/**
* Create a new ValidationException.
* @param id The id of the GMLObject containing the error.
* @param message The error message.
*/
public ValidationException(int id, String message) {
super(message);
error = new ValidationError(id, message);
}
/**
* Get the underlying ValidationError for this exception.
* @return The ValidationError causing this exception.
*/
public ValidationError getError() {
return error;
}
}
package misc;
import rescuecore2.config.Config;
import java.util.Random;
import org.uncommons.maths.random.GaussianGenerator;
import org.uncommons.maths.number.NumberGenerator;
/**
Container for information about different damage types.
*/
/*
* Implementation of Refuge Bed Capacity
* @author Farshid Faraji
* May 2020 During Covid-19 :-)))
* */
public class DamageType {
private String type;
private double k;
private double l;
private NumberGenerator<Double> noise;
private double damage;
/**
Construct a DamageType.
@param type The name of this type.
@param config The system configuration.
@param Random sequence proprietary.
*/
public DamageType(String type, Config config, Random random) {
this.type = type;
k = config.getFloatValue("misc.injury." + type + ".k");
l = config.getFloatValue("misc.injury." + type + ".l");
double mean = config.getFloatValue("misc.injury." + type + ".noise.mean");
double sd = config.getFloatValue("misc.injury." + type + ".noise.sd");
noise = new GaussianGenerator(mean, sd, random);
damage = 0;
}
/**
Get the type name.
@return The type name.
*/
public String getType() {
return type;
}
/**
Compute damage progression for this type.
@return The new damage.
*/
public double progress() {
if (damage <= 0) {
return damage;
}
double n = noise.nextValue();
damage = damage + (k * damage * damage) + l + n;
return damage;
}
public double progressInRefuge() {
if (damage <= 0) {
return damage;
}
double n = noise.nextValue();
damage = damage - (k * damage * damage) - l - (2*n);
return damage;
}
/**
Get the current damage.
@return The current damage.
*/
public double getDamage() {
return damage;
}
/**
Set the current damage.
@param d The current damage.
*/
public void setDamage(double d) {
damage = d;
}
/**
Add some damage.
@param d The amount to add.
*/
public void addDamage(double d) {
damage += d;
}
}
package misc;
import rescuecore2.config.Config;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.standard.entities.Human;
import java.util.Random;
/**
Class for holding information about humans.
*/
/*
* Implementation of Refuge Bed Capacity
* @author Farshid Faraji
* May 2020 During Covid-19 :-)))
* */
public class HumanAttributes {
private Human human;
private EntityID id;
private DamageType damageFire;
private DamageType damageCollapse;
private DamageType damageBury;
private Random random;
/**
Construct a HumanAttributes object that wraps a Human.
@param h The Human to wrap.
@param config The system configuration.
*/
public HumanAttributes(Human h, Config config) {
this.human = h;
this.id = h.getID();
// Generate Random for each Human
this.random = new Random(config.getRandom().nextLong());
damageFire = new DamageType("fire", config, random);
damageCollapse = new DamageType("collapse", config, random);
damageBury = new DamageType("bury", config, random);
}
/**
Get the ID of the wrapped human.
@return The human ID.
*/
public EntityID getID() {
return id;
}
/**
Get the wrapped human.
@return The wrapped human.
*/
public Human getHuman() {
return human;
}
/**
Get the random sequence of the wrapped human.
@return The random sequence.
*/
public Random getRandom(){
return random;
}
/**
Add some collapse damage.
@param d The amount of damage to add.
*/
public void addCollapseDamage(double d) {
damageCollapse.addDamage(d);
}
/**
Get the amount of collapse damage this human has.
@return The amount of collapse damage.
*/
public double getCollapseDamage() {
return damageCollapse.getDamage();
}
/**
Set the amount of collapse damage this human has.
@param d The new collapse damage.
*/
public void setCollapseDamage(double d) {
damageCollapse.setDamage(d);
}
/**
Add some buriedness damage.
@param d The amount of damage to add.
*/
public void addBuriednessDamage(double d) {
damageBury.addDamage(d);
}
/**
Get the amount of buriedness damage this human has.
@return The amount of buriedness damage.
*/
public double getBuriednessDamage() {
return damageBury.getDamage();
}
/**
Set the amount of buriedness damage this human has.
@param d The new buriedness damage.
*/
public void setBuriednessDamage(double d) {
damageBury.setDamage(d);
}
/**
Add some fire damage.
@param d The amount of damage to add.
*/
public void addFireDamage(double d) {
damageFire.addDamage(d);
}
/**
Get the amount of fire damage this human has.
@return The amount of fire damage.
*/
public double getFireDamage() {
return damageFire.getDamage();
}
/**
Set the amount of fire damage this human has.
@param d The new fire damage.
*/
public void setFireDamage(double d) {
damageFire.setDamage(d);
}
/**
Get the total damage of this human, rounded to the nearest integer.
@return The total damage.
*/
public int getTotalDamage() {
return (int)Math.round(damageCollapse.getDamage() + damageFire.getDamage() + damageBury.getDamage());
}
/**
Progress all damage types.
*/
public void progressDamage() {
damageCollapse.progress();
damageFire.progress();
damageBury.progress();
}
public void progressDamageInRefuge()
{
//int damage = getTotalDamage();
damageCollapse.progressInRefuge();
damageFire.progressInRefuge();
damageBury.progressInRefuge();
}
/**
Clear all damage.
*/
public void clearDamage() {
damageCollapse.setDamage(0);
damageBury.setDamage(0);
damageFire.setDamage(0);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment