Package org.fluxtream.core.services.impl

Source Code of org.fluxtream.core.services.impl.MetadataServiceImpl

package org.fluxtream.core.services.impl;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.fluxtream.core.Configuration;
import org.fluxtream.core.TimezoneMap;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.connectors.location.LocationFacet;
import org.fluxtream.core.connectors.vos.AbstractFacetVO;
import org.fluxtream.core.domain.AbstractLocalTimeFacet;
import org.fluxtream.core.domain.Guest;
import org.fluxtream.core.domain.metadata.City;
import org.fluxtream.core.domain.metadata.FoursquareVenue;
import org.fluxtream.core.domain.metadata.VisitedCity;
import org.fluxtream.core.domain.metadata.WeatherInfo;
import org.fluxtream.core.metadata.ArbitraryTimespanMetadata;
import org.fluxtream.core.metadata.DayMetadata;
import org.fluxtream.core.metadata.MonthMetadata;
import org.fluxtream.core.metadata.WeekMetadata;
import org.fluxtream.core.services.GuestService;
import org.fluxtream.core.services.MetadataService;
import org.fluxtream.core.services.NotificationsService;
import org.fluxtream.core.thirdparty.helpers.WWOHelper;
import org.fluxtream.core.utils.HttpUtils;
import org.fluxtream.core.utils.JPAUtils;
import org.fluxtream.core.utils.TimeUtils;
import org.fluxtream.core.utils.UnexpectedHttpResponseCodeException;
import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator;
import com.luckycatlabs.sunrisesunset.dto.Location;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.DateTimeZone;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Component
@Transactional(readOnly=true)
public class MetadataServiceImpl implements MetadataService {

    FlxLogger logger = FlxLogger.getLogger(MetadataServiceImpl.class);

    // threshold range in meters to consider successive location points to be in the same city
    // without checking
    private static final float CITY_RANGE = 1000.f;

  @Autowired
  Configuration env;

  @PersistenceContext
  EntityManager em;

  @Autowired
  GuestService guestService;

  @Autowired
  NotificationsService notificationsService;

    @Autowired
    WWOHelper wwoHelper;

    @Override
  public TimeZone getTimeZone(double latitude, double longitude) {
    City closestCity = getClosestCity(latitude, longitude);
    TimeZone timeZone = TimeZone.getTimeZone(closestCity.geo_timezone);
    return timeZone;
  }

  @Override
  public TimeZone getCurrentTimeZone(long guestId) {
    LocationFacet lastLocation = getLastLocation(guestId, System.currentTimeMillis());
    if (lastLocation != null) {
      TimeZone timeZone = getTimeZone(lastLocation.latitude,
          lastLocation.longitude);
      return timeZone;
    }
    return null;
  }

  @Override
  @Transactional(readOnly = false)
  public void setTimeZone(long guestId, String date, String timeZone) {
        logger.warn("component=metadata action=setTimeZone message=attempt to set timezone");
  }

    @Override
    @Transactional(readOnly = false)
    public void resetDayMainCity(final long guestId, final String date) {
        final List<VisitedCity> visitedCities = getVisitedCitiesForDate(guestId, date);
        for (VisitedCity visitedCity : visitedCities) {
            if (visitedCity.locationSource== LocationFacet.Source.USER)
                em.remove(visitedCity);
        }
        em.flush();
    }

    @Override
    public void setDayMainCity(final long guestId, final float latitude, final float longitude, final String date) {
        final City closestCity = getClosestCity(latitude, longitude);
        setDayMainCity(guestId, date, closestCity);
    }

    @Override
    public void setDayMainCity(final long guestId, final long visitedCityId, final String date) {
        final VisitedCity visitedCity = em.find(VisitedCity.class, visitedCityId);
        setDayMainCity(guestId, date, visitedCity.city);
    }

    private void setDayMainCity(final long guestId, final String date, final City closestCity) {
        clearMainCities(guestId, Arrays.asList(date));
        final DateTime dateTime = TimeUtils.dateFormatter.withZone(DateTimeZone.forID(closestCity.geo_timezone)).parseDateTime(date);
        setMainCity(guestId, closestCity, dateTime.getMillis(), dateTime.getMillis() + DateTimeConstants.MILLIS_PER_DAY - 1, date);
    }

    private void clearMainCities(final long guestId, final Collection<String> dates) {
        TypedQuery<VisitedCity> query = em.createQuery("SELECT facet FROM " +
                                                       JPAUtils.getEntityName(VisitedCity.class) +
                                                       " facet WHERE facet.guestId=:guestId AND facet.locationSource=:source  AND facet.date IN :dates" +
                                                       " ORDER BY facet.start", VisitedCity.class);
        query.setParameter("guestId", guestId);
        query.setParameter("source", LocationFacet.Source.USER);
        query.setParameter("dates", dates);
        final List<VisitedCity> resultList = query.getResultList();
        for (VisitedCity city : resultList) {
            em.remove(city);
        }
    }

    @Transactional(readOnly=false)
    private void setMainCity(final long guestId, final City city, final long start, final long end, final String timePeriod) {

        resetDayMainCity(guestId, timePeriod);

        VisitedCity visitedCity = new VisitedCity();
        visitedCity.guestId = guestId;
        visitedCity.date = timePeriod;
        visitedCity.locationSource = LocationFacet.Source.USER;
        visitedCity.city = city;
        visitedCity.start = start;
        visitedCity.end = end;
        visitedCity.count = 1;
        visitedCity.startTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(start);
        visitedCity.endTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(end);
        em.persist(visitedCity);
    }

