eu.trentorise.smartcampus.mobility.util.GamificationHelper.java Source code

Java tutorial

Introduction

Here is the source code for eu.trentorise.smartcampus.mobility.util.GamificationHelper.java

Source

/*******************************************************************************
 * Copyright 2012-2013 Trento RISE
 * 
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 * 
 *        http://www.apache.org/licenses/LICENSE-2.0
 * 
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 ******************************************************************************/

package eu.trentorise.smartcampus.mobility.util;

import it.sayservice.platform.smartplanner.data.message.Itinerary;
import it.sayservice.platform.smartplanner.data.message.Leg;
import it.sayservice.platform.smartplanner.data.message.TType;

import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.mongodb.Mongo;
import com.mongodb.MongoException;

import eu.trentorise.smartcampus.mobility.gamification.model.ExecutionDataDTO;
import eu.trentorise.smartcampus.mobility.geolocation.model.Geolocation;
import eu.trentorise.smartcampus.mobility.geolocation.model.ValidationResult;
import eu.trentorise.smartcampus.mobility.model.BasicItinerary;
import eu.trentorise.smartcampus.mobility.security.AppInfo;
import eu.trentorise.smartcampus.mobility.security.AppSetup;
import eu.trentorise.smartcampus.mobility.storage.ItineraryObject;
import eu.trentorise.smartcampus.network.JsonUtils;
import eu.trentorise.smartcampus.network.RemoteConnector;
import eu.trentorise.smartcampus.network.RemoteException;

/**
 * @author raman
 *
 */
@Component
public class GamificationHelper {

    private static final String ON_FOOT = "on_foot";
    private static final String ON_BICYCLE = "on_bicycle";
    private static final String IN_VEHICLE = "in_vehicle";
    private static final String WALKING = "walking";
    private static final String RUNNING = "running";
    private static final String UNKNOWN = "unknown";
    private static final String EMPTY = "unknown";
    private static final double SPACE_ERROR = 0.1;
    private static final double TIME_ERROR = 1000 * 60 * 15;

    private static final String SAVE_ITINERARY = "save_itinerary";
    private static final String POI_REACHED = "poi_reached";

    private static final Logger logger = LoggerFactory.getLogger(GamificationHelper.class);

    //   private static long startGameDate = Long.MAX_VALUE;

    public static final List<TType> FAST_TRANSPORTS = Lists.newArrayList(TType.BUS, TType.CAR, TType.GONDOLA,
            TType.SHUTTLE, TType.TRAIN, TType.TRANSIT);
    public static final Set<String> WALKLIKE = Sets.newHashSet(ON_FOOT, WALKING, RUNNING, UNKNOWN, EMPTY);

    @Autowired(required = false)
    @Value("${gamification.url}")
    private String gamificationUrl;

    //   @Autowired(required = false)
    //   @Value("${gamification.startgame}")
    //   private String gameStart;

    //   @Value("${gamification.user}")
    //   private String user;
    //
    //   @Value("${gamification.password}")
    //   private String password;

    @Autowired
    private AppSetup appSetup;

    @Autowired
    private ExecutorService executorService;

    private final static int EARTH_RADIUS = 6371; // Earth radius in km.

    @PostConstruct
    public void initConnector() {
        //      if (StringUtils.hasText(gameStart)) {
        //         try {
        //            startGameDate = new SimpleDateFormat("dd/MM/yyyy").parse(gameStart).getTime();
        //         } catch (ParseException e) {
        //            e.printStackTrace();
        //         }
        //      }
    }

