package com.pixeltron.maproulette.servlets; import java.io.IOException; import java.math.BigDecimal; import java.net.URL; import java.net.URLEncoder; import java.util.List; import java.util.Random; import java.util.concurrent.Future; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import com.google.gson.Gson; import com.pixeltron.mapquest.open.geocoding.GeocodingRequest; import com.pixeltron.mapquest.open.geocoding.GeocodingResponse; import com.pixeltron.mapquest.open.geocoding.LatLng; import com.pixeltron.maproulette.models.EndpointModel; import com.pixeltron.maproulette.models.FoursquareApiRequestResponse; import com.pixeltron.maproulette.responses.WaypointResponse; import fi.foyt.foursquare.api.JSONFieldParser; import fi.foyt.foursquare.api.ResultMeta; import fi.foyt.foursquare.api.entities.CompactVenue; import fi.foyt.foursquare.api.entities.Recommendation; import fi.foyt.foursquare.api.entities.RecommendationGroup; import fi.foyt.foursquare.api.io.Response; @SuppressWarnings("serial") public class RouletteServlet extends HttpServlet { public static final String FOURSQUARE_API_KEY_DEV = "GWCCYYFINDKJ1A3JUY0KMUAEXX5UQ0EGHTQPPGUGLTVAKNUK"; public static final String FOURSQUARE_API_SECRET_DEV = "JYUTNCPVW4K0JLGFYS3ROLHHDEFPZOJSPP2R0RJHZBTOCQJO"; public static final String FOURSQUARE_API_KEY_PROD = "UMGTNRDSNZV2WY1TE5WWLSLMS1UAMH4YCYJFXHEPSKKXVHYA"; public static final String FOURSQUARE_API_SECRET_PROD = "FYO552JTH34WSCYK0OZUMVMZUHTNCTOB02CVCWRPYPADP1CC"; private static int CONV_MI_LL = 69; // 69 miles = 1 latitude/longitude (average) //private static double CONV_LL_MI = 0.000621371192; // conversion factor for lat/long to miles private static int CONV_MI_M = 1760; // rough miles to meters public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String start = req.getParameter("start"); String end = req.getParameter("end"); String categories = req.getParameter("categories"); String search = req.getParameter("search"); String checkNew = req.getParameter("new"); String checkOld = req.getParameter("old"); String oauth_token = req.getParameter("oauth_token"); String responseBody = ""; Gson gson = new Gson(); WaypointResponse wayResp = new WaypointResponse(); if (StringUtils.isNotBlank(start) && StringUtils.isNotBlank(end)) { LatLng startLL = null; LatLng endLL = null; URLFetchService fetch = URLFetchServiceFactory.getURLFetchService(); GeocodingRequest geoReq = new GeocodingRequest(); Future<HTTPResponse> geoFuture = fetch.fetchAsync(new URL(geoReq.buildUrl(start, end, false))); try { HTTPResponse geoResp = geoFuture.get(); if (geoResp.getResponseCode() == 200) { GeocodingResponse geoResults = gson.fromJson(new String(geoResp.getContent(), "UTF-8"), GeocodingResponse.class); if (geoResults.results.length > 1) { if (geoResults.results[0].locations.length > 0) { startLL = geoResults.results[0].locations[0].latLng; } else { wayResp.addError("Did not get enough geocode results back for start."); } if (geoResults.results[1].locations.length > 0) { endLL = geoResults.results[1].locations[0].latLng; } else { wayResp.addError("Did not get enough geocode results back for end."); } } else { wayResp.addError("Did not get enough geocode results back."); } } else { wayResp.addError("Error during geocode: " + Integer.toString(geoResp.getResponseCode())); } } catch (Exception e) { e.printStackTrace(); wayResp.addError("Exception thrown during geocoding: " + e.getMessage()); } if (startLL != null && endLL != null) { // Set up math variables Random rand = new Random(); int numWaypoints = rand.nextInt(6) + 1; double rise = startLL.lng.doubleValue() - endLL.lng.doubleValue(); double run = startLL.lat.doubleValue() - endLL.lat.doubleValue(); double risestep = rise / numWaypoints; double runstep = run / numWaypoints; double distance = Math.sqrt(rise * rise + run * run) * CONV_MI_LL; double wpDist = distance / numWaypoints; if (numWaypoints == 0) wpDist = distance; int rad = Ints.checkedCast(Math.round(wpDist * CONV_MI_M) / 2); if (rad > 35000) rad = 35000; // Build waypoints LatLng nextWP = new LatLng(); nextWP.lat = BigDecimal.valueOf(startLL.lat.doubleValue() - runstep); nextWP.lng = BigDecimal.valueOf(startLL.lng.doubleValue() - risestep); List<LatLng> waypoints = Lists.newArrayList(); for (int i=0;i<numWaypoints;i++) { double lat = nextWP.lat.doubleValue(); double lng = nextWP.lng.doubleValue(); nextWP = new LatLng(); nextWP.lat = BigDecimal.valueOf(lat - runstep); nextWP.lng = BigDecimal.valueOf(lng - risestep); // Randomize lat/long if (rand.nextDouble() > 0.5) { lat = lat + (rand.nextDouble() * (runstep / 4)); } else { lat = lat - (rand.nextDouble() * (runstep / 4)); } if (Math.random() > 0.5) { lng = lng + (rand.nextDouble() * (risestep / 4)); } else { lng = lng - (rand.nextDouble() * (risestep / 4)); } LatLng curWP = new LatLng(); curWP.lat = BigDecimal.valueOf(lat); curWP.lng = BigDecimal.valueOf(lng); waypoints.add(curWP); } List<Future<HTTPResponse>> responses = Lists.newArrayList(); for (LatLng waypoint : waypoints) { fetch = URLFetchServiceFactory.getURLFetchService(); StringBuilder urlBuilder = new StringBuilder("https://api.foursquare.com/v2/venues/explore?ll="); urlBuilder.append(waypoint.toUrlValue()); urlBuilder.append("&limit=6&client_id=GWCCYYFINDKJ1A3JUY0KMUAEXX5UQ0EGHTQPPGUGLTVAKNUK&client_secret=JYUTNCPVW4K0JLGFYS3ROLHHDEFPZOJSPP2R0RJHZBTOCQJO&v=20131013"); if (StringUtils.isNotBlank(categories)) { urlBuilder.append("§ion="); urlBuilder.append(categories); } if (StringUtils.isNotBlank(search)) { urlBuilder.append("&query="); urlBuilder.append(URLEncoder.encode(search, "UTF-8")); } urlBuilder.append("&radius="); urlBuilder.append(rad); if (StringUtils.isNotBlank(oauth_token)) { urlBuilder.append("&oauth_token="); urlBuilder.append(URLEncoder.encode(oauth_token, "UTF-8")); urlBuilder.append("&novelty="); if (StringUtils.isNotBlank(checkNew)) { if (StringUtils.isNotBlank(checkOld)) { urlBuilder.append("both"); } else { urlBuilder.append(checkNew); } } else { urlBuilder.append(checkOld); } } responses.add(fetch.fetchAsync(new URL(urlBuilder.toString()))); } List<RecommendationGroup> foursquareResults = Lists.newArrayList(); for (Future<HTTPResponse> futureFsqresp : responses) { try { HTTPResponse fsqresp = futureFsqresp.get(); FoursquareApiRequestResponse response = handleApiResponse( new Response(new String(fsqresp.getContent(), "UTF-8"), fsqresp.getResponseCode(), null)); if (response.getMeta().getCode() == 200) { RecommendationGroup[] groups = (RecommendationGroup[]) JSONFieldParser.parseEntities( RecommendationGroup.class, response.getResponse().getJSONArray("groups"), true); if (groups.length > 0) { if (groups[0].getItems().length > 0) foursquareResults.add(groups[0]); } } } catch (Exception e) { e.printStackTrace(); } } List<CompactVenue> venueResults = Lists.newArrayList(); for (RecommendationGroup result : foursquareResults) { Recommendation[] venues = result.getItems(); List<CompactVenue> venueData = Lists.newArrayList(); for (Recommendation venue : venues) { venueData.add(venue.getVenue()); } int random = rand.nextInt(venues.length); CompactVenue currentVenue = venueData.get(random); venueData.remove(random); while (venueResults.contains(currentVenue) && !venueData.isEmpty()) { random = rand.nextInt(venueData.size()); currentVenue = venueData.get(random); venueData.remove(random); } venueResults.add(currentVenue); } if (venueResults.size() > 0) { wayResp.setData(venueResults); wayResp.setEndpoints(new EndpointModel(start, startLL), new EndpointModel(end, endLL)); } else { wayResp.addError("Venue results was size 0"); } } else { wayResp.addError("Did not get valid start and end lat/lngs"); } } else { wayResp.addError("Must specify a valid start and end address."); } wayResp.prepareForTransport(); responseBody = gson.toJson(wayResp); resp.setCharacterEncoding("UTF-8"); resp.getOutputStream().println(responseBody); } /** * Handles normal API request response * * @param response raw response * @return ApiRequestResponse * @throws JSONException when JSON parsing error occurs */ private FoursquareApiRequestResponse handleApiResponse(Response response) throws JSONException { JSONObject responseJson = null; JSONArray notificationsJson = null; String errorDetail = null; if (response.getResponseCode() == 200) { JSONObject responseObject = new JSONObject(response.getResponseContent()); responseJson = responseObject.getJSONObject("response"); notificationsJson = responseObject.optJSONArray("notifications"); } else { errorDetail = response.getMessage(); } return new FoursquareApiRequestResponse(new ResultMeta(response.getResponseCode(), "", errorDetail), responseJson, notificationsJson); } }