package net.osmand.plus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteRegion;
import net.osmand.binary.GeocodingUtilities;
import net.osmand.binary.GeocodingUtilities.GeocodingResult;
import net.osmand.binary.RouteDataObject;
import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResource;
import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResourceType;
import net.osmand.router.GeneralRouter.GeneralRouterProfile;
import net.osmand.router.RoutePlannerFrontEnd;
import net.osmand.router.RoutingConfiguration;
import net.osmand.router.RoutingContext;
import net.osmand.util.MapUtils;
import android.os.AsyncTask;
public class CurrentPositionHelper {
private RouteDataObject lastFound;
private Location lastAskedLocation = null;
private RoutingContext ctx;
private RoutingContext defCtx;
private OsmandApplication app;
private ApplicationMode am;
private List<BinaryMapReaderResource> usedReaders = new ArrayList<>();
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(CurrentPositionHelper.class);
public CurrentPositionHelper(OsmandApplication app) {
this.app = app;
}
public Location getLastAskedLocation() {
return lastAskedLocation;
}
public boolean getRouteSegment(Location loc, ResultMatcher<RouteDataObject> result) {
return scheduleRouteSegmentFind(loc, false, null, result);
}
public boolean getGeocodingResult(Location loc, ResultMatcher<GeocodingResult> result) {
return scheduleRouteSegmentFind(loc, false, result, null);
}
public RouteDataObject getLastKnownRouteSegment(Location loc) {
Location last = lastAskedLocation;
RouteDataObject r = lastFound;
if (loc == null || loc.getAccuracy() > 50) {
return null;
}
if(last != null && last.distanceTo(loc) < 10) {
return r;
}
if (r == null) {
scheduleRouteSegmentFind(loc, true, null, null);
return null;
}
double d = getOrthogonalDistance(r, loc);
if (d > 15) {
scheduleRouteSegmentFind(loc, true, null, null);
}
if (d < 70) {
return r;
}
return null;
}
///////////////////////// PRIVATE IMPLEMENTATION //////////////////////////
private boolean scheduleRouteSegmentFind(final Location loc, final boolean storeFound, final ResultMatcher<GeocodingResult> geoCoding, final ResultMatcher<RouteDataObject> result) {
boolean res = false;
if (loc != null) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
processGeocoding(loc, geoCoding, storeFound, result);
} catch (Exception e) {
log.error("Error processing geocoding", e);
e.printStackTrace();
}
return null;
}
}.execute((Void) null);
res = true;
}
return res;
}
private void initCtx(OsmandApplication app, List<BinaryMapReaderResource> checkReaders) {
am = app.getSettings().getApplicationMode();
String p ;
if (am.isDerivedRoutingFrom(ApplicationMode.BICYCLE)) {
p = GeneralRouterProfile.BICYCLE.name().toLowerCase();
} else if (am.isDerivedRoutingFrom(ApplicationMode.PEDESTRIAN)) {
p = GeneralRouterProfile.PEDESTRIAN.name().toLowerCase();
} else if (am.isDerivedRoutingFrom(ApplicationMode.CAR)) {
p = GeneralRouterProfile.CAR.name().toLowerCase();
} else {
p = "geocoding";
}
BinaryMapIndexReader[] rs = new BinaryMapIndexReader[checkReaders.size()];
if (rs.length > 0) {
int i = 0;
for (BinaryMapReaderResource rep : checkReaders) {
rs[i++] = rep.getReader(BinaryMapReaderResourceType.STREET_LOOKUP);
}
RoutingConfiguration cfg = app.getDefaultRoutingConfig().build(p, 10,
new HashMap<String, String>());
ctx = new RoutePlannerFrontEnd(false).buildRoutingContext(cfg, null, rs);
RoutingConfiguration defCfg = app.getDefaultRoutingConfig().build("geocoding", 10,
new HashMap<String, String>());
defCtx = new RoutePlannerFrontEnd(false).buildRoutingContext(defCfg, null, rs);
} else {
ctx = null;
defCtx = null;
}
usedReaders = checkReaders;
}
// single synchronized method
private synchronized void processGeocoding(Location loc, ResultMatcher<GeocodingResult> geoCoding, boolean storeFound, final ResultMatcher<RouteDataObject> result) throws IOException {
final List<GeocodingResult> gr = runUpdateInThread(loc.getLatitude(), loc.getLongitude(),
geoCoding != null);
if (storeFound) {
lastAskedLocation = loc;
lastFound = gr == null || gr.isEmpty() ? null : gr.get(0).point.getRoad();
} else if(geoCoding != null) {
justifyResult(gr, geoCoding);
} else if(result != null) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
result.publish(gr == null || gr.isEmpty() ? null : gr.get(0).point.getRoad());
}
});
}
}
private List<GeocodingResult> runUpdateInThread(double lat, double lon, boolean geocoding) throws IOException {
List<BinaryMapReaderResource> checkReaders = checkReaders(lat, lon, usedReaders);
if (ctx == null || am != app.getSettings().getApplicationMode() || checkReaders != usedReaders) {
initCtx(app, checkReaders);
if (ctx == null) {
return null;
}
}
try {
return new GeocodingUtilities().reverseGeocodingSearch(geocoding ? defCtx : ctx, lat, lon);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private List<BinaryMapReaderResource> checkReaders(double lat, double lon,
List<BinaryMapReaderResource> ur) {
List<BinaryMapReaderResource> res = ur;
for(BinaryMapReaderResource t : ur ) {
if(t.isClosed()) {
res = new ArrayList<>();
break;
}
}
int y31 = MapUtils.get31TileNumberY(lat);
int x31 = MapUtils.get31TileNumberX(lon);
for(BinaryMapReaderResource r : app.getResourceManager().getFileReaders()) {
if(!r.isClosed() && r.getShallowReader().containsRouteData(x31, y31, x31, y31, 15)) {
if(!res.contains(r)) {
res = new ArrayList<>(res);
res.add(r);
}
}
}
return res;
}
private void justifyResult(List<GeocodingResult> res, final ResultMatcher<GeocodingResult> result) {
List<GeocodingResult> complete = new ArrayList<>();
double minBuildingDistance = 0;
if (res != null) {
for (GeocodingResult r : res) {
BinaryMapIndexReader foundRepo = null;
List<BinaryMapReaderResource> rts = usedReaders;
for (BinaryMapReaderResource rt : rts) {
if(rt.isClosed()) {
continue;
}
BinaryMapIndexReader reader = rt.getReader(BinaryMapReaderResourceType.STREET_LOOKUP);
for (RouteRegion rb : reader.getRoutingIndexes()) {
if (r.regionFP == rb.getFilePointer() && r.regionLen == rb.getLength()) {
foundRepo = reader;
break;
}
}
}
if (result.isCancelled()) {
break;
} else if (foundRepo != null) {
List<GeocodingResult> justified = null;
try {
justified = new GeocodingUtilities().justifyReverseGeocodingSearch(r, foundRepo,
minBuildingDistance, result);
} catch (IOException e) {
log.error("Exception happened during reverse geocoding", e);
e.printStackTrace();
}
if (justified != null && !justified.isEmpty()) {
double md = justified.get(0).getDistance();
if (minBuildingDistance == 0) {
minBuildingDistance = md;
} else {
minBuildingDistance = Math.min(md, minBuildingDistance);
}
complete.addAll(justified);
}
} else {
complete.add(r);
}
}
}
if (result.isCancelled()) {
app.runInUIThread(new Runnable() {
public void run() {
result.publish(null);
}
});
return;
}
Collections.sort(complete, GeocodingUtilities.DISTANCE_COMPARATOR);
// for(GeocodingResult rt : complete) {
// System.out.println(rt.toString());
// }
final GeocodingResult rts = complete.size() > 0 ? complete.get(0) : new GeocodingResult();
app.runInUIThread(new Runnable() {
public void run() {
result.publish(rts);
}
});
}
public static double getOrthogonalDistance(RouteDataObject r, Location loc){
double d = 1000;
if (r.getPointsLength() > 0) {
double pLt = MapUtils.get31LatitudeY(r.getPoint31YTile(0));
double pLn = MapUtils.get31LongitudeX(r.getPoint31XTile(0));
for (int i = 1; i < r.getPointsLength(); i++) {
double lt = MapUtils.get31LatitudeY(r.getPoint31YTile(i));
double ln = MapUtils.get31LongitudeX(r.getPoint31XTile(i));
double od = MapUtils.getOrthogonalDistance(loc.getLatitude(), loc.getLongitude(), pLt, pLn, lt, ln);
if (od < d) {
d = od;
}
pLt = lt;
pLn = ln;
}
}
return d;
}
}