    public void checkFaLaCosaGiusta(final ItineraryObject itinerary,
            final Collection<Geolocation> geolocationEvents, final String appId, final String userId) {
        try {
            AppInfo app = appSetup.findAppById(appId);

            if (app.getExtra() == null || !app.getExtra().containsKey("game")) {
                logger.warn("Game data not defined.");
                return;
            }

            if (itinerary != null) {
                //         if (itinerary.getData().getLeg().size() == 1 && itinerary.getData().getLeg().get(0).getTransport().getType().equals(TType.CAR)) {
                //               return;
                //         }
                for (Leg leg : itinerary.getData().getLeg()) {
                    if (leg.getTransport().getType().equals(TType.CAR)) {
                        if (leg.getTo().getStopId().getExtra() == null
                                || !leg.getTo().getStopId().getExtra().containsKey("parkAndRide")
                                || !((Boolean) leg.getTo().getStopId().getExtra().get("parkAndRide"))
                                        .booleanValue()) {
                            logger.debug("CAR with no PnR, returning");
                            return;
                        }
                    }
                }
            }

            List<Double> coords = (List<Double>) ((Map) app.getExtra().get("game")).get("poi");
            String from = (String) ((Map) app.getExtra().get("game")).get("from");
            String to = (String) ((Map) app.getExtra().get("game")).get("to");

            SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
            Date fromDate = sdf.parse(from);
            Date toDate = sdf.parse(to);

            Geolocation poi = new Geolocation(coords.get(0), coords.get(1), new Date());
            for (Geolocation geolocation : geolocationEvents) {
                if (geolocation.getRecorded_at().after(fromDate) && geolocation.getRecorded_at().before(toDate)
                        && harvesineDistance(poi, geolocation) <= 0.05) {
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            sendFaLaCosaGiusta(appId, userId);
                        }
                    });
                    return;
                }
            }
            logger.debug("Conditions for Fa la Cosa Giusta not matched.");
        } catch (Exception e) {
            logger.error("Error sending data for Fa la Cosa Giusta");
            e.printStackTrace();
        }
    }

    private void sendFaLaCosaGiusta(final String appId, final String userId) {
        try {
            Map<String, Object> data = Maps.newTreeMap();
            data.put("poiName", "Trento Fiera");

            AppInfo app = appSetup.findAppById(appId);

            ExecutionDataDTO ed = new ExecutionDataDTO();
            ed.setGameId(app.getGameId());
            ed.setPlayerId(userId);
            ed.setActionId(POI_REACHED);
            ed.setData(data);

            String content = JsonUtils.toJSON(ed);

            logger.debug("Sending to " + gamificationUrl + "/gengine/execute (" + POI_REACHED + ") = " + data);
            HTTPConnector.doAuthenticatedPost(gamificationUrl + "/gengine/execute", content, "application/json",
                    "application/json", app.getGameUser(), app.getGamePassword());
        } catch (Exception e) {
            logger.error("Error sending gamification action: " + e.getMessage());
        }
    }

    public void saveItinerary(final BasicItinerary itinerary, final String appId, final String userId)
            throws ParseException {
        if (gamificationUrl == null) {
            return;
        }

        AppInfo app = appSetup.findAppById(appId);

        if (System.currentTimeMillis() < new SimpleDateFormat("dd/MM/yyyy").parse(app.getGameStart()).getTime()) {
            return;
        }

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                saveTrip(itinerary, appId, userId);
            }
        });
    }

    private void saveTrip(BasicItinerary itinerary, String appId, String userId) {
        try {
            Map<String, Object> data = computeTripData(itinerary.getData(), true);
            data.remove("estimatedScore");

            AppInfo app = appSetup.findAppById(appId);

            ExecutionDataDTO ed = new ExecutionDataDTO();
            ed.setGameId(app.getGameId());
            ed.setPlayerId(userId);
            ed.setActionId(SAVE_ITINERARY);
            ed.setData(data);

            String content = JsonUtils.toJSON(ed);

            logger.debug("Sending to " + gamificationUrl + "/gengine/execute (" + SAVE_ITINERARY + ") = " + data);
            HTTPConnector.doAuthenticatedPost(gamificationUrl + "/gengine/execute", content, "application/json",
                    "application/json", app.getGameUser(), app.getGamePassword());
        } catch (Exception e) {
            logger.error("Error sending gamification action: " + e.getMessage());
        }
    }

    public Map<String, Object> computeTripData(Itinerary itinerary, boolean log) {
        Map<String, Object> data = Maps.newTreeMap();

        String parkName = null; // name of the parking
        String startBikesharingName = null; // name of starting bike sharing
        // station
        String endBikesharingName = null; // name of ending bike sharing station
        boolean pnr = false; // (park-n-ride)
        boolean bikeSharing = false;
        double bikeDist = 0; // km
        double walkDist = 0; // km
        double trainDist = 0; // km
        double busDist = 0; // km
        double carDist = 0; // km
        double transitDist = 0;

        logger.debug("Analyzing itinerary for gamification.");
        if (itinerary != null) {
            for (Leg leg : itinerary.getLeg()) {
                if (leg.getTransport().getType().equals(TType.CAR)) {
                    carDist += leg.getLength() / 1000;
                    if (leg.getTo().getStopId() != null) {
                        if (leg.getTo().getStopId().getExtra() != null) {
                            if (leg.getTo().getStopId().getExtra().containsKey("parkAndRide")) {
                                pnr |= (Boolean) leg.getTo().getStopId().getExtra().get("parkAndRide");
                            }
                        }
                        parkName = leg.getTo().getStopId().getId();
                    }
                } else if (leg.getTransport().getType().equals(TType.BICYCLE)) {
                    bikeDist += leg.getLength() / 1000;
                    if (leg.getFrom().getStopId() != null && leg.getFrom().getStopId().getAgencyId() != null) {
                        if (leg.getFrom().getStopId().getAgencyId().startsWith("BIKE_SHARING")) {
                            bikeSharing = true;
                            startBikesharingName = leg.getFrom().getStopId().getId();
                        }
                    }
                    if (leg.getTo().getStopId() != null && leg.getTo().getStopId().getAgencyId() != null) {
                        if (leg.getTo().getStopId().getAgencyId().startsWith("BIKE_SHARING")) {
                            bikeSharing = true;
                            endBikesharingName = leg.getTo().getStopId().getId();
                        }
                    }
                } else if (leg.getTransport().getType().equals(TType.WALK)) {
                    walkDist += leg.getLength() / 1000;
                } else if (leg.getTransport().getType().equals(TType.TRAIN)) {
                    trainDist += leg.getLength() / 1000;
                } else if (leg.getTransport().getType().equals(TType.BUS)) {
                    busDist += leg.getLength() / 1000;
                } else if (leg.getTransport().getType().equals(TType.TRANSIT)) {
                    transitDist += leg.getLength() / 1000;
                }
            }
        }

        if (log) {
            logger.debug("Analysis results:");
            logger.debug("Distances [walk = " + walkDist + ", bike = " + bikeDist + ", train = " + trainDist
                    + ", bus = " + busDist + ", car = " + carDist + "]");
            logger.debug("Park and ride = " + pnr + " , Bikesharing = " + bikeSharing);
            logger.debug("Park = " + parkName);
            logger.debug("Bikesharing = " + startBikesharingName + " / " + endBikesharingName);
        }

        Double score = 0.0;
        // score += (walkDist < 0.1 ? 0 : Math.min(3.5, walkDist)) * 10; Rovereto
        score += (walkDist < 0.25 ? 0 : Math.min(3.5, walkDist)) * 10;
        score += Math.min(7, bikeDist) * 5;

        double busTrainTransitDist = busDist + trainDist;
        if (busTrainTransitDist > 0) {
            score += (busTrainTransitDist > 0 && busTrainTransitDist < 1) ? 10
                    : ((busTrainTransitDist > 1 && busTrainTransitDist < 5) ? 15
                            : (busTrainTransitDist >= 5 && busTrainTransitDist < 10) ? 20
                                    : (busTrainTransitDist >= 10 && busTrainTransitDist < 30) ? 30 : 40);
        }

        // Trento only
        if (transitDist > 0) {
            score += 25;
        }

        boolean zeroImpact = (busDist + carDist + trainDist + transitDist == 0 && walkDist + bikeDist > 0);
        //      Rovereto
        //      if (zeroImpact && itinerary.isPromoted()) {
        //         score *= 1.7;
        //      } else {
        //         if (zeroImpact) {
        //            score *= 1.5;
        //         }
        //         if (itinerary.isPromoted()) {
        //            score *= 1.2;
        //         }
        //      }

        if (pnr) {
            score += 10;
        }
        if (zeroImpact) {
            score *= 1.5;
        }

        if (bikeDist > 0) {
            data.put("bikeDistance", bikeDist);
        }
        if (walkDist > 0) {
            data.put("walkDistance", walkDist);
        }
        if (busDist > 0) {
            data.put("busDistance", busDist);
        }
        if (trainDist > 0) {
            data.put("trainDistance", trainDist);
        }
        if (transitDist > 0) {
            data.put("transitDistance", transitDist);
        }
        if (carDist > 0) {
            data.put("carDistance", carDist);
        }
        if (bikeSharing) {
            data.put("bikesharing", bikeSharing);
        }
        if (parkName != null) {
            data.put("park", parkName);
        }
        if (startBikesharingName != null) {
            data.put("startBike", startBikesharingName);
        }
        if (endBikesharingName != null) {
            data.put("endBike", endBikesharingName);
        }
        if (pnr) {
            data.put("p+r", pnr);
        }
        data.put("sustainable", itinerary.isPromoted());
        data.put("zeroimpact", zeroImpact);
        data.put("estimatedScore", Math.round(score));

        return data;
    }

    public Map<String, Object> computeFreeTrackingData(Collection<Geolocation> geolocationEvents, String ttype) {
        Map<String, Object> result = Maps.newTreeMap();
        Double score = 0.0;
        double distance = 0;

        if (geolocationEvents != null & geolocationEvents.size() >= 2) {

            List<Geolocation> points = new ArrayList<Geolocation>(geolocationEvents);

            Collections.sort(points, new Comparator<Geolocation>() {

                @Override
                public int compare(Geolocation o1, Geolocation o2) {
                    return (int) (o1.getRecorded_at().getTime() - o2.getRecorded_at().getTime());
                }

            });

            points = transform(points);

            for (int i = 1; i < points.size(); i++) {
                double d = harvesineDistance(points.get(i).getLatitude(), points.get(i).getLongitude(),
                        points.get(i - 1).getLatitude(), points.get(i - 1).getLongitude());
                //            System.out.println(points.get(i - 1).getLatitude() + "," + points.get(i - 1).getLongitude() + " / " +  points.get(i).getLatitude() + "," +  points.get(i).getLongitude() + " = " + d);
                distance += d;
            }

            if ("walk".equals(ttype)) {
                result.put("walkDistance", distance);
                score = (distance < 0.25 ? 0 : Math.min(3.5, distance)) * 10;
            }
            if ("bike".equals(ttype)) {
                result.put("bikeDistance", distance);
                score += Math.min(7, distance) * 5;
            }

            // always zero impact
            score *= 1.5;
        }

        result.put("estimatedScore", Math.round(score));
        return result;
    }

    public long computeEstimatedGameScore(Itinerary itinerary, boolean log) {
        Long score = (Long) (computeTripData(itinerary, log).get("estimatedScore"));
        itinerary.getCustomData().put("estimatedScore", score);
        return score;
    }

    public static boolean checkItineraryCompletion(ItineraryObject itinerary, Collection<Geolocation> geolocations)
            throws Exception {
        if (itinerary == null) {
            return false;
        }
        if (geolocations.size() > 1) {
            boolean started = false;
            boolean ended = false;

            double fromLat = Double.parseDouble(itinerary.getData().getFrom().getLat());
            double fromLon = Double.parseDouble(itinerary.getData().getFrom().getLon());
            double toLat = Double.parseDouble(itinerary.getData().getTo().getLat());
            double toLon = Double.parseDouble(itinerary.getData().getTo().getLon());
            long startTime = itinerary.getData().getStartime();
            long endTime = itinerary.getData().getEndtime();

            for (Geolocation geolocation : geolocations) {
                double lat = geolocation.getLatitude();
                double lon = geolocation.getLongitude();
                long time = geolocation.getCreated_at().getTime();
                if (!started) {
                    double fromD = harvesineDistance(lat, lon, fromLat, fromLon);
                    long fromT = Math.abs(time - startTime);
                    started = fromD <= SPACE_ERROR & fromT <= TIME_ERROR;
                }
                if (!ended) {
                    double toD = harvesineDistance(geolocation.getLatitude(), geolocation.getLongitude(), toLat,
                            toLon);
                    long toT = Math.abs(time - endTime);
                    ended = toD <= SPACE_ERROR & toT <= TIME_ERROR;
                }
                if (started && ended) {
                    return true;
                }
            }
        }

        return false;
    }

    public static ValidationResult checkItineraryMatching(ItineraryObject itinerary,
            Collection<Geolocation> geolocations) throws Exception {

        boolean legWalkOnly = true;
        boolean geolocationWalkOnly = true;

        Set<String> geolocationModes = Sets.newHashSet();
        Set<String> legsModes = Sets.newHashSet();

        ValidationResult vr = new ValidationResult();
        vr.setGeoLocationsN(geolocations.size());
        vr.setGeoActivities(geolocationModes);
        vr.setLegsActivities(legsModes);

        List<List<Geolocation>> legPositions = Lists.newArrayList();
        List<List<Geolocation>> matchedPositions = Lists.newArrayList();
        for (Leg leg : itinerary.getData().getLeg()) {
            legPositions.addAll(splitList(decodePoly(leg)));

            TType tt = leg.getTransport().getType();
            if (FAST_TRANSPORTS.contains(tt)) {
                // onLeg.setActivity_type(IN_VEHICLE);
                legsModes.add(IN_VEHICLE);
                legWalkOnly = false;
            } else if (tt.equals(TType.BICYCLE)) {
                // onLeg.setActivity_type(ON_BICYCLE);
                legsModes.add(ON_BICYCLE);
                legWalkOnly = false;
            } else if (tt.equals(TType.WALK)) {
                // onLeg.setActivity_type(ON_FOOT);
                legsModes.add(ON_FOOT);
            }
        }

        vr.setLegsLocationsN(legPositions.size());

        for (Geolocation geolocation : geolocations) {

            if (geolocation.getAccuracy() != null && geolocation.getActivity_confidence() != null
                    && geolocation.getAccuracy() > SPACE_ERROR * 1000 * 2
                    && geolocation.getActivity_confidence() < 50) {
                continue;
            }

            double lat = geolocation.getLatitude();
            double lon = geolocation.getLongitude();

            if (geolocation.getActivity_type() != null && !geolocation.getActivity_type().isEmpty()) {
                if (WALKLIKE.contains(geolocation.getActivity_type())) {
                    geolocationModes.addAll(WALKLIKE);
                } else {
                    geolocationModes.add(geolocation.getActivity_type());
                }

                if (geolocation.getActivity_type().equals(IN_VEHICLE)
                        && geolocation.getActivity_confidence() > 50) {
                    geolocationWalkOnly = false;
                }
            }
            if (geolocation.getActivity_type() == null) {
                geolocationModes.addAll(WALKLIKE);
            }

            List<Geolocation> toRemove = null;
            for (List<Geolocation> poss : legPositions) {
                for (Geolocation pos : poss) {
                    double d = harvesineDistance(lat, lon, pos.getLatitude(), pos.getLongitude());
                    double t = Math.abs(pos.getRecorded_at().getTime() - geolocation.getRecorded_at().getTime());
                    if (d <= Math.max(SPACE_ERROR,
                            geolocation.getAccuracy() != null ? ((double) geolocation.getAccuracy() / 10000) : 0)) {
                        toRemove = poss;
                        break;
                    }
                }
                if (toRemove != null) {
                    break;
                }
            }
            if (toRemove != null) {
                legPositions.remove(toRemove);
                matchedPositions.add(toRemove);
            }

        }

        SetView<String> diffModes = Sets.difference(legsModes, geolocationModes);

        vr.setMatchedLocationsN(matchedPositions.size());
        vr.setMatchedLocations(vr.getMatchedLocationsN() > Math.ceil(vr.getLegsLocationsN() / 2));
        vr.setTooFewPoints(vr.getGeoLocationsN() < 2);
        vr.setMatchedActivities(diffModes.size() == 0);
        vr.setTooFast(legWalkOnly & !geolocationWalkOnly);

        vr.setValid(vr.getMatchedActivities() && vr.getMatchedLocations() && !vr.getTooFast());

        return vr;
    }

    public static ValidationResult validateFreeTracking(Collection<Geolocation> geolocations, String ttype)
            throws Exception {
        if (geolocations == null || ttype == null) {
            return null;
        }
        ValidationResult vr = new ValidationResult();
        vr.reset();

        double averageSpeed = 0;
        double maxSpeed = 0;

        List<Geolocation> points = new ArrayList<Geolocation>(geolocations);
        Collections.sort(points, new Comparator<Geolocation>() {

            @Override
            public int compare(Geolocation o1, Geolocation o2) {
                return (int) (o1.getRecorded_at().getTime() - o2.getRecorded_at().getTime());
            }

        });

        int tooFastCountTotal = 0;
        double distance = 0;
        long time = 0;

        int origPointsSize = points.size();
        if (points.size() >= 2) {
            //         logger.debug("Original track points (remove outliers): " + points.size());
            //         removeOutliers(points);
            //         logger.debug("Transformed track points (remove outliers): " + points.size());         

            //         if (points.size() >= 2) {
            logger.debug("Original track points (transform): " + points.size());
            points = transform(points);
            logger.debug("Transformed track points (transform): " + points.size());
            //         }

            int tooFastCount = 0;
            if (points.size() >= 2) {
                for (int i = 1; i < points.size(); i++) {
                    double d = harvesineDistance(points.get(i).getLatitude(), points.get(i).getLongitude(),
                            points.get(i - 1).getLatitude(), points.get(i - 1).getLongitude());
                    //               System.out.println(points.get(i - 1).getLatitude() + "," + points.get(i - 1).getLongitude() + " / " +  points.get(i).getLatitude() + "," +  points.get(i).getLongitude() + " = " + d);

                    long t = points.get(i).getRecorded_at().getTime()
                            - points.get(i - 1).getRecorded_at().getTime();
                    if (t > 0) {
                        double s = (1000.0 * d / ((double) t / 1000)) * 3.6;
                        maxSpeed = Math.max(maxSpeed, s);
                        if (isMaximumTooFast(s, ttype)) {
                            tooFastCount++;
                            tooFastCountTotal = Math.max(tooFastCountTotal, tooFastCount);
                        } else {
                            tooFastCount = 0;
                        }
                    }
                    distance += d;
                }
                time = points.get(points.size() - 1).getRecorded_at().getTime()
                        - points.get(0).getRecorded_at().getTime();
                averageSpeed = (1000.0 * distance / ((double) time / 1000)) * 3.6;
            }
        }

        vr.setTooFast(false);
        if ("walk".equals(ttype)) {
            if (averageSpeed > 15) {
                vr.setTooFast(true);
            }
        }
        if ("bike".equals(ttype)) {
            if (averageSpeed > 27) {
                vr.setTooFast(true);
            }
        }
        if (tooFastCountTotal > 3) {
            vr.setTooFast(true);
        }

        vr.setGeoLocationsN(points.size());
        vr.setValid(!vr.getTooFast() && origPointsSize >= 2);

        vr.setAverageSpeed(averageSpeed);
        vr.setTooFewPoints(vr.getGeoLocationsN() < 2);
        vr.setMaxSpeed(maxSpeed);
        vr.setDistance(distance);
        vr.setTime(time);
        return vr;
    }

    public static void removeOutliers(List<Geolocation> points) {
        Set<Geolocation> toRemove = Sets.newHashSet();

        double averageSpeed = 0;

        double distance = 0;
        for (int i = 1; i < points.size(); i++) {
            double d = harvesineDistance(points.get(i).getLatitude(), points.get(i).getLongitude(),
                    points.get(i - 1).getLatitude(), points.get(i - 1).getLongitude());
            long t = points.get(i).getRecorded_at().getTime() - points.get(i - 1).getRecorded_at().getTime();
            if (t > 0) {
                distance += d;
            }
        }
        double time = points.get(points.size() - 1).getRecorded_at().getTime()
                - points.get(0).getRecorded_at().getTime();
        averageSpeed = 3600000 * distance / (double) time;

        for (int i = 1; i < points.size() - 1; i++) {
            double d1 = harvesineDistance(points.get(i).getLatitude(), points.get(i).getLongitude(),
                    points.get(i - 1).getLatitude(), points.get(i - 1).getLongitude());
            long t1 = points.get(i).getRecorded_at().getTime() - points.get(i - 1).getRecorded_at().getTime();
            double s1 = 0;
            if (t1 > 0) {
                s1 = 3600000 * d1 / (double) t1;
            }
            double d2 = harvesineDistance(points.get(i).getLatitude(), points.get(i).getLongitude(),
                    points.get(i + 1).getLatitude(), points.get(i + 1).getLongitude());
            long t2 = points.get(i + 1).getRecorded_at().getTime() - points.get(i).getRecorded_at().getTime();
            double s2 = 0;
            if (t2 > 0) {
                s2 = 3600000 * d2 / (double) t2;
            }

            Integer index = null;

            double d3 = harvesineDistance(points.get(i - 1).getLatitude(), points.get(i - 1).getLongitude(),
                    points.get(i + 1).getLatitude(), points.get(i + 1).getLongitude());

            double a = Math.acos((d1 * d1 + d2 * d2 - d3 * d3) / (2 * d1 * d2));

            if (a < 0.017453292519943 * 3) {
                index = i;
            } else if (a < 0.017453292519943 * 30 && s1 > 4 * averageSpeed && s2 > 4 * averageSpeed && i != 1
                    && i != points.size() - 2) {
                index = i;
            } else if (i == 1 && s1 > 4 * averageSpeed) {
                index = 0;
            } else if (i == points.size() - 2 && s2 > 4 * averageSpeed) {
                index = points.size() - 1;
            }

            if (index != null) {
                toRemove.add(points.get(index));
            }
        }

        points.removeAll(toRemove);
    }

    private static boolean isMaximumTooFast(double speed, String ttype) {
        if ("walk".equals(ttype)) {
            if (speed > 20) {
                return true;
            }
        }
        if ("bike".equals(ttype)) {
            if (speed > 65) {
                return true;
            }
        }
        return false;
    }

    private static List<Geolocation> transform(List<Geolocation> points) {
        List<Geolocation> result = Lists.newArrayList();
        for (int i = 1; i < points.size(); i++) {
            transformPair(points.get(i - 1), points.get(i), result);
        }

        Collections.sort(result);

        return result;
    }

    private static void transformPair(Geolocation p1, Geolocation p2, List<Geolocation> result) {
        double distance = harvesineDistance(p1, p2);
        if (distance == 0) {
            return;
        }
        double[] lats = computeLats(p1, p2, distance);
        double[] lngs = computeLngs(p1, p2, distance);
        Date[] recordedAt = computeRecordedAt(p1, p2);
        Geolocation p1n = new Geolocation(lats[0], lngs[0], p1.getRecorded_at());
        Geolocation p2n = new Geolocation(lats[1], lngs[1], p2.getRecorded_at());
        result.add(p1n);
        result.add(p2n);

    }

    private static double[] compute(double v1, double a1, double v2, double a2, double distance) {
        if ((a1 + a2) / 1000.0 > distance) {
            double v = a1 > a2 ? (v2 - (v2 - v1) * a2 / a1) : (v1 + (v2 - v1) * a1 / a2);
            return new double[] { v, v };
        }
        return new double[] { v1 + (v2 - v1) * a1 / distance / 1000.0, v2 - (v2 - v1) * a2 / distance / 1000.0 };
    }

    private static double[] computeLats(Geolocation p1, Geolocation p2, double distance) {
        if (p1.getLatitude() > p2.getLatitude()) {
            double[] res = computeLats(p2, p1, distance);
            return new double[] { res[1], res[0] };
        }
        return compute(p1.getLatitude(), (double) p1.getAccuracy(), p2.getLatitude(), (double) p2.getAccuracy(),
                distance);
    }

    private static double[] computeLngs(Geolocation p1, Geolocation p2, double distance) {
        if (p1.getLatitude() > p2.getLatitude()) {
            double[] res = computeLngs(p2, p1, distance);
            return new double[] { res[1], res[0] };
        }
        return compute(p1.getLongitude(), (double) p1.getAccuracy(), p2.getLongitude(), (double) p2.getAccuracy(),
                distance);
    }

    private static Date[] computeRecordedAt(Geolocation p1, Geolocation p2) {
        Date[] res = new Date[2];
        if (p1.getRecorded_at().compareTo(p2.getRecorded_at()) < 0) {
            res[0] = p1.getRecorded_at();
            res[1] = p2.getRecorded_at();
        } else {
            res[0] = p2.getRecorded_at();
            res[1] = p1.getRecorded_at();
        }
        return res;
    }

    private static double harvesineDistance(Geolocation p1, Geolocation p2) {
        return harvesineDistance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude());
    }

    private static double harvesineDistance(double lat1, double lon1, double lat2, double lon2) {
        lat1 = Math.toRadians(lat1);
        lon1 = Math.toRadians(lon1);
        lat2 = Math.toRadians(lat2);
        lon2 = Math.toRadians(lon2);

        double dlon = lon2 - lon1;
        double dlat = lat2 - lat1;

        double a = Math.pow((Math.sin(dlat / 2)), 2)
                + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2);

        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return EARTH_RADIUS * c;
    }

    public static List<Geolocation> decodePoly(Leg leg) {
        List<Geolocation> legPositions = Lists.newArrayList();
        String encoded = leg.getLegGeometery().getPoints();
        int index = 0, len = encoded.length();
        int lat = 0, lng = 0;
        while (index < len) {
            int b, shift = 0, result = 0;
            do {
                b = encoded.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 = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            Geolocation onLeg = new Geolocation();
            onLeg.setLatitude((((double) lat / 1E5)));
            onLeg.setLongitude((((double) lng / 1E5)));
            onLeg.setRecorded_at(new Date(leg.getStartime()));

            legPositions.add(onLeg);

        }
        return legPositions;
    }

    private static List<List<Geolocation>> splitList(List<Geolocation> list) {
        List<List<Geolocation>> result = Lists.newArrayList();
        int half = list.size() / 2;
        List<Geolocation> l1 = list.subList(0, half);
        List<Geolocation> l2 = list.subList(half, list.size());
        result.add(l1);
        result.add(l2);
        return result;
    }

    public static String encodePoly(List<Geolocation> path) {

        StringBuffer encodedPoints = new StringBuffer();

        int plat = 0;
        int plng = 0;

        int listSize = path.size();

        Geolocation location;

        for (int i = 0; i < listSize; i++) {
            location = (Geolocation) path.get(i);

            int late5 = floor1e5(location.getLatitude());
            int lnge5 = floor1e5(location.getLongitude());

            int dlat = late5 - plat;
            int dlng = lnge5 - plng;

            plat = late5;
            plng = lnge5;

            encodedPoints.append(encodeSignedNumber(dlat)).append(encodeSignedNumber(dlng));
        }

        return encodedPoints.toString();

    }

    private static int floor1e5(double coordinate) {
        return (int) Math.floor(coordinate * 1e5);
    }

    private static String encodeSignedNumber(int num) {
        int sgn_num = num << 1;
        if (num < 0) {
            sgn_num = ~(sgn_num);
        }
        return (encodeNumber(sgn_num));
    }

    private static String encodeNumber(int num) {
        StringBuffer encodeString = new StringBuffer();

        while (num >= 0x20) {
            encodeString.append((char) ((0x20 | (num & 0x1f)) + 63));
            num >>= 5;
        }

        encodeString.append((char) (num + 63));

        return encodeString.toString();

    }

    public static void main(String[] args)
            throws UnknownHostException, MongoException, SecurityException, RemoteException {
        MongoTemplate mg = new MongoTemplate(new Mongo("127.0.0.1", 37017), "mobility-logging");
        List<Map> findAll = mg.findAll(Map.class, "forgamification");
        for (Map m : findAll) {
            m.remove("_id");
            RemoteConnector.postJSON("http://localhost:8900", "/execute", JsonUtils.toJSON(m), null);
        }
    }

    /**
     * @param travelId
     * @param appId
     * @param playerId
     * @param geolocationEvents
     */
    public void saveFreeTracking(final String travelId, final String appId, final String playerId,
            final Collection<Geolocation> geolocationEvents, final String ttype) {
        if (gamificationUrl == null) {
            logger.debug("No gamification URL, returning.");
            return;
        }

        AppInfo app = appSetup.findAppById(appId);

        try {
            if (System.currentTimeMillis() < new SimpleDateFormat("dd/MM/yyyy").parse(app.getGameStart())
                    .getTime()) {
                logger.debug("Game not yet started, returning.");
                return;
            }
        } catch (ParseException e) {
            return;
        }

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                saveFreetracking(travelId, appId, playerId, geolocationEvents, ttype);
            }

        });
    }

    private void saveFreetracking(String travelId, String appId, String playerId,
            Collection<Geolocation> geolocationEvents, String ttype) {
        Map<String, Object> data = computeFreeTrackingData(geolocationEvents, ttype);
        if ((Long) data.get("estimatedScore") == 0) {
            logger.debug("EstimatedScore is 0, returning.");
            return;
        }
        data.remove("estimatedScore");

        if (data.isEmpty()) {
            logger.debug("Data is empty, returning.");
            return;
        }

        AppInfo app = appSetup.findAppById(appId);

        try {
            ExecutionDataDTO ed = new ExecutionDataDTO();
            ed.setGameId(app.getGameId());
            ed.setPlayerId(playerId);
            ed.setActionId(SAVE_ITINERARY);
            ed.setData(data);

            String content = JsonUtils.toJSON(ed);

            logger.debug("Sending to " + gamificationUrl + "/gengine/execute (" + SAVE_ITINERARY + ") = " + data);
            HTTPConnector.doAuthenticatedPost(gamificationUrl + "/gengine/execute", content, "application/json",
                    "application/json", app.getGameUser(), app.getGamePassword());
        } catch (Exception e) {
            logger.error("Error sending gamification action: " + e.getMessage());
        }
    }

}