Artificial intelligence in the post-deep learning era
Creating an Uber Clone - Part XVIII - Transcript.pdf
1. Creating an Uber Clone - Part XVIII
We can now move forward to the last two pieces of the SearchService class
2. public static void suggestLocations(String input, Location l,
SuccessCallback<List<SuggestionResult>> resultList) {
if(lastSuggestionRequest != null) {
if(lastSuggestionValue.equals(input)) {
return;
}
lastSuggestionRequest.kill();
}
List<SuggestionResult> lr = locationCache.get(input);
if(lr != null) {
lastSuggestionValue = null;
lastSuggestionRequest = null;
callSerially(() -> resultList.onSucess(lr));
return;
}
lastSuggestionValue = input;
lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
SearchService
If the last request is this request which can happen as a user types and deletes etc. then we don't want to do anything... Let the last request finish
3. public static void suggestLocations(String input, Location l,
SuccessCallback<List<SuggestionResult>> resultList) {
if(lastSuggestionRequest != null) {
if(lastSuggestionValue.equals(input)) {
return;
}
lastSuggestionRequest.kill();
}
List<SuggestionResult> lr = locationCache.get(input);
if(lr != null) {
lastSuggestionValue = null;
lastSuggestionRequest = null;
callSerially(() -> resultList.onSucess(lr));
return;
}
lastSuggestionValue = input;
lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
SearchService
However, if it isn't then we want to kill the last request which might still be queued and blocking us from going forward
4. public static void suggestLocations(String input, Location l,
SuccessCallback<List<SuggestionResult>> resultList) {
if(lastSuggestionRequest != null) {
if(lastSuggestionValue.equals(input)) {
return;
}
lastSuggestionRequest.kill();
}
List<SuggestionResult> lr = locationCache.get(input);
if(lr != null) {
lastSuggestionValue = null;
lastSuggestionRequest = null;
callSerially(() -> resultList.onSucess(lr));
return;
}
lastSuggestionValue = input;
lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
SearchService
We check if an entry is already in the cache as users might type and revise a lot thus triggering significant webservice cost overhead
5. public static void suggestLocations(String input, Location l,
SuccessCallback<List<SuggestionResult>> resultList) {
if(lastSuggestionRequest != null) {
if(lastSuggestionValue.equals(input)) {
return;
}
lastSuggestionRequest.kill();
}
List<SuggestionResult> lr = locationCache.get(input);
if(lr != null) {
lastSuggestionValue = null;
lastSuggestionRequest = null;
callSerially(() -> resultList.onSucess(lr));
return;
}
lastSuggestionValue = input;
lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
SearchService
We clean the variable values and then invoke the response. Notice I use callSerially in this case to defer the response to the next cycle. If we call back immediately we
might delay the input code which is currently in place. By shifting the callback to the next EDT cycle we guarantee that suggestLocations will behave in a similar way
whether the data is cached locally or not
6. lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
getAsJsonMap(callbackMap -> {
Map data = callbackMap.getResponseData();
if(data != null) {
List<Map> results = (List<Map>)data.get("predictions");
if(results != null && results.size() > 0) {
ArrayList<SuggestionResult> resultSet = new ArrayList<>();
for(Map currentResult : results) {
Map structured_formatting = (Map)
currentResult.get("structured_formatting");
String mainText = (String)
structured_formatting.get("main_text");
String secondaryText = (String)
structured_formatting.get("secondary_text");
String description = (String)
currentResult.get("description");
String placeId = (String)currentResult.get("place_id");
resultSet.add(new SuggestionResult(mainText,
secondaryText, description, placeId));
}
SearchService
The request extracts the predictions array so we can construct the result list
7. lastSuggestionRequest = Rest.
get("https://maps.googleapis.com/maps/api/place/autocomplete/json").
queryParam("input", input).
queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
getAsJsonMap(callbackMap -> {
Map data = callbackMap.getResponseData();
if(data != null) {
List<Map> results = (List<Map>)data.get("predictions");
if(results != null && results.size() > 0) {
ArrayList<SuggestionResult> resultSet = new ArrayList<>();
for(Map currentResult : results) {
Map structured_formatting = (Map)
currentResult.get("structured_formatting");
String mainText = (String)
structured_formatting.get("main_text");
String secondaryText = (String)
structured_formatting.get("secondary_text");
String description = (String)
currentResult.get("description");
String placeId = (String)currentResult.get("place_id");
resultSet.add(new SuggestionResult(mainText,
secondaryText, description, placeId));
}
SearchService
We iterate over the entries, notice I discard the generic context which is legal in Java but might produce a warning. I could have used a more elaborate syntax that would
have removed the warning but that would have created more verbose code with no actual benefit
8. queryParam("location", l.getLatitude() + "," + l.getLongitude()).
queryParam("radius", "50000").
queryParam("key", GOOGLE_PLACES_KEY).
getAsJsonMap(callbackMap -> {
Map data = callbackMap.getResponseData();
if(data != null) {
List<Map> results = (List<Map>)data.get("predictions");
if(results != null && results.size() > 0) {
ArrayList<SuggestionResult> resultSet = new ArrayList<>();
for(Map currentResult : results) {
Map structured_formatting = (Map)
currentResult.get("structured_formatting");
String mainText = (String)
structured_formatting.get("main_text");
String secondaryText = (String)
structured_formatting.get("secondary_text");
String description = (String)
currentResult.get("description");
String placeId = (String)currentResult.get("place_id");
resultSet.add(new SuggestionResult(mainText,
secondaryText, description, placeId));
}
locationCache.put(input, resultSet);
resultList.onSucess(resultSet);
}
SearchService
I extract the elements from the map and create the SuggestionResult entries then store the whole thing in cache followed by the onSuccess call. Notice that in this case I
didn't need the callSerially since the response is already asynchronous
10. {
"geocoded_waypoints" : [
... trimmed ...
],
"routes" : [
{
"bounds" : {
... trimmed ...
},
"legs" : [
{
"distance" : {
... trimmed ...
},
"duration" : {
... trimmed ...
},
... trimmed ...
"steps" : [
{
... trimmed ...
}
}
... trimmed ...
],
"overview_polyline" : {
"points" : "e`miGhmocNiI`CeFmHaAmIgDai@mNiw@qJqq@{GyFwBp@ag@fLoVpRcr@vI}d@oIg[ePa`@ax@kC_w@mV}h@af@iPa]y_@mq@xZehFje@{r@jCeDgf@}AgfBs]kiCmrAkuJog@gxAoo|
DoIm_CdX_{A_Gcb@ad@aw@mnB_pDe_D}rIcnBa_NmRuyBvBcmBnAydC_{Bu_Oe@ktBdt@qiBhL}w@eSylD}i@coIi_AcxCm[maDga@__IsiAuiJmbEyzZagBwvMkSypEkT_~C_cAk_H|Y{fB_P{qC}l@q_C{S_i@yA}
dAwj@mkB{^}jCbVi|DzUqoAkjA_`Lku@g_Lye_Dud@ilDwh@ufBeh@}hBgT{jAkm@wv@mm@s}DcMw`A_z@ifAeU{w@kRuaCieAsrEiw@uvAku@{vDez@_kCgCyqAoB}gBwXkw@om@q}AweBizEamAs`DuSwfA}
e@ctC_iAukH}hBqsKc|@_iDowAyrJ_B_v@lY}eBgEajByh@skH{`Dkzc@k]_vCeu@yuCyF_uAgBe_Bsa@mxBaDmfCiTasFmMqcJw]sgRsVaeScNggBxdA_tDdw@gxGr@kuAy[w{@qj@_iCi|
@orEyd@o`Aaj@yn@knAsvDmn@msJmm@kvOal@o~Cmf@{cGjJazDcp@w_EgxAw~EcnA_}E^y`E}f@swDoPmt@wn@ofAuc@gtAmu@mvAiu@{{@km@qrAgd@}j@ie@_RuoBwr@svBi`BcmB_kB}sBmzBqc@kz@sTihBjGkpA}
@gc@eo@kgBeVuPuoA`C_g@uPol@_|@_dA}`BgeAgd@s^}a@_w@isBidAi~AytAyvBihB_fDmvAojB_zIkaL{t@sdAuWirAgZonAohDuzFyaAkbBon@_k@spAi|@}aHgoMi{EqiJw`Di}H{uDuiMc|EytOevAe~D}|
@gQcTiQczBonHesAcrE}~Eo|PwqAalF{_@gmCyAcq@`TqbAzw@atCzLsrAbBehGkFmoAa}jAc]qqAiCmoBaMajDeSowAsdCyeIy`FmqMsrAigDwi@ir@ch@s~Auz@cvC}vBcvHeSgl@e_@oTq`AcxBkfBsaEwt@u}
CizDevJwfA}bDsqA_bBinCgvD{{HwqVwyA_`Ckc@sg@_i@ymAaXkp@mEo`Bej@}_DebA{aAeYay@eiBc|Ho`@mrE}aAueGun@gpDpIy{GaFg~@lB}rEdD_rDv]klBwAqk@aY_t@{lAmwCsdAshBkaO_x@}
bAuv@ipAmLeZqIcBeKLqIpIqEkF"
},
"summary" : "ON-401 E",
"warnings" : [],
"waypoint_order" : []
}
],
"status" : "OK"
Directions API
The response is a bit large so I trimmed a lot of it to give you a sense of what we are looking for.
The one thing that matters to us from the response is the overview_polyline entry which seems like a bunch of gibberish but it isn't. This is a special notation from Google
that encodes the latitude/longitude values of the entire trip in a single string. This encoding is described by Google in their map documentation
11. // Based on https://github.com/scoutant/polyline-decoder/
private static List<Coord> decodePolyline(String str) {
ArrayList<Coord> resultPath = new ArrayList<>();
int index = 0;
int lat = 0, lng = 0;
while (index < str.length()) {
int b, shift = 0, result = 0;
do {
b = str.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = str.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
Coord p = new Coord((double) lat / 1E5, (double) lng / 1E5);
resultPath.add(p);
}
return resultPath;
}
Polyline Decoder
Being lazy I found someone who already implemented the algorithm in Java and his code worked "as is”. I won't go into the code since it's mostly just bitwise shifting to
satisfy the requirements from Google. The method signature is the only thing that matters. It takes an encoded String and returns the path matching that string as a list of
coordinates that we will be able to add into the map shortly.
12. public static void directions(Location l, Location destination, DirectionResults response) {
Rest.get("https://maps.googleapis.com/maps/api/directions/json").
queryParam("origin", l.getLatitude() + "," + l.getLongitude()).
queryParam("destination", destination.getLatitude() + "," + destination.getLongitude()).
queryParam("mode", "driving").
queryParam("key", GOOGLE_DIRECTIONS_KEY).
getAsJsonMap(callbackMap -> {
Map data = callbackMap.getResponseData();
if(data != null) {
List results = (List)data.get("routes");
if(results != null && results.size() > 0) {
Map firstResult = (Map)results.get(0);
Map overview_polyline = (Map)firstResult.get("overview_polyline");
List<Coord> polyline = decodePolyline((String)
overview_polyline.get("points"));
List legs = (List)firstResult.get("legs");
Map firstLeg = (Map)legs.get(0);
Map distance = (Map)firstLeg.get("distance");
Map duration = (Map)firstLeg.get("duration");
response.onDirectionResult(polyline,
Util.toIntValue(duration.get("value")),
(String)distance.get("text"));
}
}
});
}
DirectionResults
Now that this is all out of the way the directions method is relatively simple. This method is just another REST call that doesn't include anything out of the ordinary. We
extract the overview_polyline value and pass it to the callback response.