package me.osm.gazetter.striper.builders;
import gnu.trove.list.TLongList;
import gnu.trove.list.array.TLongArrayList;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import me.osm.gazetter.striper.GeoJsonWriter;
import me.osm.gazetter.striper.builders.handlers.PoisHandler;
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 me.osm.osmdoc.model.Feature;
import me.osm.osmdoc.read.DOCFileReader;
import me.osm.osmdoc.read.DOCFolderReader;
import me.osm.osmdoc.read.DOCReader;
import me.osm.osmdoc.read.OSMDocFacade;
import me.osm.osmdoc.read.TagsDecisionTree;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
public class PoisBuilder extends ABuilder {
private TagsDecisionTree tagsFilter;
private static final GeometryFactory factory = new GeometryFactory();
private PoisHandler handler;
private Set<String> named;
private DOCReader reader;
public PoisBuilder(PoisHandler handler, String catalogFolder, List<String> exclude, List<String> named) {
this.handler = handler;
if(catalogFolder.endsWith(".xml") || catalogFolder.equals("jar")) {
reader = new DOCFileReader(catalogFolder);
}
else {
reader = new DOCFolderReader(catalogFolder);
}
OSMDocFacade osmDocFacade = new OSMDocFacade(reader, exclude);
this.tagsFilter = osmDocFacade.getPoiClassificator();
this.named = new HashSet<>();
for(Feature f : osmDocFacade.getBranches(named)) {
this.named.add(f.getName());
}
}
private static final Logger log = LoggerFactory.getLogger(PoisBuilder.class.getName());
private BinaryBuffer way2relation = new ByteBufferList(8 + 8);
private BinaryBuffer node2way = new ByteBufferList(8 + 8 + 2 + 8 + 8);
//Trying to save some memory
private TLongList writedAddrNodes = new TLongArrayList();
private boolean indexFilled = false;
private boolean orderedByway = false;
private boolean byRealtionOrdered = false;
private static final boolean fullGeometry = true;
private static final long MASK_16_BITS = 0xFFFFL;
private static final Accessor w2rRelAccessor = Accessors.longAccessor(8);
private static final Accessor w2rWayAccessor = Accessors.longAccessor(0);
private static final Accessor n2wWayAccessor = Accessors.longAccessor(8);
private static final Accessor n2wNodeAccessor = Accessors.longAccessor(0);
@Override
public void handle(final Relation rel) {
if(!indexFilled) {
if("multipolygon".equals(rel.tags.get("type")) && filterByTags(rel.tags)) {
indexRelation(rel);
}
}
else {
orderByRelation();
orderByWay();
buildAddrPoint4Relation(rel);
}
}
private boolean filterByTags(Map<String, String> tags) {
Set<String> type = tagsFilter.getType(tags);
boolean typeFound = !type.isEmpty();
if(typeFound && named.containsAll(type)) {
return tags.containsKey("name");
}
return typeFound;
}
private void buildAddrPoint4Relation(final Relation rel) {
int i = way2relation.find(rel.id, w2rRelAccessor);
if(i < 0) {
return;
}
Point centroid = null;
List<LineString> lines = new ArrayList<>();
for(ByteBuffer bb : way2relation.findAll(i, rel.id, w2rRelAccessor)) {
final long way = bb.getLong(0);
int p = node2way.find(way, n2wWayAccessor);
if(fullGeometry) {
List<ByteBuffer> wayPoints = getWayPoints(way);
Collections.sort(wayPoints, new Comparator<ByteBuffer>() {
@Override
public int compare(ByteBuffer o1, ByteBuffer o2) {
return Short.compare(o1.getShort(8 + 8), o2.getShort(8 + 8));
}
});
List<Coordinate> coords = new ArrayList<>();
for(ByteBuffer pbb : wayPoints) {
double lon = pbb.getDouble(8 + 8 + 2);
double lat = pbb.getDouble(8 + 8 + 2 + 8);
coords.add(new Coordinate(lon, lat));
}
if(coords.isEmpty()) {
log.warn("Failed to build geometry for relation {}. No points found.", rel.id);
return;
}
if(coords.size() >= 2) {
LineString l = factory.createLineString(coords.toArray(new Coordinate[coords.size()]));
lines.add(l);
}
else {
log.warn("Wrong geometry rel {}, way {}", rel.id, way);
centroid = factory.createPoint(new Coordinate(coords.get(0).x, coords.get(0).y));
}
}
else {
for(ByteBuffer bb2 : node2way.findAll(p, way, n2wWayAccessor)) {
double lon = bb2.getDouble(8 + 8 + 2);
double lat = bb2.getDouble(8 + 8 + 2 + 8);
centroid = factory.createPoint(new Coordinate(lon, lat));
}
break;
}
}
JSONObject meta = new JSONObject();
meta.put("id", rel.id);
meta.put("type", "relation");
if(fullGeometry) {
if(lines.isEmpty()) {
return;
}
MultiPolygon mp = BuildUtils.buildMultyPolygon(log, rel, lines, null);
if(mp != null && !mp.isEmpty() && mp.isValid()) {
centroid = mp.getCentroid();
Polygon polygon = (Polygon) mp.getGeometryN(0);
meta.put(GeoJsonWriter.FULL_GEOMETRY, GeoJsonWriter.geometryToJSON(polygon));
}
else {
centroid = lines.get(0).getCentroid();
}
}
handler.handlePoi(tagsFilter.getType(rel.tags), rel.tags, centroid, meta);
}
private void orderByRelation() {
if(!this.byRealtionOrdered) {
way2relation.sort(Builder.SECOND_LONG_FIELD_COMPARATOR);
this.byRealtionOrdered = true;
}
}
private void indexRelation(Relation rel) {
for (RelationMember rm : rel.members) {
if(rm.type == ReferenceType.WAY && (StringUtils.isEmpty(rm.role) || "outer".equals(rm.role))) {
ByteBuffer bb = ByteBuffer.allocate(8 + 8);
bb.putLong(rm.ref).putLong(rel.id);
way2relation.add(bb);
if(!fullGeometry) {
//one way (one point) will be enough
break;
}
}
}
}
@Override
public void firstRunDoneRelations() {
handler.newThreadpoolUser(getThreadPoolUser());
way2relation.sort(Builder.FIRST_LONG_FIELD_COMPARATOR);
}
@Override
public void handle(final Way line) {
if(!indexFilled) {
indexWay(line);
}
else {
orderByWay();
if(filterByTags(line.tags)) {
buildAddrPointForWay(line);
}
//Our poi node is a part of building contour
if(line.isClosed() && line.tags.containsKey("building")) {
for(int i = 0; i < line.nodes.size() - 1; i++) {
long nid = line.nodes.get(i);
int nodeIndex = ABuilder.binarySearchWithMask(writedAddrNodes, nid);
if(nodeIndex >= 0) {
long addrNodeIdWithN = writedAddrNodes.get(nodeIndex);
long n = addrNodeIdWithN & MASK_16_BITS;
long addrNodeId = addrNodeIdWithN >> 16;
this.handler.handlePoi2Building(String.format("%04d", n), addrNodeId, line.id, line.tags);
}
}
}
}
}
private List<ByteBuffer> getWayPoints(final long lineId) {
int i = node2way.find(lineId, n2wWayAccessor);
List<ByteBuffer> points = node2way.findAll(i, lineId, n2wWayAccessor);
Collections.sort(points, new Comparator<ByteBuffer>() {
@Override
public int compare(ByteBuffer o1, ByteBuffer o2) {
return Short.compare(o1.getShort(8 + 8), o2.getShort(8 + 8));
}
});
return points;
}
private void buildAddrPointForWay(final Way line) {
int i = node2way.find(line.id, n2wWayAccessor);
if(i >= 0) {
JSONObject meta = new JSONObject();
meta.put("id", line.id);
meta.put("type", "way");
Point centroid = null;
if(fullGeometry) {
List<ByteBuffer> wayPoints = getWayPoints(line.id);
Collections.sort(wayPoints, new Comparator<ByteBuffer>() {
@Override
public int compare(ByteBuffer o1, ByteBuffer o2) {
return Short.compare(o1.getShort(8 + 8), o2.getShort(8 + 8));
}
});
List<Coordinate> coords = new ArrayList<>();
for(ByteBuffer bb : wayPoints) {
double lon = bb.getDouble(8 + 8 + 2);
double lat = bb.getDouble(8 + 8 + 2 + 8);
coords.add(new Coordinate(lon, lat));
}
if(coords.isEmpty()) {
log.error("Failed to build geometry for way {}. No points found.", line.id);
return;
}
if(coords.size() != line.nodes.size()) {
log.warn("Failed to build geometry for way {}. Some points wasn't found.", line.id);
centroid = factory.createPoint(coords.get(0));
}
else if(coords.size() < 2) {
log.warn("Failed to build geometry for way {}. Only one point founded.", line.id);
centroid = factory.createPoint(coords.get(0));
}
else if(isClosed(line) && coords.size() >= 4) {
LinearRing geom = factory.createLinearRing(coords.toArray(new Coordinate[coords.size()]));
centroid = geom.getCentroid();
Polygon p = factory.createPolygon(geom);
if(p.isValid()) {
meta.put(GeoJsonWriter.FULL_GEOMETRY, GeoJsonWriter.geometryToJSON(p));
}
}
else {
LineString geom = factory.createLineString(coords.toArray(new Coordinate[coords.size()]));
centroid = geom.getCentroid();
if(geom.isValid()) {
meta.put(GeoJsonWriter.FULL_GEOMETRY, GeoJsonWriter.geometryToJSON(geom));
}
}
}
else {
ByteBuffer bb = node2way.get(i);
double lon = bb.getDouble(8 + 8 + 2);
double lat = bb.getDouble(8 + 8 + 2 + 8);
centroid = factory.createPoint(new Coordinate(lon, lat));
}
handler.handlePoi(tagsFilter.getType(line.tags), line.tags, centroid, meta);
}
}
private boolean isClosed(final Way line) {
return line.nodes.get(0).equals(line.nodes.get(line.nodes.size() - 1));
}
private void orderByWay() {
if(!this.orderedByway) {
node2way.sort(Builder.SECOND_LONG_FIELD_COMPARATOR);
this.orderedByway = true;
}
}
private void indexWay(Way line) {
if(filterByTags(line.tags) || way2relation.find(line.id, w2rWayAccessor) >= 0) {
indexLine(line);
}
}
private void indexLine(Way line) {
short i = 0;
for(long ln :line.nodes) {
ByteBuffer bb = ByteBuffer.allocate(8 + 8 + 2 + 8 + 8);
bb.putLong(ln).putLong(line.id).putShort(i++);
node2way.add(bb);
if(!fullGeometry) {
break;
}
}
}
@Override
public void firstRunDoneWays() {
node2way.sort(Builder.FIRST_LONG_FIELD_COMPARATOR);
log.info("Done read ways. {} nodes added to index.", node2way.size());
this.indexFilled = true;
}
@Override
public void handle(final Node node) {
Set<String> types = tagsFilter.getType(node.tags);
if(!types.isEmpty()) {
JSONObject meta = new JSONObject();
meta.put("id", node.id);
meta.put("type", "node");
Point point = factory.createPoint(new Coordinate(node.lon, node.lat));
handler.handlePoi(types, node.tags, point, meta);
short n = new Double((point.getX() + 180.0) * 10.0).shortValue();
long nodeWithN = node.id;
nodeWithN <<= 16;
nodeWithN |= n;
writedAddrNodes.add(nodeWithN);
}
indexNode2Way(node);
}
@Override
public void firstRunDoneNodes() {
writedAddrNodes.sort();
}
private void indexNode2Way(final Node node) {
int ni = node2way.find(node.id, n2wNodeAccessor);
for(ByteBuffer bb : node2way.findAll(ni, node.id, n2wNodeAccessor)) {
bb.putDouble(8 + 8 + 2, node.lon);
bb.putDouble(8 + 8 + 2 + 8, node.lat);
}
}
@Override
public void secondRunDoneRelations() {
handler.freeThreadPool(getThreadPoolUser());
}
}