/**
* 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
* Copyright (C) 2007, 2008 Kai Krueger
*/
package net.sharenav.osmToShareNav;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.Member;
import net.sharenav.osmToShareNav.model.Relation;
import net.sharenav.osmToShareNav.model.Node;
import net.sharenav.osmToShareNav.model.Way;
import net.sharenav.osmToShareNav.model.Entity;
import net.sharenav.osmToShareNav.model.name.Names;
import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
/**
* @author hmueller
*
*/
public class Relations {
private final OsmParser parser;
private final Configuration conf;
int triangles = 0;
int areas = 0;
public Relations(OsmParser parser, Configuration conf) {
if (conf.verbose >= 0) {
System.out.println("Triangulating relations");
}
this.parser = parser;
this.conf = conf;
processRelations();
parser.resize();
if (conf.verbose >= 0) {
System.out.println("Remaining after relation processing (triangulation etc.):");
System.out.println(" Nodes: " + parser.getNodes().size());
System.out.println(" Ways: " + parser.getWays().size());
System.out.println(" Relations: " + parser.getRelations().size());
System.out.println(" Areas: " + areas);
System.out.println(" Triangles: " + triangles);
}
}
/**
*
*/
private void processRelations() {
int validRelationCount = 0;
int invalidRelationCount = 0;
int houseNumberCount = 0;
int houseNumberInterpolationIgnoreCount = 0;
int houseNumberRelationAcceptCount = 0;
int houseNumberRelationProblemCount = 0;
int houseNumberRelationIgnoreCount = 0;
int boundaryIgnore = 0;
HashMap<Long, Way> wayHashMap = parser.getWayHashMap();
Map<Long, Node> nodeHashMap = parser.getNodeHashMap();
ArrayList<Way> removeWays = new ArrayList<Way>();
Way firstWay = null;
Iterator<Relation> i = parser.getRelations().iterator();
rel: while (i.hasNext()) {
firstWay = null;
Relation r = i.next();
// System.out.println("check relation " + r + "is valid()=" + r.isValid() );
if (r.isValid()) {
Way outerWay = null;
validRelationCount++;
if (validRelationCount % 100 == 0 && Configuration.getConfiguration().verbose >= 0) {
System.out.println("info: handled " + validRelationCount + " relations");
System.out.println("info: currently handling relation type " + r.getAttribute("type"));
}
String tagType = r.getAttribute("type");
String tagValue = r.getAttribute(tagType);
if (tagType != null && conf.getRelationExpansions().get(tagType + "=" + tagValue) != null
&& conf.getRelationExpansions().get(tagType + "=" + tagValue)
&& r.getTags().size() > 1) {
// System.out.println("Checking " + tagType + "=" + tagValue);
// FIXME check also that specialisation matches
r.getTags().remove("type");
for (Long ref : r.getWayIds()) {
Way w = wayHashMap.get(ref);
String key = "_route_" + tagValue;
//System.out.println ("way: " + w + " key: " + key);
long newId = 0;
if (w.containsKey(key) && tagType != null &&
conf.getRelationExpansionsCombine().get(tagType + "=" + tagValue) != null &&
conf.getRelationExpansionsCombine().get(tagType + "=" + tagValue)) {
// Combine the tags into one way
newId = Long.valueOf(w.getAttribute(key));
Way w2 = wayHashMap.get(newId);
//System.out.println ("found key: " + key + " w2 = " + w2 + "newId =" + newId);
if (w2 != null) {
//System.out.println ("way w2: " + w2 + " key: " + key);
for (String t : r.getTags()) {
if (w2.containsKey(t)) {
// don't add to name if already there
String [] values = w2.getAttribute(t).split(";");
boolean exists = false;
for (int v = 0; v < values.length ; v++) {
if (values[v].equals(r.getAttribute(t))) {
exists = true;
}
}
// Way.java truncates more when alternative name is also used
// (name/Names.java in ShareNav is where the EOF exception happens in reading of name)
// might be that it's a byte which is used as the string length in SearchList.java / Names.java
if (!exists && (w2.getAttribute(t).length() + r.getAttribute(t).length()) < 125)
w2.setAttribute(t, w2.getAttribute(t) + ";" + r.getAttribute(t));
} else {
w2.setAttribute(t, r.getAttribute(t));
}
}
w2.resetType(conf);
// polish.api.bigstyles
short type = w2.getType(conf);
}
} else {
newId = FakeIdGenerator.makeFakeId();
w.setAttribute(key, Long.toString(newId));
// copy path from w into w2 with new id
// FIXME should reverse way if role is "backward"
Way w2 = new Way(newId, w);
for (String t : r.getTags()) {
if (w2.containsKey(t)) {
if (!w2.getAttribute(t).equals(r.getAttribute(t)))
w2.setAttribute(t, w2.getAttribute(t) + ";" + r.getAttribute(t));
} else {
w2.setAttribute(t, r.getAttribute(t));
}
}
w2.resetType(conf);
w.setAttribute(key, Long.toString(newId));
// polish.api.bigstyles
short type = w2.getType(conf);
//System.out.println("adding way: " + w2 + " newId = " + newId);
parser.addWay(w2);
if (w2.isArea()) {
w2.saveOutline();
}
}
}
} else if (conf.useHouseNumbers && ("associatedStreet".equals(r.getAttribute("type")) ||
"street".equals(r.getAttribute("type")))) {
//System.out.println("Handling housenumber relation " + r.toUrl());
int wayCount = 0;
for (Long ref : r.getWayIds(Member.ROLE_STREET)) {
wayCount++;
// should only need to be stored for one way due to way match rewriting
if (wayCount > 1) {
break;
}
Way w = wayHashMap.get(ref);
for (Long noderef : r.getNodeIds(Member.ROLE_HOUSE)) {
Node n = nodeHashMap.get(noderef);
if (n != null) {
w.houseNumberAdd(n);
// add tag to point from
// System.out.println("setting node " + n + " __wayid to " + w.id);
houseNumberCount++;
n.setAttribute("__wayid", w.id.toString());
} else {
System.out.println("Warning: Relation " + r.toUrl() + " - ignoring house node noderef " + noderef + ", nodeHashMap.get(" + noderef + ") returned null");
System.out.println(" -- possibly because the way the node http://www.openstreetmap.org/browse/node/" + noderef + " belongs to is a member of two different housenumber relations");
}
//System.out.println("Housenumber relation " + r.toUrl() + " - added node " + );
}
for (Long wayref : r.getWayIds(Member.ROLE_HOUSE)) {
Way housew = wayHashMap.get(wayref);
//System.out.println("Adding area way " + housew + " to housenumber relation " + r.toUrl());
if (housew.containsKey("addr:interpolation")) {
// FIXME add handling of interpolations, see http://wiki.openstreetmap.org/wiki/Proposed_features/House_numbers/Karlsruhe_Schema
//
// How this is done:
// * look at the existing housenumber and do the interpolation calculations
// * for each housenumber to add, do housenumber adding like is done below inside if (!n.containsKey("__wayid"))
// A practical note: Apparently the great majority of these (at least in Germany where they're used a lot) are simple straight lines, ways with
// two nodes, so just adding the simple code for interpolating for two-node ways would go a long way in practice to have
// more ShareNav-findable addresses.
//System.out.println("Warning: Relation " + r.toUrl() + " - ignoring interpolation way " + housew.toUrl());
houseNumberInterpolationIgnoreCount++;
} else if (housew.isArea() || housew.isClockwise() || housew.isCounterClockwise()) {
// won't use just isArea() as some house roles won't work properly:
// fenced areas, caravan sites or others not recognized as areas by ShareNav
// examples:
// http://www.openstreetmap.org/browse/way/82294062
// http://www.openstreetmap.org/browse/way/42270858
// There's no need to add a node at this stage, as this will be indexed as a building.
// Node n = housew.getMidPointNodeByBounds();
if (!housew.containsKey("__wayid")) {
// if (n != null && !n.containsKey("__wayid")) {
// FIXME if we want to have nodes for housenumbers in ShareNav
// (if we want to list housenumber nodes in way data structure),
// we'll probably need to create fake IDs and add the nodes to parser. Takes more space and gets more complicated.
// w.houseNumberAdd(n);
// FIXME we should do this without fake ids like this is done for ordinary houses
//n.id = FakeIdGenerator.makeFakeId();
// n.setAttribute("__wayid", w.id.toString());
w.setAttribute("__wayid", w.id.toString());
houseNumberCount++;
//System.out.println("Housenumber relation " + r.toUrl() + " - added node " + );
//for (String t : housew.getTags()) {
// n.setAttribute(t, housew.getAttribute(t));
//}
//n.resetType(conf);
//n.getType(conf);
//parser.addNode(n);
} else {
System.out.println("Warning: ignoring map data: __wayid in house number relation was already set for way: " + housew.toUrl());
}
} else {
houseNumberRelationProblemCount++;
System.out.println("Warning: ignoring map data: house number relation way was not an area or a an interpolation way: " + housew.toUrl());
}
}
}
houseNumberRelationAcceptCount++;
i.remove();
} else if ("multipolygon".equals(r.getAttribute("type"))) {
if (r.getAttribute("admin_level") != null){
// FIXME should not be blatantly ignore, but instead should be handled
// if enabled in style file
//System.out.println("Warning: ignoring relation with admin_level tag , relation" + r);
boundaryIgnore++;
continue;
}
if ("archipelago".equalsIgnoreCase(r.getAttribute("place"))) {
// FIXME should not be blatantly ignore, but instead should be handled
// if enabled in style file
System.out.println("Warning: ignoring relation with place=archipelago tag, relation " + r);
//boundaryIgnore++;
continue;
}
if ("administrative".equalsIgnoreCase(r.getAttribute("boundary"))) {
// FIXME should not be blatantly ignore, but instead should be handled
// if enabled in style file
//System.out.println("Warning: ignoring relation with boundary=administrative tag, relation " + r);
boundaryIgnore++;
continue;
}
// System.out.println("Starting to handle multipolygon relation");
// System.out.println(" see http://www.openstreetmap.org/browse/relation/" + r.id);
if (r.getWayIds(Member.ROLE_OUTER).size() == 0) {
System.out.println("Relation has no outer member");
System.out.println(" see " + r.toUrl() + " I'll ignore this relation");
continue;
}
// System.out.println("outer size: " + r.getWayIds(Member.ROLE_OUTER).size());
// System.out.println("Triangulate relation " + r.id);
Area a = new Area();
// if (r.id == 405925 ) {
// a.debug=true;
// }
for (Long ref : r.getWayIds(Member.ROLE_OUTER)) {
// if (ref == 39123631) {
// a.debug = true;
// }
Way w = wayHashMap.get(ref);
// FIXME can be removed when proper coastline support exists
//if ("coastline".equalsIgnoreCase(w.getAttribute("natural"))) {
// this shouldn't cause trouble anymore, but give a message just in case
//System.out.println("Warning: saw natural=coastline way " + w + " in relation " + r);
//continue rel;
//}
//System.out.println("Handling outer way http://www.openstreetmap.org/browse/way/" + ref);
if (w == null) {
System.out.println("Way " + w.toUrl() + " was not found but referred as outline in ");
System.out.println(" relation " + r.toUrl() + " I'll ignore this relation");
continue rel;
}
long clonedWayId = FakeIdGenerator.makeFakeId();
Way w2 = new Way(clonedWayId, w);
//w2.cloneTagsButNoId(w);
Outline no = createOutline(w2);
if (no != null) {
// //System.out.println("Adding way " + w.toUrl() + " as OUTER");
a.addOutline(no);
if (firstWay == null) {
if (w2.triangles != null) {
System.out.println("Strange, this outline is already triangulated! May be a duplicate, I'll ignore it");
System.out.println(" way " + w.toUrl());
System.out.println(" relation " + r.toUrl());
continue rel;
}
firstWay = w2;
} else {
removeWays.add(w2);
}
}
parser.addWay(w2);
if (w2.isArea()) {
w2.saveOutline();
}
outerWay = w2;
// FIXME? Possibly outline could have more
// values for the tag than what is used
// for relation; so it's conveivable
// we should only remove one value instead
// of the key
for (String key : r.getTags()) {
if (r.getAttribute(key).equals(w.getAttribute(key))) {
w.deleteTag(key);
w.resetType(conf);
}
}
removeWays.add(w);
}
for (Long ref : r.getWayIds(Member.ROLE_INNER)) {
Way w = wayHashMap.get(ref);
if (w == null) {
System.out.println("Way " + w.toUrl() + " was not found but referred as INNER in ");
System.out.println(" relation "+ r.toUrl() + " I'll ignore this relation");
continue rel;
}
//System.out.println("Adding way " + w.toUrl() + " as INNER");
Outline no = createOutline(w);
if (no != null) {
a.addHole(no);
outerWay.addHole(w);
// FIXME? Possibly outline could have more
// values for the tag than what is used
// for relation; so it's conveivable
// we should only remove one value instead
// of the key
for (String key : r.getTags()) {
if (r.getAttribute(key).equals(w.getAttribute(key))) {
w.deleteTag(key);
w.resetType(conf);
}
if (key.equals("natural") && r.getAttribute(key).equals("water") && w.getAttribute("name") != null && !"".equals(w.getAttribute("name"))) {
// FIXME
// a hole in a water could be an islet also, perhaps decide on size. perhaps also something else, like a rock or a tree.
//System.out.println("Adding place=island for way " + w);
w.put("place", "island");
w.resetType(conf);
}
}
removeWays.add(w);
}
}
try {
if (Configuration.getConfiguration().triangleAreaFormat) {
List<Triangle> areaTriangles = a.triangulate();
firstWay.triangles = areaTriangles;
//firstWay.recreatePathAvoidDuplicates();
firstWay.recreatePath();
triangles += areaTriangles.size();
//System.out.println("areaTriangles.size for relation " + r.toUrl() + " :" + areaTriangles.size());
areas += 1;
}
// neither methdod below to remove type tag works
//r.getTags().remove("type");
//if (r.containsKey("type")) {
// r.deleteTag("type");
//}
// so move all tags except type
for (String key : r.getTags()) {
if (!"type".equals(key)) {
firstWay.setAttribute(key, r.getAttribute(key));
}
}
//if (r.getTags().size() > 0) {
// firstWay.replaceTags(r);
//}
} catch (Exception e) {
System.out.println("Something went wrong when trying to triangulate relation ");
System.out.println(" " + r.toUrl() + " I'll attempt to ignore this relation");
e.printStackTrace();
}
i.remove();
}
} else { // ! r.isValid()
invalidRelationCount++;
System.out.println("Relation not valid: " + r);
if (conf.useHouseNumbers && ("associatedStreet".equals(r.getAttribute("type"))
|| "street".equals(r.getAttribute("type")))) {
houseNumberRelationIgnoreCount++;
}
}
}
int numMultipolygonMembersNotRemoved = 0;
for (Way w : removeWays) {
if (!w.isAccessForAnyRouting()) {
// all relations have been handled, remove ways not needed for routing
// FIXME - I think this is wrong - ways which are members of a multipolygon
// can have other tags which makes them renderable, and this removes them if they're not
// routable. Actually we should check whether the way is used for any rendering (check the type?)
// maybe like this:
if (w.getType(conf) < 1) {
//System.out.println("Removing way: " + w);
parser.removeWay(w);
} else {
//System.out.println("Not removing way because type >= 1: " + w);
}
}
else {
numMultipolygonMembersNotRemoved++;
//System.out.println(" info: multipolygon member " + w.toUrl() + " not removed because it is routable");
}
}
if (numMultipolygonMembersNotRemoved > 0 && conf.verbose >= 0) {
System.out.println(" info: " + numMultipolygonMembersNotRemoved + " multipolygon members not removed because they are routable");
}
//parser.resize();
if (conf.verbose >= 0) {
System.out.println("info: processed " + validRelationCount + " valid relations");
System.out.println("info: ignored " + invalidRelationCount + " non-valid relations");
System.out.println("info: accepted " + houseNumberCount + " housenumber-to-street connections from associatedStreet relations");
System.out.println("info: ignored " + houseNumberRelationIgnoreCount + " associatedStreet (housenumber) relations");
System.out.println("info: ignored " + houseNumberInterpolationIgnoreCount + " associatedStreet (housenumber) relation interpolation ways");
System.out.println("info: processed " + houseNumberRelationAcceptCount + " associatedStreet (housenumber) relations"
+ ", of which " + houseNumberRelationProblemCount + " had problems");
System.out.println("info: ignored " + boundaryIgnore + " boundary=administrative multipolygon relations");
}
}
/**
* @param wayHashMap
* @param r
*/
private Outline createOutline(Way w) {
Outline o = null;
if (w != null) {
Node last = null;
o = new Outline();
o.setWayId(w.id);
for (Node n : w.getNodes()) {
if (last != n) {
o.append(new Vertex(n, o));
}
last = n;
}
}
return o;
}
}