diff --git a/DataExtractionOSM/src/net/osmand/data/PostCode.java b/DataExtractionOSM/src/net/osmand/data/PostCode.java index 39648710b1..32ea27ac85 100644 --- a/DataExtractionOSM/src/net/osmand/data/PostCode.java +++ b/DataExtractionOSM/src/net/osmand/data/PostCode.java @@ -26,6 +26,11 @@ public class PostCode extends MapObject { return streets.values(); } + public void removeAllStreets() + { + streets.clear(); + } + public Street registerStreet(Street street, boolean useEnglishNames){ String name = street.getName(useEnglishNames); streets.put(name, street); diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 780e3432f0..d95f89877c 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -39,6 +39,11 @@ + + + + + diff --git a/OsmAnd/res/layout/search_address_offline.xml b/OsmAnd/res/layout/search_address_offline.xml new file mode 100644 index 0000000000..599a61fa28 --- /dev/null +++ b/OsmAnd/res/layout/search_address_offline.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/OsmAnd/res/layout/search_address_offline_list_item.xml b/OsmAnd/res/layout/search_address_offline_list_item.xml new file mode 100644 index 0000000000..692b32550c --- /dev/null +++ b/OsmAnd/res/layout/search_address_offline_list_item.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 5f781ff248..f22b80f70e 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -1,5 +1,9 @@ + Error occurred in offline search + Could not parse geo intent:{0} + Search address using offline maps + System Change display language Preferred locale @@ -32,7 +36,7 @@ Destination point is widely used to measure distance and to straight forward nav \n\tTo better use them you can follow by tips and tricks available as link on main menu. Next Previous - + Change unit of length and speed Unit of length Miles/foots @@ -553,4 +557,4 @@ Destination point is widely used to measure distance and to straight forward nav Filter {0} has been created Select All - \ No newline at end of file + diff --git a/OsmAnd/src/net/osmand/activities/search/GeoIntentActivity.java b/OsmAnd/src/net/osmand/activities/search/GeoIntentActivity.java new file mode 100644 index 0000000000..4781e6708d --- /dev/null +++ b/OsmAnd/src/net/osmand/activities/search/GeoIntentActivity.java @@ -0,0 +1,337 @@ +package net.osmand.activities.search; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import net.osmand.OsmandSettings; +import net.osmand.R; +import net.osmand.RegionAddressRepository; +import net.osmand.ResourceManager; +import net.osmand.activities.MapActivity; +import net.osmand.activities.OsmandApplication; +import net.osmand.data.City; +import net.osmand.data.MapObject; +import net.osmand.data.PostCode; +import net.osmand.data.Street; +import net.osmand.osm.LatLon; +import net.osmand.osm.MapUtils; +import net.osmand.osm.Node; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +public class GeoIntentActivity extends ListActivity { + + private ProgressDialog progressDlg; + private LatLon location; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.search_address_offline); + location = OsmandSettings.getLastKnownMapLocation(OsmandSettings + .getPrefs(this)); + final Intent intent = getIntent(); + if (intent != null) { + progressDlg = ProgressDialog.show(this, + getString(R.string.searching), + getString(R.string.searching_address)); + final Thread searcher = new Thread(new Runnable() { + @Override + public void run() { + try { + Collection results = extract( + intent.getData()).execute(); + // show the first result on map, and populate the list! + if (!results.isEmpty()) { + showResult(0, new ArrayList(results)); + } else { + showResult(R.string.search_nothing_found, null); + } + } catch (Exception e) { + e.printStackTrace(); + showResult(R.string.error_doing_search, null); + } finally { + progressDlg.dismiss(); + } + } + }, "SearchingAddress"); + searcher.start(); + progressDlg.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + searcher.interrupt(); + } + }); + progressDlg.setCancelable(true); + + } + // finish(); + } + + private void showResult(final int warning, final List places) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (places == null) { + Toast.makeText(GeoIntentActivity.this, getString(warning), + Toast.LENGTH_LONG).show(); + } else { + setListAdapter(new MapObjectAdapter(places)); + if (places.size() == 1) { + onListItemClick(getListView(), getListAdapter() + .getView(0, null, null), 0, getListAdapter() + .getItemId(0)); + } + } + } + }); + } + + class MapObjectAdapter extends ArrayAdapter { + + public MapObjectAdapter(List places) { + super(GeoIntentActivity.this, + R.layout.search_address_offline_list_item, places); + } + + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + if (row == null) { + LayoutInflater inflater = getLayoutInflater(); + row = inflater.inflate( + R.layout.search_address_offline_list_item, parent, + false); + } + MapObject model = getItem(position); + TextView label = (TextView) row.findViewById(R.id.label); + TextView distanceLabel = (TextView) row + .findViewById(R.id.distance_label); + if (location != null) { + int dist = (int) (MapUtils.getDistance(location, model + .getLocation().getLatitude(), model.getLocation() + .getLongitude())); + distanceLabel.setText(MapUtils.getFormattedDistance(dist)); + } else { + distanceLabel.setText(""); //$NON-NLS-1$ + } + label.setText(model.toString()); + return row; + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + MapObject item = ((MapObjectAdapter) getListAdapter()) + .getItem(position); + OsmandSettings.setMapLocationToShow(this, item.getLocation() + .getLatitude(), item.getLocation().getLongitude(), + getString(R.string.address) + " : " + item.toString()); //$NON-NLS-1$ + startActivity(new Intent(this, MapActivity.class)); + } + + @Override + protected void onStop() { + dismiss(); + super.onStop(); + } + + private void dismiss() { + if (progressDlg != null) { + progressDlg.dismiss(); + progressDlg = null; + } + } + + /** + * geo:latitude,longitude
+ * geo:latitude,longitude?z=zoom
+ * geo:0,0?q=my+street+address
+ * geo:0,0?q=business+near+city + * + * @param data + * @return + */ + private MyService extract(Uri data) { + // it is 0,0? that means a search + if (data.getSchemeSpecificPart().indexOf("0,0?") != -1) { + return new GeoAddressSearch(data.getQuery()); + } else { + return new GeoPointSearch(data.getSchemeSpecificPart()); + } + } + + private final class GeoAddressSearch implements MyService { + private List elements; + + public GeoAddressSearch(String query) { + StringTokenizer s = new StringTokenizer(query.substring(query + .indexOf("q=") + 2), ","); + elements = new ArrayList(s.countTokens()); + while (s.hasMoreTokens()) { + elements.add(s.nextToken().replace('+', ' ').trim()); + } + } + + @Override + public Collection execute() { + if (elements.isEmpty()) { + return Collections.emptyList(); + } + + // now try to search the City, Street, Etc.. if Street is not found, + // try to search POI + ResourceManager resourceManager = resourceManager(); + List foundCountries = new ArrayList(); + RegionAddressRepository country; + for (String maybeCountry : elements) { + country = resourceManager.getRegionRepository(maybeCountry); + if (country != null) { + foundCountries.add(country); + } + } + Collection countriesToSearch = foundCountries; + if (foundCountries.isEmpty()) { + // there is no country, we have to search each country + countriesToSearch = resourceManager.getAddressRepositories(); + } + + // search cities for found countries + Map> citiesForRegion = new HashMap>(); + for (RegionAddressRepository rar : countriesToSearch) { + List citiesFound = new ArrayList(); + for (String maybeCity : elements) { + rar.fillWithSuggestedCities(maybeCity, citiesFound, null); + } + if (!citiesFound.isEmpty()) { + citiesForRegion.put(rar, citiesFound); + } + } + // no cities found, we should locate the country only + Map> streetsForCity = new HashMap>(); + if (citiesForRegion.isEmpty()) { + for (RegionAddressRepository rar : countriesToSearch) { + ArrayList allcities = new ArrayList(); + rar.fillWithSuggestedCities("", allcities, location); + findStreetsForCities(streetsForCity, rar, allcities); + } + } else { + // we have cities, now search for streets? + for (RegionAddressRepository rar : citiesForRegion.keySet()) { + findStreetsForCities(streetsForCity, rar, + citiesForRegion.get(rar)); + } + } + + // don't go deeper, now populate result list + Set results = new HashSet(); + // add all found lists + for (List streets : streetsForCity.values()) { + results.addAll(streets); + } + // add all found cities for which street was not found + for (List cities : citiesForRegion.values()) { + cities.removeAll(streetsForCity.keySet()); + results.addAll(cities); + } + // TODO add all regions for which city was not found + return results; + } + + private void findStreetsForCities( + Map> streetsForCity, + RegionAddressRepository rar, List allcities) { + for (MapObject city : allcities) { + List streets = new ArrayList(); + rar.fillWithSuggestedStreets(city, streets, + elements.toArray(new String[] {})); + // we must do this, or we will fill up the whole memory (streets + // are preloaded...) + // TODO some street iterator would be better, is it possible to + // create one? + if (city instanceof City) { + ((City) city).removeAllStreets(); + } else if (city instanceof PostCode) { + ((PostCode) city).removeAllStreets(); + } + if (!streets.isEmpty()) { + streetsForCity.put(city, streets); + } + } + } + + } + + private ResourceManager resourceManager() { + return ((OsmandApplication) getApplication()).getResourceManager(); + } + + private class GeoPointSearch implements MyService { + + private MapObject point; + + /** + * geo:latitude,longitude geo:latitude,longitude?z=zoom + */ + public GeoPointSearch(final String geo) { + int latIndex = geo.indexOf(','); + int lonIndex = geo.indexOf('?'); + lonIndex = lonIndex > 0 ? lonIndex : geo.length(); + if (latIndex > 0) { + try { + double latitude = Double.parseDouble(geo.substring(0, + latIndex)); + double longitude = Double.parseDouble(geo.substring( + latIndex + 1, lonIndex)); + // TODO zoom is omited for now + point = new MapObject(new Node(latitude, longitude, -1)) { + }; + point.setName("Lat: " + latitude + ",Lon:" + longitude); + } catch (NumberFormatException e) { + runOnUiThread(new Runnable() { + public void run() { + Toast.makeText(GeoIntentActivity.this, + getString(R.string.search_offline_geo_error, geo), + Toast.LENGTH_LONG); + } + }); + } + } + } + + @Override + public Collection execute() { + if (point != null) { + return Collections.singletonList(point); + } else { + return Collections.emptyList(); + } + } + + } + + private interface MyService { + + public Collection execute(); + } + +} diff --git a/OsmAnd/src/net/osmand/plus/RegionAddressRepository.java b/OsmAnd/src/net/osmand/plus/RegionAddressRepository.java index 4a28e1001f..7ac76d8119 100644 --- a/OsmAnd/src/net/osmand/plus/RegionAddressRepository.java +++ b/OsmAnd/src/net/osmand/plus/RegionAddressRepository.java @@ -64,7 +64,7 @@ public interface RegionAddressRepository { public void fillWithSuggestedStreetsIntersectStreets(City city, Street st, List streetsToFill); - public void fillWithSuggestedStreets(MapObject cityOrPostcode, String name, List streetsToFill); + public void fillWithSuggestedStreets(MapObject cityOrPostcode, List streetsToFill, String... name); public void fillWithSuggestedCities(String name, List citiesToFill, LatLon currentLocation); diff --git a/OsmAnd/src/net/osmand/plus/RegionAddressRepositoryBinary.java b/OsmAnd/src/net/osmand/plus/RegionAddressRepositoryBinary.java index 7ce8fb43de..4def6f7084 100644 --- a/OsmAnd/src/net/osmand/plus/RegionAddressRepositoryBinary.java +++ b/OsmAnd/src/net/osmand/plus/RegionAddressRepositoryBinary.java @@ -85,20 +85,22 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository { @Override - public void fillWithSuggestedStreets(MapObject o, String name, List streetsToFill) { + public void fillWithSuggestedStreets(MapObject o, List streetsToFill, String... names) { assert o instanceof PostCode || o instanceof City; City city = (City) (o instanceof City ? o : null); PostCode post = (PostCode) (o instanceof PostCode ? o : null); - name = name.toLowerCase(); preloadStreets(o); Collection streets = post == null ? city.getStreets() : post.getStreets() ; - if(name.length() == 0){ + if(names.length == 0){ streetsToFill.addAll(streets); - } else { - int ind = 0; - for (Street s : streets) { - String sName = useEnglishNames ? s.getEnName() : s.getName(); //lower case not needed, collator ensures that + return; + } + + int ind = 0; + for (Street s : streets) { + String sName = useEnglishNames ? s.getEnName() : s.getName(); //lower case not needed, collator ensures that + for (String name : names) { if (cstartsWith(collator,sName,name)) { streetsToFill.add(ind, s); ind++; diff --git a/OsmAnd/src/net/osmand/plus/activities/search/SearchStreetByNameActivity.java b/OsmAnd/src/net/osmand/plus/activities/search/SearchStreetByNameActivity.java index 85e6e545df..f636e87a0d 100644 --- a/OsmAnd/src/net/osmand/plus/activities/search/SearchStreetByNameActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/search/SearchStreetByNameActivity.java @@ -36,7 +36,7 @@ public class SearchStreetByNameActivity extends SearchByNameAbstractActivity getObjects(String filter) { List l = new ArrayList(); if (city != null || postcode != null) { - region.fillWithSuggestedStreets(postcode == null ? city : postcode, filter, l); + region.fillWithSuggestedStreets(postcode == null ? city : postcode, l, filter); } return l; }