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 ArrayList<Location> mLocations = new ArrayList<Location>();
31    private HashMap<String, Long> mNewHistogram = new HashMap<String, Long>();
32
33    private String mSemanticClusterId = null;
34
35    public void setSemanticClusterId(String semanticClusterId) {
36        mSemanticClusterId = semanticClusterId;
37    }
38
39    public String getSemanticClusterId() {
40        return mSemanticClusterId;
41    }
42
43    public boolean hasSemanticClusterId() {
44        return mSemanticClusterId != null;
45    }
46
47    // TODO: make it a singleton class
48    public LocationCluster(Location location, long duration) {
49        super(location);
50        addSample(location, duration);
51    }
52
53    public void addSample(Location location, long duration) {
54        updateTemporalHistogram(location.getTime(), duration);
55
56        // use time field to store duation of this location
57        // TODO: extend Location class with additional field for this.
58        location.setTime(duration);
59        mLocations.add(location);
60    }
61
62    public void consolidate() {
63        // If there is no new location added during this period, do nothing.
64        if (mLocations.size() == 0) {
65            return;
66        }
67
68        double[] newCenter = {0f, 0f, 0f};
69        long newDuration = 0l;
70        // update cluster center
71        for (Location location : mLocations) {
72            double[] vector = getLocationVector(location);
73            long duration = location.getTime(); // in seconds
74
75            if (duration == 0) {
76                throw new RuntimeException("location duration is zero");
77            }
78
79            newDuration += duration;
80            for (int i = 0; i < VECTOR_LENGTH; ++i) {
81                newCenter[i] += vector[i] * duration;
82            }
83        }
84        if (newDuration == 0l) {
85            throw new RuntimeException("new duration is zero!");
86        }
87        for (int i = 0; i < VECTOR_LENGTH; ++i) {
88            newCenter[i] /= newDuration;
89        }
90        // remove location data
91        mLocations.clear();
92
93        // The updated center is the weighted average of the existing and the new
94        // centers. Note that if the cluster is consolidated for the first time,
95        // the weight for the existing cluster would be zero.
96        averageCenter(newCenter, newDuration);
97
98        // update histogram
99        for (Map.Entry<String, Long> entry : mNewHistogram.entrySet()) {
100            String timeLabel = entry.getKey();
101            long duration = entry.getValue();
102            if (mHistogram.containsKey(timeLabel)) {
103                duration += mHistogram.get(timeLabel);
104            }
105            mHistogram.put(timeLabel, duration);
106        }
107        mDuration += newDuration;
108        mNewHistogram.clear();
109    }
110
111    /*
112     * if the new created cluster whose covered area overlaps with any existing
113     * cluster move the center away from that cluster till there is no overlap.
114     */
115    public void moveAwayCluster(LocationCluster cluster, float distance) {
116        double[] vector = new double[VECTOR_LENGTH];
117
118        double dot = 0f;
119        for (int i = 0; i < VECTOR_LENGTH; ++i) {
120            dot += mCenter[i] * cluster.mCenter[i];
121        }
122        double norm = 0f;
123        for (int i = 0; i < VECTOR_LENGTH; ++i) {
124            vector[i] = mCenter[i] - dot * cluster.mCenter[i];
125            norm += vector[i] * vector[i];
126        }
127        norm = Math.sqrt(norm);
128
129        double radian = distance / EARTH_RADIUS;
130        for (int i = 0; i < VECTOR_LENGTH; ++i) {
131            mCenter[i] = cluster.mCenter[i] * Math.cos(radian) +
132                    (vector[i] / norm) * Math.sin(radian);
133        }
134    }
135
136    private void updateTemporalHistogram(long time, long duration) {
137        HashMap<String, String> timeFeatures = TimeStatsAggregator.getAllTimeFeatures(time);
138
139        String timeOfWeek = timeFeatures.get(TimeStatsAggregator.TIME_OF_WEEK);
140        long totalDuration = (mNewHistogram.containsKey(timeOfWeek)) ?
141            mNewHistogram.get(timeOfWeek) + duration : duration;
142        mNewHistogram.put(timeOfWeek, totalDuration);
143
144        String timeOfDay = timeFeatures.get(TimeStatsAggregator.TIME_OF_DAY);
145        totalDuration = (mNewHistogram.containsKey(timeOfDay)) ?
146            mNewHistogram.get(timeOfDay) + duration : duration;
147        mNewHistogram.put(timeOfDay, totalDuration);
148    }
149}
150