LocationCluster.java revision 83954e853dc1e1a28b2c3efbe1585f188266df02
1/*
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 */
16package android.bordeaux.services;
17
18import android.location.Location;
19import android.text.format.Time;
20import android.util.Log;
21
22import java.lang.Math;
23import java.util.ArrayList;
24import java.util.HashMap;
25import java.util.Map;
26
27public class LocationCluster extends BaseCluster {
28    public static String TAG = "LocationCluster";
29
30    private static double FORGETTING_FACTOR = 0.1;
31
32    private boolean mIsNewCluster;
33
34    private ArrayList<Location> mLocations = new ArrayList<Location>();
35    private HashMap<String, Long> mNewHistogram = new HashMap<String, Long>();
36
37    // TODO: make it a singleton class
38    public LocationCluster(Location location, long duration, long avgInterval) {
39        super(location, avgInterval);
40        mIsNewCluster = true;
41        addSample(location, duration);
42    }
43
44    public void addSample(Location location, long duration) {
45        updateTemporalHistogram(location.getTime(), duration);
46
47        // use time field to store duation of this location
48        // TODO: extend Location class with additional field for this.
49        location.setTime(duration);
50        mLocations.add(location);
51    }
52
53    public void consolidate(long interval) {
54        // TODO: add check on interval
55        double[] newCenter = {0f, 0f, 0f};
56        long newDuration = 0l;
57
58        // update cluster center
59        for (Location location : mLocations) {
60            double[] vector = getLocationVector(location);
61            long duration = location.getTime(); // in seconds
62
63            newDuration += duration;
64            for (int i = 0; i < 3; ++i) {
65                newCenter[i] += vector[i] * duration;
66            }
67        }
68        for (int i = 0; i < 3; ++i) {
69            newCenter[i] /= newDuration;
70        }
71        // remove location data
72        mLocations.clear();
73
74        if (mIsNewCluster) {
75            for (int i = 0; i < 3; ++i) {
76                mCenter[i] = newCenter[i];
77            }
78            mDuration = newDuration;
79            mHistogram.clear();
80            mHistogram.putAll(mNewHistogram);
81            mNewHistogram.clear();
82
83            mIsNewCluster = false;
84        } else {
85            // the new center is weight average over latest and existing centers.
86            // fine tune the weight of new center
87            double newWeight = ((double) newDuration) / (newDuration + mDuration);
88            newWeight *= FORGETTING_FACTOR;
89            double currWeight = 1f - newWeight;
90            double norm = 0;
91            for (int i = 0; i < 3; ++i) {
92                mCenter[i] = currWeight * mCenter[i] + newWeight * newCenter[i];
93                norm += mCenter[i] * mCenter[i];
94            }
95            // normalize
96            for (int i = 0; i < 3; ++i) {
97                mCenter[i] /= norm;
98            }
99            consolidateHistogram(newWeight, newDuration);
100            mNewHistogram.clear();
101        }
102    }
103
104    /*
105     * if the new created cluster whose covered area overlaps with any existing
106     * cluster move the center away from that cluster till there is no overlap.
107     */
108    public void moveAwayCluster(LocationCluster cluster, float distance) {
109        double[] vector = new double[3];
110
111        double dot = 0f;
112        for (int i = 0; i < 3; ++i) {
113            dot += mCenter[i] * cluster.mCenter[i];
114        }
115        double norm = 0f;
116        for (int i = 0; i < 3; ++i) {
117            vector[i] = mCenter[i] - dot * cluster.mCenter[i];
118            norm += vector[i] * vector[i];
119        }
120        norm = Math.sqrt(norm);
121
122        double radian = distance / EARTH_RADIUS;
123        for (int i = 0; i < 3; ++i) {
124            mCenter[i] = cluster.mCenter[i] * Math.cos(radian) +
125                    (vector[i] / norm) * Math.sin(radian);
126        }
127    }
128
129    private void updateTemporalHistogram(long time, long duration) {
130        HashMap<String, String> timeFeatures = TimeStatsAggregator.getAllTimeFeatures(time);
131
132        String timeOfWeek = timeFeatures.get(TimeStatsAggregator.TIME_OF_WEEK);
133        long totalDuration = (mNewHistogram.containsKey(timeOfWeek)) ?
134            mNewHistogram.get(timeOfWeek) + duration : duration;
135        mNewHistogram.put(timeOfWeek, totalDuration);
136
137        String timeOfDay = timeFeatures.get(TimeStatsAggregator.TIME_OF_DAY);
138        totalDuration = (mNewHistogram.containsKey(timeOfDay)) ?
139            mNewHistogram.get(timeOfDay) + duration : duration;
140        mNewHistogram.put(timeOfDay, totalDuration);
141    }
142
143    private void consolidateHistogram(double weight, long newDuration) {
144        long base = 1000;
145        long newWeight = (long) (weight * base);
146        long currWeight = base - newWeight;
147
148        for (Map.Entry<String, Long> entry : mHistogram.entrySet()) {
149            String timeLabel = entry.getKey();
150            long duration = entry.getValue() * currWeight;
151            if (mNewHistogram.containsKey(timeLabel)) {
152                duration += mNewHistogram.get(timeLabel) * newWeight;
153            }
154            duration /= base;
155            mHistogram.put(timeLabel, duration);
156        }
157
158        for (Map.Entry<String, Long> entry : mNewHistogram.entrySet()) {
159            String timeLabel = entry.getKey();
160            if (!mHistogram.containsKey(timeLabel)) {
161                long duration = newWeight * entry.getValue();
162                duration /= base;
163                mHistogram.put(timeLabel, duration);
164            }
165        }
166        mDuration = (mDuration * currWeight + newDuration * newWeight) / base;
167    }
168}
169