package de.blau.android.osm; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.support.annotation.Nullable; import android.util.Log; import de.blau.android.exception.OsmException; import de.blau.android.exception.StorageException; import de.blau.android.util.collections.LongOsmElementMap; public class Storage implements Serializable { /** * */ private static final long serialVersionUID = 3838107046050083566L; private final LongOsmElementMap<Node> nodes; private final LongOsmElementMap<Way> ways; private final LongOsmElementMap<Relation> relations; private List<BoundingBox> bboxes; /** * Default constructor * <p> * Initializes the storage and adds a maximum valid mercator size bounding box */ Storage() { nodes = new LongOsmElementMap<Node>(1000); ways = new LongOsmElementMap<Way>(); relations = new LongOsmElementMap<Relation>(); try { bboxes = Collections.synchronizedList(new ArrayList<BoundingBox>()); // a default entry may not make sense bboxes.add(new BoundingBox(-BoundingBox.MAX_LON_E7, -BoundingBox.MAX_LAT_E7, BoundingBox.MAX_LON_E7, BoundingBox.MAX_LAT_E7)); } catch (OsmException e) { Log.e("Vespucci", "Problem with bounding box", e); } } /** * Construct a new storage object with the contents of an existing one * @param s storage object to duplicate */ Storage(Storage s) { nodes = new LongOsmElementMap<Node>(s.nodes); ways = new LongOsmElementMap<Way>(s.ways); relations = new LongOsmElementMap<Relation>(s.relations); bboxes = Collections.synchronizedList(new ArrayList<BoundingBox>(s.bboxes)); } /** * Get a specific node by id * @param nodeOsmId id of the node * @return the node or null if not found */ @Nullable public Node getNode(final long nodeOsmId) { return nodes.get(nodeOsmId); } /** * Get a specific way by id * @param wayOsmId id of the way * @return the way or null if not found */ @Nullable public Way getWay(final long wayOsmId) { return ways.get(wayOsmId); } /** * Get a specific relation by id * @param relationOsmId id of the relation * @return the relation or null if not found */ @Nullable public Relation getRelation(final long relationOsmId) { return relations.get(relationOsmId); } public OsmElement getOsmElement(final String type, final long osmId) { if (type.equalsIgnoreCase(Node.NAME)) { return getNode(osmId); } else if (type.equalsIgnoreCase(Way.NAME)) { return getWay(osmId); } else if (type.equalsIgnoreCase(Relation.NAME)) { return getRelation(osmId); } return null; } /** * Get a unmodifiable list of all nodes * * @return list containing all nodes */ public List<Node> getNodes() { return Collections.unmodifiableList(nodes.values()); } /** * Return all nodes in a bounding box * * Note: currently this does a sequential scan of all nodes * @param box bounding box to search in * @return a list of all nodes in box */ public List<Node> getNodes(BoundingBox box) { ArrayList<Node> result = new ArrayList<Node>(nodes.size()); for (Node n:nodes) { if (box.isIn(n.getLat(), n.getLon())) { result.add(n); } } return result; } /** * Get a unmodifiable list of all ways * * @return list containing all ways */ public List<Way> getWays() { return Collections.unmodifiableList(ways.values()); } /** * Return all ways covered or possibly intersecting a bounding box * <p> * Note: currently this does a sequential scan of all ways * @param box bounding box to search in * @return a list of all ways in box */ public List<Way> getWays(BoundingBox box) { ArrayList<Way> result = new ArrayList<Way>(ways.size()); BoundingBox newBox = new BoundingBox(); // avoid creating new instances for (Way w:ways) { BoundingBox wayBox = w.getBounds(newBox); if (wayBox.intersects(box)) { result.add(w); } } return result; } /** * Get a unmodifiable list of all relations * * @return list containing all relations */ public List<Relation> getRelations() { return Collections.unmodifiableList(relations.values()); } /** * Get a unmodifiable list of all elements * @return list containing all elements */ public List<OsmElement> getElements() { List<OsmElement> l = new ArrayList<OsmElement>(); l.addAll(nodes.values()); l.addAll(ways.values()); l.addAll(relations.values()); return Collections.unmodifiableList(l); } /** * Test if an element is present in storage * @param element element to check for * @return true if element is in storage */ public boolean contains(final OsmElement element) { if (element instanceof Way) { return ways.containsKey(element.getOsmId()); } else if (element instanceof Node) { return nodes.containsKey(element.getOsmId()); } else if (element instanceof Relation) { return relations.containsKey(element.getOsmId()); } return false; } /** * Insert a node in to storage regardless of it is already present or not * @param node node to insert * @throws StorageException */ void insertNodeUnsafe(final Node node) throws StorageException { try { nodes.put(node.getOsmId(),node); } catch (Error err) { // should really only be OutOfMemory throw new StorageException(StorageException.OOM); } } /** * Insert a way in to storage regardless of it is already present or not * @param way way to insert * @throws StorageException */ void insertWayUnsafe(final Way way) throws StorageException { try { ways.put(way.getOsmId(),way); } catch (Error err) { // should really only be OutOfMemory throw new StorageException(StorageException.OOM); } } /** * Insert a relation in to storage regardless of it is already present or not * @param relation relation to insert * @throws StorageException */ void insertRelationUnsafe(final Relation relation) throws StorageException { try { relations.put(relation.getOsmId(),relation); } catch (Error err) { // should really only be OutOfMemory throw new StorageException(StorageException.OOM); } } /** * Insert an element if it is not already present in storage * <p> * Note: the current data structures do not allow multiple entries for the same object in any case * @param element element to insert * @throws StorageException */ void insertElementSafe(final OsmElement element) throws StorageException { if (!contains(element)) { insertElementUnsafe(element); } } /** * Insert an element in to storage regardless of it is already present or not * @param element element to insert * @throws StorageException */ void insertElementUnsafe(final OsmElement element) throws StorageException { if (element instanceof Way) { insertWayUnsafe((Way) element); } else if (element instanceof Node) { insertNodeUnsafe((Node) element); } else if (element instanceof Relation) { insertRelationUnsafe((Relation) element); } } /** * Remove a node from storage * @param node node to remove * @return true if the node was in storage */ boolean removeNode(final Node node) { return nodes.remove(node.getOsmId())!=null; } /** * Remove a way from storage * @param way way to remove * @return true if the way was in storage */ boolean removeWay(final Way way) { return ways.remove(way.getOsmId())!=null; } /** * Remove a relation from storage * @param relation relation to remove * @return true if the relation was in storage */ boolean removeRelation(final Relation relation) { return relations.remove(relation.getOsmId())!=null; } /** * Remove an element of any type from storage * @param element element to remove * @return true if the element was in storage */ boolean removeElement(final OsmElement element) { if (element instanceof Way) { return ways.remove(element.getOsmId())!=null; } else if (element instanceof Node) { return nodes.remove(element.getOsmId())!=null; } else if (element instanceof Relation) { return relations.remove(element.getOsmId())!=null; } return false; } /** * Get all bounding boxes of downloaded data * @return all bounding boxes */ public List<BoundingBox> getBoundingBoxes() { return bboxes; } /** * Resets bounding box list and adds this boundingbox * @param bbox bounding box to add */ void setBoundingBox(final BoundingBox bbox) { this.bboxes = Collections.synchronizedList(new ArrayList<BoundingBox>()); this.bboxes.add(bbox); } /** * Add this bounding box to list * @param bbox bounding box to add */ void addBoundingBox(final BoundingBox bbox) { if (this.bboxes == null) this.bboxes = Collections.synchronizedList(new ArrayList<BoundingBox>()); this.bboxes.add(bbox); } /** * Remove bounding box from list * @param box bounding box to remove */ public void deleteBoundingBox(BoundingBox box) { if (this.bboxes != null) { this.bboxes.remove(box); } } /** * Return true if this storage is empty * @return true if empty */ public boolean isEmpty() { return nodes.isEmpty() && ways.isEmpty() && relations.isEmpty(); } /** * Get a the "first" way containing node * @param node node to search for * @return the way or null if none was found */ @Nullable public Way getFirstWay(final Node node) { for (Way way:ways) { if (way.getNodes().contains(node)) { return way; } } return null; } /** * Get all ways that node is a vertex of * <p> * This method currently does a sequential scan of all ways in storage and should be avoided * @param node node to search for * @return list containing all ways containing node */ public List<Way> getWays(final Node node) { ArrayList<Way> mWays = new ArrayList<Way>(); for (Way way:ways) { if (way.hasNode(node)) { mWays.add(way); } } return mWays; } /** * Get all nodes that are vertexes in a way * <p> * This method currently does a sequential scan of all ways in storage and should be avoided * * @return all way nodes */ public List<Node> getWaynodes() { ArrayList<Node> waynodes = new ArrayList<Node>(); for (Way way:ways) { waynodes.addAll(way.getNodes()); } return waynodes; } /** * Tests if node is first or last node of any way in storage * <p> * This method currently does a sequential scan of all ways in storage and should be avoided * * @param node node to check * @return true if node is the first or last node of at least one way */ public boolean isEndNode(final Node node) { for (Way way:ways) { if (way.isEndNode(node)) { return true; } } return false; } /** * Calculate a bounding box just covering the data * @return a bounding box * @throws OsmException if no valid BoundingBox could be created */ public BoundingBox calcBoundingBoxFromData() throws OsmException { int top = 0; int bottom = 0; int left = 0; int right = 0; if (nodes != null) { for (Node n:nodes) { if (n.getLat() > top) top = n.getLat(); else if (n.getLat() < bottom) bottom = n.getLat(); if (n.getLon() > right) right = n.getLon(); else if (n.getLon() < left) left = n.getLon(); } } return new BoundingBox(left, bottom, right, top); } /** * Get the node map * @return the map indexing nodes */ public LongOsmElementMap<Node> getNodeIndex() { return nodes; } /** * Get the way map * @return the map indexing ways */ public LongOsmElementMap<Way> getWayIndex() { return ways; } /** * Get the relation map * @return the map indexing relations */ public LongOsmElementMap<Relation> getRelationIndex() { return relations; } /** * Rehash the maps used for storing elements. * <p> * This is required since elements will change their id when being saved to the OSM database the first time. */ public void rehash() { nodes.rehash(); ways.rehash(); relations.rehash(); } /** * Log the contents */ public void logStorage() { // for (Node n:nodes) { Log.d("Storage","Node " + n.getOsmId()); for (String k:n.getTags().keySet()) { Log.d("Storage",k + "=" + n.getTags().get(k)); } } for (Way w:ways) { Log.d("Storage","Way " + w.getOsmId()); for (String k:w.getTags().keySet()) { Log.d("Storage",k + "=" + w.getTags().get(k)); } } for (Relation r:relations) { Log.d("Storage","Relation " + r.getOsmId()); for (String k:r.getTags().keySet()) { Log.d("Storage",k + "=" + r.getTags().get(k)); } } } }