    @Override
    public ArbitraryTimespanMetadata getArbitraryTimespanMetadata(final long guestId, final long start, final long end) {
        final TreeSet<String> dates = getDatesBetween(start, end);
        List<VisitedCity> cities = getVisitedCitiesForDates(guestId, dates);
        VisitedCity previousInferredCity = null, nextInferredCity = null;
        if (cities.size()==0) {
            previousInferredCity = searchCityBeforeDate(guestId, dates.first());
            nextInferredCity = searchCityAfterDate(guestId, dates.last());
            if (previousInferredCity==null&&nextInferredCity==null) {
                ArbitraryTimespanMetadata info = new ArbitraryTimespanMetadata(start, end);
                return info;
            }
        }
        final VisitedCity consensusVisitedCity = getConsensusVisitedCity(cities, previousInferredCity, nextInferredCity);
        final List<DayMetadata> dayMetadataForDates = getDayMetadataForDates(guestId, dates);
        final TreeMap<String, TimeZone> consensusTimezoneMap = getConsensusTimezoneMap(dayMetadataForDates);
        final List<VisitedCity> consensusCities = extractConsensusCities(dayMetadataForDates);
        TimezoneMap timezoneMap = TimezoneMap.fromConsensusTimezoneMap(consensusTimezoneMap);
        ArbitraryTimespanMetadata info = new ArbitraryTimespanMetadata(consensusVisitedCity, previousInferredCity, nextInferredCity, consensusTimezoneMap, timezoneMap, cities, consensusCities, start, end);
        return info;
    }

    TreeMap<String,TimeZone> getConsensusTimezoneMap(List<DayMetadata> metadata) {
        TreeMap<String, TimeZone> tzMap = new TreeMap<String, TimeZone>();
        for (DayMetadata dayMetadata : metadata)
            tzMap.put(dayMetadata.date, TimeZone.getTimeZone(dayMetadata.consensusVisitedCity.city.geo_timezone));
        return tzMap;
    }

    List<DayMetadata> getDayMetadataForDates(final long guestId, final TreeSet<String> dates) {
        List<DayMetadata> metadata = new ArrayList<DayMetadata>();
        for (String date : dates) {
            final DayMetadata dayMetadata = getDayMetadata(guestId, date);
            metadata.add(dayMetadata);
        }
        return metadata;
    }

    List<VisitedCity> extractConsensusCities(List<DayMetadata> metadata) {
        List<VisitedCity> visitedCities = new ArrayList<VisitedCity>();
        for (DayMetadata dayMetadata : metadata) {
            visitedCities.add(dayMetadata.consensusVisitedCity);
        }
        return visitedCities;
    }

    TreeSet<String> getDatesBetween(long start, final long end) {
        TreeSet<String> dates = new TreeSet<String>();
        String startDate = TimeUtils.dateFormatter.print(start-DateTimeConstants.MILLIS_PER_DAY/2);
        String endDate = TimeUtils.dateFormatter.print(end + DateTimeConstants.MILLIS_PER_DAY/2);
        dates.add(startDate);
        for(;!startDate.equals(endDate);start+=DateTimeConstants.MILLIS_PER_DAY) {
            startDate = TimeUtils.dateFormatter.print(start);
            dates.add(startDate);
        }
        return dates;
    }

    @Override
  public DayMetadata getDayMetadata(long guestId, String date) {
        // get visited cities for a specific date . If we don't have any data for that date,
        // retrieve cities for the first date for which we do have data
        List<VisitedCity> cities = getVisitedCitiesForDate(guestId, date);
        VisitedCity previousInferredCity = null, nextInferredCity = null;
        if (cities.size()==0) {
            previousInferredCity = searchCityBeforeDate(guestId, date);
            nextInferredCity = searchCityAfterDate(guestId, date);
            if (previousInferredCity==null&&nextInferredCity==null) {
                DayMetadata info = new DayMetadata(date);
                return info;
            }
        }
        final VisitedCity consensusVisitedCity = getConsensusVisitedCity(cities, previousInferredCity, nextInferredCity);
        TreeMap<String, TimeZone> consensusTimezoneMap = new TreeMap<String, TimeZone>();
        consensusTimezoneMap.put(date, TimeZone.getTimeZone(consensusVisitedCity.city.geo_timezone));
        TimezoneMap timezoneMap = TimezoneMap.fromConsensusTimezoneMap(consensusTimezoneMap);
        List<VisitedCity> consensusVisitedCities = Arrays.asList(consensusVisitedCity);
        DayMetadata info = new DayMetadata(consensusVisitedCity, previousInferredCity, nextInferredCity, consensusTimezoneMap, timezoneMap, cities, consensusVisitedCities, date);
        return info;
    }

    private int daysBetween(String date, VisitedCity vcity) {
        final DateTime wantedDate = TimeUtils.dateFormatter.withZone(DateTimeZone.forID(vcity.city.geo_timezone)).parseDateTime(date);
        final DateTime availableDate = TimeUtils.dateFormatter.withZone(DateTimeZone.forID(vcity.city.geo_timezone)).parseDateTime(vcity.date);
        final int days = Days.daysBetween(wantedDate, availableDate).getDays();
        return days;
    }

