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.Iterator; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.ListIterator; 023 024import org.apache.commons.lang3.ArrayUtils; 025 026import core.tut.pori.context.ServiceInitializer; 027import service.tut.pori.apilta.ApiltaProperties; 028import service.tut.pori.apilta.shock.datatypes.LocationData; 029import service.tut.pori.apilta.shock.datatypes.ShockMeasurement; 030import service.tut.pori.apilta.utils.MathUtils; 031 032/** 033 * calculate measurement grouping based on the given data 034 * 035 */ 036public class GroupCalculator { 037 private static final String GROUP_METHOD_AVERAGE = "AVERAGE"; 038 private static final String GROUP_METHOD_MEDIAN = "MEDIAN"; 039 private GroupMethod _method = null; 040 private double _range = 0; // in km 041 private int[] _levelFilter = null; 042 043 /** 044 * method used for grouping 045 * 046 */ 047 public enum GroupMethod { 048 /** 049 * calculate group value based on average (arithmetic mean) 050 */ 051 AVERAGE(GROUP_METHOD_AVERAGE), 052 /** 053 * calculate group value based on median 054 */ 055 MEDIAN(GROUP_METHOD_MEDIAN); 056 057 private String _value; 058 059 /** 060 * 061 * @param value 062 */ 063 private GroupMethod(String value) { 064 _value = value; 065 } 066 067 /** 068 * 069 * @param value 070 * @return group method 071 * @throws IllegalArgumentException 072 */ 073 public static GroupMethod fromString(String value) throws IllegalArgumentException { 074 for(GroupMethod m : values()) { 075 if(m._value.equalsIgnoreCase(value)) { 076 return m; 077 } 078 } 079 throw new IllegalArgumentException("Invalid value: "+value); 080 } 081 } // enum GroupMethod 082 083 /** 084 * 085 * @param method 086 * @param range in meters, if null or < 1 default is used 087 */ 088 public GroupCalculator(GroupMethod method, Integer range) { 089 _method = method; 090 if(range == null || range < 1) { 091 _range = ServiceInitializer.getPropertyHandler().getSystemProperties(ApiltaProperties.class).getShockGroupRange(); 092 }else { 093 _range = range; 094 _range /= 1000; 095 } 096 } 097 098 /** 099 * @param levelFilter the levelFilter to set 100 */ 101 public void setLevelFilter(int[] levelFilter) { 102 _levelFilter = levelFilter; 103 } 104 105 /** 106 * 107 * @param measurements note: the passed list might be modified by this method 108 * @return the grouped list 109 */ 110 public List<ShockMeasurement> group(List<ShockMeasurement> measurements) { 111 switch(_method) { 112 case AVERAGE: 113 return groupAverage(measurements); 114 case MEDIAN: 115 return groupMedian(measurements); 116 default: 117 throw new IllegalArgumentException("Unknown method: "+_method); 118 } 119 } 120 121 /** 122 * 123 * @param measurements 124 * @return values grouped by median 125 */ 126 private List<ShockMeasurement> groupMedian(List<ShockMeasurement> measurements) { 127 ArrayList<ShockMeasurement> grouped = new ArrayList<>(); 128 129 int levelSum = 0; 130 int levelCount = 0; 131 ShockMeasurement center = null; 132 double centerLat = 0; 133 double centerLon = 0; 134 Iterator<ShockMeasurement> iter = measurements.iterator(); 135 while(iter.hasNext()) { 136 while(iter.hasNext()) { 137 ShockMeasurement m = iter.next(); 138 Integer level = m.getLevel(); 139 if(level == null) { // ignore measurements without known level 140 iter.remove(); 141 continue; 142 } 143 144 LocationData d = m.getLocationData(); 145 if(center == null) { 146 center = m; 147 centerLat = d.getLatitude(); 148 centerLon = d.getLongitude(); 149 levelSum += level; 150 ++levelCount; 151 iter.remove(); 152 }else if(MathUtils.haversine(centerLat, centerLon, d.getLatitude(), d.getLongitude()) < _range) { 153 levelSum += level; 154 ++levelCount; 155 iter.remove(); 156 } // if 157 } // while 158 159 if(levelCount > 0) { 160 int level = Math.round(levelSum/levelCount); 161 if(_levelFilter == null || ArrayUtils.contains(_levelFilter, level)) { 162 center.setLevel(level); 163 grouped.add(center); 164 } 165 } 166 center = null; 167 levelCount = 0; 168 levelSum = 0; 169 iter = measurements.iterator(); 170 } // for 171 172 return (grouped.isEmpty() ? null : grouped); 173 } 174 175 /** 176 * 177 * @param measurements 178 * @return values grouped by median 179 */ 180 private List<ShockMeasurement> groupAverage(List<ShockMeasurement> measurements) { 181 ArrayList<ShockMeasurement> grouped = new ArrayList<>(); 182 183 LinkedList<Integer> groupLevels = new LinkedList<>(); 184 int levelCount = 0; 185 ShockMeasurement center = null; 186 double centerLat = 0; 187 double centerLon = 0; 188 Iterator<ShockMeasurement> iter = measurements.iterator(); 189 while(iter.hasNext()) { 190 while(iter.hasNext()) { 191 ShockMeasurement m = iter.next(); 192 Integer level = m.getLevel(); 193 if(level == null) { // ignore measurements without known level 194 iter.remove(); 195 continue; 196 } 197 198 LocationData d = m.getLocationData(); 199 if(center == null) { 200 center = m; 201 centerLat = d.getLatitude(); 202 centerLon = d.getLongitude(); 203 groupLevels.add(level); 204 ++levelCount; 205 iter.remove(); 206 }else if(MathUtils.haversine(centerLat, centerLon, d.getLatitude(), d.getLongitude()) < _range) { 207 int sLevel = level; 208 ListIterator<Integer> lIter = groupLevels.listIterator(levelCount); 209 while(true) { 210 if(sLevel >= lIter.previous()) { 211 lIter.add(level); 212 break; 213 }else if(!lIter.hasPrevious()) { 214 groupLevels.addFirst(level); 215 break; 216 } 217 } 218 ++levelCount; 219 iter.remove(); 220 } // if 221 } // while 222 223 if(levelCount == 0) { 224 continue; 225 }else if(levelCount % 2 == 0) { 226 int mIndex = levelCount/2; 227 int level = Math.round((groupLevels.get(mIndex)+groupLevels.get(mIndex-1))/2); 228 if(_levelFilter == null || ArrayUtils.contains(_levelFilter, level)) { 229 center.setLevel(level); 230 grouped.add(center); 231 } 232 }else { 233 center.setLevel(groupLevels.get(levelCount/2)); 234 grouped.add(center); 235 } 236 center = null; 237 levelCount = 0; 238 groupLevels.clear(); 239 iter = measurements.iterator(); 240 } // for 241 242 return (grouped.isEmpty() ? null : grouped); 243 } 244}