package net.osmand.binary; import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; import net.osmand.CollatorStringMatcher.StringMatcherMode; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.data.Building; import net.osmand.data.Building.BuildingInterpolation; import net.osmand.data.City; import net.osmand.data.LatLon; import net.osmand.data.MapObject; import net.osmand.data.Street; import net.osmand.router.BinaryRoutePlanner; import net.osmand.router.BinaryRoutePlanner.RouteSegmentPoint; import net.osmand.router.RoutePlannerFrontEnd; import net.osmand.router.RoutingContext; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; import org.apache.commons.logging.Log; import java.io.IOException; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import gnu.trove.set.hash.TLongHashSet; public class GeocodingUtilities { private static final Log log = PlatformUtil.getLog(GeocodingUtilities.class); // Location to test parameters https://www.openstreetmap.org/#map=18/53.896473/27.540071 (hno 44) // BUG https://www.openstreetmap.org/#map=19/50.9356/13.35348 (hno 26) street is public static final float THRESHOLD_MULTIPLIER_SKIP_STREETS_AFTER = 5; public static final float STOP_SEARCHING_STREET_WITH_MULTIPLIER_RADIUS = 250; public static final float STOP_SEARCHING_STREET_WITHOUT_MULTIPLIER_RADIUS = 400; public static final int DISTANCE_STREET_NAME_PROXIMITY_BY_NAME = 15000; public static final float DISTANCE_STREET_FROM_CLOSEST_WITH_SAME_NAME = 1000; public static final float THRESHOLD_MULTIPLIER_SKIP_BUILDINGS_AFTER = 1.5f; public static final float DISTANCE_BUILDING_PROXIMITY = 100; public static final Comparator DISTANCE_COMPARATOR = new Comparator() { @Override public int compare(GeocodingResult o1, GeocodingResult o2) { LatLon l1 = o1.getLocation(); LatLon l2 = o2.getLocation(); if (l1 == null || l2 == null) { return l2 == l1 ? 0 : (l1 == null ? -1 : 1); } return Double.compare(MapUtils.getDistance(l1, o1.searchPoint), MapUtils.getDistance(l2, o2.searchPoint)); } }; public static class GeocodingResult { public GeocodingResult() { } public GeocodingResult(GeocodingResult r) { this.searchPoint = r.searchPoint; this.regionFP = r.regionFP; this.regionLen = r.regionLen; this.connectionPoint = r.connectionPoint; this.streetName = r.streetName; this.point = r.point; this.building = r.building; this.city = r.city; this.street = r.street; } // input public LatLon searchPoint; // 1st step public LatLon connectionPoint; public int regionFP; public int regionLen; public RouteSegmentPoint point; public String streetName; // justification public Building building; public String buildingInterpolation; public Street street; public City city; private double dist = -1; public LatLon getLocation() { return connectionPoint; } public double getDistance() { if (dist == -1 && connectionPoint != null && searchPoint != null) { dist = MapUtils.getDistance(connectionPoint, searchPoint); } return dist; } public double getDistanceP() { if (point != null && searchPoint != null) { // Need distance between searchPoint and nearest RouteSegmentPoint here, to approximate distance from neareest named road return Math.sqrt(point.distSquare); } else { return -1; } } @Override public String toString() { StringBuilder bld = new StringBuilder(); if (building != null) { if(buildingInterpolation != null) { bld.append(buildingInterpolation); } else { bld.append(building.getName()); } } if (street != null) { bld.append(" str. ").append(street.getName()).append(" city ").append(city.getName()); } else if (streetName != null) { bld.append(" str. ").append(streetName); } else if (city != null) { bld.append(" city ").append(city.getName()); } if (connectionPoint != null && searchPoint != null) { bld.append(" dist=").append((int) getDistance()); } return bld.toString(); } } public List reverseGeocodingSearch(RoutingContext ctx, double lat, double lon, boolean allowEmptyNames) throws IOException { RoutePlannerFrontEnd rp = new RoutePlannerFrontEnd(false); List lst = new ArrayList(); List listR = new ArrayList(); rp.findRouteSegment(lat, lon, ctx, listR); double distSquare = 0; TLongHashSet set = new TLongHashSet(); Set streetNames = new HashSet(); for (RouteSegmentPoint p : listR) { RouteDataObject road = p.getRoad(); if (!set.add(road.getId())) { continue; } // System.out.println(road.toString() + " " + Math.sqrt(p.distSquare)); String name = Algorithms.isEmpty(road.getName()) ? road.getRef("", false, true) : road.getName(); if (allowEmptyNames || !Algorithms.isEmpty(name)) { if (distSquare == 0 || distSquare > p.distSquare) { distSquare = p.distSquare; } GeocodingResult sr = new GeocodingResult(); sr.searchPoint = new LatLon(lat, lon); sr.streetName = name == null ? "" : name; sr.point = p; sr.connectionPoint = new LatLon(MapUtils.get31LatitudeY(p.preciseY), MapUtils.get31LongitudeX(p.preciseX)); sr.regionFP = road.region.getFilePointer(); sr.regionLen = road.region.getLength(); if (streetNames.add(sr.streetName)) { lst.add(sr); } } if (p.distSquare > STOP_SEARCHING_STREET_WITH_MULTIPLIER_RADIUS * STOP_SEARCHING_STREET_WITH_MULTIPLIER_RADIUS && distSquare != 0 && p.distSquare > THRESHOLD_MULTIPLIER_SKIP_STREETS_AFTER * distSquare) { break; } if (p.distSquare > STOP_SEARCHING_STREET_WITHOUT_MULTIPLIER_RADIUS * STOP_SEARCHING_STREET_WITHOUT_MULTIPLIER_RADIUS) { break; } } Collections.sort(lst, GeocodingUtilities.DISTANCE_COMPARATOR); return lst; } public List prepareStreetName(String s, boolean addCommonWords) { List ls = new ArrayList(); int beginning = 0; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == ' ') { addWord(ls, s.substring(beginning, i), addCommonWords); beginning = i; } else if (s.charAt(i) == '(') { addWord(ls, s.substring(beginning, i), addCommonWords); while (i < s.length()) { char c = s.charAt(i); i++; beginning = i; if (c == ')') break; } } } if (beginning < s.length()) { String lastWord = s.substring(beginning, s.length()); addWord(ls, lastWord, addCommonWords); } Collections.sort(ls, Collator.getInstance()); return ls; } private void addWord(List ls, String word, boolean addCommonWords) { String w = word.trim().toLowerCase(); if (!Algorithms.isEmpty(w)) { if(!addCommonWords && CommonWords.getCommonGeocoding(word) != -1) { return; } ls.add(w); } } public List justifyReverseGeocodingSearch(final GeocodingResult road, BinaryMapIndexReader reader, double knownMinBuildingDistance, final ResultMatcher result) throws IOException { // test address index search final List streetsList = new ArrayList(); final List streetNamesUsed = prepareStreetName(road.streetName, true); final List streetNamesPacked = streetNamesUsed.size() == 0 ? prepareStreetName(road.streetName, false) : streetNamesUsed; if (streetNamesPacked.size() > 0) { log.info("Search street by name " + road.streetName + " " + streetNamesPacked); String mainWord = ""; for (int i = 0; i < streetNamesPacked.size(); i++) { String s = streetNamesPacked.get(i); if (s.length() > mainWord.length()) { mainWord = s; } } SearchRequest req = BinaryMapIndexReader.buildAddressByNameRequest( new ResultMatcher() { @Override public boolean publish(MapObject object) { if (object instanceof Street && prepareStreetName(object.getName(), true).equals(streetNamesUsed)) { double d = MapUtils.getDistance(object.getLocation(), road.searchPoint.getLatitude(), road.searchPoint.getLongitude()); // double check to suport old format if (d < DISTANCE_STREET_NAME_PROXIMITY_BY_NAME) { GeocodingResult rs = new GeocodingResult(road); rs.street = (Street) object; // set connection point to sort rs.connectionPoint = rs.street.getLocation(); rs.city = rs.street.getCity(); streetsList.add(rs); return true; } return false; } return false; } @Override public boolean isCancelled() { return result != null && result.isCancelled(); } }, mainWord, StringMatcherMode.CHECK_EQUALS_FROM_SPACE); req.setBBoxRadius(road.getLocation().getLatitude(), road.getLocation().getLongitude(), DISTANCE_STREET_NAME_PROXIMITY_BY_NAME); reader.searchAddressDataByName(req); } final List res = new ArrayList(); if (streetsList.size() == 0) { res.add(road); } else { Collections.sort(streetsList, DISTANCE_COMPARATOR); double streetDistance = 0; for (GeocodingResult street : streetsList) { if (streetDistance == 0) { streetDistance = street.getDistance(); } else if (street.getDistance() > streetDistance + DISTANCE_STREET_FROM_CLOSEST_WITH_SAME_NAME) { continue; } street.connectionPoint = road.connectionPoint; final List streetBuildings = loadStreetBuildings(road, reader, street); Collections.sort(streetBuildings, DISTANCE_COMPARATOR); if (streetBuildings.size() > 0) { Iterator it = streetBuildings.iterator(); if (knownMinBuildingDistance == 0) { GeocodingResult firstBld = it.next(); knownMinBuildingDistance = firstBld.getDistance(); res.add(firstBld); } while (it.hasNext()) { GeocodingResult nextBld = it.next(); if (nextBld.getDistance() > knownMinBuildingDistance * THRESHOLD_MULTIPLIER_SKIP_BUILDINGS_AFTER) { break; } res.add(nextBld); } } res.add(street); } } Collections.sort(res, DISTANCE_COMPARATOR); return res; } private List loadStreetBuildings(final GeocodingResult road, BinaryMapIndexReader reader, GeocodingResult street) throws IOException { final List streetBuildings = new ArrayList(); reader.preloadBuildings(street.street, null); log.info("Preload buildings " + street.street.getName() + " " + street.city.getName() + " " + street.street.getId()); for (Building b : street.street.getBuildings()) { if (b.getLatLon2() != null) { double slat = b.getLocation().getLatitude(); double slon = b.getLocation().getLongitude(); double tolat = b.getLatLon2().getLatitude(); double tolon = b.getLatLon2().getLongitude(); double coeff = MapUtils.getProjectionCoeff(road.searchPoint.getLatitude(), road.searchPoint.getLongitude(), slat, slon, tolat, tolon); double plat = slat + (tolat - slat) * coeff; double plon = slon + (tolon - slon) * coeff; if (MapUtils.getDistance(road.searchPoint, plat, plon) < DISTANCE_BUILDING_PROXIMITY) { GeocodingResult bld = new GeocodingResult(street); bld.building = b; //bld.connectionPoint = b.getLocation(); bld.connectionPoint = new LatLon(plat, plon); streetBuildings.add(bld); String nm = b.getInterpolationName(coeff); if(!Algorithms.isEmpty(nm)) { bld.buildingInterpolation = nm; } } } else if (MapUtils.getDistance(b.getLocation(), road.searchPoint) < DISTANCE_BUILDING_PROXIMITY) { GeocodingResult bld = new GeocodingResult(street); bld.building = b; bld.connectionPoint = b.getLocation(); streetBuildings.add(bld); } } return streetBuildings; } }