    @Override
    public WeekMetadata getWeekMetadata(final long guestId, final int year, final int week) {
        TreeSet<String> dates = getDatesForWeek(year, week);
        List<VisitedCity> cities = getVisitedCitiesForDates(guestId, dates);
        VisitedCity previousInferredCity = null, nextInferredCity = null;
        if (cities.size()==0) {
            previousInferredCity = searchCityBeforeDate(guestId, dates.first());
            nextInferredCity = searchCityAfterDate(guestId, dates.last());
            if (previousInferredCity==null&&nextInferredCity==null) {
                WeekMetadata info = new WeekMetadata(year, week);
                return info;
            }
        }
        final VisitedCity consensusVisitedCity = getConsensusVisitedCity(cities, previousInferredCity, nextInferredCity);
        final List<DayMetadata> dayMetadataForDates = getDayMetadataForDates(guestId, dates);
        final TreeMap<String, TimeZone> consensusTimezoneMap = getConsensusTimezoneMap(dayMetadataForDates);
        final List<VisitedCity> consensusCities = extractConsensusCities(dayMetadataForDates);
        TimezoneMap timezoneMap = TimezoneMap.fromConsensusTimezoneMap(consensusTimezoneMap);
        WeekMetadata info = new WeekMetadata(consensusVisitedCity, previousInferredCity, nextInferredCity, consensusTimezoneMap, timezoneMap, cities, consensusCities, year, week);
        return info;
    }

    public List<VisitedCity> getConsensusCities(final long guestId, final TreeSet<String> dates) {
        List<VisitedCity> consensusCities = new ArrayList<VisitedCity>();
        Collections.sort(consensusCities,
            new Comparator<VisitedCity>(){
                @Override
                public int compare(final VisitedCity o1, final VisitedCity o2) {
                    return o1.date.compareTo(o2.date);
                }
            });
        for (String date : dates) {
            final DayMetadata dayMetadata = getDayMetadata(guestId, date);
            final VisitedCity consensusVisitedCity = dayMetadata.consensusVisitedCity;
            // Explicitely set the date on this visitedCity to enable time boundaries checking
            VisitedCity copy = new VisitedCity(consensusVisitedCity);
            copy.setDate(date);
            copy.start = copy.getDayStart();
            copy.end = copy.getDayEnd();
            consensusCities.add(copy);
        }
        return consensusCities;
    }

    @Override
    public MonthMetadata getMonthMetadata(final long guestId, final int year, final int month) {
        TreeSet<String> dates = getDatesForMonth(year, month);
        List<VisitedCity> cities = getVisitedCitiesForDates(guestId, dates);
        VisitedCity previousInferredCity = null, nextInferredCity = null;
        if (cities.size()==0) {
            previousInferredCity = searchCityBeforeDate(guestId, dates.first());
            nextInferredCity = searchCityAfterDate(guestId, dates.last());
            if (previousInferredCity==null && nextInferredCity==null) {
                MonthMetadata info = new MonthMetadata(year, month);
                return info;
            }
        }
        final VisitedCity consensusVisitedCity = getConsensusVisitedCity(cities, previousInferredCity, nextInferredCity);
        final List<DayMetadata> dayMetadataForDates = getDayMetadataForDates(guestId, dates);
        final TreeMap<String, TimeZone> consensusTimezoneMap = getConsensusTimezoneMap(dayMetadataForDates);
        final List<VisitedCity> consensusCities = extractConsensusCities(dayMetadataForDates);
        TimezoneMap timezoneMap = TimezoneMap.fromConsensusTimezoneMap(consensusTimezoneMap);
        MonthMetadata info = new MonthMetadata(consensusVisitedCity, previousInferredCity, nextInferredCity, consensusTimezoneMap, timezoneMap, cities, consensusCities, year, month);
        return info;
    }

    public TreeSet<String> getDatesForWeek(final int year, final int week) {
        LocalDate weekDay = TimeUtils.getBeginningOfWeek(year, week);
        final LocalDate nextWeekStart = weekDay.plusWeeks(1);
        TreeSet<String> dates = new TreeSet<String>();
        while(weekDay.isBefore(nextWeekStart)) {
            final String date = TimeUtils.dateFormatterUTC.print(weekDay);
            dates.add(date);
            weekDay = weekDay.plusDays(1);
        }
        return dates;
    }

    public TreeSet<String> getDatesForMonth(final int year, final int month) {
        LocalDate dayOfMonth = TimeUtils.getBeginningOfMonth(year, month);
        final LocalDate nextMonthStart = dayOfMonth.plusMonths(1);
        TreeSet<String> dates = new TreeSet<String>();
        while(dayOfMonth.isBefore(nextMonthStart)) {
            final String date = TimeUtils.dateFormatterUTC.print(dayOfMonth);
            dates.add(date);
            dayOfMonth = dayOfMonth.plusDays(1);
        }
        return dates;
    }

    public List<VisitedCity> getVisitedCitiesForDate(final long guestId, final String date) {
        TypedQuery<VisitedCity> query = em.createQuery("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=? AND facet.date=?" + " ORDER BY facet.start", VisitedCity.class);
        query.setParameter(1, guestId);
        query.setParameter(2, date);
        final List<VisitedCity> cities = query.getResultList();
        return cities;
    }

    public List<VisitedCity> getVisitedCitiesForDates(final long guestId, final TreeSet<String> dates) {
        TypedQuery<VisitedCity> query = em.createQuery("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=:guestId AND facet.date IN :dates" + " ORDER BY facet.start", VisitedCity.class);
        query.setParameter("guestId", guestId);
        query.setParameter("dates", dates);
        List<VisitedCity> cities = query.getResultList();
        return cities;
    }

