/*
* Copyright (C) 2006 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 17-Dec-2006
*/
package uk.me.parabola.mkgmap.reader.osm;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
/**
* Represent a OSM way in the 0.5 api. A way consists of an ordered list of
* nodes.
*
* @author Steve Ratcliffe
*/
public class Way extends Element {
private static final Logger log = Logger.getLogger(Way.class);
private final List<Coord> points;
private long fullArea = Long.MAX_VALUE; // meaning unset
// This will be set if a way is read from an OSM file and the first node is the same node as the last
// one in the way. This can be set to true even if there are missing nodes and so the nodes that we
// have do not form a closed loop.
// Note: this is not always set
private boolean closedInOSM;
// This is set to false if, we know that there are nodes missing from this way.
// If you set this to false, then you *must* also set closed to the correct value.
private boolean complete = true;
private boolean isViaWay;
public Way(long id) {
points = new ArrayList<Coord>(5);
setId(id);
}
public Way(long id, List<Coord> points) {
this.points = new ArrayList<Coord>(points);
setId(id);
}
public Way copy() {
Way dup = new Way(getId(), points);
dup.copyTags(this);
dup.closedInOSM = this.closedInOSM;
dup.complete = this.complete;
dup.isViaWay = this.isViaWay;
dup.fullArea = this.getFullArea();
return dup;
}
/**
* Get the points that make up the way. We attempt to re-order the segments
* and return a list of points that traces the route of the way.
*
* @return A simple list of points that form a line.
*/
public List<Coord> getPoints() {
return points;
}
public void addPoint(Coord co) {
points.add(co);
}
public void addPointIfNotEqualToLastPoint(Coord co) {
if(points.isEmpty() || !co.equals(points.get(points.size() - 1)))
points.add(co);
}
public void reverse() {
Collections.reverse(points);
}
/**
* Returns true if the way is really closed in OSM.
*
* Will return true even if the way is incomplete in the tile that we are reading, but the way is
* really closed in OSM.
*
* @return True if the way is really closed.
*/
public boolean isClosed() {
if (!isComplete())
return closedInOSM;
return !points.isEmpty() && hasIdenticalEndPoints();
}
/**
*
* @return true if the way is really closed in OSM,
* false if the way was created by mkgmap or read from polish
* input file (*.mp).
*
*/
public boolean isClosedInOSM() {
return closedInOSM;
}
/**
*
* @return Returns true if the first point in the way is identical to the last.
*/
public boolean hasIdenticalEndPoints() {
return !points.isEmpty() && points.get(0) == points.get(points.size()-1);
}
/**
*
* @return Returns true if the first point in the way is identical to the last.
*/
public boolean hasEqualEndPoints() {
return !points.isEmpty() && points.get(0).equals(points.get(points.size()-1));
}
public void setClosedInOSM(boolean closed) {
this.closedInOSM = closed;
}
public boolean isComplete() {
return complete;
}
/**
* Set this to false if you know that the way does not have its complete set of nodes.
*
* If you do set this to false, then you must also call {@link #setClosed} to indicate if the way
* is really closed or not.
*/
public void setComplete(boolean complete) {
this.complete = complete;
}
/**
* A simple representation of this way.
* @return A string with the name and start point
*/
public String toString() {
if (points.isEmpty())
return "Way: empty";
Coord coord = points.get(0);
StringBuilder sb = new StringBuilder();
sb.append("WAY: ").append(getId()).append(" ");
sb.append(getName());
sb.append('(');
sb.append(Utils.toDegrees(coord.getLatitude()));
sb.append('/');
sb.append(Utils.toDegrees(coord.getLongitude()));
sb.append(')');
sb.append(' ');
sb.append(toTagString());
return sb.toString();
}
public int hashCode() {
return (int) getId();
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return getId() == ((Way) o).getId();
}
/**
* calculate weighted centre of way, using high precision
* @return
*/
public Coord getCofG() {
int numPoints = points.size();
if(numPoints < 1)
return null;
double lat = 0;
double lon = 0;
if (hasIdenticalEndPoints())
numPoints--;
for (int i = 0; i < numPoints; i++){
Coord p = points.get(i);
lat += (double)p.getHighPrecLat()/numPoints;
lon += (double)p.getHighPrecLon()/numPoints;
}
return Coord.makeHighPrecCoord((int)Math.round(lat), (int)Math.round(lon));
}
public String kind() {
return "way";
}
// returns true if the way is a closed polygon with a clockwise
// direction
public static boolean clockwise(List<Coord> points) {
if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1)))
return false;
if (points.get(0).highPrecEquals(points.get(points.size() - 1)) == false){
log.error("Way.clockwise was called for way that is not closed in high precision");
}
long area = 0;
Coord p1 = points.get(0);
for(int i = 1; i < points.size(); ++i) {
Coord p2 = points.get(i);
area += ((long)p1.getHighPrecLon() * p2.getHighPrecLat() -
(long)p2.getHighPrecLon() * p1.getHighPrecLat());
p1 = p2;
}
// this test looks to be inverted but gives the expected result!
// empty linear areas are defined as clockwise
return area <= 0;
}
// simplistic check to see if this way "contains" another - for
// speed, all we do is check that all of the other way's points
// are inside this way's polygon
public boolean containsPointsOf(Way other) {
Polygon thisPoly = new Polygon();
for(Coord p : points)
thisPoly.addPoint(p.getHighPrecLon(), p.getHighPrecLat());
for(Coord p : other.points)
if(!thisPoly.contains(p.getHighPrecLon(), p.getHighPrecLat()))
return false;
return true;
}
public boolean isViaWay() {
return isViaWay;
}
public void setViaWay(boolean isViaWay) {
this.isViaWay = isViaWay;
}
public void setFullArea(long fullArea) {
this.fullArea = fullArea;
}
public long getFullArea() { // this is unadulterated size, +ve if clockwise
if (this.fullArea == Long.MAX_VALUE && points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1))) {
this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
}
return this.fullArea;
}
}