package de.blau.android.osm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.xmlpull.v1.XmlSerializer;
import android.content.Context;
import android.util.Log;
import de.blau.android.App;
import de.blau.android.R;
import de.blau.android.presets.Preset;
import de.blau.android.presets.Preset.PresetItem;
import de.blau.android.util.rtree.BoundedObject;
/**
* Relation represents an OSM relation element which essentially is a collection of other OSM elements.
*
* @author simon
*
*/
public class Relation extends OsmElement implements BoundedObject {
/**
*
*/
private static final long serialVersionUID = 1104911642016294265L;
final ArrayList<RelationMember> members;
public static final String NAME = "relation";
public static final String MEMBER = "member";
Relation(final long osmId, final long osmVersion, final byte status) {
super(osmId, osmVersion, status);
members = new ArrayList<RelationMember>();
}
void addMember(final RelationMember member) {
members.add(member);
}
/**
* Return complete list of relation members
* @return list of members, or null if there are none
*/
public List<RelationMember> getMembers() {
return members;
}
/**
* Return first relation member element for this OSM element
* Note: if the element is present more than once you will only get one
* @param e
* @return
*/
public RelationMember getMember(OsmElement e) {
for (int i = 0; i < members.size(); i++) {
RelationMember member = members.get(i);
if (member.getElement() == e) {
return member;
}
}
return null;
}
/**
* Return all relation member elements for this OSM element
* @param e
* @return
*/
public List<RelationMember> getAllMembers(OsmElement e) {
ArrayList<RelationMember> result = new ArrayList<RelationMember>();
for (int i = 0; i < members.size(); i++) {
RelationMember member = members.get(i);
if (member.getElement() == e) {
result.add(member);
}
}
return result;
}
/**
* Return first relation member element for this OSM element
* Note: if the element is present more than once you will only get ont
* @param type
* @param id
* @return
*/
public RelationMember getMember(String type, long id) {
for (int i = 0; i < members.size(); i++) {
RelationMember member = members.get(i);
if (member.getRef() == id && member.getType().equals(type)) {
return member;
}
}
return null;
}
public int getPosition(RelationMember e) {
return members.indexOf(e);
}
/**
*
* @return list of members allowing {@link Iterator#remove()}.
*/
Iterator<RelationMember> getRemovableMembers() {
return members.iterator();
}
@Override
public String getName() {
return NAME;
}
@Override
public String toString() {
// String res = super.toString();
// for (Map.Entry<String, String> tag : tags.entrySet()) {
// res += "\t" + tag.getKey() + "=" + tag.getValue();
// }
// for (RelationMember m:members) {
// res += "\t" + m.toString();
// }
// return res;
return getDescription();
}
@Override
public void toXml(final XmlSerializer s, final Long changeSetId) throws IllegalArgumentException,
IllegalStateException, IOException {
s.startTag("", "relation");
s.attribute("", "id", Long.toString(osmId));
if (changeSetId != null) s.attribute("", "changeset", Long.toString(changeSetId));
s.attribute("", "version", Long.toString(osmVersion));
for (RelationMember member : members) {
s.startTag("", "member");
s.attribute("", "type", member.getType());
s.attribute("", "ref", Long.toString(member.getRef()));
s.attribute("", "role", member.getRole());
s.endTag("", "member");
}
tagsToXml(s);
s.endTag("", "relation");
}
@Override
public void toJosmXml(final XmlSerializer s) throws IllegalArgumentException,
IllegalStateException, IOException {
s.startTag("", "relation");
s.attribute("", "id", Long.toString(osmId));
if (state == OsmElement.STATE_DELETED) {
s.attribute("", "action", "delete");
} else if (state == OsmElement.STATE_CREATED || state == OsmElement.STATE_MODIFIED) {
s.attribute("", "action", "modify");
}
s.attribute("", "version", Long.toString(osmVersion));
s.attribute("", "visible", "true");
for (RelationMember member : members) {
s.startTag("", "member");
s.attribute("", "type", member.getType());
s.attribute("", "ref", Long.toString(member.getRef()));
s.attribute("", "role", member.getRole());
s.endTag("", "member");
}
tagsToXml(s);
s.endTag("", "relation");
}
public boolean hasMember(final RelationMember member) {
return members.contains(member);
}
/**
* Completely remove member from relation (even if present more than once)
* Does not update backlink
* @param member
*/
void removeMember(final RelationMember member) {
while (members.remove(member)) {
}
}
protected void appendMember(final RelationMember refMember, final RelationMember newMember) {
if (members != null && members.size() > 0 && members.get(0) == refMember) {
members.add(0, newMember);
} else if (members != null && members.get(members.size() - 1) == refMember) {
members.add(newMember);
}
}
void addMemberAfter(final RelationMember memberBefore, final RelationMember newMember) {
members.add(members.indexOf(memberBefore) + 1, newMember);
}
void addMember(int pos, final RelationMember newMember) {
if (pos < 0 || pos > members.size()) {
pos = members.size(); // append
}
members.add(pos, newMember);
}
/**
* Adds multiple elements to the relation in the order in which they appear in the list.
* They can be either prepended or appended to the existing nodes.
* @param newMembers a list of new members
* @param atBeginning if true, nodes are prepended, otherwise, they are appended
*/
protected void addMembers(List<RelationMember> newMembers, boolean atBeginning) {
if (atBeginning) {
members.addAll(0, newMembers);
} else {
members.addAll(newMembers);
}
}
public ArrayList <RelationMember> getMembersWithRole(String role) {
ArrayList <RelationMember> rl = new ArrayList<RelationMember>();
for (RelationMember rm : members) {
Log.d("Relation", "getMembersWithRole " + rm.getRole());
if (role.equals(rm.getRole())) {
rl.add(rm);
}
}
return rl;
}
/**
* Replace an existing member in a relation with a different member.
* @param existing The existing member to be replaced.
* @param newMember The new member.
*/
void replaceMember(RelationMember existing, RelationMember newMember) {
int idx;
while ((idx = members.indexOf(existing)) != -1) {
members.set(idx, newMember);
}
}
/**
* Replace all existing members in a relation.
* @param existing The existing member to be replaced.
* @param newMember The new member.
*/
void replaceMembers(Collection<RelationMember> newMembers) {
members.clear();
members.addAll(newMembers);
}
/**
* rough implementation for now
*/
@Override
public String getDescription() {
return getDescription(null);
}
@Override
public String getDescription(Context ctx) {
String description = "";
PresetItem p = null;
if (ctx != null) {
p = Preset.findBestMatch(App.getCurrentPresets(ctx),tags);
}
if (p!=null) {
description = p.getTranslatedName();
} else {
String type = getTagWithKey(Tags.KEY_TYPE);
if (type != null && !type.equals("")){
description = type;
if (type.equals(Tags.VALUE_RESTRICTION)) {
String restriction = getTagWithKey(Tags.VALUE_RESTRICTION);
if (restriction != null) {
description = restriction + " " + description;
}
} else if (type.equals(Tags.VALUE_ROUTE)) {
String route = getTagWithKey(Tags.VALUE_ROUTE);
if (route != null) {
description = route + " " + description ;
}
} else if (type.equals(Tags.VALUE_MULTIPOLYGON)) {
String b = getTagWithKey(Tags.KEY_BOUNDARY);
if (b != null) {
description = b + " " + Tags.KEY_BOUNDARY + " " + description ;
} else {
String l = getTagWithKey(Tags.KEY_LANDUSE);
if (l != null) {
description = Tags.KEY_LANDUSE + " " + l + " " + description ;
} else {
String n = getTagWithKey(Tags.KEY_NATURAL);
if (n != null) {
description = Tags.KEY_NATURAL + " " + n + " " + description ;
}
}
}
}
} else {
if (ctx == null) {
description = "unset relation type"; // fallback so that we have something to display
} else {
description = ctx.getResources().getString(R.string.unset_relation_type);
}
}
}
String name = getTagWithKey(Tags.KEY_NAME);
if (name != null) {
description = description + " " + name;
} else {
description = description + " #" + osmId;
}
return description;
}
/**
* Test if the relation has a problem.
* @return true if the relation has a problem, false if it doesn't.
*/
@Override
protected boolean calcProblem() {
String type = getTagWithKey(Tags.KEY_TYPE);
return type == null || type.equals("") || super.calcProblem();
}
@Override
public String describeProblem() {
String superProblem = super.describeProblem();
String relationProblem = "";
String type = getTagWithKey(Tags.KEY_TYPE);
if (type==null || type.equals("")) {
relationProblem = App.resources().getString(R.string.toast_notype);
}
if (!superProblem.equals(""))
return superProblem + (!relationProblem.equals("") ? "\n" + relationProblem : "");
else
return relationProblem;
}
@Override
public ElementType getType() {
return getType(tags);
}
@Override
public ElementType getType(Map<String,String> tags) {
if (hasTag(tags, Tags.KEY_TYPE,Tags.VALUE_MULTIPOLYGON) || hasTag(tags, Tags.KEY_TYPE,Tags.VALUE_BOUNDARY)) {
return ElementType.AREA;
}
return ElementType.RELATION;
}
/**
* return a list of the downloaded elements
* @return
*/
public ArrayList<OsmElement> getMemberElements() {
ArrayList<OsmElement> result = new ArrayList<OsmElement>();
for (RelationMember rm:getMembers()) {
if (rm.getElement()!=null)
result.add(rm.getElement());
}
return result;
}
@Override
public BoundingBox getBounds() {
// NOTE this will only return a bb covering the downloaded elements
BoundingBox result = null;
for (RelationMember rm:members) {
OsmElement e = rm.getElement();
if (e != null) {
if (result == null) {
result = e.getBounds();
} else {
result.union(e.getBounds());
}
}
}
return result;
}
}