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));
}
}