/** * ***************************************************************************** * Copyright 2013 Johannes Mitlmeier * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * **************************************************************************** */ package de.fub.agg2graph.agg; import de.fub.agg2graph.agg.tiling.ICachingStrategy; import de.fub.agg2graph.agg.tiling.Tile; import de.fub.agg2graph.roadgen.Intersection; import de.fub.agg2graph.structs.GPSPoint; import de.fub.agg2graph.structs.ILocation; import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Node in the aggregation. A node can have multiple incoming and outgoing * {@link AggConnection}s linking other AggNodes. It is capable of saving the * intersection information (how many tracks from which input to which output). * * @author Johannes Mitlmeier * */ public class AggNode extends GPSPoint { private boolean loaded = false; private AggContainer aggContainer; private Intersection intersection; public Set<AggConnection> out = new HashSet<AggConnection>(); public Set<AggConnection> in = new HashSet<AggConnection>(); private double weight = 2; private final Map<String, Integer> turnMap = new HashMap<String, Integer>(); public AggNode(double lat, double lon, AggContainer aggContainer) { init(null, lat, lon, aggContainer); } public AggNode(ILocation location, AggContainer aggContainer) { this.aggContainer = aggContainer; if (location != null) { init(location.getID(), location.getLat(), location.getLon(), aggContainer); } } public AggNode(String ID, double lat, double lon, AggContainer aggContainer) { init(ID, lat, lon, aggContainer); } public AggNode(AggNode node) { this(node, node.aggContainer); } /** * Initialize AggNode. Sets lat, lon and {@link AggContainer}. * * @param ID * @param lat * @param lon * @param aggContainer */ private void init(String ID, double lat, double lon, AggContainer aggContainer) { this.aggContainer = aggContainer; setID(ID); setLatLon(lat, lon); } /** * Check if the AggNode is loaded (i.e. all {@link AggConnection}s are in * memory). * * @return */ public boolean isLoaded() { return loaded; } public void setLoaded(boolean loaded) { this.loaded = loaded; } @Override public String getID() { return ID; } @Override public void setID(String ID) { this.ID = ID; } public void addIn(AggConnection inConn) { if (!isLoaded()) { // TODO load node } getIn().add(inConn); } public int getNumberOfTurnInformation() { int result = 0; for (String key : turnMap.keySet()) { result += turnMap.get(key); } return result; } public Map<String, Integer> getTurnMap() { return turnMap; } public void addTurn(AggNode before, AggNode after) { // keep it tracked String key = MessageFormat.format("{0}#{1}", before.getID(), after.getID()); // System.out.println("adding turn " + key); int counter = 0; if (turnMap.containsKey(key)) { counter = turnMap.get(key); } counter++; turnMap.put(key, counter); } /** * * @param from * @param to * @return */ public int getTurnCount(AggNode from, AggNode to) { String key = from.getID() + "#" + to.getID(); if (turnMap.containsKey(key)) { return turnMap.get(key); } return 0; } /** * * @param from * @param to * @return */ public int getTurnCount(AggConnection from, AggConnection to) { return getTurnCount(from.getFrom(), to.getTo()); } public void addIn(AggConnection inConn, AggConnection afterConn) { addIn(inConn); addTurn(inConn.getFrom(), afterConn.getTo()); } public void addOut(AggConnection outConn) { if (!isLoaded()) { // TODO load node } getOut().add(outConn); } public void addOut(AggConnection outConn, AggConnection beforeConn) { addOut(outConn); addTurn(beforeConn.getFrom(), outConn.getTo()); } public Set<AggConnection> getIn() { return in; } public Set<AggConnection> getVisibleIn() { if (getIn() == null) { return getIn(); } Set<AggConnection> resultSet = new HashSet<AggConnection>(); for (AggConnection element : getIn()) { if (element.isVisible()) { // i.remove(); resultSet.add(element); } } return resultSet; } public Set<AggConnection> getOut() { return out; } public Set<AggConnection> getVisibleOut() { if (getOut() == null) { return getOut(); } Set<AggConnection> resultSet = new HashSet<AggConnection>(); for (AggConnection element : getOut()) { if (element.isVisible()) { // i.remove(); resultSet.add(element); } } return resultSet; } public AggContainer getAggContainer() { return aggContainer; } public void setAggContainer(AggContainer aggContainer) { this.aggContainer = aggContainer; } @Override public String toString() { if (ID != null) { return MessageFormat.format("{0}", ID); } return MessageFormat.format("AggNode [lat={0}, lon={1}]", getLat(), getLon()); } @Override public String toDebugString() { return MessageFormat.format("AggNode [ID={0}, lat={1}, lon={2}]", ID, getLat(), getLon()); } @Override public double getWeight() { return weight; } /** * Update the weight of the node. This sums the weight of all incoming and * outgoing {@link AggConnection}s. */ public void refreshWeight() { double inWeight = 0, outWeight = 0; // loop all connections and sum their weights if (getIn() == null) { inWeight += 1; } else { for (AggConnection conn : getIn()) { if (conn.isVisible()) { inWeight += conn.getWeight(); } } } if (getOut() == null) { outWeight += 1; } else { for (AggConnection conn : getOut()) { if (conn.isVisible()) { outWeight += conn.getWeight(); } } } weight = inWeight + outWeight; } @Override public int hashCode() { return (MessageFormat.format("{0}:{1}", getID(), Arrays.toString(getLatLon()))).hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!AggNode.class.isAssignableFrom(this.getClass()) || !AggNode.class.isAssignableFrom(obj.getClass())) { return false; } AggNode other = (AggNode) obj; if (ID == null) { if (other.ID != null) { return false; } } else { if (!ID.equals(other.ID)) { return false; // } else { // return true; } } return latlon[0] == other.latlon[0] && latlon[1] == other.latlon[1]; } public String getInternalID() { return MessageFormat.format("{0}:{1}:{2}", getLat(), getLon(), ID); } /** * Update latitude and longitude values. This method takes care of making * the {@link ICachingStrategy} aware of the move because the {@link Tile} * this AggNode belongs to might change. * * @param lat * @param lon */ @Override public void setLatLon(double lat, double lon) { boolean moved = (this.latlon[0] != lat || this.latlon[1] != lon) && (this.latlon[0] != Double.MAX_VALUE && this.latlon[1] != Double.MAX_VALUE); if (moved) { // remove from cache aggContainer.getCachingStrategy().removeNode(this); } super.setLatLon(lat, lon); if (moved) { // reload to cache aggContainer.getCachingStrategy().addNode(this); } } /** * Update x and y values. This method takes care of making the * {@link ICachingStrategy} aware of the move because the {@link Tile} this * AggNode belongs to might change. * * @param x * @param y */ @Override public void setXY(double x, double y) { boolean moved = (this.xy[0] != x || this.xy[1] != y) && (this.xy[0] != Double.MAX_VALUE && this.xy[1] != Double.MAX_VALUE); if (moved) { // remove from cache aggContainer.getCachingStrategy().removeNode(this); } super.setXY(x, y); if (moved) { // reload to cache aggContainer.getCachingStrategy().addNode(this); } } /** * Find the connection to an other AggNode. * * @param otherNode * @return Connection to otherNode, null otherwise. */ public AggConnection getConnectionTo(AggNode otherNode) { // TODO make sure this node is fully loaded for (AggConnection conn : getOut()) { if (conn.getTo().equals(otherNode)) { return conn; } } return null; } public boolean isShallow() { return false; } /** * Check if this AggNode is an intersection. In this case an intersection is * defined as an AggNode with more than one incoming or outgoing * {@link AggConnection}. * * @return */ public boolean isAggIntersection() { return getVisibleOut().size() > 1 || getVisibleIn().size() > 1; } /** * Check if this AggNode is a node at the end of a street. This means it * either has no incoming or no outgoing {@link AggConnection}s while having * at least one {@link AggConnection} of the other type. * * @return */ public boolean isEndNode() { return getVisibleOut().isEmpty() ^ getVisibleIn().isEmpty(); } public Intersection getIntersection() { return intersection; } /** * Set which intersection this {@link AggNode} belongs to. * * @param intersection */ public void setIntersection(Intersection intersection) { this.intersection = intersection; } // TODO Martinus public boolean isSource() { return in.isEmpty() && out.size() > 0; } public boolean isSink() { return in.size() > 0 && out.isEmpty(); } public boolean isRegular() { return in.size() == 1 && out.size() == 1; } public boolean isEmpty() { return in.isEmpty() && out.isEmpty(); } }