    /**
     * Get only the consensus city. Return user's preference if it has been set, otherwise return
     * the city where the user has spent the most time.
     *
     * @param cities
     * @return
     */
    private VisitedCity getConsensusVisitedCity(final List<VisitedCity> cities, final VisitedCity previousInferredCity, final VisitedCity nextInferredCity) {

        for (VisitedCity city : cities)
            if (city.locationSource== LocationFacet.Source.USER)
                return city;

        if (previousInferredCity!=null&&nextInferredCity!=null) {
            if (Math.abs(previousInferredCity.daysInferred)>Math.abs(nextInferredCity.daysInferred))
                return nextInferredCity;
            else
                return previousInferredCity;
        } else if (previousInferredCity!=null)
            return previousInferredCity;
        else if (nextInferredCity!=null)
            return nextInferredCity;

        List<VisitedCity> cityList = new ArrayList<VisitedCity>(cities);
        Collections.sort(cityList, new Comparator<VisitedCity>() {
            @Override
            public int compare(final VisitedCity a, final VisitedCity b) {
                int timeSpentInA = (int) (a.end-a.start+1); //add one if start and end are equal
                int timeSpentInB = (int) (b.end-b.start+1);
                return timeSpentInB-timeSpentInA;
            }
        });

        if (cityList.size()>0)
            return cityList.get(0);
        return null;
    }

    private String findClosestKnownDateForTime(final long guestId, final long time) {
        VisitedCity existingCity = searchCityBefore(guestId, time);
        if (existingCity==null)
            existingCity = searchCityAfter(guestId, time);
        if (existingCity!=null) {
            return existingCity.date;
        }
        return TimeUtils.dateFormatterUTC.print(time);
    }

    private VisitedCity searchCityBefore(final long guestId, final long instant) {
        return searchCity("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=? AND facet.start<? ORDER BY facet.start", guestId, instant);
    }

    private VisitedCity searchCityAfter(final long guestId, final long instant) {
        return searchCity("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=? AND facet.start>? ORDER BY facet.start", guestId, instant);
    }

    private VisitedCity searchCity(final String queryString, final long guestId, final long instant) {
        final TypedQuery<VisitedCity> query = em.createQuery(queryString, VisitedCity.class);
        return getVisitedCity(guestId, instant, query);
    }

    private VisitedCity searchCity(final String queryString, final long guestId, final String date) {
        final TypedQuery<VisitedCity> query = em.createQuery(queryString, VisitedCity.class);
        return getVisitedCity(guestId, date, query);
    }

    private VisitedCity getVisitedCity(final long guestId, final String date, final TypedQuery<VisitedCity> query) {
        query.setMaxResults(1);
        query.setParameter(1, guestId);
        query.setParameter(2, date);
        final List<VisitedCity> cities = query.getResultList();
        if (cities.size()>0)
            return cities.get(0);
        return null;
    }

    private VisitedCity getVisitedCity(final long guestId, final long instant, final TypedQuery<VisitedCity> query) {
        query.setMaxResults(1);
        query.setParameter(1, guestId);
        query.setParameter(2, instant);
        final List<VisitedCity> cities = query.getResultList();
        if (cities.size()>0)
            return cities.get(0);
        return null;
    }

    private VisitedCity searchCityBeforeDate(final long guestId, final String date) {
        VisitedCity visitedCity = searchCity("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=? AND facet.date<? ORDER BY facet.start DESC", guestId, date);
        if (visitedCity!=null) {
            final List<VisitedCity> visitedCitiesForDate = getVisitedCitiesForDate(guestId, visitedCity.date);
            visitedCity = getConsensusVisitedCity(visitedCitiesForDate, null, null);
            visitedCity.daysInferred = daysBetween(date, visitedCity);
        }
        return visitedCity;
    }

    private VisitedCity searchCityAfterDate(final long guestId, final String date) {
        VisitedCity visitedCity = searchCity("SELECT facet FROM " + JPAUtils.getEntityName(VisitedCity.class) + " facet WHERE facet.guestId=? AND facet.date>? ORDER BY facet.start", guestId, date);
        if (visitedCity!=null) {
            final List<VisitedCity> visitedCitiesForDate = getVisitedCitiesForDate(guestId, visitedCity.date);
            visitedCity = getConsensusVisitedCity(visitedCitiesForDate, null, null);
            visitedCity.daysInferred = daysBetween(date, visitedCity);
        }
        return visitedCity;
    }

    @Override
    public List<DayMetadata> getAllDayMetadata(final long guestId) {
        return JPAUtils.find(em, DayMetadata.class,"context.all",guestId);
    }

  @Override
  public LocationFacet getLastLocation(long guestId, long time) {
    LocationFacet lastSeen = JPAUtils.findUnique(em, LocationFacet.class,
                                                     "location.lastSeen", guestId, time);
    return lastSeen;
  }

  @Override
  public TimeZone getTimeZone(long guestId, String date) {
        final DayMetadata dayMetadata = getDayMetadata(guestId, date);
        return dayMetadata.getTimeInterval().getMainTimeZone();
  }

    @Override
  public TimeZone getTimeZone(long guestId, long time) {
        String date = findClosestKnownDateForTime(guestId, time);
        return getTimeZone(guestId, date);
  }

    @Override
    public City getClosestCity(double latitude, double longitude) {

        List<City> cities = new ArrayList<City>();
        for (int dist = 10, i = 1; cities.size() == 0;)
            cities = getClosestCities(latitude, longitude,
                                      Double.valueOf(dist ^ i++));

        return cities.get(0);
    }

