/** * Created by Nicholas Hallahan on 12/24/14. * nhallahan@spatialdev.com */ package com.spatialdev.osm.model; import com.mapbox.mapboxsdk.views.MapView; import com.spatialdev.osm.renderer.OSMPath; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; public class OSMWay extends OSMElement { /** * Used to keep track of the IDs of modified ways. This can be used * to check to see if we are already using a modified way. * * * * */ private static Set<Long> modifiedWayIdSet = new HashSet<>(); /** * As the XML document is being parsed, ways have references to nodes' IDs. * The node itself may not yet be parsed, so we create a list of Node IDs * as we parse and will then do postprocessing to create that association. */ private LinkedList<Long> nodeRefs = new LinkedList<>(); private LinkedList<OSMNode> linkedNodes = new LinkedList<>(); /** * If a way is in a relation, it's relation is added to this list. */ private LinkedList<OSMRelation> linkedRelations = new LinkedList<>(); /** * isClosed checks to see if this way is closed and sets it to true if so. */ private boolean closed = false; /** * If we do not have all of the nodes linked to the refs, the way is incomplete * and cannot be rendered. * * @return - if way is incomplete */ public boolean incomplete() { return nodeRefs.size() > 0; } public static boolean containsModifiedWay(long wayId) { return modifiedWayIdSet.contains(wayId); } public OSMWay(String idStr, String versionStr, String timestampStr, String changesetStr, String uidStr, String userStr, String action) { super(idStr, versionStr, timestampStr, changesetStr, uidStr, userStr, action); } @Override public String checksum() { String str = preChecksum(); return new String(Hex.encodeHex(DigestUtils.sha1(str))); } public String preChecksum() { StringBuilder str = tagsAsSortedKVString(); for (OSMNode n : linkedNodes) { str.append(n.checksum()); } return str.toString(); } @Override void xml(XmlSerializer xmlSerializer, String omkOsmUser) throws IOException { for (OSMNode node : linkedNodes) { node.xml(xmlSerializer, omkOsmUser); } xmlSerializer.startTag(null, "way"); setOsmElementXmlAttributes(xmlSerializer, omkOsmUser); // generate nds setWayXmlNds(xmlSerializer); // generate tags super.xml(xmlSerializer, omkOsmUser); xmlSerializer.endTag(null, "way"); for (OSMRelation relation : linkedRelations) { relation.xml(xmlSerializer, omkOsmUser); } } private void setWayXmlNds(XmlSerializer xmlSerializer) throws IOException { for (OSMNode node : linkedNodes) { xmlSerializer.startTag(null, "nd"); xmlSerializer.attribute(null, "ref", String.valueOf(node.getId())); xmlSerializer.endTag(null, "nd"); } } public void addNodeRef(long id) { nodeRefs.add(id); } /** * Populates linked list of nodes referred to by this way. * * Takes nodes from nodes hash and puts them in the wayNodes hash * for nodes that are in the actual way. * * @param nodes * @return the number of node references NOT linked. */ int linkNodes(Map<Long, OSMNode> nodes, Set<Long> wayNodes) { // first check if the way is closed before doing this processing... checkIfClosed(); LinkedList<Long> unlinkedRefs = new LinkedList<>(); for (Long refId : nodeRefs) { OSMNode node = nodes.get(refId); wayNodes.add(refId); if (node == null) { unlinkedRefs.add(refId); } else { linkedNodes.add(node); } } nodeRefs = unlinkedRefs; return nodeRefs.size(); } public int getUnlinkedNodesCount() { return nodeRefs.size(); } public int getLinkedNodesCount() { return linkedNodes.size(); } private void checkIfClosed() { Long firstId = nodeRefs.getFirst(); Long lastId = nodeRefs.getLast(); if (firstId.equals(lastId)) { closed = true; } } /** * If the starting node is the same as ending node, this way * is closed. * * WARNING: This will be correct only AFTER linkNodes has been run. * * @return closed */ public boolean isClosed() { if (closed) { return true; } return false; } /** * This allows you to iterate through the nodes. This is great if you * want to give a renderer all of the lat longs to paint a line... */ public Iterator<OSMNode> getNodeIterator() { return linkedNodes.listIterator(); } public List<OSMNode> getNodes() { return linkedNodes; } /** * If this is in a relation, it's parent relation is added to an internal linked list. * @param relation */ public void addRelation(OSMRelation relation) { linkedRelations.push(relation); } public List<OSMRelation> getRelations() { return linkedRelations; } public OSMPath getOSMPath(MapView mv) { // if there is no overlay, make it for this element if (osmPath == null) { osmPath = OSMPath.createOSMPath(this, mv); } // Sometimes the app exists or gets a new MapView, and we need to make // sure things get drawn on the actual active map view currently on the screen. else { osmPath.setMapView(mv); } return osmPath; } @Override public void select() { super.select(); if (osmPath != null) { osmPath.select(); } } @Override public void deselect() { super.deselect(); if (osmPath != null) { osmPath.deselect(); } } @Override protected void setAsModified() { super.setAsModified(); modifiedWayIdSet.add(id); } }