/*
* Copyright (C) 2012, Gerd Petermann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* 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.
*/
package uk.me.parabola.splitter;
import uk.me.parabola.splitter.Relation.Member;
import uk.me.parabola.splitter.tools.Long2IntClosedMap;
import uk.me.parabola.splitter.tools.Long2IntClosedMapFunction;
import uk.me.parabola.splitter.tools.OSMId2ObjectMap;
import uk.me.parabola.splitter.tools.SparseBitSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Analyzes elements that should be written to multiple tiles
* to find out what details are needed in each tile.
*/
class MultiTileProcessor
extends AbstractMapProcessor
{
private final static int PHASE1_RELS_ONLY =
1;
private final static int PHASE2_WAYS_ONLY =
2;
private final static int PHASE3_NODES_AND_WAYS =
3;
private final static int PHASE4_WAYS_ONLY =
4;
private final boolean addParentRels =
false;
private final static byte MEM_NODE_TYPE =
1;
private final static byte MEM_WAY_TYPE =
2;
private final static byte MEM_REL_TYPE =
3;
private final static byte MEM_INVALID_TYPE = -
1;
private final static int PROBLEM_WIDTH = Utils.
toMapUnit(180.0);
protected final static String[] NAME_TAGS =
{"name",
"name:en",
"int_name",
"note"};
private final static String NOT_SORTED_MSG =
"Maybe the IDs are not sorted. This is not supported with keep-complete=true or --problem-list";
private int phase = PHASE1_RELS_ONLY
;
private final DataStorer dataStorer
;
private final AreaDictionary areaDictionary
;
private Long2ObjectLinkedOpenHashMap
<MTRelation
> relMap =
new Long2ObjectLinkedOpenHashMap
<>();
private Long2IntClosedMapFunction nodeWriterMap
;
private Long2IntClosedMapFunction wayWriterMap
;
private Long2IntClosedMapFunction relWriterMap
;
private int [] nodeLons
;
private int [] nodeLats
;
private SparseBitSet problemRels =
new SparseBitSet
();
private SparseBitSet neededWays =
new SparseBitSet
();
private SparseBitSet neededNodes =
new SparseBitSet
();
private OSMId2ObjectMap
<Rectangle> wayBboxMap =
new OSMId2ObjectMap
<>();
private SparseBitSet mpWays =
new SparseBitSet
();
private OSMId2ObjectMap
<JoinedWay
> mpWayEndNodesMap =
new OSMId2ObjectMap
<>();
/** each bit represents one area/tile */
private final AreaSet workWriterSet =
new AreaSet
();
private long lastCoordId =
Long.
MIN_VALUE;
private int foundWays
;
private int neededNodesCount
;
private int neededWaysCount
;
private int neededMpWaysCount
;
private int visitId
;
MultiTileProcessor
(DataStorer dataStorer, LongArrayList problemWayList, LongArrayList problemRelList
) {
this.
dataStorer = dataStorer
;
this.
areaDictionary = dataStorer.
getAreaDictionary();
for (long id: problemWayList
){
neededWays.
set(id
);
}
for (long id: problemRelList
){
problemRels.
set(id
);
}
// we allocate this once to avoid massive resizing with large number of tiles
neededMpWaysCount = mpWays.
cardinality();
if (problemRelList.
isEmpty()) {
phase = PHASE2_WAYS_ONLY
;
}
return;
}
@
Override
public boolean skipTags
() {
if (phase == PHASE1_RELS_ONLY
)
return false;
return true;
}
@
Override
public boolean skipNodes
() {
if (phase == PHASE3_NODES_AND_WAYS
)
return false;
return true;
}
@
Override
public boolean skipWays
() {
if (phase == PHASE1_RELS_ONLY
)
return true;
return false;
}
@
Override
public boolean skipRels
() {
if (phase == PHASE1_RELS_ONLY
&& problemRels.
cardinality() > 0)
return false;
return true;
}
@
Override
public int getPhase
() {
return phase
;
}
@
Override
public void processNode
(Node node
) {
if (phase == PHASE3_NODES_AND_WAYS
&& neededNodes.
get(node.
getId())) {
storeCoord
(node
);
// return memory to GC
neededNodes.
clear(node.
getId());
}
}
@
Override
public void processWay
(Way way
) {
if (phase == PHASE2_WAYS_ONLY
){
if (!neededWays.
get(way.
getId()))
return;
for (long id : way.
getRefs()) {
neededNodes.
set(id
);
}
if (mpWays.
get(way.
getId())){
mpWays.
clear(way.
getId());
int numRefs = way.
getRefs().
size();
if (numRefs
>=
2){
JoinedWay joinedWay =
new JoinedWay
(way.
getRefs().
getLong(0), way.
getRefs().
getLong(numRefs-
1));
mpWayEndNodesMap.
put(way.
getId(), joinedWay
);
}
}
foundWays++
;
}
else if (phase == PHASE3_NODES_AND_WAYS
){
if (!neededWays.
get(way.
getId()))
return;
// calculate the bbox
int numRefs = way.
getRefs().
size();
boolean isClosed = numRefs
> 1 && way.
getRefs().
get(0).
equals(way.
getRefs().
get(numRefs-
1));
workWriterSet.
clear();
Rectangle wayBbox = getWayBbox
(way.
getId(), way.
getRefs());
if (wayBbox ==
null)
return;
wayBboxMap.
put(way.
getId(), wayBbox
);
if (isClosed
){
checkBoundingBox
(workWriterSet, wayBbox
);
}
else {
addWritersOfWay
(workWriterSet, wayBbox, way.
getId(), way.
getRefs());
}
int wayWriterIdx
;
if (workWriterSet.
isEmpty())
wayWriterIdx = UNASSIGNED
;
else
wayWriterIdx = areaDictionary.
translate(workWriterSet
);
try{
wayWriterMap.
add(way.
getId(), wayWriterIdx
);
}catch (IllegalArgumentException e
){
System.
err.
println(e.
getMessage());
throw new SplitFailedException
(NOT_SORTED_MSG
);
}
}
else if (phase == PHASE4_WAYS_ONLY
){
// propagate the ways writers to all nodes
if (!neededWays.
get(way.
getId()))
return;
int wayWriterIdx = wayWriterMap.
getRandom(way.
getId());
if (wayWriterIdx
!= UNASSIGNED
){
AreaSet wayWriterSet = areaDictionary.
getSet(wayWriterIdx
);
for (long id : way.
getRefs()) {
addOrMergeWriters
(nodeWriterMap, wayWriterSet, wayWriterIdx, id
);
}
}
}
}
@
Override
public void processRelation
(Relation rel
) {
// TODO: we store all relations here, no matter how many are needed. Another approach would be to store
// the rels in the problem list and read again until all sub rels of these problem rels are found or
// known as missing. This can require many more read passes for relations, but can help if this phase
// starts to be a memory bottleneck.
if (phase == PHASE1_RELS_ONLY
){
MTRelation myRel =
new MTRelation
(rel
);
relMap.
put(myRel.
getId(), myRel
);
}
}
@
Override
public boolean endMap
() {
if (phase == PHASE1_RELS_ONLY
){
stats
("Finished collecting relations.");
Utils.
printMem();
System.
out.
println("starting to resolve relations containing problem relations ...");
// add all ways and nodes of problem rels so that we collect the coordinates
markProblemMembers
();
if (addParentRels
){
// we want to see the parent rels, but not all children of all parents
markParentRels
();
}
// free memory for rels that are not causing any trouble
relMap.
long2ObjectEntrySet().
removeIf(e -
> !problemRels.
get(e.
getLongKey()));
problemRels =
null;
// reallocate to the needed size
relMap =
new Long2ObjectLinkedOpenHashMap
<>(relMap
);
//System.out.println("Finished adding parents and members of problem relations to problem lists.");
System.
out.
println("Finished adding members of problem relations to problem lists.");
stats
("starting to collect ids of needed way nodes ...");
neededMpWaysCount = mpWays.
cardinality();
neededWaysCount = neededWays.
cardinality();
++phase
;
}
else if (phase == PHASE2_WAYS_ONLY
){
stats
("Finished collecting problem ways.");
neededNodesCount = neededNodes.
cardinality();
// critical part: we have to allocate possibly large arrays here
nodeWriterMap =
new Long2IntClosedMap
("node", neededNodesCount, UNASSIGNED
);
wayWriterMap =
new Long2IntClosedMap
("way", foundWays, UNASSIGNED
);
dataStorer.
setWriterMap(DataStorer.
NODE_TYPE, nodeWriterMap
);
dataStorer.
setWriterMap(DataStorer.
WAY_TYPE, wayWriterMap
);
nodeLons =
new int[neededNodesCount
];
nodeLats =
new int[neededNodesCount
];
System.
out.
println("Found " + Utils.
format(foundWays
) +
" of " + Utils.
format(neededWaysCount
) +
" needed ways.");
System.
out.
println("Found " + Utils.
format(mpWayEndNodesMap.
size()) +
" of " + Utils.
format(neededMpWaysCount
) +
" needed multipolygon ways.");
stats
("Starting to collect coordinates for " + Utils.
format(neededNodesCount
) +
" needed nodes.");
Utils.
printMem();
++phase
;
}
else if (phase == PHASE3_NODES_AND_WAYS
){
System.
out.
println("Found " + Utils.
format(nodeWriterMap.
size()) +
" of " + Utils.
format(neededNodesCount
) +
" needed nodes.");
Utils.
printMem();
mpWays =
null;
neededNodes =
null;
System.
out.
println("Calculating tiles for problem relations...");
calcWritersOfRelWaysAndNodes
();
// return coordinate memory to GC
nodeLats =
null;
nodeLons =
null;
calcWritersOfMultiPolygonRels
();
mergeRelMemWriters
();
propagateWritersOfRelsToMembers
();
mpWayEndNodesMap.
clear();
wayBboxMap =
null;
relWriterMap =
new Long2IntClosedMap
("rel", relMap.
size(), UNASSIGNED
);
for (Entry
<MTRelation
> entry : relMap.
long2ObjectEntrySet()){
int val = entry.
getValue().
getMultiTileWriterIndex();
if (val
!= UNASSIGNED
){
try{
relWriterMap.
add(entry.
getLongKey(), val
);
}catch (IllegalArgumentException e
){
System.
err.
println(e
);
throw new SplitFailedException
(NOT_SORTED_MSG
);
}
}
}
relMap =
null;
dataStorer.
setWriterMap(DataStorer.
REL_TYPE, relWriterMap
);
stats
("Making sure that needed way nodes of relations are written to the correct tiles...");
++phase
;
}
else if (phase == PHASE4_WAYS_ONLY
){
stats
("Finished processing problem lists.");
return true;
}
return false; // not done yet
}
/**
* Mark all members of given problem relations as problem cases.
*/
private void markProblemMembers
() {
ArrayList<MTRelation
> visited =
new ArrayList<>();
for (MTRelation rel: relMap.
values()){
if (!problemRels.
get(rel.
getId()))
continue;
incVisitID
();
visited.
clear();
MarkNeededMembers
(rel,
0, visited
);
assert visited.
size() ==
0;
}
}
/**
* Mark the ways and nodes of a relation as problem cases. If the relation
* contains sub relations, the routine calls itself recursively.
* @param rel the relation
* @param depth used to detect loops
* @param visited
* @return
*/
private void MarkNeededMembers
(MTRelation rel,
int depth,
ArrayList<MTRelation
> visited
){
if (rel.
getLastVisitId() == visitId
)
return;
rel.
setLastVisitId(visitId
);
if (depth
> 15){
System.
out.
println("MarkNeededMembers reached max. depth: " + rel.
getId() +
" " + depth
);
return ;
}
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
byte memType = rel.
memTypes[i
];
if (memType == MEM_WAY_TYPE
){
neededWays.
set(memId
);
if (rel.
isMultiPolygon())
mpWays.
set(memId
);
}
else if (memType == MEM_NODE_TYPE
)
neededNodes.
set(memId
);
else if (memType == MEM_REL_TYPE
){
MTRelation subRel = relMap.
get(memId
);
if (subRel ==
null)
continue;
if (subRel.
getLastVisitId() == visitId
)
loopAction
(rel, subRel, visited
);
else {
problemRels.
set(memId
);
visited.
add(subRel
);
MarkNeededMembers
(subRel, depth+
1, visited
);
visited.
remove(visited.
size()-
1);
}
}
}
}
/**
* Mark the parents of problem relations as problem relations.
*/
private void markParentRels
(){
while (true){
boolean changed =
false;
for (MTRelation rel: relMap.
values()){
if (rel.
hasRelMembers() ==
false || problemRels.
get(rel.
getId()))
continue;
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
if (rel.
memTypes[i
] == MEM_REL_TYPE
&& problemRels.
get(memId
)) {
problemRels.
set(rel.
getId());
rel.
setAddedAsParent();
System.
out.
println("Adding parent of problem rel " + memId +
" to problem list: " + rel.
getId());
changed =
true;
break;
}
}
}
if (!changed
)
return;
}
}
/**
* Calculate the writers for each relation based on the
* nodes and ways.
*/
private void calcWritersOfRelWaysAndNodes
() {
for (MTRelation rel: relMap.
values()){
if (false ==
(rel.
hasWayMembers() || rel.
hasNodeMembers()) )
continue;
AreaSet writerSet =
new AreaSet
();
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
boolean memFound =
false;
if (rel.
memTypes[i
] == MEM_NODE_TYPE
){
int pos = nodeWriterMap.
getKeyPos(memId
);
if (pos
>=
0){
addWritersOfPoint
(writerSet, nodeLats
[pos
], nodeLons
[pos
]);
memFound =
true;
}
}
else if (rel.
memTypes[i
] == MEM_WAY_TYPE
){
int idx = wayWriterMap.
getRandom(memId
);
if (idx
!= UNASSIGNED
){
writerSet.
or(areaDictionary.
getSet(idx
));
memFound =
true;
}
if (wayBboxMap.
get(memId
) !=
null)
memFound =
true;
}
else if (rel.
memTypes[i
] == MEM_REL_TYPE
)
continue; // handled later
if (!memFound
) {
rel.
setNotComplete();
continue;
}
}
if (!writerSet.
isEmpty()){
int idx = areaDictionary.
translate(writerSet
);
rel.
setMultiTileWriterIndex(idx
);
}
}
}
/**
* Multipolygon relations should describe one or more closed polygons.
* We calculate the writers for each of the polygons.
*/
private void calcWritersOfMultiPolygonRels
() {
// recurse thru sub relations
ArrayList<MTRelation
> visited =
new ArrayList<>();
for (MTRelation rel: relMap.
values()){
AreaSet relWriters =
new AreaSet
();
if (rel.
isMultiPolygon()){
if (rel.
hasRelMembers()){
incVisitID
();
visited.
clear();
orSubRelWriters
(rel,
0, visited
);
}
checkSpecialMP
(relWriters, rel
);
if (!relWriters.
isEmpty()){
int writerIdx = areaDictionary.
translate(relWriters
);
rel.
setMultiTileWriterIndex(writerIdx
);
int touchedTiles = relWriters.
cardinality();
if (touchedTiles
> dataStorer.
getNumOfAreas() /
2 && dataStorer.
getNumOfAreas() > 10){
System.
out.
println("Warning: rel " + rel.
getId() +
" touches " + touchedTiles +
" tiles.");
}
}
}
}
}
/**
* Or-combine all writers of the members of a relation
*/
private void mergeRelMemWriters
() {
// or combine the writers of sub-relations with the parent relation
ArrayList<MTRelation
> visited =
new ArrayList<>();
for (MTRelation rel: relMap.
values()){
incVisitID
();
visited.
clear();
orSubRelWriters
(rel,
0, visited
);
}
}
/**
* Make sure that all the elements of a relation are written to the same tiles as the relation info itself.
*/
private void propagateWritersOfRelsToMembers
() {
// make sure that the ways and nodes of the problem relations are written to all needed tiles
for (MTRelation rel: relMap.
values()){
if (rel.
wasAddedAsParent())
continue;
int relWriterIdx = rel.
getMultiTileWriterIndex();
if (relWriterIdx == UNASSIGNED
)
continue;
AreaSet relWriters = areaDictionary.
getSet(relWriterIdx
);
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
switch (rel.
memTypes[i
]){
case MEM_WAY_TYPE:
addOrMergeWriters
(wayWriterMap, relWriters, relWriterIdx, memId
);
break;
case MEM_NODE_TYPE:
addOrMergeWriters
(nodeWriterMap, relWriters, relWriterIdx, memId
);
break;
default:
}
}
}
}
/**
* Store the coordinates of a node in the most appropriate data structure.
* @param node
*/
private void storeCoord
(Node node
) {
long id = node.
getId();
if (lastCoordId
>= id
){
System.
err.
println("Error: Node ids are not sorted. Use e.g. osmosis to sort the input data.");
System.
err.
println("This is not supported with keep-complete=true or --problem-list");
throw new SplitFailedException
("Node ids are not sorted");
}
int nodePos = -
1;
try{
nodePos = nodeWriterMap.
add(id, UNASSIGNED
);
}catch (IllegalArgumentException e
){
System.
err.
println(e.
getMessage());
throw new SplitFailedException
(NOT_SORTED_MSG
);
}
nodeLons
[nodePos
] = node.
getMapLon();
nodeLats
[nodePos
] = node.
getMapLat();
lastCoordId = id
;
}
/**
* If a relation contains relations, or-combine the writers of the sub-
* relation with the writes of the parent relation . The routine calls
* itself recursively when the sub relation contains sub relations.
* @param rel the relation
* @param depth used to detect loops
* @return
*/
private void orSubRelWriters
(MTRelation rel,
int depth,
ArrayList<MTRelation
> visited
){
if (rel.
getLastVisitId() == visitId
)
return;
rel.
setLastVisitId(visitId
);
if (depth
> 15){
System.
out.
println("orSubRelWriters reached max. depth: " + rel.
getId() +
" " + depth
);
return ;
}
AreaSet relWriters =
new AreaSet
();
int relWriterIdx = rel.
getMultiTileWriterIndex();
if (relWriterIdx
!= UNASSIGNED
)
relWriters.
or(areaDictionary.
getSet(relWriterIdx
));
boolean changed =
false;
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
if (rel.
memTypes[i
] == MEM_REL_TYPE
){
MTRelation subRel = relMap.
get(memId
);
if (subRel ==
null)
continue;
if (subRel.
getLastVisitId() == visitId
)
loopAction
(rel, subRel, visited
);
else {
visited.
add(rel
);
orSubRelWriters
(subRel, depth+
1, visited
);
visited.
remove(visited.
size()-
1);
int memWriterIdx = subRel.
getMultiTileWriterIndex();
if (memWriterIdx == UNASSIGNED || memWriterIdx == relWriterIdx
){
continue;
}
AreaSet memWriters = areaDictionary.
getSet(memWriterIdx
);
int oldSize = relWriters.
cardinality();
relWriters.
or(memWriters
);
if (oldSize
!= relWriters.
cardinality())
changed =
true;
}
}
}
if (changed
){
rel.
setMultiTileWriterIndex(areaDictionary.
translate(relWriters
));
}
}
/**
* Report some numbers regarding memory usage
* @param msg
*/
private void stats
(String msg
){
System.
out.
println("Stats for " + getClass
().
getSimpleName() +
" pass " + phase
);
if (problemRels
!=
null)
System.
out.
println(" " + problemRels.
getClass().
getSimpleName() +
" problemRels contains now " + Utils.
format(problemRels.
cardinality()) +
" Ids.");
if (neededWays
!=
null)
System.
out.
println(" " + neededWays.
getClass().
getSimpleName() +
" neededWays contains now " + Utils.
format(neededWays.
cardinality())+
" Ids.");
if (mpWays
!=
null)
System.
out.
println(" " + mpWays.
getClass().
getSimpleName() +
" mpWays contains now " + Utils.
format(mpWays.
cardinality())+
" Ids.");
if (neededNodes
!=
null)
System.
out.
println(" " + neededNodes.
getClass().
getSimpleName() +
" neededNodes contains now " + Utils.
format(neededNodes.
cardinality())+
" Ids.");
if (relMap
!=
null)
System.
out.
println(" Number of stored relations: " + Utils.
format(relMap.
size()));
System.
out.
println(" Number of stored tile combinations in multiTileDictionary: " + Utils.
format(areaDictionary.
size()));
if (phase == PHASE4_WAYS_ONLY
)
dataStorer.
stats(" ");
System.
out.
println("Status: " + msg
);
}
/**
* Find all writer areas that intersect with a given bounding box.
* @param writerSet an already allocate AreaSet which may be modified
* @param polygonBbox the bounding box
* @return true if any writer bbox intersects the polygon bbox
*/
private boolean checkBoundingBox
(AreaSet writerSet,
Rectangle polygonBbox
){
boolean foundIntersection =
false;
if (polygonBbox
!=
null){
for (int i =
0; i
< dataStorer.
getNumOfAreas(); i++
) {
Rectangle writerBbox = Utils.
area2Rectangle(dataStorer.
getArea(i
),
1);
if (writerBbox.
intersects(polygonBbox
)){
writerSet.
set(i
);
foundIntersection =
true;
}
}
}
return foundIntersection
;
}
/**
* Merge the writers of a parent object with the writes of the child,
* add or update the entry in the Map
* @param map
* @param parentWriters
* @param parentWriterIdx
* @param childId
*/
private void addOrMergeWriters
(Long2IntClosedMapFunction map, AreaSet parentWriters,
int parentWriterIdx,
long childId
) {
int pos = map.
getKeyPos(childId
);
if (pos
< 0)
return;
int childWriterIdx = map.
getRandom(childId
);
if (childWriterIdx
!= UNASSIGNED
){
// we have already calculated writers for this child
if (parentWriterIdx == childWriterIdx
)
return;
// we have to merge (without changing the stored BitSets!)
AreaSet childWriters = areaDictionary.
getSet(childWriterIdx
);
AreaSet mergedWriters =
new AreaSet
(parentWriters
);
mergedWriters.
or(childWriters
);
childWriterIdx = areaDictionary.
translate(mergedWriters
);
}
else
childWriterIdx = parentWriterIdx
;
map.
replace(childId, childWriterIdx
);
}
/**
* Calculate the writers for a given point specified by coordinates.
* Set the corresponding bit in the AreaSet.
* @param writerSet an already allocate AreaSet which may be modified
* @param mapLat latitude value
* @param mapLon longitude value
* @return true if a writer was found
*/
private boolean addWritersOfPoint
(AreaSet writerSet,
int mapLat,
int mapLon
){
AreaGridResult writerCandidates = dataStorer.
getGrid().
get(mapLat,mapLon
);
if (writerCandidates ==
null)
return false;
boolean foundWriter =
false;
for (int n : writerCandidates.
set) {
Area extbbox = dataStorer.
getExtendedArea(n
);
boolean found =
(writerCandidates.
testNeeded) ? extbbox.
contains(mapLat, mapLon
) :
true;
foundWriter |= found
;
if (found
)
writerSet.
set(n
);
}
return foundWriter
;
}
/**
* Find tiles that are crossed by a line specified by two points.
* @param writerSet an already allocate AreaSet which may be modified
* @param possibleWriters a AreaSet that contains the writers to be checked
* @param p1 first point of line
* @param p2 second point of line
*/
private void addWritersOfCrossedTiles
(AreaSet writerSet,
final AreaSet possibleWriters,
final Point p1,
final Point p2
){
for (int i : possibleWriters
) {
Rectangle writerBbox = Utils.
area2Rectangle(dataStorer.
getArea(i
),
1);
if (writerBbox.
intersectsLine(p1.
x,p1.
y,p2.
x,p2.
y))
writerSet.
set(i
);
}
}
/**
* Calculate all writer areas that are crossed or directly "touched" by a way.
* @param writerSet an already allocate AreaSet which may be modified
* @param wayBbox
* @param wayId the id that identifies the way
* @param wayRefs list with the node references
*/
private void addWritersOfWay
(AreaSet writerSet,
Rectangle wayBbox,
long wayId, LongArrayList wayRefs
){
int numRefs = wayRefs.
size();
int foundNodes =
0;
boolean needsCrossTileCheck =
false;
Point p1 =
null,p2 =
null;
for (int i =
0; i
<numRefs
; i++
) {
long id = wayRefs.
getLong(i
);
int pos = nodeWriterMap.
getKeyPos(id
);
if (pos
>=
0){
foundNodes++
;
boolean hasWriters = addWritersOfPoint
(writerSet, nodeLats
[pos
], nodeLons
[pos
]);
if (!hasWriters
)
needsCrossTileCheck =
true;
}
}
if (foundNodes
< numRefs
)
System.
out.
println("Sorry, way " + wayId +
" is missing " +
(numRefs-foundNodes
) +
" node(s).");
if (needsCrossTileCheck ==
false){
int numWriters = writerSet.
cardinality();
if (numWriters ==
0)
needsCrossTileCheck =
true;
else if (numWriters
> 1 && dataStorer.
getAreaDictionary().
mayCross(writerSet
)) {
needsCrossTileCheck =
true;
}
}
if (needsCrossTileCheck
){
AreaSet possibleWriters =
new AreaSet
();
checkBoundingBox
(possibleWriters ,wayBbox
);
// the way did cross a border tile
for (int i =
0; i
<numRefs
; i++
) {
long id = wayRefs.
getLong(i
);
int pos = nodeWriterMap.
getKeyPos(id
);
if (pos
>=
0){
if (i
> 0){
p1 = p2
;
}
p2 =
new Point(nodeLons
[pos
],nodeLats
[pos
]);
if (p1
!=
null){
addWritersOfCrossedTiles
(writerSet, possibleWriters, p1, p2
);
}
}
}
}
}
/**
* Calculate the bbox of the way.
* @param wayId the id that identifies the way
* @param wayRefs the list of node references
* @return a new Area object or null if no node is known
*/
private Rectangle getWayBbox
(long wayId, LongArrayList wayRefs
){
// calculate the bbox
int minLat =
Integer.
MAX_VALUE,minLon =
Integer.
MAX_VALUE;
int maxLat =
Integer.
MIN_VALUE,maxLon =
Integer.
MIN_VALUE;
int numRefs = wayRefs.
size();
for (int i =
0; i
<numRefs
; i++
) {
long id = wayRefs.
getLong(i
);
int pos = nodeWriterMap.
getKeyPos(id
);
if (pos
>=
0){
int lat = nodeLats
[pos
];
int lon = nodeLons
[pos
];
if (lat
< minLat
) minLat = lat
;
if (lat
> maxLat
) maxLat = lat
;
if (lon
< minLon
) minLon = lon
;
if (lon
> maxLon
) maxLon = lon
;
}
}
if (maxLon ==
Integer.
MIN_VALUE|| maxLat ==
Integer.
MIN_VALUE){
System.
out.
println("Sorry, no nodes found for needed way " + wayId
);
return null;
}
return new Rectangle(minLon, minLat,
Math.
max(1, maxLon-minLon
),
Math.
max(1,maxLat-minLat
));
}
/**
* Increment the loop detection ID. If the maximum value is reached,
* reset all IDs and start again.
*/
private void incVisitID
() {
if (visitId ==
Integer.
MAX_VALUE){
// unlikely
visitId =
0;
for (Entry
<MTRelation
> entry : relMap.
long2ObjectEntrySet()){
entry.
getValue().
setLastVisitId(visitId
);
}
}
visitId++
;
}
/*
* Report a loop in a relation
*/
static void loopAction
(MTRelation rel, MTRelation subRel,
ArrayList<MTRelation
> visited
){
if (subRel.
isOnLoop())
return; // don't complain again
if (rel.
getId() == subRel.
getId()){
System.
out.
println("Loop in relation " + rel.
getId() +
": Contains itself as sub relation.");
rel.
markOnLoop();
}
else if (visited.
contains(rel
)){
subRel.
markOnLoop();
StringBuilder sb =
new StringBuilder("Loop in relation " + subRel.
getId() +
". Loop contains relation(s): ");
for (MTRelation r: visited
){
sb.
append(r.
getId());
sb.
append(' ');
r.
markOnLoop();
}
System.
out.
println(sb
);
}
else {
System.
out.
println("Duplicate sub relation in relation " + rel.
getId() +
". Already looked at member " + subRel.
getId() +
"." );
}
}
/**
* Handle multipolygon relations that have too large bboxes.
* TODO: handle polygons that cross the 180/-180 border
* @param relWriters
* @param rel
*/
private void checkSpecialMP
(AreaSet relWriters, MTRelation rel
) {
long[] joinedWays =
null;
List<Long> wayMembers =
new LinkedList<>();
LongArrayList polygonWays =
new LongArrayList
();
for (int i =
0; i
< rel.
numMembers; i++
){
long memId = rel.
memRefs[i
];
if (rel.
memTypes[i
] == MEM_WAY_TYPE
&& "inner".
equals(rel.
memRoles[i
]) ==
false){
wayMembers.
add(memId
);
}
}
boolean complainedAboutSize =
false;
Rectangle mpBbox =
null;
boolean hasMissingWays =
false;
while (wayMembers.
size() > 0){
polygonWays.
clear();
mpBbox =
null;
boolean closed =
false;
while (true){
boolean changed =
false;
for (int i = wayMembers.
size()-
1; i
>=
0; i--
){
boolean added =
false;
long memId = wayMembers.
get(i
);
JoinedWay mpWay = mpWayEndNodesMap.
get(memId
);
if (mpWay ==
null){
wayMembers.
remove(i
);
hasMissingWays =
true;
continue;
}
long mpWayStart = mpWay.
startNode;
long mpWayEnd = mpWay.
endNode;
added =
true;
if (joinedWays ==
null){
joinedWays =
new long[2];
joinedWays
[0] = mpWayStart
;
joinedWays
[1] = mpWayEnd
;
}
else if (joinedWays
[0] == mpWayStart
){
joinedWays
[0] = mpWayEnd
;
}
else if (joinedWays
[0] == mpWayEnd
){
joinedWays
[0] = mpWayStart
;
}
else if (joinedWays
[1] == mpWayStart
){
joinedWays
[1] = mpWayEnd
;
}
else if (joinedWays
[1] == mpWayEnd
){
joinedWays
[1] = mpWayStart
;
}
else
added =
false;
if (added
){
changed =
true;
wayMembers.
remove(i
);
polygonWays.
add(memId
);
int pos = wayWriterMap.
getKeyPos(memId
);
if (pos
< 0)
continue;
Rectangle wayBbox = wayBboxMap.
get(memId
);
if (wayBbox ==
null)
continue;
if (wayBbox.
x < 0 && wayBbox.
getMaxX() > 0 && wayBbox.
width >= PROBLEM_WIDTH
){
System.
out.
println("way crosses -180/180: " + memId
);
}
if (mpBbox ==
null)
mpBbox =
new Rectangle(wayBbox
);
else
mpBbox.
add(wayBbox
);
if (!complainedAboutSize
&& mpBbox.
x < 0 && mpBbox.
getMaxX() > 0 && mpBbox.
width >= PROBLEM_WIDTH
){
System.
out.
println("rel crosses -180/180: " + rel.
getId());
complainedAboutSize =
true;
}
}
if (joinedWays
[0] == joinedWays
[1]){
closed =
true;
break;
}
}
if (!changed || closed
){
break;
}
}
if (mpBbox
!=
null){
// found closed polygon or nothing more to add
boolean isRelevant = checkBoundingBox
(relWriters, mpBbox
);
if (isRelevant
& hasMissingWays
)
System.
out.
println("Warning: Incomplete multipolygon relation " + rel.
getId() +
" (" + rel.
getName() +
"): using bbox of " +
(closed
? "closed":
"unclosed") +
" polygon to calc tiles, ways: " + polygonWays
);
mpBbox =
null;
}
joinedWays =
null;
}
return;
}
/**
* Stores the IDs of the end nodes of a way
* @author GerdP
*
*/
class JoinedWay
{
long startNode, endNode
;
public JoinedWay
(long startNode,
long endNode
) {
this.
startNode = startNode
;
this.
endNode = endNode
;
}
}
/**
* A helper class that just contains all information about relation that we need
* in the MultiTileProcessor.
* @author GerdP
*
*/
private class MTRelation
{
private final static short IS_MP = 0x01
;
private final static short ON_LOOP = 0x02
;
private final static short HAS_NODES = 0x04
;
private final static short HAS_WAYS = 0x08
;
private final static short HAS_RELS = 0x10
;
private final static short IS_JUST_PARENT = 0x20
;
private final static short IS_NOT_COMPLETE = 0x40
;
private final long id
;
protected final byte[] memTypes
;
protected final String[] memRoles
;
protected final long[] memRefs
;
protected final int numMembers
;
private final String name
;
private int multiTileWriterIndex = UNASSIGNED
;
private int lastVisitId
;
private short flags
; // flags for the MultiTileProcessor
public MTRelation
(Relation rel
){
numMembers = rel.
getMembers().
size();
memTypes =
new byte[numMembers
];
memRoles =
new String[numMembers
];
memRefs =
new long[numMembers
];
id = rel.
getId();
for (int i =
0; i
<numMembers
; i++
){
Member mem = rel.
getMembers().
get(i
);
memRefs
[i
] = mem.
getRef();
memRoles
[i
] = mem.
getRole().
intern();
if ("node".
equals(mem.
getType())){
memTypes
[i
] = MEM_NODE_TYPE
;
flags |= HAS_NODES
;
}
else if ("way".
equals(mem.
getType())){
memTypes
[i
] = MEM_WAY_TYPE
;
flags |= HAS_WAYS
;
}
else if ("relation".
equals(mem.
getType())){
memTypes
[i
] = MEM_REL_TYPE
;
flags |= HAS_RELS
;
}
else
memTypes
[i
] = MEM_INVALID_TYPE
;
}
String type = rel.
getTag("type");
if ("multipolygon".
equals(type
) ||
"boundary".
equals(type
))
markAsMultiPolygon
();
String goodNameCandidate =
null;
String nameCandidate =
null;
String zipCode =
null;
Iterator<Element.
Tag> tags = rel.
tagsIterator();
while(tags.
hasNext()) {
Element.
Tag t = tags.
next();
for (String nameTag: NAME_TAGS
){
if (nameTag.
equals(t.
key)){
goodNameCandidate = t.
value;
break;
}
}
if (goodNameCandidate
!=
null)
break;
if (t.
key.
contains("name"))
nameCandidate = t.
value;
else if ("postal_code".
equals(t.
key))
zipCode = t.
value;
}
if (goodNameCandidate
!=
null)
name = goodNameCandidate
;
else if (nameCandidate
!=
null)
name = nameCandidate
;
else if (zipCode
!=
null)
name =
"postal_code=" + zipCode
;
else
name =
"?";
}
public long getId
() {
return id
;
}
public boolean isOnLoop
() {
return (flags
& ON_LOOP
) !=
0;
}
public void markOnLoop
() {
this.
flags |= ON_LOOP
;
}
public int getMultiTileWriterIndex
() {
return multiTileWriterIndex
;
}
public void setMultiTileWriterIndex
(int multiTileWriterIndex
) {
this.
multiTileWriterIndex = multiTileWriterIndex
;
}
public boolean hasNodeMembers
() {
return (flags
& HAS_NODES
) !=
0;
}
public boolean hasWayMembers
() {
return (flags
& HAS_WAYS
) !=
0;
}
public boolean hasRelMembers
() {
return (flags
& HAS_RELS
) !=
0;
}
public boolean wasAddedAsParent
() {
return (flags
& IS_JUST_PARENT
) !=
0;
}
public void setAddedAsParent
() {
this.
flags |= IS_JUST_PARENT
;
}
public boolean isNotComplete
() {
return (flags
& IS_NOT_COMPLETE
) !=
0;
}
public void setNotComplete
() {
this.
flags |= IS_NOT_COMPLETE
;
}
public boolean isMultiPolygon
() {
return (flags
& IS_MP
) !=
0;
}
public void markAsMultiPolygon
() {
this.
flags |= IS_MP
;
}
public int getLastVisitId
() {
return lastVisitId
;
}
public void setLastVisitId
(int visitId
) {
this.
lastVisitId = visitId
;
}
public String getName
(){
return name
;
}
@
Override
public String toString
(){
return "r" + id +
" " + name +
" subrels:" + hasRelMembers
() +
" incomplete:" + isNotComplete
();
}
}
}