    @Override
    public List<City> getClosestCities(double latitude, double longitude,
                                       double dist) {

        double lon1 = longitude - dist
                                  / Math.abs(Math.cos(Math.toRadians(latitude)) * 69d);
        double lon2 = longitude + dist
                                  / Math.abs(Math.cos(Math.toRadians(latitude)) * 69d);
        double lat1 = latitude - (dist / 69.d);
        double lat2 = latitude + (dist / 69.d);

        Query query = em
                .createNativeQuery(
                        "SELECT cities1000.geo_id, cities1000.geo_name, "
                        + "cities1000.geo_timezone, cities1000.geo_latitude, "
                        + "cities1000.geo_longitude, cities1000.geo_country_code, "
                        + "cities1000.geo_admin1_code, cities1000.population, "
                        + "3956 * 2 * ASIN(SQRT(POWER(SIN((:mylat - geo_latitude) * pi()/180 / 2), 2) + COS(:mylat * pi()/180) *COS(geo_latitude * pi()/180) * POWER(SIN((:mylon -geo_longitude) * pi()/180 / 2), 2))) as distance "
                        + "FROM cities1000 "
                        + "WHERE geo_longitude between :lon1 and :lon2 "
                        + "and geo_latitude between :lat1 and :lat2 "
                        + "HAVING distance < :dist ORDER BY distance limit 1;",
                        City.class);

        query.setParameter("mylat", latitude);
        query.setParameter("mylon", longitude);
        query.setParameter("lon1", lon1);
        query.setParameter("lon2", lon2);
        query.setParameter("lat1", lat1);
        query.setParameter("lat2", lat2);
        query.setParameter("dist", dist);

        @SuppressWarnings("unchecked")
        List<City> resultList = query.getResultList();
        return resultList;
    }

    @Override
    public List<WeatherInfo> getWeatherInfo(double latitude, double longitude,
                                            String date) {
        City closestCity = getClosestCity(latitude, longitude);
        List<WeatherInfo> weather = JPAUtils.find(em, WeatherInfo.class, "weather.byDateAndCity.between", closestCity.geo_name, date);

        if (weather != null && weather.size() > 0) {
            addIcons(weather);
            return weather;
        }
        else {
            try {
                fetchWeatherInfo(latitude, longitude, closestCity.geo_name,
                                 date);
            } catch (Exception e) {
                logger.warn("action=fetchWeather error date="+ date+", lat=" + latitude + ", lon=" +longitude+", city="+closestCity.geo_name);
            }
            weather = JPAUtils.find(em, WeatherInfo.class,
                                    "weather.byDateAndCity.between", closestCity.geo_name,
                                    date);
            addIcons(weather);
        }
        return weather;
    }

    @Override
    public void rebuildMetadata(final String username) {
        Guest guest = null;
        // Accept guest ID as well as username
        try {
            guest = guestService.getGuest(username);
            if(guest==null) {
                // Try to treat arg as guestId
                Long guestId = Long.valueOf(username);
                if(guestId!=null) {
                    guest = guestService.getGuestById(guestId);
                }
            }
        }
        catch (Exception e) {
            // Might get exception if username doesn't exist and non-numeric
            guest=null;
        }
        // Check if we succeeded, return.  This isn't really right because we don't get
        // error reporting, but would take to long to fix error reporting right now.
        // TODO: fix error reporting
        if(guest==null) {
            return;
        }

        String entityName = JPAUtils.getEntityName(LocationFacet.class);
        final Query nativeQuery = em.createNativeQuery(String.format("SELECT DISTINCT apiKeyId FROM %s WHERE guestId=%s", entityName, guest.getId()));
        final List<BigInteger> resultList = nativeQuery.getResultList();
        for (BigInteger apiKeyId : resultList) {
            if(apiKeyId!=null && apiKeyId.longValue()>0) {
                rebuildMetadata(guest.username, apiKeyId.longValue());
            }
        }
    }

    private void rebuildMetadata(final String username, long apiKeyId) {
        final Guest guest = guestService.getGuest(username);
        String entityName = JPAUtils.getEntityName(LocationFacet.class);
        int i=0;
        final Query facetsQuery = em.createQuery(String.format("SELECT facet FROM %s facet WHERE facet.apiKeyId=? ORDER BY facet.start ASC", entityName));
        facetsQuery.setParameter(1, apiKeyId);
        while(true) {
            facetsQuery.setFirstResult(i);
            facetsQuery.setMaxResults(1000);
            final List<LocationFacet> rawLocations = facetsQuery.getResultList();
            //System.out.println(username + ": retrieved " + rawLocations.size() + " location datapoints (offset is " + i + ")");
            if (rawLocations.size()==0)
                break;
            //System.out.println(username + ":   " + AbstractLocalTimeFacet.timeStorageFormat.withZoneUTC().print(rawLocations.get(0).start));

            long then = System.currentTimeMillis();
            // Loop over the points to see if they're already included in visited cities entries
            // it's important that the location points in the locations list are for a single apiKeyId
            // and are in forward chronological order.  Only add locations that aren't already contained
            // within a VisitedCity item to the newLocations list
            List<LocationFacet> newLocations=new ArrayList<LocationFacet>();
            VisitedCity existingVisitedCityRecord = null;

            for (LocationFacet locationFacet : rawLocations) {
                // Check to see if this location is in the current existingVisitedCityRecord (if any)
                if(existingVisitedCityRecord !=null && locationFacet.start <=existingVisitedCityRecord.end) {
                    // This location falls within the last fetched visited cities record, skip it
                    continue;
                }
                // This location doesn't fall within the last fetched visited cities record (if any).
                // See if it fits in a new one.  Note that this really assumes that locationFacet.start
                // and locationFacet.end are the same and VisitedCity records are non-overlapping.
                // It returns null if there are no visited city
                // records overlapping the current point and non-null if there is one.
                existingVisitedCityRecord = JPAUtils.findUnique(em, VisitedCity.class,
                                                              "visitedCities.byApiAndTime",
                                                              locationFacet.apiKeyId,
                                                              locationFacet.start,
                                                              locationFacet.end);
                if(existingVisitedCityRecord == null) {
                    // This is a new point, add it
                    newLocations.add(locationFacet);
                }
                else {
                    // This point is already covered, skip it
                }
            }

            if(newLocations.size()>0) {
                long start = newLocations.get(0).start;
                System.out.println(username + ": processing " + newLocations.size() + " new " + entityName +
                                   " datapoints (offset is " + i + ", start is " + start +
                                   " = " + AbstractLocalTimeFacet.timeStorageFormat.withZoneUTC().print(start) + " UTC)");
                updateLocationMetadata(guest.getId(), newLocations);
            }
            else {
                System.out.println(username + ": no new " + entityName + " location datapoints (offset is " + i + ")");
            }

            long now = System.currentTimeMillis();
            System.out.println(String.format(username + ":   updateLocationMetadata took %s ms to complete", (now - then)));
            i+=rawLocations.size();
        }
    }

