001/**
002 * Copyright 2018 Tampere University of Technology, Pori Department
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 *   http://www.apache.org/licenses/LICENSE-2.0
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package service.tut.pori.apilta.shock;
017
018import java.util.ArrayList;
019import java.util.Date;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.List;
023
024import org.apache.log4j.Logger;
025
026import core.tut.pori.context.ServiceInitializer;
027import core.tut.pori.http.parameters.DataGroups;
028import core.tut.pori.http.parameters.DateIntervalParameter;
029import core.tut.pori.http.parameters.Limits;
030import core.tut.pori.users.UserIdentity;
031import service.tut.pori.apilta.ApiltaProperties;
032import service.tut.pori.apilta.shock.GroupCalculator.GroupMethod;
033import service.tut.pori.apilta.shock.datatypes.LocationData;
034import service.tut.pori.apilta.shock.datatypes.LocationLimits;
035import service.tut.pori.apilta.shock.datatypes.ShockHighlight;
036import service.tut.pori.apilta.shock.datatypes.ShockHighlightList;
037import service.tut.pori.apilta.shock.datatypes.ShockMeasurement;
038import service.tut.pori.apilta.shock.datatypes.ShockMeasurementList;
039import service.tut.pori.apilta.utils.MathUtils;
040
041/**
042 * 
043 *
044 */
045public final class ShockCore {
046  private static final DataGroups DATAGROUP_LOCATION_DATA = new DataGroups(Definitions.DATA_GROUP_LOCATION_DATA);
047  private static final Logger LOGGER = Logger.getLogger(ShockCore.class);
048  
049  /**
050   * 
051   */
052  private ShockCore() {
053    // nothing needed
054  }
055
056  /**
057   * 
058   * @param userIdentity
059   * @param list
060   * @throws IllegalArgumentException on invalid data
061   */
062  public static void createMeasurement(UserIdentity userIdentity, ShockMeasurementList list) throws IllegalArgumentException {
063    if(!ShockMeasurementList.isValid(list)){
064      throw new IllegalArgumentException("Empty or invalid measurement list provided.");
065    }
066    
067    ShockDAO dao = ServiceInitializer.getDAOHandler().getDAO(ShockDAO.class);
068    for(ShockMeasurement measurement : list.getShockMeasurements()){
069      measurement.setUserId(userIdentity); // set the authenticated user as the owner for all measurements
070      
071      if(measurement.getVisibility() == null){
072        LOGGER.debug("Empty visibility for measurement provided by user, id: "+userIdentity.getUserId()+", defaulting to: "+Definitions.DEFAULT_VISIBILITY);
073        measurement.setVisibility(Definitions.DEFAULT_VISIBILITY);
074      }
075      
076      dao.createMeasurement(measurement);
077    }
078  }
079
080  /**
081   * 
082   * @param userIdentity
083   * @param locationLimits
084   * @param dataGroups
085   * @param dateInterval timestamp interval
086   * @param levelFilter 
087   * @param limits
088   * @param groupMethod 
089   * @param groupRange in meters
090   * @param userIdFilter 
091   * @return measurement list or null if nothing was found
092   */
093  public static ShockMeasurementList getMeasurements(UserIdentity userIdentity, LocationLimits locationLimits, DataGroups dataGroups, DateIntervalParameter dateInterval, int[] levelFilter, Limits limits, GroupMethod groupMethod, Integer groupRange, long[] userIdFilter) {
094    ShockMeasurementList measurements = ServiceInitializer.getDAOHandler().getDAO(ShockDAO.class).getMeasurements(userIdentity, locationLimits, dataGroups, dateInterval, levelFilter, limits, userIdFilter);
095    if(!ShockMeasurementList.isEmpty(measurements) && groupMethod != null) {
096      GroupCalculator gc = new GroupCalculator(groupMethod, groupRange);
097      gc.setLevelFilter(levelFilter);
098      List<ShockMeasurement> list = gc.group(measurements.getShockMeasurements());
099      if(list == null) {
100        return null;
101      }
102      measurements.setShockMeasurements(list);
103    }
104    return measurements;
105  }
106
107  /**
108   * 
109   * @param userIdentity
110   * @param minMeasurements the highlight group's minimum number of measurements
111   * @param range maximum range (in km) around a central point when calculating groups
112   * @param locationLimits
113   * @param dateInterval timestamp interval
114   * @param levelFilter
115   * @param limits
116   * @param userIdFilter
117   * @return highlight list of null if nothing was found
118   */
119  public static ShockHighlightList getHighlights(UserIdentity userIdentity, int minMeasurements, double range, LocationLimits locationLimits, DateIntervalParameter dateInterval, int[] levelFilter, Limits limits, long[] userIdFilter) {
120    ShockMeasurementList list = getMeasurements(userIdentity, locationLimits, DATAGROUP_LOCATION_DATA, dateInterval, levelFilter, limits, null, null, userIdFilter);
121    if(ShockMeasurementList.isEmpty(list)) {
122      LOGGER.debug("No measurements found with the given values.");
123      return null;
124    }
125    
126    ArrayList<ShockHighlight> highlights = new ArrayList<>();
127    HashSet<Long> userIds = new HashSet<>();
128    long minTimeDifference = ServiceInitializer.getPropertyHandler().getSystemProperties(ApiltaProperties.class).getShockGroupTimeDifference();
129    List<ShockMeasurement> measurements = list.getShockMeasurements();
130    int size = 0;
131    while((size = measurements.size()) > minMeasurements) {
132      ShockMeasurement center = measurements.remove(size-1);
133      Date from = center.getTimestamp();
134      Date to = from;
135      long centerTimestamp = center.getTimestamp().getTime();
136      int minLevel = center.getLevel();
137      int maxLevel = minLevel;
138      double maxRange = 0;
139      int measurementCount = 0;
140      userIds.clear();
141      Long userId = center.getUserId().getUserId();
142      userIds.add(userId);
143      LocationData location = center.getLocationData();
144      double lat = location.getLatitude();
145      double lon = location.getLongitude();
146      
147      for(Iterator<ShockMeasurement> iter = measurements.iterator(); iter.hasNext();){
148        ShockMeasurement m = iter.next();
149        Date mt = m.getTimestamp();
150        UserIdentity mUserId = m.getUserId();
151        if(UserIdentity.equals(mUserId, userId) && Math.abs(mt.getTime()-centerTimestamp) < minTimeDifference){ // if the measurements are from the same user, check that the measurements are from different sessions
152          continue;
153        }
154        location = m.getLocationData();
155        double tRange = MathUtils.haversine(lat, lon, location.getLatitude(), location.getLongitude());
156        if(tRange < range){
157          userIds.add(mUserId.getUserId());
158          ++measurementCount;
159          if(tRange > maxRange){
160            maxRange = tRange;
161          }
162          int level = m.getLevel();
163          if(level < minLevel){
164            minLevel = level;
165          }else if(level > maxLevel){
166            maxLevel = level;
167          }
168          
169          if(mt.before(from)) {
170            from = mt;
171          }else if(mt.after(to)) {
172            to = mt;
173          }
174          iter.remove();
175        } // if
176      } // for
177      
178      if(measurementCount > minMeasurements) {
179        ShockHighlight hl = new ShockHighlight();
180        hl.setLatitude(lat); // naively use the center point even though it may not be the exact center
181        hl.setLongitude(lon); // naively use the center point even though it may not be the exact center
182        hl.setFrom(from);
183        hl.setTo(to);
184        hl.setMaxLevel(maxLevel);
185        hl.setMinLevel(minLevel);
186        hl.setUserCount(userIds.size());
187        hl.setMaxRange(maxRange);
188        hl.setMeasurementCount(measurementCount);
189        highlights.add(hl);
190      }
191    }
192    
193    return ShockHighlightList.getShockMeasurementList(highlights);
194  }
195}