package me.osm.gazetter.join; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import me.osm.gazetter.striper.GeoJsonWriter; import org.json.JSONArray; import org.json.JSONObject; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; public class PoiAddrJoinBuilder { private static final GeometryFactory factory = new GeometryFactory(); public BestFitAddresses join(JSONObject poi, List<JSONObject> addresses) { return getBestAddress(poi, addresses); } public static class BestFitAddresses { // Same source - it's when we have poi tags and addr tags on a same geometry public JSONObject sameSource; // Nearest addr point public JSONObject nearest; // Poi contains addr points or geometry with addr tags contains poi public Set<JSONObject> contains; // Represent situation when poi point is a part of bulding way (poi is on entrance) // and other entrances have their own addresses public Set<JSONObject> shareBuildingWay; // Near the same as shareBuildingWay but poi point is inside building // and different entrances have different addresses. // In some regions poi address will have address // with all shared entrances house numbers range like // hn4-hn9, SomeStreet // Say hello to Kaliningrad (Kenigsberg). public Set<JSONObject> nearestShareBuildingWay; public JSONObject asJSON() { JSONObject obj = new JSONObject(); obj.put("sameSource", PoiAddrJoinBuilder.asAddrRefer(sameSource)); obj.put("nearest", PoiAddrJoinBuilder.asAddrRefer(nearest)); obj.put("contains", PoiAddrJoinBuilder.asAddrRefers(contains)); obj.put("shareBuildingWay", PoiAddrJoinBuilder.asAddrRefers(shareBuildingWay)); obj.put("nearestShareBuildingWay", PoiAddrJoinBuilder.asAddrRefers(nearestShareBuildingWay)); return obj; } } private BestFitAddresses getBestAddress(JSONObject poi, List<JSONObject> addresses) { BestFitAddresses result = new BestFitAddresses(); result.contains = new LinkedHashSet<>(); JSONArray coords = poi.getJSONObject(GeoJsonWriter.GEOMETRY).getJSONArray(GeoJsonWriter.COORDINATES); final Coordinate pc = new Coordinate(coords.getDouble(0), coords.getDouble(1)); JSONObject poiMeta = poi.getJSONObject(GeoJsonWriter.META); Point poiPointGeometry = factory.createPoint(pc); Collections.sort(addresses, new Comparator<JSONObject>() { @Override public int compare(JSONObject o1, JSONObject o2) { JSONArray coords1 = o1.getJSONObject(GeoJsonWriter.GEOMETRY).getJSONArray(GeoJsonWriter.COORDINATES); JSONArray coords2 = o2.getJSONObject(GeoJsonWriter.GEOMETRY).getJSONArray(GeoJsonWriter.COORDINATES); Coordinate pc1 = new Coordinate(coords1.getDouble(0), coords1.getDouble(1)); Coordinate pc2 = new Coordinate(coords2.getDouble(0), coords2.getDouble(1)); return Double.compare(pc.distance(pc1), pc.distance(pc2)); } }); Polygon poiOrigignalPolygon = getOriginalGeometry(poi); Map<Long, List<JSONObject>> building2Address = new HashMap<Long, List<JSONObject>>(); result.shareBuildingWay = new LinkedHashSet<>(); // Share one building way Long poiSharedBuildingWay = null; JSONObject poiBndg = poi.optJSONObject("bndgWay"); if(poiBndg != null) { poiSharedBuildingWay = poiBndg.getLong("id"); } List<JSONObject> tenNearest = addresses.subList(0, Math.min(10, addresses.size())); for(JSONObject addrOBJ : tenNearest) { JSONObject addrMeta = addrOBJ.getJSONObject(GeoJsonWriter.META); if(isSameSource(poiMeta, addrMeta)) { result.sameSource = addrOBJ; break; } if("way".equals(addrMeta.getString("type")) && poiSharedBuildingWay != null && poiSharedBuildingWay.equals(addrMeta.getLong("id"))) { result.shareBuildingWay.add(addrOBJ); //It's possible when poi point share way with two addressable buildings //so didn't break cycle here. } JSONArray addrCoords = addrOBJ.getJSONObject(GeoJsonWriter.GEOMETRY) .getJSONArray(GeoJsonWriter.COORDINATES); Coordinate ac = new Coordinate(addrCoords.getDouble(0), addrCoords.getDouble(1)); Point addrPointGeometry = factory.createPoint(ac); // Search for addr points inside poi polygon if(poiOrigignalPolygon != null && poiOrigignalPolygon.contains(addrPointGeometry)) { result.contains.add(addrOBJ); } // Search for addr building polygon around poi Polygon addrOriginalPolygon = getOriginalGeometry(addrOBJ); if(addrOriginalPolygon != null && addrOriginalPolygon.contains(poiPointGeometry)) { result.contains.add(addrOBJ); // POI point inside 2 or more buildings? // I don't think it's normal situation. break; } JSONObject bndg = addrOBJ.optJSONObject("bndgWay"); if(bndg != null) { long bid = bndg.getLong("id"); if(building2Address.get(bid) == null) { building2Address.put(bid, new ArrayList<JSONObject>()); } building2Address.get(bid).add(addrOBJ); } } //Big poi with addr point(s) on entrances if("way".equals(poiMeta.getString("type"))) { long poiWayId = poiMeta.getLong("id"); List<JSONObject> shareWayAddresses = building2Address.get(poiWayId); if(shareWayAddresses != null) { result.shareBuildingWay.addAll(shareWayAddresses); } } //Poi is on aone of the entrances and other entrances have their own addresses. if(poiSharedBuildingWay != null) { List<JSONObject> shareWayAddresses = building2Address.get(poiSharedBuildingWay); if(shareWayAddresses != null) { result.shareBuildingWay.addAll(shareWayAddresses); } } //Nearest if(!addresses.isEmpty()) { result.nearest = addresses.get(0); //nearestShareBuildingWay JSONObject nearestB = result.nearest.optJSONObject("bndgWay"); if(nearestB != null) { long bid = nearestB.getLong("id"); List<JSONObject> sharedBndng = building2Address.get(bid); if(sharedBndng != null) { result.nearestShareBuildingWay = new LinkedHashSet<>(sharedBndng); } } } return result; } public static JSONArray asAddrRefers( Set<JSONObject> source) { JSONArray result = new JSONArray(); if(source != null) { for(JSONObject obj : source) { result.put(asAddrRefer(obj)); } } return result; } public static JSONObject asAddrRefer(JSONObject source) { if(source == null) { return null; } return new JSONObject(source, new String[]{"id", "meta", "ftype", "addresses", "properties"}); } private boolean isSameSource(JSONObject poiMeta, JSONObject addrMeta) { return poiMeta.getString("type").equals(addrMeta.getString("type")) && poiMeta.getLong("id") == addrMeta.getLong("id"); } private Polygon getOriginalGeometry(JSONObject obj) { JSONObject meta = obj.getJSONObject(GeoJsonWriter.META); JSONObject fullGeometry = meta.optJSONObject(GeoJsonWriter.FULL_GEOMETRY); if(fullGeometry != null && "Polygon".equals(fullGeometry.optString("type"))) { Polygon polygon = GeoJsonWriter.getPolygonGeometry(fullGeometry.getJSONArray("coordinates")); if(polygon.isValid()) { return polygon; } } return null; } }