    /**
     * We want to have an explicit city "check-in" each time that we know we have been some place.
     * Thus, as we loop through a batch of new location datapoints, we keep track of the current date
     * and the current city and when either change we store a new datapoint in the VisitedCity table.
     * Then, for each date in the
     * @param guestId
     * @param locationResources
     */
    @Override
    @Transactional(readOnly=false)
    public void updateLocationMetadata(final long guestId, final List<LocationFacet> locationResources) {
        if (locationResources == null || locationResources.size()==0)
            return;
        System.out.println("processing " + locationResources.size() + " location datapoints...");
        // sort the location data in ascending time order
        Collections.sort(locationResources, new Comparator<LocationFacet>() {
            @Override
            public int compare(final LocationFacet o1, final LocationFacet o2) {
                return o1.start>o2.start?1:-1;
            }
        });
        // local vars: current city and current day
        String currentDate = "";
        Point2D.Double anchorLocation = new Point2D.Double(locationResources.get(0).latitude, locationResources.get(0).longitude);
        City anchorCity = getClosestCity(anchorLocation.x, anchorLocation.y);
        int count = 0;
        LocationFacet lastLocationResourceMatchingAnchor=locationResources.get(0);
        long start = locationResources.get(0).start;

        for (LocationFacet locationResource : locationResources) {
            try {
                City newCity = anchorCity;
                Point2D.Double location = new Point2D.Double(locationResource.latitude, locationResource.longitude);
                boolean withinAnchorRange = isWithinRange(location, anchorLocation);

                if (!withinAnchorRange) {
                    anchorLocation = new Point2D.Double(locationResource.latitude, locationResource.longitude);
                    newCity = getClosestCity(locationResource.latitude, locationResource.longitude);
                }

                String newDate = TimeUtils.dateFormatter.withZone(DateTimeZone.forID(newCity.geo_timezone)).print(locationResource.timestampMs);
                final boolean dateChanged = !newDate.equals(currentDate);
                final boolean cityChanged = newCity.geo_id!=anchorCity.geo_id;
                if (dateChanged||cityChanged) {
                    if (count>0)
                        storeCityInfo(lastLocationResourceMatchingAnchor, currentDate, anchorCity, start, count);
                    anchorCity = newCity;
                    start = locationResource.start;
                    count = 0;
                }
                count++;
                // update count on the last location before we finish
                if (locationResources.indexOf(locationResource)==locationResources.size()-1)
                    storeCityInfo(locationResource, newDate, newCity, start, count);
                currentDate = newDate;
                lastLocationResourceMatchingAnchor = locationResource;
            }
            catch (Exception e){
                System.err.println("Exception occurred trying to store location metadata. Skipping datapoint...");
                e.printStackTrace();
            }
        }
        em.flush();
    }

    @Override
    @Transactional(readOnly=false)
    public JSONObject getFoursquareVenueJSON(final String venueId) {
        String url = String.format("https://api.foursquare.com/v2/venues/%s?client_id=%s&client_secret=%s&v=20130624", venueId,
                                   env.get("foursquare.client.id"), env.get("foursquare.client.secret"));
        try {
            final String fetched = HttpUtils.fetch(url);
            JSONObject json = JSONObject.fromObject(fetched);
            return json;
        }
        catch (Exception e) {
            e.printStackTrace()//To change body of catch statement use File | Settings | File Templates.
            return null;
        }
    }

