/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.webapp.gwt.common.control; import org.onebusaway.utility.collections.TreeUnionFind; import com.google.gwt.core.client.JsArray; import com.google.gwt.maps.client.geocode.Geocoder; import com.google.gwt.maps.client.geocode.LocationCallback; import com.google.gwt.maps.client.geocode.Placemark; import com.google.gwt.maps.client.geom.LatLngBounds; import com.google.gwt.search.client.AddressLookupMode; import com.google.gwt.search.client.LocalResult; import com.google.gwt.search.client.LocalSearch; import com.google.gwt.search.client.Result; import com.google.gwt.search.client.SearchCompleteHandler; import com.google.gwt.search.client.SearchCompleteHandler.SearchCompleteEvent; import com.google.gwt.user.client.Timer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; public class PlaceSearch { private static final PlaceComparator _comparator = new PlaceComparator(); private double _mergeDistance = 25.0; public void query(String query, PlaceSearchListener listener) { query(query, listener, null); } /** * Results with * * @param mergeDistance in meters */ public void setMergeDistance(double mergeDistance) { _mergeDistance = mergeDistance; } public void query(String query, PlaceSearchListener listener, LatLngBounds view) { final LocationHandler handler = new LocationHandler(listener, view); // Google Local Search LocalSearch search = new LocalSearch(); search.setAddressLookupMode(AddressLookupMode.ENABLED); if (view != null) search.setCenterPoint(view.getCenter()); search.addSearchCompleteHandler(handler); search.execute(query); // Google Maps Geocoder Search Geocoder geocoder = new Geocoder(); if (view != null) geocoder.setViewport(view); geocoder.getLocations(query, handler); handler.scheduleRepeating(1000); } public static List<Place> getSeachCompleteEventAsPlaces( SearchCompleteEvent event, List<Place> places) { JsArray<? extends Result> results = event.getSearch().getResults(); for (int i = 0; i < results.length(); i++) { Result result = results.get(i); if (result instanceof LocalResult) { LocalResult lsr = (LocalResult) result; places.add(new LocalResultPlaceImpl(lsr)); } } return places; } public static List<Place> mergeNearbyEntries(List<Place> places, double mergeDistance) { TreeUnionFind<Place> unionFind = new TreeUnionFind<Place>(); for (int i = 0; i < places.size(); i++) { Place place = places.get(i); unionFind.find(place); for (int j = i + 1; j < places.size(); j++) { Place other = places.get(j); double distance = place.getLocation().distanceFrom(other.getLocation()); if (distance < mergeDistance) unionFind.union(place, other); } } List<Place> reduced = new ArrayList<Place>(); for (Set<Place> cluster : unionFind.getSetMembers()) { List<Place> go = new ArrayList<Place>(cluster); Collections.sort(go, _comparator); reduced.add(go.get(0)); } return reduced; } private class LocationHandler extends Timer implements LocationCallback, SearchCompleteHandler { private List<Place> _places = new ArrayList<Place>(); private int _count = 0; private boolean _flushed = false; private PlaceSearchListener _listener; private LatLngBounds _view; private boolean _seenFirstOnSearchComplete = false; public LocationHandler(PlaceSearchListener listener, LatLngBounds view) { _listener = listener; _view = view; } public void onSuccess(JsArray<Placemark> placemarks) { for (int i = 0; i < placemarks.length(); i++) { Placemark mark = placemarks.get(i); addPlace(new PlacemarkPlaceImpl(mark)); } handleResult(false); } public void onFailure(int statusCode) { handleResult(true); } public void onSearchComplete(SearchCompleteEvent event) { if (_seenFirstOnSearchComplete) return; _seenFirstOnSearchComplete = true; List<Place> places = getSeachCompleteEventAsPlaces(event, new ArrayList<Place>()); for (Place place : places) addPlace(place); handleResult(false); } private void addPlace(Place place) { if (place.getName() == null) return; if (_view != null && !_view.containsLatLng(place.getLocation())) return; _places.add(place); } private void handleResult(boolean isError) { _count++; if (_count >= 2) flush(); } public void flush() { if (_flushed) return; _flushed = true; if (_mergeDistance > 0) _places = mergeNearbyEntries(_places,_mergeDistance); if (_places.isEmpty()) { _listener.handleNoResult(); } else if (_places.size() == 1) { _listener.handleSingleResult(_places.get(0)); } else { _listener.handleMultipleResults(_places); } } /**** * {@link Timer} Interface ****/ @Override public void run() { if (_count >= 2 || (_count > 0 && _places.size() > 0)) { this.cancel(); flush(); } } } private static class PlaceComparator implements Comparator<Place> { public int compare(Place o1, Place o2) { boolean placemarkA = o1 instanceof PlacemarkPlaceImpl; boolean placemarkB = o2 instanceof PlacemarkPlaceImpl; if (placemarkA && !placemarkB) return -1; if (placemarkB && !placemarkA) return 1; int accuracyA = o1.getAccuracy(); int accuracyB = o2.getAccuracy(); if (accuracyA < accuracyB) return -1; if (accuracyB < accuracyA) return 1; return 0; } } }