package me.osm.gazetter.join;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import me.osm.gazetter.Options;
import me.osm.gazetter.addresses.AddressesUtils;
import me.osm.gazetter.addresses.NamesMatcher;
import me.osm.gazetter.striper.GeoJsonWriter;
import me.osm.gazetter.striper.JSONFeature;
import me.osm.gazetter.utils.HilbertCurveHasher;
import me.osm.gazetter.utils.LocatePoint;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
public class HighwayNetworksJoiner {
private final NamesMatcher namesMatcher = Options.get().getNamesMatcher();
private boolean dropHghNetGeometries;
private static final String[] HGHNET_COPY_KEYS_GEOMETRY = new String[]{"id", "properties", "geometry"};
private static final String[] HGHNET_COPY_KEYS_DROP_GEOMETRY = new String[]{"id", "properties"};
private List<JSONObject> streets;
private JoinSliceRunable jsr;
public HighwayNetworksJoiner(List<JSONObject> streets, boolean dropHghNetGeometries, JoinSliceRunable father) {
this.streets = streets;
this.dropHghNetGeometries = dropHghNetGeometries;
this.jsr = father;
}
/**
* Find unique streets' addresses
* @throws StreetNetworkJoinError
* */
public void createStreetsNetworks() throws StreetNetworkJoinError {
Map<Coordinate, List<JSONObject>> streetParts = Collections.synchronizedMap(new TreeMap<Coordinate, List<JSONObject>>());
fillHghNetEndPointsToObjMap(streetParts);
for (Entry<Coordinate, List<JSONObject>> entry : streetParts.entrySet()) {
List<JSONObject> value = entry.getValue();
Set<String> visited = new TreeSet<>();
for (JSONObject segment : value) {
List<JSONObject> thread = new LinkedList<>();
fillHigHwaysThread(thread, segment, getEndPoints(segment), visited, streetParts);
if(!thread.isEmpty()) {
joinStreetsNet(thread);
}
}
}
}
private void fillHghNetEndPointsToObjMap (
Map<Coordinate, List<JSONObject>> streetParts) {
Iterator<JSONObject> iterator = streets.iterator();
while(iterator.hasNext()) {
JSONObject jsonObject = iterator.next();
JSONArray boundaries = jsonObject.optJSONArray("boundaries");
if(boundaries != null) {
for(int i = 0; i < boundaries.length(); i++) {
JSONObject b = boundaries.getJSONObject(i);
long bhash = b.getLong("boundariesHash");
String[] keys = this.dropHghNetGeometries ?
HGHNET_COPY_KEYS_DROP_GEOMETRY : HGHNET_COPY_KEYS_GEOMETRY;
JSONFeature hghnet = new JSONFeature(jsonObject, keys);
hghnet.put("boundariesHash", bhash);
hghnet.put("boundaries", new JSONArray(Arrays.asList(b)));
JSONArray coords = jsonObject.getJSONObject("geometry").getJSONArray("coordinates");
if(coords.length() >= 2 ) {
Coordinate first = new Coordinate(coords.getJSONArray(0).getDouble(0),
coords.getJSONArray(0).getDouble(1)) ;
Coordinate last = new Coordinate(
coords.getJSONArray(coords.length() - 1).getDouble(0),
coords.getJSONArray(coords.length() - 1).getDouble(1));
hghnet.put("pointA", new JSONArray(new double[]{first.x, first.y}));
hghnet.put("pointZ", new JSONArray(new double[]{last.x, last.y}));
if(streetParts.get(first) == null) {
streetParts.put(first, new ArrayList<JSONObject>());
}
if(streetParts.get(last) == null) {
streetParts.put(last, new ArrayList<JSONObject>());
}
streetParts.get(first).add(hghnet);
streetParts.get(last).add(hghnet);
}
}
}
iterator.remove();
}
}
private void joinStreetsNet(List<JSONObject> streetsNetBunch) {
if(!streetsNetBunch.isEmpty()) {
Collections.sort(streetsNetBunch, JoinSliceRunable.BY_ID_COMPARATOR);
JSONObject first = streetsNetBunch.get(0);
JSONFeature hghnet = new JSONFeature(first);
String name = commonName(streetsNetBunch);
JSONObject cp = new JSONObject();
cp.put("type", "Point");
JSONObject geometryJSON = first.getJSONObject(GeoJsonWriter.GEOMETRY);
LineString ls = GeoJsonWriter.getLineStringGeometry(
geometryJSON.getJSONArray(GeoJsonWriter.COORDINATES));
Coordinate c = new LocatePoint(ls, ls.getLength() * 0.5).getPoint();
cp.put("lon", c.x);
cp.put("lat", c.y);
String nameHash = StringUtils.replaceChars(
String.valueOf(name.hashCode()), '-', 'm');
String bhash = StringUtils.replaceChars(
String.valueOf(first.optInt("boundariesHash")), '-', 'm');
String id = "hghnet" + "-" + bhash + "-" + nameHash;
hghnet.put("id", id);
hghnet.put("feature_id", id);
hghnet.put("type", "hghnet");
hghnet.put("ftype", "hghnet");
hghnet.put("center_point", cp);
hghnet.remove("full_geometry");
hghnet.remove("geometry");
JSONArray members = new JSONArray();
JSONArray geometries = new JSONArray();
for(JSONObject o : streetsNetBunch) {
members.put(o.getString("id"));
JSONObject g = o.getJSONObject(GeoJsonWriter.GEOMETRY);
g.put("id", o.getString("id"));
geometries.put(g);
}
hghnet.put("members", members);
hghnet.put("geometries", geometries);
GeoJsonWriter.addTimestamp(hghnet);
GeoJsonWriter.addMD5(hghnet);
jsr.handleOut(hghnet);
}
}
private String commonName(List<JSONObject> streetsNetBunch) {
String result = null;
for(JSONObject obj : streetsNetBunch) {
Map<String, String> names = AddressesUtils.filterNameTags(obj);
String testName = StringUtils.lowerCase(names.get("name"));
if(result == null) {
result = testName;
}
else if(StringUtils.contains(result, testName)) {
// result = result;
}
else if(StringUtils.contains(testName, result)) {
result = testName;
}
}
return result;
}
private void fillHigHwaysThread(List<JSONObject> thread,
JSONObject segment, Coordinate[] endPoints, Set<String> visited,
Map<Coordinate, List<JSONObject>> streetParts) {
thread.add(segment);
visited.add(segment.getString("id"));
// for endpointA
fillByNode(thread, segment, endPoints[0], endPoints, visited, streetParts,
streetParts.get(endPoints[0]));
// for endpointZ
fillByNode(thread, segment, endPoints[1], endPoints, visited, streetParts,
streetParts.get(endPoints[1]));
}
private void fillByNode(List<JSONObject> thread, JSONObject segment, Coordinate node,
Coordinate[] endPoints, Set<String> visited,
Map<Coordinate, List<JSONObject>> streetParts,
List<JSONObject> joinedByNode) {
for(JSONObject other : joinedByNode) {
if(visited.add(other.getString("id"))) {
if(doesHghWaysMathcByTags(segment, other)) {
Coordinate[] otherEndPoints = getEndPoints(other);
Coordinate newEndPoint = node.equals(otherEndPoints[0])
? otherEndPoints[1] : otherEndPoints[0];
fillHigHwaysThread(thread, other,
new Coordinate[]{endPoints[0], newEndPoint}, visited, streetParts);
}
}
}
}
private boolean doesHghWaysMathcByTags(JSONObject segment, JSONObject other) {
return namesMatcher.doesStreetsMatch(
AddressesUtils.filterNameTags(segment), AddressesUtils.filterNameTags(other));
}
private Coordinate[] getEndPoints(JSONObject segment) {
return new Coordinate[]{
new Coordinate(segment.getJSONArray("pointA").getDouble(0),
segment.getJSONArray("pointA").getDouble(1)),
new Coordinate(segment.getJSONArray("pointZ").getDouble(0),
segment.getJSONArray("pointZ").getDouble(1))
};
}
}