// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.czechaddress.addressdatabase;
import static org.openstreetmap.josm.plugins.czechaddress.proposal.ProposalFactory.getListFieldDiff;
import static org.openstreetmap.josm.plugins.czechaddress.proposal.ProposalFactory.getStringFieldDiff;
import java.util.List;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.plugins.czechaddress.NotNullList;
import org.openstreetmap.josm.plugins.czechaddress.PrimUtils;
import org.openstreetmap.josm.plugins.czechaddress.proposal.Proposal;
/**
* Represents a single house.
*
* <p> Every house contains a number within the street
* (číslo orientační) or the number within the suburb (číslo popisné).
* Both numbers are stored as string to allow house numbers like '1a'.</p>
*
* <p><b>WARNING:</b> Every house must have a parent, whose type is
* {@link ElementWithHouses}. The {@code setParent()} method should
* be called immediatelly after the constructor.</p>
*
* @author Radomír Černoch, radomir.cernoch@gmail.com
*/
public class House extends AddressElement {
/** Stores the number unique in a suburb (číslo popisné). */
protected String cp = null;
/** Stores the number unique in a street (číslo orientační). */
protected String co = null;
/**
* Default constructor setting the numbers of this House.
*
* @param cp number unique in a suburb (číslo popisné)
* @param co number unique in a street (číslo orientační)
*/
public House(String cp, String co) {
// Firstly we pretend that this element has no name...
super("");
if (cp != null) this.cp = cp.toLowerCase();
if (co != null) this.co = co.toLowerCase();
assert (co != null) || (cp != null);
//... but the name is overwritten.
this.name = generateName(this.cp, this.co);
}
/**
* Returns the number unique in a suburb (číslo popisné)
*/
public String getCP() {
return cp;
}
/**
* Returns the number unique in a street (číslo orientační)
*/
public String getCO() {
return co;
}
/**
* Returns the number unique in a suburb (číslo popisné)
*/
public void setCP(String cp) {
this.cp = cp;
this.name = generateName(this.cp, this.co);
}
/**
* Returns the number unique in a street (číslo orientační)
*/
public void setCO(String co) {
this.co = co;
this.name = generateName(this.cp, this.co);
}
/**
* Generates a name from house numbers.
*
* <p><b>WARNING:</b> Only one number can be {@link null}.</p>
*
* <p>If only one number is given, it is returned directly. If both numbers
* are given, they are combined in the way, which is standard in CZ.</p>
*
* @param cp number unique in a suburb (číslo popisné)
* @param co number unique in a street (číslo orientační)
* @return string representation of a house with given house numbers.
*/
public static String generateName(String cp, String co) {
if (co == null)
return cp;
else {
if (cp == null)
return "?/"+co;
else
return cp+"/"+co;
}
}
/**
* Returns the full name of this house (including street/suburb).
*
* <p><b>WARNING:</b> Every house must have a parent, whose type is
* {@link ElementWithHouses}. The {@code setParent()} method should
* be called immediatelly after the constructor.</p>
*/
@Override
public String getName() {
assert parent != null : "A house must always have a parent.";
return parent.getName() + " " + name;
}
/**
* Sets the parent of this house with type-checking.
*
* <p><b>WARNING:</b> Every house must have a parent, whose type is
* {@link ElementWithHouses}. The {@code setParent()} method should
* be called immediatelly after the constructor.</p>
*/
@Override
public void setParent(AddressElement parent) {
assert parent instanceof ElementWithHouses;
super.setParent(parent);
}
/**
* Returns the parent of this house with type-checking.
*
* <p><b>WARNING:</b> Every house must have a parent, whose type is
* {@link ElementWithHouses}. The {@code setParent()} method should
* be called immediatelly after the constructor.</p>
*/
@Override
public ElementWithHouses getParent() {
assert parent instanceof ElementWithHouses;
return (ElementWithHouses) parent;
}
/**
* Returns an array describing how the house fits to given primitive.
*
* <p>Values of the array are described in
* {@link AddressElement}{@code .getFieldMatchList()}.</p>
*
* <p>First element of the returned array corresponds to the CP
* (číslo popisné), the second one to the combination of Street+CO.</p>
*/
@Override
protected int[] getFieldMatchList(OsmPrimitive prim) {
int[] result = {0, 0, 0};
if (!isMatchable(prim)) return result;
// First field is the AlternateNubmer
result[0] = matchField(this.cp, prim.get(PrimUtils.KEY_ADDR_CP));
result[2] = matchField(name, prim.get(PrimUtils.KEY_ADDR_HOUSE_N));
// Second field is the Housenumber
if (parent instanceof Street)
result[1] = Math.min(
matchFieldAbbrev(parent.getName(), prim.get(PrimUtils.KEY_ADDR_STREET)),
matchField(this.co, prim.get(PrimUtils.KEY_ADDR_CO)));
return result;
}
@Override
protected int[] getAdditionalFieldMatchList(OsmPrimitive prim) {
int[] result = {0};
if (!isMatchable(prim)) return result;
ParentResolver resolver = new ParentResolver(this);
result[0] = matchField(resolver.getIsIn(), prim.get(PrimUtils.KEY_IS_IN));
return result;
}
/**
* Gives all proposals to make the primitive be an address primitive.
*
* <p>Tries to fill {@code prim}'s attributes to have all attributes that
* this House has.</p>
*/
@Override
public List<Proposal> getDiff(OsmPrimitive prim) {
List<Proposal> props = new NotNullList<>();
ParentResolver resolver = new ParentResolver(this);
props.add(getStringFieldDiff(PrimUtils.KEY_ADDR_HOUSE_N, prim.get(PrimUtils.KEY_ADDR_HOUSE_N), name));
props.add(getStringFieldDiff(PrimUtils.KEY_ADDR_CP, prim.get(PrimUtils.KEY_ADDR_CP), getCP()));
props.add(getStringFieldDiff(PrimUtils.KEY_ADDR_CO, prim.get(PrimUtils.KEY_ADDR_CO), getCO()));
props.add(getStringFieldDiff(PrimUtils.KEY_ADDR_COUNTRY,
prim.get(PrimUtils.KEY_ADDR_COUNTRY), "CZ"));
if (resolver.parentStreet != null)
props.add(getStringFieldDiff(PrimUtils.KEY_ADDR_STREET,
prim.get(PrimUtils.KEY_ADDR_STREET),
resolver.parentStreet.getName()));
AddressElement isInElem = parent;
if (isInElem instanceof Street) isInElem = parent.parent;
if (isInElem != null) // For sure our parent is a ElemWithStreets
props.add(getStringFieldDiff(PrimUtils.KEY_IS_IN,
prim.get(PrimUtils.KEY_IS_IN),
resolver.getIsIn()));
// If we have added any proposal so far, add the source info as well.
if (props.size() > 0)
props.add(getListFieldDiff("source:addr", prim.get("source:addr"), "mvcr:adresa"));
return props;
}
/**
* Determies whether the given primitive can be matched to a House.
*/
public static boolean isMatchable(OsmPrimitive prim) {
for (String key : prim.keySet()) {
if (key.startsWith("addr:"))
return true;
}
return false;
}
@Override
public int compareTo(AddressElement o) {
// Most important criterion is the street
int val = super.compareTo(o);
if (val != 0) return val;
if (!(o instanceof House)) return val;
House house = (House) o;
// Second most important is the "CO"
if (co != null && house.co != null)
val = co.compareTo(house.co);
if (val != 0) return val;
// Third most important is the "CP"
if (cp != null && house.cp != null)
val = cp.compareTo(house.cp);
return val;
}
}