/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.map.writer.model;
import gnu.trove.set.TShortSet;
import gnu.trove.set.hash.TShortHashSet;
import java.util.Arrays;
import java.util.logging.Logger;
import org.mapsforge.map.writer.OSMTagMapping;
import org.mapsforge.map.writer.util.GeoUtils;
import org.mapsforge.map.writer.util.OSMUtils;
import org.openstreetmap.osmosis.core.domain.v0_6.Way;
import org.openstreetmap.osmosis.core.domain.v0_6.WayNode;
/**
* Represents an OSM way.
*
* @author bross
*/
public class TDWay {
private static final Logger LOGGER = Logger.getLogger(TDWay.class.getName());
// TODO these constants are not necessary anymore
/**
* Represents a line.
*/
public static final byte LINE = 0x0;
/**
* A simple closed polygon.
*/
public static final byte SIMPLE_POLYGON = 0x1;
/**
* A simple closed polygon with holes.
*/
public static final byte MULTI_POLYGON = 0x2;
private final long id;
private final byte layer;
private String name;
private String ref;
private final String houseNumber;
private short[] tags;
private byte shape;
private final TDNode[] wayNodes;
private boolean reversedInRelation;
private boolean invalid;
/**
* Creates a new TDWay from an osmosis way entity using the given NodeResolver.
*
* @param way
* the way
* @param resolver
* the resolver
* @param preferredLanguage
* the preferred language or null if no preference
* @return a new TDWay if it is valid, null otherwise
*/
public static TDWay fromWay(Way way, NodeResolver resolver, String preferredLanguage) {
if (way == null)
return null;
SpecialTagExtractionResult ster = OSMUtils.extractSpecialFields(way, preferredLanguage);
short[] knownWayTags = OSMUtils.extractKnownWayTags(way);
// only ways with at least 2 way nodes are valid ways
if (way.getWayNodes().size() >= 2) {
boolean validWay = true;
// retrieve way nodes from data store
TDNode[] waynodes = new TDNode[way.getWayNodes().size()];
int i = 0;
for (WayNode waynode : way.getWayNodes()) {
// TODO adjust interface to support a method getWayNodes()
waynodes[i] = resolver.getNode(waynode.getNodeId());
if (waynodes[i] == null) {
validWay = false;
LOGGER.finer("unknown way node: " + waynode.getNodeId() + " in way " + way.getId());
}
i++;
}
// for a valid way all way nodes must be existent in the input data
if (validWay) {
// mark the way as polygon if the first and the last way node are the same
// and if the way has at least 4 way nodes
byte shape = LINE;
if (waynodes[0].getId() == waynodes[waynodes.length - 1].getId()) {
if (waynodes.length >= GeoUtils.MIN_NODES_POLYGON) {
shape = SIMPLE_POLYGON;
} else {
LOGGER.finer("Found closed polygon with fewer than 4 way nodes. Way-id: " + way.getId());
return null;
}
}
return new TDWay(way.getId(), ster.getLayer(), ster.getName(), ster.getHousenumber(), ster.getRef(),
knownWayTags, shape, waynodes);
}
}
return null;
}
/**
* Constructor.
*
* @param id
* the id
* @param layer
* the layer
* @param name
* the name if existent
* @param houseNumber
* the house number if existent
* @param ref
* the ref if existent
* @param wayNodes
* the way nodes
*/
public TDWay(long id, byte layer, String name, String houseNumber, String ref, TDNode[] wayNodes) {
this.id = id;
this.layer = layer;
this.name = name;
this.houseNumber = houseNumber;
this.ref = ref;
this.wayNodes = wayNodes;
}
/**
* Constructor.
*
* @param id
* the id
* @param layer
* the layer
* @param name
* the name if existent
* @param houseNumber
* the house number if existent
* @param ref
* the ref if existent
* @param tags
* the tags
* @param shape
* the shape
* @param wayNodes
* the way nodes
*/
public TDWay(long id, byte layer, String name, String houseNumber, String ref, short[] tags, byte shape,
TDNode[] wayNodes) {
this.id = id;
this.layer = layer;
this.name = name;
this.houseNumber = houseNumber;
this.ref = ref;
this.tags = tags;
this.shape = shape;
this.wayNodes = wayNodes;
}
/**
* Merges tags from a relation with the tags of this way and puts the result into the way tags of this way.
*
* @param relation
* the relation
*/
public void mergeRelationInformation(TDRelation relation) {
if (relation.hasTags()) {
addTags(relation.getTags());
}
if (getName() == null && relation.getName() != null) {
setName(relation.getName());
}
if (getRef() == null && relation.getRef() != null) {
setRef(relation.getRef());
}
}
/**
* @return the zoom level this entity appears first
*/
public byte getMinimumZoomLevel() {
return OSMTagMapping.getInstance().getZoomAppearWay(this.tags);
}
/**
* @return the name
*/
public String getName() {
return this.name;
}
/**
* @param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the house number
*/
public String getHouseNumber() {
return this.houseNumber;
}
/**
* @return the ref
*/
public String getRef() {
return this.ref;
}
/**
* @param ref
* the ref to set
*/
public void setRef(String ref) {
this.ref = ref;
}
/**
* @return the tags
*/
public short[] getTags() {
return this.tags;
}
/**
* @param tags
* the tags to set
*/
public void setTags(short[] tags) {
this.tags = tags;
}
/**
* @return the shape
*/
public byte getShape() {
return this.shape;
}
/**
* @param shape
* the shape to set
*/
public void setShape(byte shape) {
this.shape = shape;
}
/**
* @return the id
*/
public long getId() {
return this.id;
}
/**
* @return the layer
*/
public byte getLayer() {
return this.layer;
}
/**
* @return true, if the way has tags
*/
public boolean hasTags() {
return this.tags != null && this.tags.length > 0;
}
/**
* @return true, if the way is relevant for rendering
*/
public boolean isRenderRelevant() {
return hasTags() || getName() != null && !getName().isEmpty() || getRef() != null && !getRef().isEmpty();
}
private void addTags(short[] addendum) {
if (this.tags == null) {
this.tags = addendum;
} else {
TShortSet tags2 = new TShortHashSet();
tags2.addAll(this.tags);
tags2.addAll(addendum);
this.tags = tags2.toArray();
}
}
/**
* @return true, if the way has at least 4 coordinates and the first and last coordinate are equal
*/
public boolean isPolygon() {
return this.wayNodes != null && this.wayNodes.length >= GeoUtils.MIN_NODES_POLYGON
&& this.wayNodes[0].getId() == this.wayNodes[this.wayNodes.length - 1].getId();
}
/**
* @return true, if the way represents a coastline
*/
public boolean isCoastline() {
if (this.tags == null) {
return false;
}
OSMTag tag;
for (short tagID : this.tags) {
tag = OSMTagMapping.getInstance().getWayTag(tagID);
if (tag.isCoastline()) {
return true;
}
}
return false;
}
/**
* @return the way nodes
*/
public TDNode[] getWayNodes() {
return this.wayNodes;
}
/**
* @return true, if the way nodes have been reversed with respect to a particular relation
*/
public boolean isReversedInRelation() {
return this.reversedInRelation;
}
/**
* @param reversedInRelation
* set the flag that indicates whether the order of the way nodes are reversed by a particular relation
*/
public void setReversedInRelation(boolean reversedInRelation) {
this.reversedInRelation = reversedInRelation;
}
/**
* @return true, if the way has a tag that forces a closed way to be a polygon line (instead of an area)
*/
public boolean isForcePolygonLine() {
if (!hasTags()) {
return false;
}
OSMTagMapping mapping = OSMTagMapping.getInstance();
for (short tag : this.tags) {
if (mapping.getWayTag(tag).isForcePolygonLine()) {
return true;
}
}
return false;
}
/**
* @return the invalid
*/
public boolean isInvalid() {
return this.invalid;
}
/**
* @param invalid
* the invalid to set
*/
public void setInvalid(boolean invalid) {
this.invalid = invalid;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (this.id ^ (this.id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TDWay other = (TDWay) obj;
if (this.id != other.id) {
return false;
}
return true;
}
@Override
public String toString() {
return "TDWay [id=" + this.id + ", name=" + this.name + ", tags=" + Arrays.toString(this.tags) + ", polygon="
+ this.shape + "]";
}
}