        @Override
    @Transactional(readOnly=false)
    public FoursquareVenue getFoursquareVenue(final String venueId) {
        final TypedQuery<FoursquareVenue> query = em.createQuery("SELECT venue FROM FoursquareVenue venue WHERE venue.foursquareId=:venueId", FoursquareVenue.class);
        query.setParameter("venueId", venueId);
        final List<FoursquareVenue> resultList = query.getResultList();
        if (resultList.size()>0)
            return resultList.get(0);
        else {
            String url = String.format("https://api.foursquare.com/v2/venues/%s?client_id=%s&client_secret=%s&v=20130624", venueId,
                                       env.get("foursquare.client.id"), env.get("foursquare.client.secret"));
            try {
                final String fetched = HttpUtils.fetch(url);
                JSONObject json = JSONObject.fromObject(fetched);
                if (!json.has("meta"))
                    throw new Exception("no meta information");
                JSONObject meta = json.getJSONObject("meta");
                final int code = meta.getInt("code");
                if (code!=200)
                    throw new Exception("error code is " + code);
                JSONObject responseWrapper = json.getJSONObject("response");
                JSONObject response = responseWrapper.getJSONObject("venue");

                // we only use the primary category and flatten its information in the venue
                final Object categoriesObject = response.get("categories");
                JSONArray categories = null;
                if (categoriesObject instanceof JSONArray)
                    categories = (JSONArray) categoriesObject;
                else {
                    categories = new JSONArray();
                    JSONObject categoriesJson = (JSONObject)categoriesObject;
                    categories.add(categoriesJson);
                }
                FoursquareVenue venue = new FoursquareVenue();

                String venueName = response.getString("name");
                String canonicalUrl = response.getString("canonicalUrl");
                venue.name = venueName;
                venue.canonicalUrl = canonicalUrl;
                venue.foursquareId = venueId;

                for(int i=0; i<categories.size(); i++) {
                    JSONObject categoryInfo = categories.getJSONObject(i);
                    if (categoryInfo.has("primary")&&categoryInfo.getBoolean("primary")) {
                        if (categoryInfo.has("id"))
                            venue.categoryFoursquareId = categoryInfo.getString("id");
                        if (categoryInfo.has("name"))
                            venue.categoryName = categoryInfo.getString("name");
                        if (categoryInfo.has("shortName"))
                            venue.categoryShortName = categoryInfo.getString("shortName");
                        if (categoryInfo.has("icon")) {
                            JSONObject iconJson = categoryInfo.getJSONObject("icon");
                            venue.categoryIconUrlPrefix = iconJson.getString("prefix");
                            venue.categoryIconUrlSuffix = iconJson.getString("suffix");
                        }
                    }
                }
                em.persist(venue);
                return venue;
            }
            catch (Exception e) {
                logger.warn("action=getFoursquareVenue venueId=" + venueId + " message=" + e.getMessage());
            }
        }
        return null;
    }

    private void storeCityInfo(final LocationFacet locationResource, String date, final City city, final long start, final int count) {
        VisitedCity previousRecord = JPAUtils.findUnique(em, VisitedCity.class,
                                                         "visitedCities.byApiDateAndCity",
                                                         locationResource.guestId,
                                                         locationResource.apiKeyId,
                                                         date,
                                                         city.geo_id);
        if (previousRecord==null) {
            persistCity(locationResource, date, start, count, city);
        } else {
            // update start time and end time if necessary
            if (start<previousRecord.start) {
                previousRecord.start = start;
                previousRecord.startTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(start);
            }
            if (locationResource.end>previousRecord.end) {
                previousRecord.end = locationResource.end;
                previousRecord.endTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(locationResource.end);
            }
            // update timeUpdated
            previousRecord.timeUpdated = System.currentTimeMillis();
            previousRecord.count += count;
            em.persist(previousRecord);
        }
    }

