package me.osm.gazetter.striper.builders;
import gnu.trove.map.TLongIntMap;
import gnu.trove.map.hash.TLongIntHashMap;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import me.osm.gazetter.Options;
import me.osm.gazetter.striper.builders.handlers.HighwaysHandler;
import me.osm.gazetter.striper.builders.handlers.JunctionsHandler;
import me.osm.gazetter.striper.readers.PointsReader.Node;
import me.osm.gazetter.striper.readers.RelationsReader.Relation;
import me.osm.gazetter.striper.readers.RelationsReader.Relation.RelationMember;
import me.osm.gazetter.striper.readers.RelationsReader.Relation.RelationMember.ReferenceType;
import me.osm.gazetter.striper.readers.WaysReader.Way;
import me.osm.gazetter.utils.binary.Accessor;
import me.osm.gazetter.utils.binary.Accessors;
import me.osm.gazetter.utils.binary.BinaryBuffer;
import me.osm.gazetter.utils.binary.ByteBufferList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
/**
* Класс строит геометрию way'ев
* */
public class HighwaysBuilder extends ABuilder implements HighwaysHandler {
private static final Logger log = LoggerFactory
.getLogger(HighwaysBuilder.class.getName());
private static final ExecutorService executorService =
Executors.newFixedThreadPool(Options.get().getNumberOfThreads());
private static final class BuildWayGeometryTask implements Runnable {
private Way way;
private HighwaysHandler handler;
public BuildWayGeometryTask(Way way, HighwaysHandler handler) {
this.way = way;
this.handler = handler;
}
@Override
public void run() {
buildLine(way, this.handler);
}
}
private static final String HIGHWAY_TAG = "highway";
private JunctionsHandler junctionsHandler;
private HighwaysHandler highwaysHandler;
private TLongIntMap w2n = new TLongIntHashMap();
private static final int LON_OFFSET = 8 + 8;
private static final int LAT_OFFSET = 8 + 8 + 8;
public HighwaysBuilder(HighwaysHandler highwaysHandler,
JunctionsHandler junctionsHandler) {
this.highwaysHandler = highwaysHandler;
this.junctionsHandler = junctionsHandler;
}
private static final BinaryBuffer node2way = new ByteBufferList(8 + 8 + 8 + 8 + 2);
private boolean indexFilled = false;
private boolean byWayOrdered = false;
private boolean doneReadNodes = false;
private static final GeometryFactory factory = new GeometryFactory();
@Override
public void handle(Relation rel) {
if("associatedStreet".equals(rel.tags.get("type"))) {
if(indexFilled) {
buildAssociatedStreet(rel);
}
else {
for(RelationMember m : rel.members) {
if(m.type == ReferenceType.WAY && ("street".equals(m.role) || !("house".equals(m.role)))) {
w2n.put(m.ref, -1);
}
}
}
}
}
private void buildAssociatedStreet(Relation rel) {
List<Long> waysIds = new ArrayList<>();
List<RelationMember> buildingsIds = new ArrayList<>();
for(RelationMember m : rel.members) {
if(m.type == ReferenceType.WAY && ("street".equals(m.role) || !("house".equals(m.role)))) {
waysIds.add(m.ref);
}
else {
buildingsIds.add(m);
}
}
if(!buildingsIds.isEmpty()) {
for(Long wid : waysIds) {
int fromto = w2n.get(wid);
if(fromto >= 0) {
int max = fromto & 0x0000FFFF;
int min = fromto >> 16;
//drop suspiciously long relations
if(Math.abs(max - min) < 10 ) {
this.highwaysHandler.handleAssociatedStreet(
min, max, waysIds, buildingsIds, rel.id, rel.tags);
}
}
else {
log.warn("No streets found for associated street relation, id {}", rel.id);
}
}
}
}
@Override
public void handle(Way line) {
if (isHighway(line) && isNamed(line)) {
if (indexFilled) {
if(!this.doneReadNodes) {
doneReadNodes();
this.doneReadNodes = true;
}
orderByWay();
executorService.execute(new BuildWayGeometryTask(line, this));
} else {
short i = 0;
for (Long id : line.nodes) {
ByteBuffer bb = ByteBuffer.allocate(8 + 8 + 8 + 8 + 2);
bb.putLong(0, id);
bb.putLong(8, line.id);
bb.putDouble(LON_OFFSET, Double.NaN);
bb.putDouble(LAT_OFFSET, Double.NaN);
bb.putShort(8 + 8 + 8 + 8, i++);
node2way.add(bb);
}
}
}
}
private void doneReadNodes() {
log.info("Nodes coordinates loaded");
}
private boolean isHighway(Way line) {
return line.tags.containsKey(HIGHWAY_TAG)
&& !line.tags.get(HIGHWAY_TAG).equals("bus_stop")
&& !line.tags.get(HIGHWAY_TAG).equals("platform");
}
private boolean isNamed(Way line) {
return line.tags.containsKey("name");
}
private static void buildLine(final Way line, HighwaysHandler handler) {
Accessor lneIDAccessor = Accessors.longAccessor(8);
int li = node2way.find(line.id, lneIDAccessor);
List<ByteBuffer> nodeRows = node2way.findAll(li, line.id, lneIDAccessor);
//sort by node
Collections.sort(nodeRows, Builder.FIRST_LONG_FIELD_COMPARATOR);
List<Coordinate> coords = new ArrayList<>(line.nodes.size());
for (final long pid : line.nodes) {
int ni = Collections.binarySearch(nodeRows, null,
new Comparator<ByteBuffer>() {
@Override
public int compare(ByteBuffer row, ByteBuffer key) {
return Long.compare(row.getLong(0), pid);
}
});
if (ni >= 0) {
ByteBuffer bb = nodeRows.get(ni);
double lon = bb.getDouble(LON_OFFSET);
double lat = bb.getDouble(LAT_OFFSET);
if(!Double.isNaN(lon) && !Double.isNaN(lat)) {
coords.add(new Coordinate(lon, lat));
}
else {
log.debug("node {} not found for way {}", pid, line.id);
}
} else {
log.debug("node {} not found for way {}", pid, line.id);
}
}
if (handler != null) {
if (coords.size() > 1) {
LineString linestring = factory.createLineString(coords
.toArray(new Coordinate[coords.size()]));
handler.handleHighway(linestring, line);
}
}
}
private void orderByWay() {
if (!byWayOrdered) {
node2way.sort(Builder.SECOND_LONG_FIELD_COMPARATOR);
byWayOrdered = true;
}
}
@Override
public void handle(final Node node) {
Accessor nodeIdAccessor = Accessors.longAccessor(0);
int ni = node2way.find(node.id, nodeIdAccessor);
List<ByteBuffer> nodeRows = node2way.findAll(ni, node.id, nodeIdAccessor);
for (ByteBuffer row : nodeRows) {
row.putDouble(LON_OFFSET, node.lon);
row.putDouble(LAT_OFFSET, node.lat);
}
}
@Override
public void firstRunDoneWays() {
indexFilled = true;
node2way.sort(Builder.FIRST_LONG_FIELD_COMPARATOR);
this.highwaysHandler.newThreadpoolUser(getThreadPoolUser());
this.junctionsHandler.newThreadpoolUser(getThreadPoolUser());
}
@Override
public void secondRunDoneRelations() {
if (this.junctionsHandler != null) {
node2way.sort(Builder.FIRST_LONG_FIELD_COMPARATOR);
findJunctions();
}
executorService.shutdown();
try {
while(!executorService.awaitTermination(1, TimeUnit.MINUTES)) {
//wait
}
} catch (InterruptedException e) {
throw new RuntimeException("Executor service awaiting shutdown interrupted.");
}
this.highwaysHandler.freeThreadPool(getThreadPoolUser());
this.junctionsHandler.freeThreadPool(getThreadPoolUser());
}
private void findJunctions() {
long nodeId = -1;
double lon = 0;
double lat = 0;
List<Long> highways = new ArrayList<>();
for (ByteBuffer bb : node2way) {
long nid = bb.getLong(0);
if (nid == nodeId) {
highways.add(bb.getLong(8));
} else {
if (highways.size() > 1) {
this.junctionsHandler.handleJunction(new Coordinate(lon,
lat), nodeId, new ArrayList<>(highways));
}
highways.clear();
highways.add(bb.getLong(8));
}
nodeId = nid;
lon = bb.getDouble(LON_OFFSET);
lat = bb.getDouble(LAT_OFFSET);
}
}
@Override
public void freeThreadPool(String user) {
//do nothing
}
@Override
public void newThreadpoolUser(String user) {
//do nothing
}
@Override
public void handleHighway(LineString geometry, Way way) {
if(w2n.containsKey(way.id)) {
Envelope env = geometry.getEnvelopeInternal();
short n = new Double((env.getMinX() + 180.0) * 10.0).shortValue();
int fromto = n << 16;
n = new Double((env.getMaxX() + 180.0) * 10.0).shortValue();
fromto |= n;
w2n.put(way.id, fromto);
}
highwaysHandler.handleHighway(geometry, way);
}
@Override
public void handleAssociatedStreet(int minN, int maxN, List<Long> wayId,
List<RelationMember> buildings, long relationId,
Map<String, String> relAttributes) {
//do nothing
}
}