public class OsmXmlMember {

	@XmlAttribute
	public long ref;

	@XmlAttribute
	public String role;

	@XmlAttribute
	public String type;

}
 public class OsmXmlNd {

	@XmlAttribute
	public long ref;

}
 public class OsmXmlNode {

	@XmlAttribute
	public Long id;

	@XmlAttribute
	public int version;

	@XmlAttribute
	public Long changeset;

	@XmlAttribute
	public String user;

	@XmlAttribute
	public String visible;

	@XmlAttribute
	public Integer uid;

	@XmlAttribute
	public String timestamp;

	@XmlAttribute
	public String action;

	// Specific attributes

	@XmlAttribute
	public String lat;

	@XmlAttribute
	public String lon;

	@XmlElement(name = "tag")
	public List<OsmXmlTag> tags = new ArrayList<>();;

}
 public class OsmXmlRelation {

	@XmlAttribute
	public Long id;

	@XmlAttribute
	public int version;

	@XmlAttribute
	public long changeset;

	@XmlAttribute
	public String user;

	@XmlAttribute
	public String visible;

	@XmlAttribute
	public int uid;

	@XmlAttribute
	public String timestamp;

	@XmlAttribute
	public String action;

	// Specific attributes

	@XmlElement(name = "tag")
	public List<OsmXmlTag> tags = new ArrayList<>();

	@XmlElement(name = "members")
	public List<OsmXmlMember> members = new ArrayList<>();

}
 @XmlRootElement(name = "osm")
public class OsmXmlRoot {

	@XmlAttribute
	public float version;

	@XmlAttribute
	public String generator;

	@XmlElement(name = "node")
	public List<OsmXmlNode> nodes = new ArrayList<>();;

	@XmlElement(name = "way")
	public List<OsmXmlWay> ways = new ArrayList<>();;