    private static float getMeterDistance(final Point2D.Double location, final Point2D.Double anchorLocation) {
        double earthRadius = 3958.75;
        double dLat = Math.toRadians(anchorLocation.x-location.x);
        double dLng = Math.toRadians(anchorLocation.y-location.y);
        double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                   Math.cos(Math.toRadians(location.x)) * Math.cos(Math.toRadians(anchorLocation.x)) *
                   Math.sin(dLng/2) * Math.sin(dLng/2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        double dist = earthRadius * c;
        int meterConversion = 1609;
        float meters = new Float(dist * meterConversion).floatValue();
        return meters;
    }

    private boolean isWithinRange(final Point2D.Double location, final Point2D.Double anchorLocation) {
        float meters = getMeterDistance(location, anchorLocation);
        return meters< CITY_RANGE;
    }

    @Transactional(readOnly=false)
    private void persistCity(final LocationFacet locationResource, final String date, long start, int count, final City city) {
        VisitedCity visitedCity = new VisitedCity(locationResource.apiKeyId);
        visitedCity.guestId = locationResource.guestId;
        visitedCity.locationSource = locationResource.source;
        visitedCity.api = locationResource.api;
        visitedCity.date = date;
        visitedCity.city = city;
        visitedCity.count = count;
        visitedCity.start = start;
        visitedCity.end = locationResource.end;
        visitedCity.startTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(start);
        visitedCity.endTimeStorage = AbstractLocalTimeFacet.timeStorageFormat.withZone(DateTimeZone.forID(city.geo_timezone)).print(locationResource.end);
        computeSunriseSunset(locationResource, city, visitedCity);
        em.persist(visitedCity);
    }

    private void computeSunriseSunset(final LocationFacet locationFacet, final City city, final VisitedCity mdFacet) {
        Location location = new Location(String.valueOf(locationFacet.latitude), String.valueOf(locationFacet.longitude));
        final TimeZone timeZone = TimeZone.getTimeZone(city.geo_timezone);
        SunriseSunsetCalculator calc = new SunriseSunsetCalculator(location, timeZone);
        Calendar c = Calendar.getInstance(timeZone);
        c.setTimeInMillis(locationFacet.start);
        Calendar sunrise = calc.getOfficialSunriseCalendarForDate(c);
        Calendar sunset = calc.getOfficialSunsetCalendarForDate(c);
        if (sunrise==null||sunset==null)
            return;
        if (sunrise.getTimeInMillis() > sunset.getTimeInMillis()) {
            Calendar sr = sunrise;
            Calendar ss = sunset;
            sunset = sr;
            sunrise = ss;
        }
        mdFacet.sunrise = AbstractFacetVO.toMinuteOfDay(sunrise.getTime(), timeZone);
        mdFacet.sunset = AbstractFacetVO.toMinuteOfDay(sunset.getTime(),
                                                         timeZone);
    }

    @Transactional(readOnly = false)
    private void fetchWeatherInfo(double latitude, double longitude,
                                  String city, String date) throws IOException {
        List<WeatherInfo> weatherInfo = null;
        try {
            weatherInfo = wwoHelper.getWeatherInfo(latitude,
                                                   longitude, date);
        }
        catch (UnexpectedHttpResponseCodeException e) {
            logger.warn(String.format("Weather Info service down? http code is %s, message: '%s'",
                                      e.getHttpResponseCode(),
                                      e.getHttpResponseMessage()));
        }
        for (WeatherInfo info : weatherInfo) {
            info.city = city;
            info.fdate = date;
            em.persist(info);
        }
    }

    private void addIcons(List<WeatherInfo> weather){
        for (WeatherInfo weatherInfo : weather){
            switch (weatherInfo.weatherCode){
                case 395://Moderate or heavy snow in area with thunder
                    weatherInfo.weatherIconUrl = "images/climacons/CS.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CSS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CSM.png";
                    break;
                case 389://Moderate or heavy rain in area with thunder
                    weatherInfo.weatherIconUrl = "images/climacons/CL.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CL.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CL.png";
                    break;
                case 200://Thundery outbreaks in nearby
                case 386://Patchy light rain in area with thunder
                case 392://Patchy light snow in area with thunder
                    weatherInfo.weatherIconUrl = "images/climacons/CL.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CLS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CLM.png";
                    break;
                case 113://Clear/Sunny
                    weatherInfo.weatherIconUrl = "images/climacons/Sun.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/Sun.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/Moon.png";
                    break;
                case 116://Partly Cloudy
                    weatherInfo.weatherIconUrl = "images/climacons/Cloud.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CS1.png";//CS#1.png
                    weatherInfo.weatherIconUrlNight = "images/climacons/CM.png";
                    break;
                case 122://Overcast
                case 119://Cloudy
                    weatherInfo.weatherIconUrl = "images/climacons/Cloud.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/Cloud.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/Cloud.png";
                    break;
                case 299://Moderate rain at times
                case 302://Moderate rain
                case 305://Heavy rain at times
                case 308://Heavy rain
                case 296: //Light rain
                case 293: //Patchy light rain
                case 266://Light drizzle
                case 353://Light rain shower
                case 356://Moderate or heavy rain shower
                case 359://Torrentail rain shower
                    weatherInfo.weatherIconUrl = "images/climacons/CD.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CDS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CDM.png";
                    break;
                case 263://patchy light drizzle
                case 176://patchy rain nearby
                case 143://Mist
                    weatherInfo.weatherIconUrl = "images/climacons/CD_Alt.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CDS_Alt.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CDM_Alt.png";
                    break;
                case 227://Blowing snow
                case 230://Blizzard
                case 329://Patchy moderate snow
                case 332://Moderate snow
                case 335://Patchy heavy snow
                case 338://Heavy snow
                case 368://Light snow showers
                case 371://Moderate or heavy snow showers
                    weatherInfo.weatherIconUrl = "images/climacons/CS.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CSS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CSM.png";
                    break;
                case 179://Patchy snow nearby
                case 323://Patchy Light snow
                case 325://Light snow
                    weatherInfo.weatherIconUrl = "images/climacons/CSA.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CSSA.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CSMA.png";
                    break;
                case 281://Freezing drizzle
                case 185: //Patchy freezing drizzle nearby
                case 182://Patchy sleet nearby
                case 311://Light freezing rain
                case 317://Light sleet
                    weatherInfo.weatherIconUrl = "images/climacons/CH.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CHS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CHM.png";
                    break;
                case 260://Freezing Fog
                case 248://Fog
                    weatherInfo.weatherIconUrl = "images/climacons/Fog.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/FS.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/FM.png";
                    break;
                case 314://Moderate or Heavy freezing rain
                case 320://Moderate or heavy sleet
                case 284://Heavy freezing drizzle
                case 350://Ice pellets
                case 362://Light sleet showers
                case 365://Moderate or heavy sleet
                case 374://Light showrs of ice pellets
                case 377://Moderate or heavy showres of ice pellets
                    weatherInfo.weatherIconUrl = "images/climacons/CH_Alt.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/CHS_Alt.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/CHM_Alt.png";
                    break;
                default:
                    weatherInfo.weatherIconUrl = "images/climacons/WC.png";
                    weatherInfo.weatherIconUrlDay = "images/climacons/WC.png";
                    weatherInfo.weatherIconUrlNight = "images/climacons/WC.png";
                    break;
            }
        }
    }

    public static void main(final String[] args) {
        long now = System.currentTimeMillis();
        //final DateTime dateTime = formatter.withZone(DateTimeZone.forID("Europe/Brussels")).parseDateTime("2013-06-03");
        //System.out.println(dateTime.getMillis());
        //System.out.println(dateTime.getMillis()+DateTimeConstants.MILLIS_PER_DAY);
        //Point2D.Double p1 = new Point2D.Double(0,0);
        //Point2D.Double p2 = new Point2D.Double(0,0.0089);
        //System.out.println(getMeterDistance(p1, p2));
        //p2 = new Point2D.Double(0.00904,0.00);
        //System.out.println(getMeterDistance(p1, p2));
        //p2 = new Point2D.Double(0.00639, 0.00629);
        //System.out.println(getMeterDistance(p1, p2));
        //p1 = new Point2D.Double(40.0, 0.0);
        //p2 = new Point2D.Double(40, 0.0117647);
        //System.out.println(getMeterDistance(p1, p2));
        //p2 = new Point2D.Double(40.00904, 0.0);
        //System.out.println(getMeterDistance(p1, p2));
        //p2 = new Point2D.Double(40.00639, 0.0083);
        //System.out.println(getMeterDistance(p1, p2));
    }

}
TOP

Related Classes of org.fluxtream.core.services.impl.MetadataServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.