blob: f6217e328bcf80cf3538295c580a9ce2c100a0f9 [file] [log] [blame]
Ruei-sung Linf0f78442012-08-13 19:04:29 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bordeaux.services;
18
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070019import android.content.Context;
Ruei-sung Linf0f78442012-08-13 19:04:29 -070020import android.location.Location;
21import android.text.format.Time;
22import android.util.Log;
23
24import java.util.ArrayList;
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070025import java.util.HashMap;
Ruei-sung Linf0f78442012-08-13 19:04:29 -070026import java.util.HashSet;
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070027import java.util.List;
28import java.util.Map;
Ruei-sung Linf0f78442012-08-13 19:04:29 -070029
30/**
31 * ClusterManager incrementally indentify representitve clusters from the input location
32 * stream. Clusters are updated online using leader based clustering algorithm. The input
33 * locations initially are kept by the clusters. Periodially, a cluster consolidating
34 * procedure is carried out to refine the cluster centers. After consolidation, the
35 * location data are released.
36 */
37public class ClusterManager {
38
39 private static String TAG = "ClusterManager";
40
Ruei-sung Lin83954e82012-08-28 18:00:48 -070041 private static float LOCATION_CLUSTER_RADIUS = 50; // meter
Ruei-sung Linf0f78442012-08-13 19:04:29 -070042
Ruei-sung Lin83954e82012-08-28 18:00:48 -070043 private static float SEMANTIC_CLUSTER_RADIUS = 100; // meter
Ruei-sung Linf0f78442012-08-13 19:04:29 -070044
Ruei-sung Lin83954e82012-08-28 18:00:48 -070045 private static long CONSOLIDATE_INTERVAL = 60000; //
46 // private static long CONSOLIDATE_INTERVAL = 21600000; //
Ruei-sung Linf0f78442012-08-13 19:04:29 -070047
Ruei-sung Linf0f78442012-08-13 19:04:29 -070048
Ruei-sung Lin83954e82012-08-28 18:00:48 -070049 private static long LOCATION_CLUSTER_THRESHOLD = 10000; // in milliseconds
50 // private static long LOCATION_CLUSTER_THRESHOLD = 180000; // in milliseconds
51
52 private static long SEMANTIC_CLUSTER_THRESHOLD = 30000; // in milliseconds
53 // private static long SEMANTIC_CLUSTER_THRESHOLD = 1800000; // in milliseconds
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070054
55 private static String UNKNOWN_LOCATION = "Unknown Location";
56
57 private static String HOME = "Home";
58
59 private static String OFFICE = "Office";
Ruei-sung Linf0f78442012-08-13 19:04:29 -070060
61 private Location mLastLocation = null;
62
63 private long mTimeRef = 0;
64
65 private long mSemanticClusterCount = 0;
66
67 private ArrayList<LocationCluster> mLocClusters = new ArrayList<LocationCluster>();
68
69 private ArrayList<SemanticCluster> mSemanticClusters = new ArrayList<SemanticCluster>();
70
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070071 private AggregatorRecordStorage mStorage;
72
73 private static String SEMANTIC_TABLE = "SemanticTable";
74
75 private static String SEMANTIC_ID = "ID";
76
Ruei-sung Lin83954e82012-08-28 18:00:48 -070077 private static final String SEMANTIC_LONGITUDE = "Longitude";
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070078
Ruei-sung Lin83954e82012-08-28 18:00:48 -070079 private static final String SEMANTIC_LATITUDE = "Latitude";
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070080
Ruei-sung Lin83954e82012-08-28 18:00:48 -070081 private static final String[] SEMANTIC_COLUMNS =
82 new String[]{ SEMANTIC_ID,
83 SEMANTIC_LONGITUDE,
84 SEMANTIC_LATITUDE,
85 TimeStatsAggregator.WEEKEND,
86 TimeStatsAggregator.WEEKDAY,
87 TimeStatsAggregator.MORNING,
88 TimeStatsAggregator.NOON,
89 TimeStatsAggregator.AFTERNOON,
90 TimeStatsAggregator.EVENING,
91 TimeStatsAggregator.NIGHT,
92 TimeStatsAggregator.LATENIGHT };
93
94 private static final int mFeatureValueStart = 3;
95 private static final int mFeatureValueEnd = 10;
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070096
97 public ClusterManager(Context context) {
98 mStorage = new AggregatorRecordStorage(context, SEMANTIC_TABLE, SEMANTIC_COLUMNS);
99
100 loadSemanticClusters();
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700101 }
102
103 public void addSample(Location location) {
104 float bestClusterDistance = Float.MAX_VALUE;
105 int bestClusterIndex = -1;
106 long lastDuration;
Ruei-sung Lin83954e82012-08-28 18:00:48 -0700107 long currentTime = location.getTime() / 1000; // measure time in seconds
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700108
109 if (mLastLocation != null) {
110 // get the duration spent in the last location
111 long duration = location.getTime() - mLastLocation.getTime();
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700112 Log.v(TAG, "sample duration: " + duration +
113 ", number of clusters: " + mLocClusters.size());
114
115 // add the last location to cluster.
116 // first find the cluster it belongs to.
117 for (int i = 0; i < mLocClusters.size(); ++i) {
118 float distance = mLocClusters.get(i).distanceToCenter(mLastLocation);
119 Log.v(TAG, "clulster " + i + " is within " + distance + " meters");
120 if (distance < bestClusterDistance) {
121 bestClusterDistance = distance;
122 bestClusterIndex = i;
123 }
124 }
125
126 // add the location to the selected cluster
127 if (bestClusterDistance < LOCATION_CLUSTER_RADIUS) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700128 Log.v(TAG, "add sample to cluster: " + bestClusterIndex + ",( " +
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700129 location.getLongitude() + ", " + location.getLatitude() + ")");
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700130 mLocClusters.get(bestClusterIndex).addSample(mLastLocation, duration);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700131 } else {
132 // if it is far away from all existing clusters, create a new cluster.
133 LocationCluster cluster =
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700134 new LocationCluster(mLastLocation, duration, CONSOLIDATE_INTERVAL);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700135 // move the center of the new cluster if its covering region overlaps
136 // with an existing cluster.
137 if (bestClusterDistance < 2 * LOCATION_CLUSTER_RADIUS) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700138 Log.e(TAG, "move away activated");
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700139 cluster.moveAwayCluster(mLocClusters.get(bestClusterIndex),
140 ((float) 2 * LOCATION_CLUSTER_RADIUS));
141 }
142 mLocClusters.add(cluster);
143 }
144 } else {
145 mTimeRef = currentTime;
146 }
147
148 long collectDuration = currentTime - mTimeRef;
149 Log.e(TAG, "collect duration: " + collectDuration);
150 if (collectDuration > CONSOLIDATE_INTERVAL) {
151 // TODO : conslidation takes time. move this to a separate thread later.
152 consolidateClusters(collectDuration);
153 mTimeRef = currentTime;
154 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700155 mLastLocation = location;
156 }
157
158 private void consolidateClusters(long duration) {
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700159 LocationCluster cluster;
160
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700161 for (int i = mLocClusters.size() - 1; i >= 0; --i) {
162 cluster = mLocClusters.get(i);
163 cluster.consolidate(duration);
164
165 // TODO: currently set threshold to 1 sec so almost none of the location
166 // clusters will be removed.
167 if (!cluster.passThreshold(LOCATION_CLUSTER_THRESHOLD)) {
168 mLocClusters.remove(cluster);
169 }
170 }
171
172 // merge clusters whose regions are overlapped. note that after merge
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700173 // cluster center changes but cluster size remains unchanged.
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700174 for (int i = mLocClusters.size() - 1; i >= 0; --i) {
175 cluster = mLocClusters.get(i);
176 for (int j = i - 1; j >= 0; --j) {
177 float distance = mLocClusters.get(j).distanceToCluster(cluster);
178 if (distance < LOCATION_CLUSTER_RADIUS) {
179 mLocClusters.get(j).absorbCluster(cluster);
180 mLocClusters.remove(cluster);
181 }
182 }
183 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700184
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700185 updateSemanticClusters();
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700186
187 saveSemanticClusters();
188 }
189
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700190 private void loadSemanticClusters() {
191 List<Map<String, String> > allData = mStorage.getAllData();
Ruei-sung Lin83954e82012-08-28 18:00:48 -0700192 HashMap<String, Long> histogram = new HashMap<String, Long>();
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700193
194 mSemanticClusters.clear();
195 for (Map<String, String> map : allData) {
196 String semanticId = map.get(SEMANTIC_ID);
197 double longitude = Double.valueOf(map.get(SEMANTIC_LONGITUDE));
198 double latitude = Double.valueOf(map.get(SEMANTIC_LATITUDE));
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700199 SemanticCluster cluster = new SemanticCluster(
200 semanticId, longitude, latitude, CONSOLIDATE_INTERVAL);
Ruei-sung Lin83954e82012-08-28 18:00:48 -0700201
202 histogram.clear();
203 for (int i = mFeatureValueStart; i <= mFeatureValueEnd; i++) {
204 String featureValue = SEMANTIC_COLUMNS[i];
205 if (map.containsKey(featureValue)) {
206 histogram.put(featureValue, Long.valueOf(map.get(featureValue)));
207 }
208 }
209 cluster.setHistogram(histogram);
210
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700211 mSemanticClusters.add(cluster);
212 }
213
214 mSemanticClusterCount = mSemanticClusters.size();
215 Log.e(TAG, "load " + mSemanticClusterCount + " semantic clusters.");
216 }
217
218 private void saveSemanticClusters() {
219 HashMap<String, String> rowFeatures = new HashMap<String, String>();
220 Log.e(TAG, "save " + mSemanticClusters.size() + " semantic clusters.");
221
222 mStorage.removeAllData();
223 for (SemanticCluster cluster : mSemanticClusters) {
224 rowFeatures.clear();
225 rowFeatures.put(SEMANTIC_ID, cluster.getSemanticId());
226
227 rowFeatures.put(SEMANTIC_LONGITUDE,
228 String.valueOf(cluster.getCenterLongitude()));
229 rowFeatures.put(SEMANTIC_LATITUDE,
230 String.valueOf(cluster.getCenterLatitude()));
Ruei-sung Lin83954e82012-08-28 18:00:48 -0700231
232 HashMap<String, Long> histogram = cluster.getHistogram();
233 for (Map.Entry<String, Long> entry : histogram.entrySet()) {
234 rowFeatures.put(entry.getKey(), String.valueOf(entry.getValue()));
235 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700236 mStorage.addData(rowFeatures);
237 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700238 }
239
240 private void updateSemanticClusters() {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700241 HashMap<String, ArrayList<BaseCluster> > semanticMap =
242 new HashMap<String, ArrayList<BaseCluster> >();
243 for (SemanticCluster cluster : mSemanticClusters) {
244 String semanticId = cluster.getSemanticId();
245 semanticMap.put(cluster.getSemanticId(), new ArrayList<BaseCluster>());
246 semanticMap.get(semanticId).add(cluster);
247 }
248
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700249 // select candidate location clusters
250 ArrayList<LocationCluster> candidates = new ArrayList<LocationCluster>();
251 for (LocationCluster cluster : mLocClusters) {
252 if (cluster.passThreshold(SEMANTIC_CLUSTER_THRESHOLD)) {
253 candidates.add(cluster);
254 }
255 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700256
257 // assign each candidate to a semantic cluster
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700258 for (LocationCluster candidate : candidates) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700259 if (candidate.hasSemanticId()) {
260 // candidate has been assigned to a semantic cluster
261 continue;
262 }
263
264 // find the closest semantic cluster
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700265 float bestClusterDistance = Float.MAX_VALUE;
266 SemanticCluster bestCluster = null;
267 for (SemanticCluster cluster : mSemanticClusters) {
268 float distance = cluster.distanceToCluster(candidate);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700269 Log.e(TAG, "distance to semantic cluster: " + cluster.getSemanticId());
270
271 if (distance < bestClusterDistance) {
272 bestClusterDistance = distance;
273 bestCluster = cluster;
274 }
275 }
276
277 // add the location to the selected cluster
278 SemanticCluster semanticCluster;
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700279 if (bestClusterDistance > SEMANTIC_CLUSTER_RADIUS) {
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700280 // if it is far away from all existing clusters, create a new cluster.
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700281 bestCluster = new SemanticCluster(candidate, CONSOLIDATE_INTERVAL,
282 mSemanticClusterCount++);
283 String id = bestCluster.getSemanticId();
284 semanticMap.put(id, new ArrayList<BaseCluster>());
285 semanticMap.get(id).add(bestCluster);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700286 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700287 String semanticId = bestCluster.getSemanticId();
288 candidate.setSemanticId(semanticId);
289 semanticMap.get(semanticId).add(candidate);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700290 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700291 candidates.clear();
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700292 Log.e(TAG, "number of semantic clusters: " + semanticMap.size());
293
294 // use candidates semantic cluster
295 mSemanticClusters.clear();
296 for (ArrayList<BaseCluster> clusterList : semanticMap.values()) {
297 SemanticCluster semanticCluster = (SemanticCluster) clusterList.get(0);
298
299 Log.e(TAG, "id: " + semanticCluster.getSemanticId() + ", list size: " +
300 clusterList.size());
301
302 if (clusterList.size() > 1) {
303 // cluster with no new candidate
304 semanticCluster.setCluster(clusterList.get(1));
305 for (int i = 2; i < clusterList.size(); i++) {
306 semanticCluster.absorbCluster(clusterList.get(i));
307 }
308 }
309 mSemanticClusters.add(semanticCluster);
310 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700311 }
312
313 public String getSemanticLocation() {
Ruei-sung Linc7c9cf12012-08-28 11:35:19 -0700314 String label = LocationStatsAggregator.UNKNOWN_LOCATION;
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700315
Ruei-sung Linc7c9cf12012-08-28 11:35:19 -0700316 // instead of using the last location, try acquiring the latest location.
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700317 if (mLastLocation != null) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700318 // TODO: use fast neatest neighbor search speed up location search
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700319 for (SemanticCluster cluster: mSemanticClusters) {
320 if (cluster.distanceToCenter(mLastLocation) < SEMANTIC_CLUSTER_RADIUS) {
321 return cluster.getSemanticId();
322 }
323 }
324 }
325 return label;
326 }
327}