/**
* This file is part of OSM2ShareNav
*
* 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.
*
* Copyright (C) 2007 Harald Mueller
*/
package net.sharenav.osmToShareNav.model;
import java.awt.Polygon;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
import net.sharenav.osmToShareNav.Configuration;
import net.sharenav.osmToShareNav.Constants;
import net.sharenav.osmToShareNav.LegendParser;
import net.sharenav.osmToShareNav.MyMath;
import net.sharenav.osmToShareNav.area.Area;
import net.sharenav.osmToShareNav.area.Outline;
import net.sharenav.osmToShareNav.area.Triangle;
import net.sharenav.osmToShareNav.area.Vertex;
import net.sharenav.osmToShareNav.model.name.Names;
import net.sharenav.osmToShareNav.model.TravelModes;
import net.sharenav.osmToShareNav.model.url.Urls;
public class Way extends Entity implements Comparable<Way> {
public static final byte WAY_FLAG_NAME = 1;
public static final byte WAY_FLAG_MAXSPEED = 2;
public static final byte WAY_FLAG_LAYER = 4;
public static final byte WAY_FLAG_RESERVED_FLAG = 8;
public static final byte WAY_FLAG_ONEWAY = 16;
public static final byte WAY_FLAG_NAMEHIGH = 32;
public static final byte WAY_FLAG_AREA = 64;
public static final int WAY_FLAG_ADDITIONALFLAG = 128;
public static final byte WAY_FLAG2_ROUNDABOUT = 1;
public static final byte WAY_FLAG2_TUNNEL = 2;
public static final byte WAY_FLAG2_BRIDGE = 4;
public static final byte WAY_FLAG2_CYCLE_OPPOSITE = 8;
/** TODO: Is this really in use??? */
public static final byte WAY_FLAG2_LONGWAY = 16;
public static final byte WAY_FLAG2_MAXSPEED_WINTER = 32;
/** http://wiki.openstreetmap.org/wiki/WikiProject_Haiti */
public static final byte WAY_FLAG2_COLLAPSED_OR_IMPASSABLE = 64;
public static final int WAY_FLAG2_ADDITIONALFLAG = 128;
public static final byte WAY_FLAG3_URL = 1;
public static final byte WAY_FLAG3_URLHIGH = 2;
public static final byte WAY_FLAG3_PHONE = 4;
public static final byte WAY_FLAG3_PHONEHIGH = 8;
public static final byte WAY_FLAG3_NAMEASFORAREA = 16;
public static final byte WAY_FLAG3_HAS_HOUSENUMBERS = 32;
public static final byte WAY_FLAG3_LONGHOUSENUMBERS = 64;
public static final int WAY_FLAG3_ADDITIONALFLAG = 128;
public static final byte WAY_FLAG4_ALERT = 1;
public static final byte WAY_FLAG4_CLICKABLE = 2;
public static final byte WAY_FLAG4_HOLES = 4;
public static final byte WAY_FLAG4_TRIANGLES_IN_OUTLINE_MODE = 8;
public Long id;
public Path path = null;
private Path outlinePath = null;
private ArrayList<Path> holes = null;
public HouseNumber housenumber = null;
public List<Triangle> triangles = null;
//private Bounds bound = null;
/** Up to 4 travel modes for which this way can be used (motorcar, bicycle, etc.)
* The upper 4 bits equal to Connection.CONNTYPE_* flags
*/
public byte wayTravelModes = 0;
/** Toll flag for up to 4 travel modes for which this way can be used (motorcar, bicycle, etc.)
* upper bytes are unused
*/
public byte wayTravelModes2 = 0;
public static Configuration config;
/**
* Indicates that this way was already written to output;
*/
public boolean used = false;
// polish.api.bigstyles
public short type = -1;
/**
* Way id of the last unhandled maxSpeed - by using this to detect repeats,
* we can quiet down the console output for unhandled maxspeeds.
*/
public static long lastUnhandledMaxSpeedWayId = -1;
private static boolean writingAreaOutlines = false;
private static boolean deleteAreaOutlines = true;
public Way(long id) {
this.id = id;
this.path = new Path();
}
/**
* create a new Way with path clone from the other way, but doesn't share tags
* used e.g. for relation expansion
*
* @param other
*/
public Way(long id, Way other) {
this.id = id;
this.path = new Path(other.path);
if (other.outlinePath != null) {
this.outlinePath = new Path(other.outlinePath);
}
}
public Way(long id, ArrayList<Node> newPath) {
this.id = id;
this.path = new Path(newPath);
}
/**
* create a new Way which shares the tags with the other way, has the same type and id, but direction reversed
*
* @param other
*/
public Way(Way other, boolean reverse) {
super(other);
this.id = other.id;
this.type = other.type;
this.path = new Path(other.path, reverse);
if (other.outlinePath != null) {
this.outlinePath = new Path(other.outlinePath, reverse);
}
}
/**
* create a new Way which shares the tags with the other way, has the same type and id, but no Nodes
*
* @param other
*/
public Way(Way other) {
super(other);
this.id = other.id;
this.type = other.type;
this.path = new Path();
}
public void addHole(Way holeWay) {
if (holes == null) {
holes = new ArrayList<Path>();
}
Path holePath = new Path((ArrayList<Node>) holeWay.getNodes());
holes.add(holePath);
}
public ArrayList<Path> getHoles() {
return holes;
}
public void deletePath() {
path = null;
}
public void cloneTags(Way other) {
super.cloneTags(other);
this.id = other.id;
this.type = other.type;
}
public boolean isMainstreet() {
WayDescription wayDesc = config.getWayDesc(type);
if (wayDesc == null) {
return false;
}
return wayDesc.isMainstreet();
}
public boolean isHighway() {
return (getAttribute("highway") != null);
}
public void determineWayRouteModes() {
if (config == null) {
config = Configuration.getConfiguration();
}
// check if wayDesc is null otherwise we could route along a way we have no description how to render, etc.
WayDescription wayDesc = config.getWayDesc(type);
if (wayDesc == null) {
return;
}
// for each way the default route accessibility comes from its way description
wayTravelModes = wayDesc.wayDescTravelModes;
// modify the way's route accessibility according to the route access restriction for each routeMode
for (int i = 0; i < TravelModes.travelModeCount; i++) {
switch (isAccessPermittedOrForbiddenFor(i)) {
case 1:
wayTravelModes |= 1 << i;
break;
case -1:
wayTravelModes &= ~(1 << i);
break;
}
// mark only connections of accessible ways as toll connections
if (isAccessForRouting(i) && isTollRoad(i)) {
wayTravelModes2 |= (1 << i);
}
}
if (getAttribute("toll") != null && "|yes|true|".indexOf("|" + getAttribute("toll").toLowerCase() + "|") >= 0 ) {
wayTravelModes |= Connection.CONNTYPE_TOLLROAD;
}
}
public boolean isAccessForRouting(int travelModeNr) {
return ((wayTravelModes & (1 << travelModeNr)) != 0);
}
public boolean isAccessForAnyRouting() {
return (wayTravelModes != 0);
}
public boolean isAccessForRoutingInAnyTurnRestrictionTravelMode() {
return (wayTravelModes & TravelModes.applyTurnRestrictionsTravelModes) > 0;
}
/**
* Check way tags for tollRules from style-file
*
* @param travelModeNr
* : e.g. for motorcar or bicycle
* @return true if toll road, false if no toll road
*/
public boolean isTollRoad(int travelModeNr) {
String value;
boolean isToll = false;
for (TollRule rToll : TravelModes.getTravelMode(travelModeNr).getTollRules()) {
value = getAttribute(rToll.key);
if (value != null && rToll.values.indexOf(value) != -1) {
if (rToll.enableToll) {
isToll = true;
} else {
isToll = false;
}
if (rToll.debugTollRule) {
System.out.println(this.toUrl() + " matches toll rule: " + rToll.toString());
}
}
}
return isToll;
}
public boolean isRoundabout() {
String jType = getAttribute("junction");
if (jType != null) {
return (jType.equalsIgnoreCase("roundabout"));
} else {
return false;
}
}
public boolean isTunnel() {
return (containsKey("tunnel"));
}
public boolean isBridge() {
return (containsKey("bridge"));
}
// FIXME should not be hard-coded but taken from style-file
public boolean hasHouseNumberTag() {
return (containsKey("addr:housenumber"));
}
public boolean hasHouseNumber() {
return housenumber != null;
}
public boolean isDamaged() {
Vector<Damage> damages = LegendParser.getDamages();
if (damages.size() != 0) {
for (Damage damage : LegendParser.getDamages()) {
for (String s : getTags()) {
if (damage.key.equals("*") || s.equalsIgnoreCase(damage.key)) {
if (damage.values.equals("*") || ("|" + damage.values + "|").indexOf("|" + getAttribute(s) + "|") != -1) { return true; }
}
}
}
}
return false;
}
public void resetType(Configuration c) {
type = -1;
}
// polish.api.bigstyles
public short getType(Configuration c) {
if (type == -1) {
type = calcType(c);
}
//Different negative values are used to indicate internal state, canonicalise it back to -1
if (type > -1) {
return type;
} else {
return -1;
}
}
// polish.api.bigstyles
public short getType() {
if (type > -1) {
return type;
} else {
return -1;
}
}
// polish.api.bigstyles
private short calcType(Configuration c) {
WayDescription way = (WayDescription) super.calcType(c.getWayLegend());
if (way == null) {
type = -1;
} else {
type = way.typeNum;
way.noWaysOfType++;
}
/**
* Check to see if the way corresponds to any of the POI types If it does, then we insert a POI node to reflect
* this, as otherwise the nearest POI search or other POI features don't work on ways
* housenumber search for buildings also requires this
*/
POIdescription poi = (POIdescription) super.calcType(c.getPOIlegend());
if (poi != null && poi.createPOIsForAreas) {
if (isValid()) {
/**
* TODO: Come up with a sane solution to find out where to place the node to represent the area POI
*/
Node n = getFirstNodeWithoutPOIType();
if (n != null) {
n.wayToPOItransfer(this, poi);
//Indicate that this way has been dealt with, even though the way itself has no type.
//Some stylefiles might have both way styling and areaPOI styling for the same type.
if (type < 0) {
type = -2;
}
} else {
System.out.println("WARNING: No way poi assigned because no node without a poi type has been available on way "
+ toString());
}
}
}
return type;
}
@Override
public String getName() {
if (type > -1) {
WayDescription desc = Configuration.getConfiguration().getWayDesc(type);
if (desc != null) {
String name = getAttribute(desc.nameKey);
String nameFallback = null;
if (desc.nameFallbackKey != null && desc.nameFallbackKey.equals("*")) {
nameFallback = getAttribute(desc.key);
} else {
nameFallback = getAttribute(desc.nameFallbackKey);
}
if (name != null && nameFallback != null && (!desc.nameFallbackKey.equals("*") || !desc.key.equals(desc.nameKey))) {
if (name.length() + nameFallback.length() > 125) {
// cut too long names
int namelen = name.length();
if (namelen > 60) {
namelen = 60;
}
int nameFallbackLen = nameFallback.length();
if (namelen + nameFallbackLen > 125) {
nameFallbackLen = 120-namelen;
}
name = name.substring(0, namelen) + ".. (" + nameFallback.substring(0, nameFallbackLen) + ")";
} else {
name += " (" + nameFallback + ")";
}
} else if ((name == null) && (nameFallback != null)) {
name = nameFallback;
}
// System.out.println("New style name: " + name);
return (name != null ? name.trim() : "");
}
}
return null;
}
public byte getZoomlevel(Configuration c) {
Bounds b = null;
byte tileLevelFromDiameter = 0;
// calculate tile level for areas based on diameter
if (isArea()) {
b = getBounds();
int diameter = (int) (MyMath.calcDistance(
Math.toRadians(b.minLat),
Math.toRadians(b.minLon),
Math.toRadians(b.maxLat),
Math.toRadians(b.maxLon)
) * MyMath.PLANET_RADIUS);
// if (getName().indexOf("Volksbad") >= 0) {
// System.out.println ("lat: " + b.minLat + " lon: " + b.minLon + " : " + diameter + " " + getName());
// }
if ( diameter < LegendParser.tileLevelAttractsAreasWithSmallerBoundsDiameterThan[3]
&&
(LegendParser.tileScaleLevelIsAllowedForRoutableWays[3] || !isAccessForAnyRouting())
) {
tileLevelFromDiameter = 3;
} else if ( diameter < LegendParser.tileLevelAttractsAreasWithSmallerBoundsDiameterThan[2]
&&
(LegendParser.tileScaleLevelIsAllowedForRoutableWays[2] || !isAccessForAnyRouting())
) {
tileLevelFromDiameter = 2;
} else if ( diameter < LegendParser.tileLevelAttractsAreasWithSmallerBoundsDiameterThan[1]
&&
(LegendParser.tileScaleLevelIsAllowedForRoutableWays[1] || !isAccessForAnyRouting())
) {
tileLevelFromDiameter = 1;
}
}
// polish.api.bigstyles
short type = getType(c);
if (type < 0) {
// System.out.println("unknown type for node " + toString());
return 3;
}
int maxScale = c.getWayDesc(type).minEntityScale;
if (maxScale < LegendParser.tileScaleLevel[3]) { // 45000 in ShareNav 0.5.0
if (LegendParser.tileScaleLevelIsAllowedForRoutableWays[3] || !isAccessForAnyRouting()) {
return 3;
}
}
if (maxScale < LegendParser.tileScaleLevel[2]) { // 180000 in ShareNav 0.5.0
if (LegendParser.tileScaleLevelIsAllowedForRoutableWays[2]|| !isAccessForAnyRouting()) {
if (tileLevelFromDiameter > 2) { // if based on diameter the area would be in a higher zoom level put it there
return tileLevelFromDiameter;
}
return 2;
}
}
if (maxScale < LegendParser.tileScaleLevel[1]) { // 900000 in ShareNav 0.5.0
if (LegendParser.tileScaleLevelIsAllowedForRoutableWays[1] || !isAccessForAnyRouting()) {
if (tileLevelFromDiameter > 1) { // if based on diameter the area would be in a higher zoom level put it there
return tileLevelFromDiameter;
}
return 1;
}
}
if (tileLevelFromDiameter > 0) { // if based on diameter the area would be in a higher zoom level put it there
return tileLevelFromDiameter;
}
return 0;
}
/**
* Returns the maximum speed in km/h if explicitly set for this way, if not, it returns -1.0.
*/
public float getMaxSpeed() {
float maxSpeed = -1.0f;
String maxSpeedAttr = getAttribute("maxspeed");
if (maxSpeedAttr != null) {
maxSpeed = parseMaxSpeed(maxSpeedAttr, false);
}
return maxSpeed;
}
/**
* Returns the winter maximum speed in km/h if explicitly set for this way, if not, it returns -1.0.
*/
public float getMaxSpeedWinter() {
float maxSpeed = -1.0f;
String maxSpeedAttr = getAttribute("maxspeed:seasonal:winter");
if (maxSpeedAttr != null) {
maxSpeed = parseMaxSpeed(maxSpeedAttr, true);
}
return maxSpeed;
}
/** Parses the given maxspeed string
* @param maxSpeedAttr String to parse
* @param winterFlag Flag if it's a winter maxspeed, only needed for warning output
* @return Maxspeed as float or -1.0 if the template couldn't be found
*/
private float parseMaxSpeed(String maxSpeedAttr, boolean winterFlag) {
float maxSpeed = -1.0f;
if (maxSpeedAttr.equalsIgnoreCase("default")) {
/**
* We can't match this, so ignore it and notify about it.
*/
if (this.id != lastUnhandledMaxSpeedWayId) {
System.out.println("Warning: Ignoring map data: Unhandled maxspeed"
+ (winterFlag ? "winter" : "") + " for way " +
toString() + ": " + maxSpeedAttr);
lastUnhandledMaxSpeedWayId = this.id;
}
} else if (maxSpeedAttr.equalsIgnoreCase("variable")
|| maxSpeedAttr.equalsIgnoreCase("signals")) {
maxSpeed = Configuration.MAXSPEED_MARKER_VARIABLE;
} else if (maxSpeedAttr.equalsIgnoreCase("none")
|| maxSpeedAttr.equalsIgnoreCase("no")) {
maxSpeed = Configuration.MAXSPEED_MARKER_NONE;
} else {
try {
boolean mph = false;
if (maxSpeedAttr.toLowerCase().endsWith("mph")) {
mph = true;
maxSpeedAttr = maxSpeedAttr.substring(0, maxSpeedAttr.length() - 3).trim();
} else if (maxSpeedAttr.toLowerCase().endsWith("km/h")) {
maxSpeedAttr = maxSpeedAttr.substring(0, maxSpeedAttr.length() - 4).trim();
} else if (maxSpeedAttr.toLowerCase().endsWith("kmh")) {
maxSpeedAttr = maxSpeedAttr.substring(0, maxSpeedAttr.length() - 3).trim();
} else if (maxSpeedAttr.toLowerCase().endsWith("kph")) {
maxSpeedAttr = maxSpeedAttr.substring(0, maxSpeedAttr.length() - 3).trim();
}
maxSpeed = (Float.parseFloat(maxSpeedAttr));
if (mph) {
maxSpeed *= 1.609; // Convert to km/h
}
} catch (NumberFormatException e) {
try {
int maxs = config.getMaxspeedTemplate(maxSpeedAttr);
if (maxs > 0) {
maxSpeed = maxs;
}
} catch (Exception ex) {
if (this.id != lastUnhandledMaxSpeedWayId) {
System.out.println("Warning: Ignoring map data: Unhandled maxspeed"
+ (winterFlag ? "winter" : "") + " for way " +
toString() + ": " + maxSpeedAttr);
lastUnhandledMaxSpeedWayId = this.id;
}
}
}
}
return maxSpeed;
}
/**
* Get or estimate speed in m/s for routing purposes.
*
* @param routeModeNr
* Route mode to use
* @return routing speed
*/
public float getRoutingSpeed(int routeModeNr) {
if (config == null) {
config = Configuration.getConfiguration();
}
float maxSpeed = getMaxSpeed();
float typicalSpeed = config.getWayDesc(type).typicalSpeed[routeModeNr];
if (typicalSpeed != 0) {
if (typicalSpeed < maxSpeed || maxSpeed < 0) {
maxSpeed = typicalSpeed;
}
}
if (maxSpeed <= 0) {
maxSpeed = 60.0f; // Default case;
}
return maxSpeed / 3.6f;
}
public int compareTo(Way o) {
// polish.api.bigstyles
short t1 = getType();
short t2 = o.getType();
if (t1 < t2) {
return 1;
} else if (t1 > t2) { return -1; }
return 0;
}
public Bounds getBounds() {
// always calculate current bounds
Bounds bound = new Bounds();
if (triangles != null && triangles.size() > 0) {
for (Triangle t : triangles) {
bound.extend(t.getVert()[0].getLat(), t.getVert()[0].getLon());
bound.extend(t.getVert()[1].getLat(), t.getVert()[1].getLon());
bound.extend(t.getVert()[2].getLat(), t.getVert()[2].getLon());
}
} else {
if (path != null) {
path.extendBounds(bound);
}
if (outlinePath != null) {
outlinePath.extendBounds(bound);
}
}
return bound;
}
/*public void clearBounds() {
bound = null;
}*/
/** Simplistic check to see if this way/area "contains" another - for
* speed, all we do is check that all of the other way's points
* are inside this way's polygon.
* @param other
* @return
*/
public boolean containsPointsOf(Way other) {
// This method was ported from mkgmap (uk.me.parabola.mkgmap.reader.osm.Way).
Polygon thisPoly = new Polygon();
for (Node n : getNodes()) {
thisPoly.addPoint((int)(n.getLon() * MyMath.FIXPT_MULT),
(int)(n.getLat() * MyMath.FIXPT_MULT));
}
for (Node n : other.getNodes()) {
if (!thisPoly.contains((int)(n.getLon() * MyMath.FIXPT_MULT),
(int)(n.getLat() * MyMath.FIXPT_MULT))) {
return false;
}
}
return true;
}
@Override
public String toString() {
String res = "id=" + id + ((nearBy == null) ? "" : (" near " + nearBy)) + " type=" + getType() + " [";
Set<String> tags = getTags();
if (tags != null) {
for (String key : tags) {
res = res + key + "=" + getAttribute(key) + " ";
}
}
res = res + "]";
return res;
}
/**
* @return String with the URL to inspect this way on the OSM website
*/
public String toUrl() {
return "http://www.openstreetmap.org/browse/way/" + id;
}
/**
* @return The value of the attribute "is_in"
*/
public String getIsIn() {
return getAttribute("is_in");
}
/**
* @return
*/
// polish.api.bigstyles
public short getNameType() {
String t = getAttribute("highway");
if (t != null) { return (Constants.NAME_STREET); }
return Constants.NAME_AMENITY;
}
/**
* @return Node in the middle or null if way has no nodes
*/
public Node getMidPointNodeByBounds() {
Bounds b = getBounds();
return (new Node((b.maxLat + b.minLat) / 2, (b.maxLon + b.minLon) / 2, -1));
}
/**
* @return Node in the middle or null if way has no nodes
*/
public Node getMidPoint() {
if (isArea()) {
Bounds b = getBounds();
return (new Node((b.maxLat + b.minLat) / 2, (b.maxLon + b.minLon) / 2, -1));
}
List<Node> nl = path.getNodes();
if (nl.size() > 1) {
int splitp = nl.size() / 2;
return (nl.get(splitp));
} else {
return null;
}
}
/**
* @return
*/
public boolean isOneWay() {
return (Configuration.attrToBoolean(getAttribute("oneway")) > 0);
}
/**
* @return
*/
public boolean isOneWayMinusOne() {
return ("-1".equals(getAttribute("oneway")));
}
/** Check if cycleway=opposite or cycleway=opposite_track or cycleway=opposite_lane is set */
public boolean isOppositeDirectionForBicycleAllowed() {
String s = getAttribute("cycleway");
if (s == null) { return false; }
return ("|opposite|opposite_track|opposite_lane|".indexOf("|" + s.toLowerCase() + "|") >= 0);
}
/** Writes this way's data (flags, type, travel modes, indices, node count etc.).
*
* @param ds Stream to write to
* @param names1 The way's name index is in this list
* @param urls1 The way's URL index is in this list
* @param t Tile to which this way belongs - bounds coordinates are relative to its center
* @throws IOException
*/
public void write(DataOutputStream ds, Names names1, Urls urls1, Tile t, boolean outlineFormat) throws IOException {
Bounds b = new Bounds();
int flags = 0;
int flags2 = 0;
int flags3 = 0;
int flags4 = 0;
int maxspeed = 50;
int maxspeedwinter = 50;
int nameIdx = -1;
int urlIdx = -1;
int phoneIdx = -1;
byte layer = 0;
if (outlineFormat) {
writingAreaOutlines = true;
} else {
writingAreaOutlines = false;
}
if (config == null) {
config = Configuration.getConfiguration();
}
if (id == 39123631) {
System.out.println("Write way 39123631");
}
// polish.api.bigstyles
short type = getType();
if (getName() != null && getName().trim().length() > 0) {
flags += WAY_FLAG_NAME;
nameIdx = names1.getNameIdx(getName());
if (nameIdx >= Short.MAX_VALUE) {
flags += WAY_FLAG_NAMEHIGH;
}
}
if (config.useUrlTags && getUrl() != null && getUrl().trim().length() > 0){
flags3+=WAY_FLAG3_URL;
urlIdx = urls1.getUrlIdx(getUrl());
if (urlIdx >= Short.MAX_VALUE) {
flags3 += WAY_FLAG3_URLHIGH;
}
}
if (config.usePhoneTags && getPhone() != null && getPhone().trim().length() > 0){
flags3+=WAY_FLAG3_PHONE;
phoneIdx = urls1.getUrlIdx(getPhone());
if (phoneIdx >= Short.MAX_VALUE) {
flags3 += WAY_FLAG3_PHONEHIGH;
}
}
if (showNameAsForArea()) {
flags3 += WAY_FLAG3_NAMEASFORAREA;
}
maxspeed = (int) getMaxSpeed();
maxspeedwinter = (int) getMaxSpeedWinter();
if (maxspeedwinter > 0) {
flags2 += WAY_FLAG2_MAXSPEED_WINTER;
} else {
maxspeedwinter = 0;
}
if (maxspeed > 0) {
flags += WAY_FLAG_MAXSPEED;
}
if (containsKey("layer")) {
try {
layer = (byte) Integer.parseInt(getAttribute("layer"));
flags += WAY_FLAG_LAYER;
} catch (NumberFormatException e) {
}
}
if ((config.getWayDesc(type).forceToLayer != 0)) {
layer = config.getWayDesc(type).forceToLayer;
flags |= WAY_FLAG_LAYER;
}
boolean longWays = false;
boolean longHouseNumbers = false;
if (type < 1) {
System.out.println("ERROR! Invalid way type for way " + toString());
}
boolean trianglesInOutlineMode = false;
if (isArea() && writingAreaOutlines) {
if (getOutlineNodeCount() == 0) {
// work around by outputting triangles
trianglesInOutlineMode = true;
flags4 |= WAY_FLAG4_TRIANGLES_IN_OUTLINE_MODE;
if (triangles == null) {
triangulate();
}
if (getNodeCount() > 255) {
longWays = true;
}
} else {
if (getOutlineNodeCount() > 255) {
longWays = true;
}
}
} else {
if (getNodeCount() > 255) {
longWays = true;
}
}
if (holes != null && writingAreaOutlines && !trianglesInOutlineMode) {
flags4 += WAY_FLAG4_HOLES;
} else {
flags4 &= ~WAY_FLAG4_HOLES;
}
if (housenumber != null) {
// FIXME maybe enable later for viewing way-related house numbers on map;
// disable for now, as ShareNav doesn't use this yet
// flags3 += WAY_FLAG3_HAS_HOUSENUMBERS;
if (getHouseNumberCount() > 255) {
longHouseNumbers = true;
}
}
if (isOneWay()) {
flags += WAY_FLAG_ONEWAY;
}
if (isArea()) {
// if (isExplicitArea()) {
flags += WAY_FLAG_AREA;
}
if (isRoundabout()) {
flags2 += WAY_FLAG2_ROUNDABOUT;
}
if (isTunnel()) {
flags2 += WAY_FLAG2_TUNNEL;
}
if (isBridge()) {
flags2 += WAY_FLAG2_BRIDGE;
}
if (isDamaged()) {
flags2 += WAY_FLAG2_COLLAPSED_OR_IMPASSABLE;
}
if (isOppositeDirectionForBicycleAllowed()) {
flags2 += WAY_FLAG2_CYCLE_OPPOSITE;
}
if (longWays) {
flags2 += WAY_FLAG2_LONGWAY;
}
if (flags4 != 0) {
flags3 += WAY_FLAG3_ADDITIONALFLAG;
}
if (flags3 != 0) {
flags2 += WAY_FLAG2_ADDITIONALFLAG;
}
if (flags2 != 0) {
flags += WAY_FLAG_ADDITIONALFLAG;
}
ds.writeByte(flags);
b = getBounds();
ds.writeShort((short) (MyMath.degToRad(b.minLat - t.centerLat) * MyMath.FIXPT_MULT));
ds.writeShort((short) (MyMath.degToRad(b.minLon - t.centerLon) * MyMath.FIXPT_MULT));
ds.writeShort((short) (MyMath.degToRad(b.maxLat - t.centerLat) * MyMath.FIXPT_MULT));
ds.writeShort((short) (MyMath.degToRad(b.maxLon - t.centerLon) * MyMath.FIXPT_MULT));
// polish.api.bigstyles
if (Configuration.getConfiguration().bigStyles) {
ds.writeShort(type);
} else {
ds.writeByte(type);
}
ds.writeByte(wayTravelModes);
if ((flags & WAY_FLAG_NAME) == WAY_FLAG_NAME) {
if ((flags & WAY_FLAG_NAMEHIGH) == WAY_FLAG_NAMEHIGH) {
ds.writeInt(nameIdx);
} else {
ds.writeShort(nameIdx);
}
}
if ((flags & WAY_FLAG_MAXSPEED) == WAY_FLAG_MAXSPEED) {
ds.writeByte(maxspeed);
}
// must be below maxspeed as this is a combined flag and ShareNav relies it's below maxspeed
if (flags2 != 0) {
ds.writeByte(flags2);
}
if (flags3 != 0) {
ds.writeByte(flags3);
}
if (flags4 != 0) {
ds.writeByte(flags4);
}
if ((flags3 & WAY_FLAG3_URL) == WAY_FLAG3_URL){
if ((flags3 & WAY_FLAG3_URLHIGH) == WAY_FLAG3_URLHIGH){
ds.writeInt(urlIdx);
} else {
ds.writeShort(urlIdx);
}
}
if ((flags3 & WAY_FLAG3_PHONE) == WAY_FLAG3_PHONE){
if ((flags3 & WAY_FLAG3_PHONEHIGH) == WAY_FLAG3_PHONEHIGH){
ds.writeInt(phoneIdx);
} else {
ds.writeShort(phoneIdx);
}
}
if ((flags2 & WAY_FLAG2_MAXSPEED_WINTER) == WAY_FLAG2_MAXSPEED_WINTER) {
ds.writeByte(maxspeedwinter);
}
if ((flags & WAY_FLAG_LAYER) == WAY_FLAG_LAYER) {
ds.writeByte(layer);
}
if ((flags3 & WAY_FLAG3_HAS_HOUSENUMBERS) == WAY_FLAG3_HAS_HOUSENUMBERS){
System.out.println("Writing housenumbers (nodecount=" + getHouseNumberCount() + ") for way " + this);
if (longHouseNumbers) {
ds.writeShort(getHouseNumberCount());
} else {
ds.writeByte(getHouseNumberCount());
}
for (Node n : housenumber.getNodes()) {
ds.writeLong(n.id);
System.out.println("Writing node " + n);
}
}
if (isArea() && (!writingAreaOutlines || trianglesInOutlineMode)) {
if (longWays) {
ds.writeShort(getNodeCount());
} else {
ds.writeByte(getNodeCount());
}
for (Triangle tri : checkTriangles()) {
ds.writeShort(tri.getVert()[0].getNode().renumberdId);
ds.writeShort(tri.getVert()[1].getNode().renumberdId);
ds.writeShort(tri.getVert()[2].getNode().renumberdId);
}
} else if (isArea() && writingAreaOutlines) {
if (longWays) {
ds.writeShort(getOutlineNodeCount());
} else {
ds.writeByte(getOutlineNodeCount());
}
int oCount = getOutlineNodeCount();
int c = 0;
Node startNode = null;
if (oCount > 0) {
for (Node n : getOutlineNodes()) {
if (c == 0) {
startNode = n;
}
if (c < oCount) {
ds.writeShort(n.renumberdId);
} else {
System.out.println("Error: skipping nodes, oCount = " + oCount + ", way " + this);
}
c++;
}
// patch up if getOutlineNodeCount() differs from written nodecount
if (c < oCount) {
while (c++ < oCount) {
//ds.writeShort(startNode.renumberdId);
System.out.println("Error: padding nodes, oCount = " + oCount + ", way " + this);
ds.writeShort(0);
}
}
}
if (c != getOutlineNodeCount()) {
System.out.println("Error: c (" + c + ") != outlineNodeCount(" + getOutlineNodeCount() + "), way " + this);
} else {
//System.out.println("outlineNodeCount OK, way " + this);
}
} else { // not an area
if (longWays) {
ds.writeShort(getNodeCount());
} else {
ds.writeByte(getNodeCount());
}
int c2 = 0;
for (Node n : getNodes()) {
ds.writeShort(n.renumberdId);
c2++;
}
if (c2 != getNodeCount()) {
System.out.println("Error: c2 (" + c2 + ") != nodeCount(" + getOutlineNodeCount() + "), way " + this);
} else {
//System.out.println("nodeCount OK, way " + this);
}
}
if ((flags4 & WAY_FLAG4_HOLES) == WAY_FLAG4_HOLES) {
int holeCount = holes.size();
//System.out.println("Way.java: holecount " + holes.size());
//int n = 0;
//for (Path hole : holes) {
// Path holeNodes = holes.get(n++);
//System.out.println("Way.java: hole " + n + " nodecount: " + holeNodes.getNodeCount());
//}
ds.writeShort(holeCount);
int nCount = 0;
for (Path hole : holes) {
//System.out.println("Way.java: hole " + nCount++ + " nodecount: " + hole.getNodeCount());
ds.writeShort(hole.getNodeCount());
for (Node n : hole.getNodes()) {
ds.writeShort(n.renumberdId);
}
}
}
if (config.enableEditingSupport) {
if (id > Integer.MAX_VALUE) {
// commented out by jkpj 2011-04-24 as the fake id generates triggers a lot of these
// FIXME make a scheme of marking fake ids so we can show real warnings
//System.out.println("WARNING: OSM-ID won't fit in 32 bits for way " + this);
ds.writeInt(-1);
} else {
ds.writeInt(id.intValue());
}
}
}
public void addNode(Node n) {
path.add(n);
}
public void addNodeIfNotEqualToLastNode(Node node) {
if (path.getNodeCount() == 0 || !node.equals(path.getNode(path.getNodeCount() - 1))) {
path.add(node);
}
}
public void addNodeIfNotEqualToFirstNodeOfTwo(Node node) {
if (!(path.getNodeCount() == 2 && node.equals(path.getNode(0)))) {
path.add(node);
}
}
// add node with interim nodes to make splitting possible
public void addNodeIfNotEqualToLastNodeWithInterimNodes(Node node) {
//System.out.println("nodecount: " + path.getNodeCount());
if (path.getNodeCount() != 0) {
Node oldNode = path.getNode(path.getNodeCount() - 1);
if (oldNode != null && !node.equals(oldNode)) {
double dist = MyMath.dist(oldNode, node);
int count = (int) dist / 50;
//System.out.println("Dist = " + dist + " count= " + count);
float oldLat = oldNode.getLat();
float oldLon = oldNode.getLon();
float latDiff = node.getLat() - oldLat;
float lonDiff = node.getLon() - oldLon;
for (int i = 1 ; i < count ; i++) {
Node interim = new Node(oldLat + latDiff * i / count, oldLon + lonDiff * i / count, FakeIdGenerator.makeFakeId());
path.add(interim);
}
}
}
if (path.getNodeCount() == 0 || !node.equals(path.getNode(path.getNodeCount() - 1))) {
path.add(node);
}
}
public void houseNumberAdd(Node n) {
if (housenumber == null) {
housenumber = new HouseNumber();
}
housenumber.add(n);
}
public boolean containsNode(Node nSearch) {
for (Node n : path.getNodes()) {
if (nSearch.id == n.id) {
return true;
}
}
return false;
}
public Node getFirstNodeWithoutPOIType() {
for (Node n : ((path != null ? path : outlinePath).getNodes())) {
if (n.getType(config) == -1) {
return n;
}
}
return null;
}
public ArrayList<RouteNode> getAllRouteNodesOnTheWay() {
ArrayList<RouteNode> returnNodes = new ArrayList<RouteNode>();
for (Node n : ((path != null && getNodeCount() > 0) ? path : outlinePath).getNodes()) {
if (n.routeNode != null) {
returnNodes.add(n.routeNode);
}
}
return returnNodes;
}
/**
* Replaces node1 with node2 in this way.
*
* @param node1 Node to be replaced
* @param node2 Node by which to replace node1.
*/
public void replace(Node node1, Node node2) {
path.replace(node1, node2);
}
/**
* replaceNodes lists nodes and by which nodes they have to be replaced.
* This method applies the specified list to this way.
*
* @param replaceNodes Hashmap of pairs of nodes
*/
public void replace(HashMap<Node, Node> replaceNodes) {
if (path != null) {
path.replace(replaceNodes);
}
if (outlinePath != null) {
outlinePath.replace(replaceNodes);
}
}
// public List<SubPath> getSubPaths() {
// return path.getSubPaths();
// }
/** Checks if this is an areas and triangulates it if this hasn't been done yet.
*
* @return List of triangles for this way
*/
public List<Triangle> checkTriangles() {
if (!Configuration.getConfiguration().triangleAreaFormat) {
return null;
}
if (isArea() && triangles == null) {
triangulate();
}
return triangles;
}
public int getLineCount() {
// FIXME if we're writing both formats, strictly taken this is incorrect,
// so if used for other than statistics, rewrite the logic
if (isArea() && Configuration.getConfiguration().triangleAreaFormat) {
return checkTriangles().size();
} else {
return path.getLineCount();
}
}
// For areas, triangle areas takes priority if both formats are enabled.
// Use getOutlineNodeCount() instead if you need node count for the outline
public int getNodeCount() {
if (isArea() && Configuration.getConfiguration().triangleAreaFormat) {
return checkTriangles().size() * 3;
} else {
return path.getNodeCount();
}
}
public int getOutlineNodeCount() {
if (!Configuration.getConfiguration().triangleAreaFormat || !isArea()) {
return path.getNodeCount();
} else {
if (outlinePath == null) {
System.out.println("outlinePath == null, way: " + this);
return 0;
}
return outlinePath.getNodeCount();
}
}
public int getHouseNumberCount() {
return housenumber.getHouseNumberCount();
}
public Way split() {
if (isArea()) {
return splitArea();
} else {
return splitNormalWay();
}
}
private Way splitArea() {
// FIXME write code to also split the outlines of the area
if (!Configuration.getConfiguration().triangleAreaFormat) {
return null;
}
if (triangles == null) {
triangulate();
}
// System.out.println("Split area id=" + id + " s=" + triangles.size());
Way newWay = new Way(this);
Bounds newBbounds = getBounds().split()[0];
ArrayList<Triangle> tri = new ArrayList<Triangle>(1);
ArrayList<Triangle> tri2 = new ArrayList<Triangle>(1);
for (Triangle t : triangles) {
Vertex midpoint = t.getMidpoint();
if (!newBbounds.isIn(midpoint.getLat(), midpoint.getLon())) {
tri.add(t);
} else {
tri2.add(t);
}
}
if (tri.size() == triangles.size()) {
// System.out.println("split area triangles: all " + tri.size() + " would go the other way");
// all triangles would go to the other way
return null;
}
// for (Triangle t : tri) {
// triangles.remove(t);
// }
triangles = tri2;
// System.out.println("split area triangles " + "s1=" + triangles.size() + " s2=" + tri.size());
if (tri.size() == 0) { return null; }
//clearBounds();
newWay.triangles = tri;
newWay.recreatePath();
//newWay.recreatePathAvoidDuplicates();
//recreatePathAvoidDuplicates();
recreatePath();
return newWay;
}
private Way splitNormalWay() {
if (isValid() == false) {
System.out.println("Way before split is not valid");
}
Path split = path.split();
if (split != null) {
// If we split the way, the bounds are no longer valid
//this.clearBounds();
Way newWay = new Way(this);
newWay.path = split;
if (newWay.isValid() == false) {
System.out.println("New Way after split is not valid");
}
if (isValid() == false) {
System.out.println("Old Way after split is not valid");
}
return newWay;
}
return null;
}
public boolean isValid() {
if ((path == null || path.getNodeCount() == 0) && (outlinePath == null || outlinePath.getNodeCount() == 0)) {
return false;
}
return true;
}
public boolean isClosed() {
if (!isValid()) {
return false;
}
List<Node> nlist = path.getNodes();
if (nlist.get(0) == nlist.get(nlist.size() - 1)) {
return true;
}
return false;
}
public boolean isArea() {
if (triangles != null) {
return true;
}
if (isExplicitArea()) {
return true;
}
if (type >= 0) {
return Configuration.getConfiguration().getWayDesc(type).isArea;
}
return false;
}
public boolean showNameAsForArea() {
if (type >= 0) {
return Configuration.getConfiguration().getWayDesc(type).showNameAsForArea;
}
return false;
}
public boolean isExplicitArea() {
// Ignore area=yes in OSM data if the style file for way type says so
if (type >= 0 && Configuration.getConfiguration().getWayDesc(type).ignoreOsmAreaTag) {
return false;
} else {
return Configuration.attrToBoolean(getAttribute("area")) > 0;
}
}
/**
* @return True if the way is a closed polygon with a clockwise direction.
*/
public boolean isCounterClockwise() {
// adapted from isCclockwise()
if (getNodes().size() < 3 ||
!getNodes().get(0).equals(getNodes().get(getNodes().size() - 1))) {
return false;
}
long area = 0;
Node n1 = getNodes().get(getNodes().size()-1);
for (int i = getNodes().size()-2; i >= 0; --i) {
Node n2 = getNodes().get(i);
area += ((long)n1.getLongLon() * n2.getLongLat() -
(long)n2.getLongLon() * n1.getLongLat());
n1 = n2;
}
// this test looks to be inverted but gives the expected result!
//System.out.println("Counterclockwise for way " + toUrl() + " area = " + area);
return area < 0;
}
/**
* @return True if the way is a closed polygon with a clockwise direction.
*/
public boolean isClockwise() {
// This method was ported from mkgmap (uk.me.parabola.mkgmap.reader.osm.Way).
if (getNodes().size() < 3 ||
!getNodes().get(0).equals(getNodes().get(getNodes().size() - 1))) {
return false;
}
long area = 0;
Node n1 = getNodes().get(0);
for (int i = 1; i < getNodes().size(); ++i) {
Node n2 = getNodes().get(i);
area += ((long)n1.getLongLon() * n2.getLongLat() -
(long)n2.getLongLon() * n1.getLongLat());
n1 = n2;
}
// this test looks to be inverted but gives the expected result!
return area < 0;
}
/**
* @return The OSM ID of this way
*/
public long getId() {
return id;
}
/**
* @return List of all nodes of this way
*/
public List<Node> getNodes() {
return path.getNodes();
}
public List<Node> getOutlineNodes() {
if (outlinePath == null) {
outlinePath = new Path();
}
return outlinePath.getNodes();
}
/**
* @param wayHashMap
* @param r
*/
public void triangulate() {
if (!Configuration.getConfiguration().triangleAreaFormat) {
return;
}
if (isArea()) {
Outline o = null;
o = new Outline();
o.setWayId(id);
for (Node n : getNodes()) {
o.append(new Vertex(n, o));
}
Area a = new Area();
a.addOutline(o);
triangles = a.triangulate();
recreatePathAvoidDuplicates();
} else {
System.err.println("Can't triangulate normal ways");
}
}
/**
* Regenerates this way's path object, rough version for speed
*/
public void recreatePath() {
if (isArea() && triangles.size() > 0) {
path = new Path();
}
for (Triangle t : triangles) {
for (int l = 0; l < 3; l++) {
Node n = t.getVert()[l].getNode();
//if (!path.getNodes().contains(n)) {
path.add(n);
//}
}
}
((ArrayList)triangles).trimToSize();
trimPath();
//clearBounds();
}
/**
* Regenerates this way's path object, avoiding duplicates
*/
public void recreatePathAvoidDuplicates() {
if (isArea() && triangles.size() > 0) {
path = new Path();
}
for (Triangle t : triangles) {
for (int l = 0; l < 3; l++) {
Node n = t.getVert()[l].getNode();
// FIXME this is costly
if (!path.getNodes().contains(n)) {
path.add(n);
}
}
}
((ArrayList)triangles).trimToSize();
trimPath();
//clearBounds();
}
public void trimOutlinePath()
{
if (outlinePath != null )
outlinePath.trimPath();
}
public void saveOutline()
{
if (Configuration.getConfiguration().outlineAreaFormat) {
outlinePath = path;
}
}
public void trimPath()
{
if (path != null )
path.trimPath();
}
}