blob: 625f5ad4b1950f006835a6b249b95753ab5f9108 [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
41 private static float LOCATION_CLUSTER_RADIUS = 25; // meter
42
43 private static float SEMANTIC_CLUSTER_RADIUS = 50; // meter
44
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070045 private static long CONSOLIDATE_INTERVAL = 21600000; //
Ruei-sung Linf0f78442012-08-13 19:04:29 -070046
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070047 private static long LOCATION_CLUSTER_THRESHOLD = 180000; // in milliseconds
Ruei-sung Linf0f78442012-08-13 19:04:29 -070048
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070049 private static long SEMANTIC_CLUSTER_THRESHOLD = 1800000; // in milliseconds
50
51 private static String UNKNOWN_LOCATION = "Unknown Location";
52
53 private static String HOME = "Home";
54
55 private static String OFFICE = "Office";
Ruei-sung Linf0f78442012-08-13 19:04:29 -070056
57 private Location mLastLocation = null;
58
59 private long mTimeRef = 0;
60
61 private long mSemanticClusterCount = 0;
62
63 private ArrayList<LocationCluster> mLocClusters = new ArrayList<LocationCluster>();
64
65 private ArrayList<SemanticCluster> mSemanticClusters = new ArrayList<SemanticCluster>();
66
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -070067 private AggregatorRecordStorage mStorage;
68
69 private static String SEMANTIC_TABLE = "SemanticTable";
70
71 private static String SEMANTIC_ID = "ID";
72
73 private static String SEMANTIC_LONGITUDE = "Longitude";
74
75 private static String SEMANTIC_LATITUDE = "Latitude";
76
77 private static String[] SEMANTIC_COLUMNS =
78 new String[]{ SEMANTIC_ID, SEMANTIC_LONGITUDE, SEMANTIC_LATITUDE};
79
80 public ClusterManager(Context context) {
81 mStorage = new AggregatorRecordStorage(context, SEMANTIC_TABLE, SEMANTIC_COLUMNS);
82
83 loadSemanticClusters();
Ruei-sung Linf0f78442012-08-13 19:04:29 -070084 }
85
86 public void addSample(Location location) {
87 float bestClusterDistance = Float.MAX_VALUE;
88 int bestClusterIndex = -1;
89 long lastDuration;
90 long currentTime = location.getTime();
91
92 if (mLastLocation != null) {
93 // get the duration spent in the last location
94 long duration = location.getTime() - mLastLocation.getTime();
Ruei-sung Linf0f78442012-08-13 19:04:29 -070095 Log.v(TAG, "sample duration: " + duration +
96 ", number of clusters: " + mLocClusters.size());
97
98 // add the last location to cluster.
99 // first find the cluster it belongs to.
100 for (int i = 0; i < mLocClusters.size(); ++i) {
101 float distance = mLocClusters.get(i).distanceToCenter(mLastLocation);
102 Log.v(TAG, "clulster " + i + " is within " + distance + " meters");
103 if (distance < bestClusterDistance) {
104 bestClusterDistance = distance;
105 bestClusterIndex = i;
106 }
107 }
108
109 // add the location to the selected cluster
110 if (bestClusterDistance < LOCATION_CLUSTER_RADIUS) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700111 Log.v(TAG, "add sample to cluster: " + bestClusterIndex + ",( " +
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700112 location.getLongitude() + ", " + location.getLatitude() + ")");
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700113 mLocClusters.get(bestClusterIndex).addSample(mLastLocation, duration);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700114 } else {
115 // if it is far away from all existing clusters, create a new cluster.
116 LocationCluster cluster =
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700117 new LocationCluster(mLastLocation, duration, CONSOLIDATE_INTERVAL);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700118 // move the center of the new cluster if its covering region overlaps
119 // with an existing cluster.
120 if (bestClusterDistance < 2 * LOCATION_CLUSTER_RADIUS) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700121 Log.e(TAG, "move away activated");
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700122 cluster.moveAwayCluster(mLocClusters.get(bestClusterIndex),
123 ((float) 2 * LOCATION_CLUSTER_RADIUS));
124 }
125 mLocClusters.add(cluster);
126 }
127 } else {
128 mTimeRef = currentTime;
129 }
130
131 long collectDuration = currentTime - mTimeRef;
132 Log.e(TAG, "collect duration: " + collectDuration);
133 if (collectDuration > CONSOLIDATE_INTERVAL) {
134 // TODO : conslidation takes time. move this to a separate thread later.
135 consolidateClusters(collectDuration);
136 mTimeRef = currentTime;
137 }
138
139 /*
140 // TODO: this could be removed
141 Log.i(TAG, "location : " + location.getLongitude() + ", " + location.getLatitude());
142 if (mLastLocation != null) {
143 Log.i(TAG, "mLastLocation: " + mLastLocation.getLongitude() + ", " +
144 mLastLocation.getLatitude());
145 } // end of deletion
146 */
147
148 mLastLocation = location;
149 }
150
151 private void consolidateClusters(long duration) {
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700152 LocationCluster cluster;
153
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700154 for (int i = mLocClusters.size() - 1; i >= 0; --i) {
155 cluster = mLocClusters.get(i);
156 cluster.consolidate(duration);
157
158 // TODO: currently set threshold to 1 sec so almost none of the location
159 // clusters will be removed.
160 if (!cluster.passThreshold(LOCATION_CLUSTER_THRESHOLD)) {
161 mLocClusters.remove(cluster);
162 }
163 }
164
165 // merge clusters whose regions are overlapped. note that after merge
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700166 // cluster center changes but cluster size remains unchanged.
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700167 for (int i = mLocClusters.size() - 1; i >= 0; --i) {
168 cluster = mLocClusters.get(i);
169 for (int j = i - 1; j >= 0; --j) {
170 float distance = mLocClusters.get(j).distanceToCluster(cluster);
171 if (distance < LOCATION_CLUSTER_RADIUS) {
172 mLocClusters.get(j).absorbCluster(cluster);
173 mLocClusters.remove(cluster);
174 }
175 }
176 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700177
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700178 updateSemanticClusters();
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700179
180 saveSemanticClusters();
181 }
182
183
184 private void loadSemanticClusters() {
185 List<Map<String, String> > allData = mStorage.getAllData();
186
187 mSemanticClusters.clear();
188 for (Map<String, String> map : allData) {
189 String semanticId = map.get(SEMANTIC_ID);
190 double longitude = Double.valueOf(map.get(SEMANTIC_LONGITUDE));
191 double latitude = Double.valueOf(map.get(SEMANTIC_LATITUDE));
192
193 SemanticCluster cluster = new SemanticCluster(
194 semanticId, longitude, latitude, CONSOLIDATE_INTERVAL);
195 mSemanticClusters.add(cluster);
196 }
197
198 mSemanticClusterCount = mSemanticClusters.size();
199 Log.e(TAG, "load " + mSemanticClusterCount + " semantic clusters.");
200 }
201
202 private void saveSemanticClusters() {
203 HashMap<String, String> rowFeatures = new HashMap<String, String>();
204 Log.e(TAG, "save " + mSemanticClusters.size() + " semantic clusters.");
205
206 mStorage.removeAllData();
207 for (SemanticCluster cluster : mSemanticClusters) {
208 rowFeatures.clear();
209 rowFeatures.put(SEMANTIC_ID, cluster.getSemanticId());
210
211 rowFeatures.put(SEMANTIC_LONGITUDE,
212 String.valueOf(cluster.getCenterLongitude()));
213 rowFeatures.put(SEMANTIC_LATITUDE,
214 String.valueOf(cluster.getCenterLatitude()));
215 mStorage.addData(rowFeatures);
216 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700217 }
218
219 private void updateSemanticClusters() {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700220
221 HashMap<String, ArrayList<BaseCluster> > semanticMap =
222 new HashMap<String, ArrayList<BaseCluster> >();
223 for (SemanticCluster cluster : mSemanticClusters) {
224 String semanticId = cluster.getSemanticId();
225 semanticMap.put(cluster.getSemanticId(), new ArrayList<BaseCluster>());
226 semanticMap.get(semanticId).add(cluster);
227 }
228
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700229 // select candidate location clusters
230 ArrayList<LocationCluster> candidates = new ArrayList<LocationCluster>();
231 for (LocationCluster cluster : mLocClusters) {
232 if (cluster.passThreshold(SEMANTIC_CLUSTER_THRESHOLD)) {
233 candidates.add(cluster);
234 }
235 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700236
237 // assign each candidate to a semantic cluster
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700238 for (LocationCluster candidate : candidates) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700239 if (candidate.hasSemanticId()) {
240 // candidate has been assigned to a semantic cluster
241 continue;
242 }
243
244 // find the closest semantic cluster
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700245 float bestClusterDistance = Float.MAX_VALUE;
246 SemanticCluster bestCluster = null;
247 for (SemanticCluster cluster : mSemanticClusters) {
248 float distance = cluster.distanceToCluster(candidate);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700249 Log.e(TAG, "distance to semantic cluster: " + cluster.getSemanticId());
250
251 if (distance < bestClusterDistance) {
252 bestClusterDistance = distance;
253 bestCluster = cluster;
254 }
255 }
256
257 // add the location to the selected cluster
258 SemanticCluster semanticCluster;
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700259 if (bestClusterDistance > SEMANTIC_CLUSTER_RADIUS) {
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700260 // if it is far away from all existing clusters, create a new cluster.
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700261 bestCluster = new SemanticCluster(candidate, CONSOLIDATE_INTERVAL,
262 mSemanticClusterCount++);
263 String id = bestCluster.getSemanticId();
264 semanticMap.put(id, new ArrayList<BaseCluster>());
265 semanticMap.get(id).add(bestCluster);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700266 }
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700267 String semanticId = bestCluster.getSemanticId();
268 candidate.setSemanticId(semanticId);
269 semanticMap.get(semanticId).add(candidate);
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700270 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700271 candidates.clear();
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700272 Log.e(TAG, "number of semantic clusters: " + semanticMap.size());
273
274 // use candidates semantic cluster
275 mSemanticClusters.clear();
276 for (ArrayList<BaseCluster> clusterList : semanticMap.values()) {
277 SemanticCluster semanticCluster = (SemanticCluster) clusterList.get(0);
278
279 Log.e(TAG, "id: " + semanticCluster.getSemanticId() + ", list size: " +
280 clusterList.size());
281
282 if (clusterList.size() > 1) {
283 // cluster with no new candidate
284 semanticCluster.setCluster(clusterList.get(1));
285 for (int i = 2; i < clusterList.size(); i++) {
286 semanticCluster.absorbCluster(clusterList.get(i));
287 }
288 }
289 mSemanticClusters.add(semanticCluster);
290 }
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700291 }
292
293 public String getSemanticLocation() {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700294 String label = UNKNOWN_LOCATION;
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700295
296 if (mLastLocation != null) {
Ruei-sung Lin5d42ffa2012-08-23 16:01:57 -0700297 // TODO: use fast neatest neighbor search speed up location search
Ruei-sung Linf0f78442012-08-13 19:04:29 -0700298 for (SemanticCluster cluster: mSemanticClusters) {
299 if (cluster.distanceToCenter(mLastLocation) < SEMANTIC_CLUSTER_RADIUS) {
300 return cluster.getSemanticId();
301 }
302 }
303 }
304 return label;
305 }
306}