	@XmlElement(name = "relations")
	public List<OsmXmlRelation> relations = new ArrayList<>();

}
 public class OsmXmlTag {

	@XmlAttribute
	public String v;

	@XmlAttribute
	public String k;
	
	public OsmXmlTag() {
	}
	
	public OsmXmlTag(String key, String value) {
		this.k = key;
		this.v = value;
	}
}
 public class OsmXmlWay {

	@XmlAttribute
	public Long id;

	@XmlAttribute
	public int version;

	@XmlAttribute
	public long changeset;

	@XmlAttribute
	public String user;

	@XmlAttribute
	public String visible;

	@XmlAttribute
	public int uid;

	@XmlAttribute
	public String timestamp;

	@XmlAttribute
	public String action;

	// Specific attributes

	@XmlElement(name = "nd")
	public List<OsmXmlNd> nds = new ArrayList<>();;

	@XmlElement(name = "tag")
	public List<OsmXmlTag> tags = new ArrayList<>();;
}
 public abstract class AbstractImport extends MatchableObject {

	protected long id;

	protected String name;

	protected Double latitude;

	protected Double longitude;

	protected AbstractElement matchingElement;

	abstract public String getValueByTagName(String tagName);

	@Override
	public String toString() {
		return "Element with id=[" + this.id + "] and name=[" + this.name + "]";
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Double getLatitude() {
		return latitude;
	}

	public void setLatitude(Double latitude) {
		this.latitude = latitude;
	}

	public Double getLongitude() {
		return longitude;
	}

	public void setLongitude(Double longitude) {
		this.longitude = longitude;
	}

	public AbstractElement getMatchingElement() {
		return matchingElement;
	}

	public void setMatchingElement(AbstractElement matchingElement) {
		this.matchingElement = matchingElement;
	}

}
 public class BuildingElement extends AbstractElement {

	private Integer computedArea;

	private String innerGeometryString; // could be used by building which has a "hole" (multipolygon)

	public BuildingElement(long osmId) {
		super(osmId);
	}

	@Override
	public ElementType getType() {
		return ElementType.Way;
	}

	@Override
	public void updateChangeset(long changesetId) {
		this.getApiData().ways.get(0).changeset = changesetId;
	}

	@Override
	public List<OsmXmlTag> getTags() {
		if (this.getApiData() == null || this.getApiData().ways == null || this.getApiData().ways.get(0) == null) {
			return new ArrayList<>();
		}
		return this.getApiData().ways.get(0).tags;
	}

	@Override
	public void setTags(List<OsmXmlTag> tags) {
		this.getApiData().ways.get(0).tags = tags;
	}

	@Override
	public String toString() {
		return "OSM building has id=[" + this.getOsmId() + "], levels=[" + this.getLevels() + "], height=[" + this.getHeight() + "], name=["
				+ this.getName() + "], part=[" + this.isPart() + "]";
	}

	public boolean isPart() {
		return "yes".equals(this.getTagValue(ElementTag.BUILDING_PART));
	}

	public Integer getLevels() {
		try {
			String s = (String) this.getTagValue(ElementTag.BUILDING_LEVELS);
			return (s != null ? Integer.parseInt(s) : null);
		} catch (Exception e) {
			LOGGER.warn("Unable to get levels for building import " + this.getOsmId() + " (" + e.getMessage() + ")");
			return null;
		}
	}

	public boolean setLevels(Integer value) {
		return this.setTagValue(ElementTag.BUILDING_LEVELS, value.toString());
	}

	public Integer getComputedArea() {
		return computedArea;
	}

	public void setComputedArea(int computedArea) {
		this.computedArea = computedArea;
	}

	public String getInnerGeometryString() {
		return innerGeometryString;
	}

	public void setInnerGeometryString(String innerGeometryString) {
		this.innerGeometryString = innerGeometryString;
	}

	public static List<Long> getOuterOrInnerMemberIds(long relationId, String membersString, boolean outer) {
		List<Long> results = new ArrayList<>();
		membersString = membersString.substring(1, membersString.length() - 1);
		String[] members = membersString.split(",");
		for (int i = 0; i < members.length; i++) {
			if ((outer ? "outer" : "inner").equals(members[i]) && members[i - 1].startsWith("w")) {
				results.add(Long.parseLong(members[i - 1].substring(1)));
			}
		}
		return results;
	}
}
 public class BuildingImport extends AbstractImport {

	protected Integer levels;

	protected Float height;

	protected String url;

	protected Integer area;

	private String geometryAsWKT;

	protected String geometryRawString;

	protected List<Point> points = new ArrayList<>();

	protected List<Coordinates> coordinates = new ArrayList<>(); // keep coordinates as strings (no more rounding issues)

	@Override
	public String getValueByTagName(String tagName) {
		if (ElementTag.BUILDING_LEVELS.equals(tagName)) {
			return this.levels.toString();
		} else if (ElementTag.HEIGHT.equals(tagName)) {
			return this.height.toString();
		} else if (ElementTag.URL.equals(tagName)) {
			return this.url;
		} else if (ElementTag.NAME.equals(tagName)) {
			return this.name;
		}
		return null;
	}

	@Override
	public String toString() {
		return "Building import with id=[" + this.id + "] and name=[" + this.name + "], lat=[" + this.latitude + "], lon=[" + this.longitude
				+ "], levels=[" + this.levels + "], height=[" + this.height + "], area=[" + this.area + "], url=[" + this.url + "]";
	}

	public Integer getLevels() {
		return levels;
	}

	public void setLevels(Integer levels) {
		this.levels = levels;
	}

	public Float getHeight() {
		return height;
	}

	public void setHeight(Float height) {
		this.height = height;
	}

	public Integer getArea() {
		return area;
	}

	public void setArea(Integer area) {
		this.area = area;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getGeometryRawString() {
		return geometryRawString;
	}

	public void setGeometryRawString(String geometryString) {
		this.geometryRawString = geometryString;
	}

	public String getGeometryAsWKT() {
		return geometryAsWKT;
	}

	public void setGeometryAsWKT(String geometryWKT) {
		this.geometryAsWKT = geometryWKT;
	}

	public List<Point> getPoints() {
		return points;
	}

	public void setPoints(List<Point> points) {
		this.points = points;
	}

	public List<Coordinates> getCoordinates() {
		return coordinates;
	}

	public void setCoordinates(List<Coordinates> coordinates) {
		this.coordinates = coordinates;
	}

} public class ElementTag {

	// ==========================================
	// Common tags
	// ==========================================

	public static final String NAME = "name";

	public static final String SOURCE = "source";
	
	public static final String REF = "ref";
	
	public static final String URL = "url";
	
	public static final String WEBSITE = "website";

	public static final String HEIGHT = "height";
	
	public static final String START_DATE = "start_date";

	// ==========================================
	// Building tags
	// ==========================================

	public static final String BUILDING_LEVELS = "building:levels";

	public static final String BUILDING_PART = "building:part";

	// ==========================================
	// Vegetation tags
	// ==========================================

	public static final String NATURAL = "natural";

	public static final String GENUS = "genus";

	public static final String SPECIES = "species";

	public static final String CIRCUMFERENCE = "circumference";

}
 public enum ElementType {

	Node("node"),

	Way("way"),

	Relation("relation");

	private String name;

	private ElementType(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}
}
 public class ElementIdWithParentFlags {

	Long osmId;

	ElementType type;

	List<Parent> parents = new ArrayList<>();

	public class Parent {

		private Long osmId;

		private Boolean flag;

		public Long getOsmId() {
			return osmId;
		}

		public void setOsmId(Long osmId) {
			this.osmId = osmId;
		}

		public Boolean getFlag() {
			return flag;
		}

		public void setFlag(Boolean flag) {
			this.flag = flag;
		}
	}

	public Long getOsmId() {
		return osmId;
	}

	public void setOsmId(Long osmId) {
		this.osmId = osmId;
	}

	public List<Parent> getParents() {
		return parents;
	}

	public void setParents(List<Parent> parents) {
		this.parents = parents;
	}

	public ElementType getType() {
		return type;
	}

	public void setType(ElementType type) {
		this.type = type;
	}

}

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlMember;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlNd;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlNode;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlRelation;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlRoot;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlTag;
import org.openstreetmap.osmaxil.dao.xml.osm.OsmXmlWay;
import org.openstreetmap.osmaxil.model.AbstractImport;
import org.openstreetmap.osmaxil.model.BuildingElement;
import org.openstreetmap.osmaxil.model.BuildingImport;
import org.openstreetmap.osmaxil.model.ElementTag;
import org.openstreetmap.osmaxil.model.ElementType;
import org.openstreetmap.osmaxil.model.misc.ElementIdWithParentFlags;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.vividsolutions.jts.geom.Point;

package org.openstreetmap.osmaxil.flow;




@Component("BuildingRemaker")
@Lazy
public class BuildingRemakerFlow extends AbstractRemakerFlow<BuildingElement, BuildingImport> {

	// =========================================================================
	// Instance variables
	// =========================================================================

	private Map<Long, OsmXmlRoot> newBuildingsByRemakableBuilding = new HashMap<>();

	private List<ElementIdWithParentFlags> oldNodesToDelete = new ArrayList<>();

	private Map<String, OsmXmlNode> newNodesByCoordinates = new HashMap<>();

	// =========================================================================
	// Overrided methods
	// =========================================================================

	@Override
	protected BuildingElement instanciateElement(long osmId) {
		return new BuildingElement(osmId);
	}

	@Override
	protected boolean isElementRemakable(BuildingElement element) {
		// TODO Check if element has only common tags for the Cadastre import or if it's complex structure ?
		return true;
	}

	@Override
	protected void processElement(BuildingElement element) {
		LOGGER.debug("Building XML for remaking of element #" + element.getOsmId() + ":");
		this.newBuildingsByRemakableBuilding.put(element.getOsmId(), this.buildNewBuildings(element));
		this.oldNodesToDelete.addAll(this.buildNodesToDelete(element));
	}

	@Override
	protected void buildDataForCreation() {
		OsmXmlRoot root = new OsmXmlRoot();
		// Merge all new buildings (with their new nodes)
		for (Long id : this.newBuildingsByRemakableBuilding.keySet()) {
			OsmXmlRoot data = this.newBuildingsByRemakableBuilding.get(id);
			root.nodes.addAll(data.nodes);
			root.ways.addAll(data.ways);
			root.relations.addAll(data.relations);
		}
		this.dataForCreation = root;
	}

	@Override
	protected void buildDataForDeletion() {
		OsmXmlRoot root = new OsmXmlRoot();
		// Merge deletions of all remakable buildings
		for (BuildingElement element : this.remakableElements.values()) {
			OsmXmlWay way = new OsmXmlWay();
			way.id = element.getOsmId();
			way.action = "delete";
			way.version = element.getApiData().ways.get(0).version;
			way.changeset = element.getApiData().ways.get(0).changeset;
			root.ways.add(way);
		}
		// Merge deletions of nodes of all remakable buildings
		for (ElementIdWithParentFlags e : this.oldNodesToDelete) {
			// TODO Check if they can be deleted
			if (true) {
				OsmXmlNode node = new OsmXmlNode();
				node.id = e.getOsmId();
				node.action = "delete";
				root.nodes.add(node);
			}
		}
		this.dataForDeletion = root;
	}

	@Override
	public void displayProcessingStatistics() {
		super.displayProcessingStatistics();
		LOGGER_FOR_STATS.info("Remaking data has been prepared as follow:");
		LOGGER_FOR_STATS.info("\tRemakable buildings: " + this.remakableElements.values().size() + "");
		LOGGER_FOR_STATS.info("\tNew buildings: " + this.newBuildingsByRemakableBuilding.size() + "");
		LOGGER_FOR_STATS.info("\tNew nodes: " + this.newNodesByCoordinates.size() + "");
	}

	// =========================================================================
	// Private methods
	// =========================================================================

	private List<ElementIdWithParentFlags> buildNodesToDelete(BuildingElement element) {
		ArrayList<ElementIdWithParentFlags> result = new ArrayList<>();
		for (OsmXmlNd nd : element.getApiData().ways.get(0).nds) {
			ElementIdWithParentFlags node = new ElementIdWithParentFlags();
			node.setOsmId(nd.ref);
			node.setType(ElementType.Node);
			// TODO use OverPass API to request all ways referencing current point ?
			List<Long> relatedWayIds = new ArrayList<>();
			for (Long relatedWayId : relatedWayIds) {
				ElementIdWithParentFlags.Parent parent = node.new Parent();
				parent.setOsmId(relatedWayId);
				parent.setFlag(false);
				node.getParents().add(parent);
			}
			result.add(node);
		}
		return result;
	}

	private OsmXmlRoot buildNewBuildings(BuildingElement element) {
		OsmXmlRoot root = new OsmXmlRoot();
		// Create the relation

		OsmXmlRelation relation = new OsmXmlRelation();
