@Override public void run(MediaSet baseSet) { final int total = baseSet.getTotalMediaItemCount(); final SmallItem[] buf = new SmallItem[total]; // Separate items to two sets: with or without lat-long. final double[] latLong = new double[2]; baseSet.enumerateTotalMediaItems( new MediaSet.ItemConsumer() { public void consume(int index, MediaItem item) { if (index < 0 || index >= total) return; SmallItem s = new SmallItem(); s.path = item.getPath(); item.getLatLong(latLong); s.lat = latLong[0]; s.lng = latLong[1]; buf[index] = s; } }); final ArrayList<SmallItem> withLatLong = new ArrayList<SmallItem>(); final ArrayList<SmallItem> withoutLatLong = new ArrayList<SmallItem>(); final ArrayList<Point> points = new ArrayList<Point>(); for (int i = 0; i < total; i++) { SmallItem s = buf[i]; if (s == null) continue; if (GalleryUtils.isValidLocation(s.lat, s.lng)) { withLatLong.add(s); points.add(new Point(s.lat, s.lng)); } else { withoutLatLong.add(s); } } ArrayList<ArrayList<SmallItem>> clusters = new ArrayList<ArrayList<SmallItem>>(); int m = withLatLong.size(); if (m > 0) { // cluster the items with lat-long Point[] pointsArray = new Point[m]; pointsArray = points.toArray(pointsArray); int[] bestK = new int[1]; int[] index = kMeans(pointsArray, bestK); for (int i = 0; i < bestK[0]; i++) { clusters.add(new ArrayList<SmallItem>()); } for (int i = 0; i < m; i++) { clusters.get(index[i]).add(withLatLong.get(i)); } } ReverseGeocoder geocoder = new ReverseGeocoder(mContext); mNames = new ArrayList<String>(); boolean hasUnresolvedAddress = false; mClusters = new ArrayList<ArrayList<SmallItem>>(); for (ArrayList<SmallItem> cluster : clusters) { String name = generateName(cluster, geocoder); if (name != null) { mNames.add(name); mClusters.add(cluster); } else { // move cluster-i to no location cluster withoutLatLong.addAll(cluster); hasUnresolvedAddress = true; } } if (withoutLatLong.size() > 0) { mNames.add(mNoLocationString); mClusters.add(withoutLatLong); } if (hasUnresolvedAddress) { mHandler.post( new Runnable() { public void run() { Toast.makeText(mContext, R.string.no_connectivity, Toast.LENGTH_LONG).show(); } }); } }
// Input: n points // Output: the best k is stored in bestK[0], and the return value is the // an array which specifies the group that each point belongs (0 to k - 1). private static int[] kMeans(Point points[], int[] bestK) { int n = points.length; // min and max number of groups wanted int minK = Math.min(n, MIN_GROUPS); int maxK = Math.min(n, MAX_GROUPS); Point[] center = new Point[maxK]; // center of each group. Point[] groupSum = new Point[maxK]; // sum of points in each group. int[] groupCount = new int[maxK]; // number of points in each group. int[] grouping = new int[n]; // The group assignment for each point. for (int i = 0; i < maxK; i++) { center[i] = new Point(); groupSum[i] = new Point(); } // The score we want to minimize is: // (sum of distance from each point to its group center) * sqrt(k). float bestScore = Float.MAX_VALUE; // The best group assignment up to now. int[] bestGrouping = new int[n]; // The best K up to now. bestK[0] = 1; float lastDistance = 0; float totalDistance = 0; for (int k = minK; k <= maxK; k++) { // step 1: (arbitrarily) pick k points as the initial centers. int delta = n / k; for (int i = 0; i < k; i++) { Point p = points[i * delta]; center[i].latRad = p.latRad; center[i].lngRad = p.lngRad; } for (int iter = 0; iter < MAX_ITERATIONS; iter++) { // step 2: assign each point to the nearest center. for (int i = 0; i < k; i++) { groupSum[i].latRad = 0; groupSum[i].lngRad = 0; groupCount[i] = 0; } totalDistance = 0; for (int i = 0; i < n; i++) { Point p = points[i]; float bestDistance = Float.MAX_VALUE; int bestIndex = 0; for (int j = 0; j < k; j++) { float distance = (float) GalleryUtils.fastDistanceMeters( p.latRad, p.lngRad, center[j].latRad, center[j].lngRad); // We may have small non-zero distance introduced by // floating point calculation, so zero out small // distances less than 1 meter. if (distance < 1) { distance = 0; } if (distance < bestDistance) { bestDistance = distance; bestIndex = j; } } grouping[i] = bestIndex; groupCount[bestIndex]++; groupSum[bestIndex].latRad += p.latRad; groupSum[bestIndex].lngRad += p.lngRad; totalDistance += bestDistance; } // step 3: calculate new centers for (int i = 0; i < k; i++) { if (groupCount[i] > 0) { center[i].latRad = groupSum[i].latRad / groupCount[i]; center[i].lngRad = groupSum[i].lngRad / groupCount[i]; } } if (totalDistance == 0 || (Math.abs(lastDistance - totalDistance) / totalDistance) < STOP_CHANGE_RATIO) { break; } lastDistance = totalDistance; } // step 4: remove empty groups and reassign group number int reassign[] = new int[k]; int realK = 0; for (int i = 0; i < k; i++) { if (groupCount[i] > 0) { reassign[i] = realK++; } } // step 5: calculate the final score float score = totalDistance * (float) Math.sqrt(realK); if (score < bestScore) { bestScore = score; bestK[0] = realK; for (int i = 0; i < n; i++) { bestGrouping[i] = reassign[grouping[i]]; } if (score == 0) { break; } } } return bestGrouping; }