diff --git a/OsmAnd-java/src/net/osmand/search/core/SearchCoreFactory.java b/OsmAnd-java/src/net/osmand/search/core/SearchCoreFactory.java index 364e06351a..e4562b0f93 100644 --- a/OsmAnd-java/src/net/osmand/search/core/SearchCoreFactory.java +++ b/OsmAnd-java/src/net/osmand/search/core/SearchCoreFactory.java @@ -15,7 +15,6 @@ import java.util.TreeSet; import net.osmand.CollatorStringMatcher.StringMatcherMode; import net.osmand.LocationConvert; -import net.osmand.OsmAndCollator; import net.osmand.ResultMatcher; import net.osmand.binary.BinaryMapAddressReaderAdapter; import net.osmand.binary.BinaryMapIndexReader; @@ -48,24 +47,46 @@ import com.jwetherell.openmap.common.UTMPoint; public class SearchCoreFactory { - // TODO add location parse (+) - // TODO add location partial (+) - // TODO geo:34.99393,-106.61568 (Treasure Island) and display url parse (+) - - // TODO location 43°38′33.24″N 79°23′13.7″W, - // TODO tests for geo:, location, - // TODO add UTM support + // TODO tests for geo:, location (+) + // TODO add UTM support (+) + // TODO location 43°38′33.24″N 79°23′13.7″W (+) + // TODO partial location // TODO add full text search with comma correct order // TODO MED add full text search without comma and different word order - // TODO MED edit in the middle (with words and comma)? + // TODO exclude duplicate streets/cities/pois... - // TODO MED support poi additional select type and search - // TODO LOW display results momentarily - // TODO LOW automatically increase radius if nothing found (log radius search) - + // TODO UI support poi additional select type and search + // TODO UI display results momentarily + // TODO UI automatically increase radius if nothing found (log radius search) + + + //////////////// CONSTANTS ////////// + public static final int SEARCH_REGION_API_PRIORITY = 3; + public static final int SEARCH_REGION_OBJECT_PRIORITY = 10; + + // context less + public static final int SEARCH_LOCATION_PRIORITY = 0; + public static final int SEARCH_AMENITY_TYPE_PRIORITY = 1; + public static final int SEARCH_AMENITY_TYPE_API_PRIORITY = 1; + + // context + public static final int SEARCH_STREET_BY_CITY_PRIORITY = 2; + public static final int SEARCH_BUILDING_BY_CITY_PRIORITY = 3; + public static final int SEARCH_BUILDING_BY_STREET_PRIORITY = 1; + public static final int SEARCH_AMENITY_BY_TYPE_PRIORITY = 3; + + // context less (slow) + public static final int SEARCH_ADDRESS_BY_NAME_API_PRIORITY = 5; + public static final int SEARCH_ADDRESS_BY_NAME_API_PRIORITY_RADIUS2 = 5; + public static final int SEARCH_ADDRESS_BY_NAME_PRIORITY = 5; + public static final int SEARCH_ADDRESS_BY_NAME_PRIORITY_RADIUS2 = 5; + // context less (slower) + public static final int SEARCH_AMENITY_BY_NAME_PRIORITY = 7; + public static final int SEARCH_AMENITY_BY_NAME_API_PRIORITY_IF_POI_TYPE = 7; + public static final int SEARCH_AMENITY_BY_NAME_API_PRIORITY_IF_3_CHAR = 7; public static abstract class SearchBaseAPI implements SearchCoreAPI { @Override @@ -79,8 +100,11 @@ public class SearchCoreFactory { } } + public static class SearchRegionByNameAPI extends SearchBaseAPI { + + @Override public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) { for (BinaryMapIndexReader bmir : phrase.getOfflineIndexes()) { @@ -89,7 +113,7 @@ public class SearchCoreFactory { sr.localeName = bmir.getRegionName(); sr.object = bmir; sr.file = bmir; - sr.priority = 10; + sr.priority = SEARCH_REGION_OBJECT_PRIORITY; sr.objectType = ObjectType.REGION; sr.location = bmir.getRegionCenter(); sr.preferredZoom = 6; @@ -108,7 +132,7 @@ public class SearchCoreFactory { if(!p.isNoSelectedType()) { return -1; } - return 3; + return SEARCH_REGION_API_PRIORITY; } } @@ -116,6 +140,8 @@ public class SearchCoreFactory { private static final int DEFAULT_ADDRESS_BBOX_RADIUS = 1000*1000; private static final int LIMIT = 10000; + + private Map> townCities = new LinkedHashMap<>(); private QuadTree townCitiesQR = new QuadTree(new QuadRect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE), 8, 0.55f); @@ -123,10 +149,13 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { - if (p.isNoSelectedType()) { - return 5; + if (!p.isNoSelectedType() && p.getRadiusLevel() == 1) { + return -1; } - return 10; + if (p.isNoSelectedType()) { + return SEARCH_ADDRESS_BY_NAME_API_PRIORITY; + } + return SEARCH_ADDRESS_BY_NAME_API_PRIORITY_RADIUS2; } @Override @@ -198,7 +227,8 @@ public class SearchCoreFactory { final QuadRect postcodeBbox = phrase.getRadiusBBoxToSearch(DEFAULT_ADDRESS_BBOX_RADIUS * 5); final QuadRect villagesBbox = phrase.getRadiusBBoxToSearch(DEFAULT_ADDRESS_BBOX_RADIUS * 3); final QuadRect cityBbox = phrase.getRadiusBBoxToSearch(DEFAULT_ADDRESS_BBOX_RADIUS * 10); - final int priority = phrase.isNoSelectedType() ? 3 : 5; + final int priority = phrase.isNoSelectedType() ? + SEARCH_ADDRESS_BY_NAME_PRIORITY : SEARCH_ADDRESS_BY_NAME_PRIORITY_RADIUS2; final BinaryMapIndexReader[] currentFile = new BinaryMapIndexReader[1]; ResultMatcher rm = new ResultMatcher() { int limit = 0; @@ -306,6 +336,7 @@ public class SearchCoreFactory { public static class SearchAmenityByNameAPI extends SearchBaseAPI { private static final int LIMIT = 10000; private static final int BBOX_RADIUS = 1000 * 1000; + @Override public boolean search(final SearchPhrase phrase, final SearchResultMatcher resultMatcher) throws IOException { if(phrase.getLastWord().length() == 0) { @@ -334,7 +365,7 @@ public class SearchCoreFactory { sr.preferredZoom = 17; sr.file = currentFile[0]; sr.location = object.getLocation(); - sr.priority = 3; + sr.priority = SEARCH_AMENITY_BY_NAME_PRIORITY; sr.priorityDistance = 1; sr.objectType = ObjectType.POI; @@ -363,13 +394,13 @@ public class SearchCoreFactory { } if(p.hasObjectType(ObjectType.POI_TYPE)) { if(p.getRadiusLevel() > 1) { - return 5; + return SEARCH_AMENITY_BY_NAME_API_PRIORITY_IF_POI_TYPE; } else { return -1; } } if(p.getLastWord().length() > 3 || p.getRadiusLevel() > 1) { - return 3; + return SEARCH_AMENITY_BY_NAME_API_PRIORITY_IF_3_CHAR; } return -1; } @@ -378,28 +409,17 @@ public class SearchCoreFactory { public static class SearchAmenityTypesAPI extends SearchBaseAPI { - private Map translatedNames; private List topVisibleFilters; - private TreeSet results; public SearchAmenityTypesAPI(MapPoiTypes types) { translatedNames = types.getAllTranslatedNames(false); topVisibleFilters = types.getTopVisibleFilters(); - final net.osmand.Collator clt = OsmAndCollator.primaryCollator(); - results = new TreeSet<>(new Comparator() { - - @Override - public int compare(AbstractPoiType o1, AbstractPoiType o2) { - return clt.compare(o1.getTranslation(), o2.getTranslation()); - } - }); } @Override public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) throws IOException { // results.clear(); -// final net.osmand.Collator clt = OsmAndCollator.primaryCollator(); TreeSet results = new TreeSet<>(new Comparator() { @Override @@ -426,7 +446,7 @@ public class SearchCoreFactory { SearchResult res = new SearchResult(phrase); res.localeName = p.getTranslation(); res.object = p; - res.priority = 5; + res.priority = SEARCH_AMENITY_TYPE_PRIORITY; res.priorityDistance = 0; res.objectType = ObjectType.POI_TYPE; resultMatcher.publish(res); @@ -436,15 +456,16 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { - if(p.hasObjectType(ObjectType.POI) ||p.hasObjectType(ObjectType.POI_TYPE)) { + if (p.hasObjectType(ObjectType.POI) || p.hasObjectType(ObjectType.POI_TYPE)) { return -1; } - return 1; + return SEARCH_AMENITY_TYPE_API_PRIORITY; } } public static class SearchAmenityByTypeAPI extends SearchBaseAPI { + private MapPoiTypes types; public SearchAmenityByTypeAPI(MapPoiTypes types) { @@ -542,7 +563,7 @@ public class SearchCoreFactory { res.preferredZoom = 17; res.file = selected[0]; res.location = object.getLocation(); - res.priority = 3; + res.priority = SEARCH_AMENITY_BY_TYPE_PRIORITY; res.priorityDistance = 1; res.objectType = ObjectType.POI; resultMatcher.publish(res); @@ -566,7 +587,7 @@ public class SearchCoreFactory { public int getSearchPriority(SearchPhrase p) { if(p.isLastWord(ObjectType.POI_TYPE) && p.getLastTokenLocation() != null) { - return 3; + return SEARCH_AMENITY_BY_TYPE_PRIORITY; } return -1; } @@ -575,6 +596,7 @@ public class SearchCoreFactory { public static class SearchStreetByCityAPI extends SearchBaseAPI { + private static int LIMIT = 10000; @Override public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) throws IOException { @@ -604,7 +626,7 @@ public class SearchCoreFactory { res.preferredZoom = 17; res.file = sw.getResult().file; res.location = object.getLocation(); - res.priority = 1; + res.priority = SEARCH_STREET_BY_CITY_PRIORITY; //res.priorityDistance = 1; res.objectType = ObjectType.STREET; if (limit++ > LIMIT) { @@ -621,7 +643,7 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { if(isLastWordCityGroup(p)) { - return 1; + return SEARCH_STREET_BY_CITY_PRIORITY; } return -1; } @@ -639,12 +661,12 @@ public class SearchCoreFactory { @Override public boolean search(SearchPhrase phrase, final SearchResultMatcher resultMatcher) throws IOException { Street s = null; - int priority = 1; + int priority = SEARCH_BUILDING_BY_STREET_PRIORITY; if(phrase.isLastWord(ObjectType.STREET)) { s = (Street) phrase.getLastSelectedWord().getResult().object; } if(isLastWordCityGroup(phrase)) { - priority = 3; + priority = SEARCH_BUILDING_BY_CITY_PRIORITY; Object o = phrase.getLastSelectedWord().getResult().object; if(o instanceof City) { List streets = ((City) o).getStreets(); @@ -745,19 +767,21 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { if(isLastWordCityGroup(p)) { - return 10; + return SEARCH_BUILDING_BY_CITY_PRIORITY; } if(!p.isLastWord(ObjectType.STREET)) { return -1; } - return 1; + return SEARCH_BUILDING_BY_STREET_PRIORITY; } } + public static class SearchLocationAndUrlAPI extends SearchBaseAPI { + // newFormat = PointDescription.FORMAT_DEGREES; // newFormat = PointDescription.FORMAT_MINUTES; // newFormat = PointDescription.FORMAT_SECONDS; @@ -777,8 +801,10 @@ public class SearchCoreFactory { if(phrase.getLastWord().length() == 0) { return false; } - parseLocation(phrase, resultMatcher); - parseUrl(phrase, resultMatcher); + boolean parseUrl = parseUrl(phrase, resultMatcher); + if(!parseUrl) { + parseLocation2(phrase, resultMatcher); + } return super.search(phrase, resultMatcher); } private boolean isKindOfNumber(String s) { @@ -793,6 +819,277 @@ public class SearchCoreFactory { return true; } + LatLon parseLocation(String s) { + s = s.trim(); + if(s.length() == 0 || !(s.charAt(0) == '-' || Character.isDigit(s.charAt(0)) + || s.charAt(0) == 'S' || s.charAt(0) == 's' + || s.charAt(0) == 'N' || s.charAt(0) == 'n' + || s.contains("://"))) { + return null; + } + List d = new ArrayList<>(); + List all = new ArrayList<>(); + List strings = new ArrayList<>(); + splitObjects(s, d, all, strings); + if(d.size() == 0) { + return null; + } + // detect UTM + if (all.size() == 4 && d.size() == 3 && all.get(1) instanceof String) { + char ch = all.get(1).toString().charAt(0); + if (Character.isLetter(ch)) { + UTMPoint upoint = new UTMPoint(d.get(2), d.get(1), d.get(0).intValue(), ch); + LatLonPoint ll = upoint.toLatLonPoint(); + return new LatLon(ll.getLatitude(), ll.getLongitude()); + } + } + + if (all.size() == 3 && d.size() == 2 && all.get(1) instanceof String) { + char ch = all.get(1).toString().charAt(0); + String combined = strings.get(2); + if (Character.isLetter(ch)) { + try { + String east = combined.substring(0, combined.length() / 2); + String north = combined.substring(combined.length() / 2, combined.length()); + UTMPoint upoint = new UTMPoint(Double.parseDouble(north), Double.parseDouble(east), d.get(0) + .intValue(), ch); + LatLonPoint ll = upoint.toLatLonPoint(); + return new LatLon(ll.getLatitude(), ll.getLongitude()); + } catch (NumberFormatException e) { + } + } + } + // try to find split lat/lon position + int jointNumbers = 0; + int lastJoin = 0; + int degSplit = -1; + int degType = -1; // 0 - degree, 1 - minutes, 2 - seconds + int northSplit = -1; + int eastSplit = -1; + for(int i = 1; i < all.size(); i++ ) { + if(all.get(i - 1) instanceof Double && all.get(i) instanceof Double) { + jointNumbers ++; + lastJoin = i; + } + if(all.get(i).equals("n") || all.get(i).equals("s") || + all.get(i).equals("N") || all.get(i).equals("S")) { + northSplit = i + 1; + } + if(all.get(i).equals("e") || all.get(i).equals("w") || + all.get(i).equals("E") || all.get(i).equals("W")) { + eastSplit = i; + } + int dg = -1; + if (all.get(i).equals("°")) { + dg = 0; + } else if (all.get(i).equals("\'") || all.get(i).equals("′")) { + dg = 1; + } else if (all.get(i).equals("″") || all.get(i).equals("\"")) { + dg = 2; + } + if (dg != -1 && degType != -2) { + if (degType < dg) { + degSplit = i + 1; + } else { + degType = -2; // finish search + } + } + } + int split = -1; + if(jointNumbers == 1) { + split = lastJoin; + } + if(northSplit != -1 && northSplit < all.size() -1) { + split = northSplit; + } + if(eastSplit != -1 && eastSplit < all.size() -1) { + split = eastSplit; + } + if(degSplit != -1 && degSplit < all.size() -1) { + split = degSplit; + } + + if(split != -1) { + double lat = parse1Coordinate(all, 0, split); + double lon = parse1Coordinate(all, split, all.size()); + return new LatLon(lat, lon); + } + if(d.size() == 2) { + return new LatLon(d.get(0), d.get(1)); + } + // simple url case + if (s.contains("://")) { + double lat = 0; + double lon = 0; + boolean only2decimals = true; + for (int i = 0; i < d.size(); i++) { + if (d.get(i).doubleValue() != d.get(i).intValue()) { + if (lat == 0) { + lat = d.get(i); + } else if (lon == 0) { + lon = d.get(i); + } else { + only2decimals = false; + } + } + } + if (lat != 0 && lon != 0 && only2decimals) { + return new LatLon(lat, lon); + } + } + // split by equal number of digits + if (d.size() > 2 && d.size() % 2 == 0) { + int ind = d.size() / 2 + 1; + int splitEq = -1; + for (int i = 0; i < all.size(); i++) { + if(all.get(i) instanceof Double) { + ind --; + } + if(ind == 0) { + splitEq = i; + break; + } + } + if (splitEq != -1) { + double lat = parse1Coordinate(all, 0, splitEq); + double lon = parse1Coordinate(all, splitEq, all.size()); + return new LatLon(lat, lon); + } + } + return null; + + } + + public double parse1Coordinate(List all, int begin, int end) { + boolean neg = false; + double d = 0; + int type = 0; // degree - 0, minutes - 1, seconds = 2 + Double prevDouble = null; + for(int i = begin; i <= end; i++) { + Object o = i == end ? "" : all.get(i); + if(o.equals("S") || o.equals("W")) { + neg = !neg; + } + if (prevDouble != null) { + if(o.equals("°")) { + type = 0; + } else if(o.equals("′") /*o.equals("'")*/) { + // ' can be used as delimeter ignore it + type = 1; + } else if(o.equals("\"") || o.equals("″")) { + type = 2; + } + if (type == 0) { + double ld = prevDouble.doubleValue(); + if(ld < 0) { + ld = -ld; + neg = true; + } + d += ld; + } else if (type == 1) { + d += prevDouble.doubleValue() / 60.f; + } else /*if (type == 1) */ { + d += prevDouble.doubleValue() / 3600.f; + } + type++; + } + if(o instanceof Double) { + prevDouble = (Double) o; + } else { + prevDouble = null; + } + } + if(neg) { + d = -d; + } + return d; + } + + private void splitObjects(String s, List d, List all, List strings) { + boolean digit = false; + int word = -1; + for(int i = 0; i <= s.length(); i++) { + char ch = i == s.length() ? ' ' : s.charAt(i); + boolean dg = Character.isDigit(ch); + boolean nonwh = ch != ',' && ch != ' ' && ch != ';'; + if (ch == '.' || dg || ch == '-' ) { + if(!digit) { + if(word != -1) { + all.add(s.substring(word, i)); + strings.add(s.substring(word, i)); + } + digit = true; + word = i; + } else { + if(word == -1) { + word = i; + } + // if digit + // continue + } + } else { + if(digit){ + try { + double dl = Double.parseDouble(s.substring(word, i)); + d.add(dl); + all.add(dl); + strings.add(s.substring(word, i)); + digit = false; + word = -1; + } catch (NumberFormatException e) { + } + } + if(nonwh) { + if(!Character.isLetter(ch)) { + if(word != -1) { + all.add(s.substring(word, i)); + strings.add(s.substring(word, i)); + } + all.add(s.substring(i, i + 1)); + strings.add(s.substring(i, i +1)); + word = -1; + } else if(word == -1) { + word = i; + } + } else { + if(word != -1) { + all.add(s.substring(word, i)); + strings.add(s.substring(word, i)); + } + word = -1; + } + } + } + } + + private void parseLocation2(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + String lw = phrase.getLastWord(); + SearchWord sw = phrase.getLastSelectedWord(); + LatLon l = null; + if (sw != null && sw.getType() == ObjectType.UNKNOWN_NAME_FILTER) { + l = parseLocation(sw.getWord() + ", " + lw); + } + if (l == null) { + l = parseLocation(lw); + } + if (l != null) { + SearchResult sp = new SearchResult(phrase); + sp.priority = SEARCH_LOCATION_PRIORITY; + sp.object = sp.location = l; + sp.localeName = ((float) sp.location.getLatitude()) + ", " + ((float) sp.location.getLongitude()); + sp.objectType = ObjectType.LOCATION; + sp.wordsSpan = 2; + resultMatcher.publish(sp); + } else if (phrase.isNoSelectedType()) { + // SearchResult sp = new SearchResult(phrase); + // sp.priority = SEARCH_LOCATION_PRIORITY; + // sp.object = sp.location = new LatLon(dd, 0); + // sp.localeName = ((float) sp.location.getLatitude()) + ", "; + // sp.objectType = ObjectType.PARTIAL_LOCATION; + // resultMatcher.publish(sp); + } + } + private void parseLocation(SearchPhrase phrase, SearchResultMatcher resultMatcher) { String lw = phrase.getLastWord(); SearchWord sw = phrase.getLastSelectedWord(); @@ -806,7 +1103,7 @@ public class SearchCoreFactory { } if(!Double.isNaN(pd)) { SearchResult sp = new SearchResult(phrase); - sp.priority = 0; + sp.priority = SEARCH_LOCATION_PRIORITY; sp.object = sp.location = new LatLon(pd, dd); sp.localeName = ((float)sp.location.getLatitude()) +", " + ((float) sp.location.getLongitude()); sp.objectType = ObjectType.LOCATION; @@ -814,7 +1111,7 @@ public class SearchCoreFactory { resultMatcher.publish(sp); } else if (phrase.isNoSelectedType()) { SearchResult sp = new SearchResult(phrase); - sp.priority = 0; + sp.priority = SEARCH_LOCATION_PRIORITY; sp.object = sp.location = new LatLon(dd, 0); sp.localeName = ((float) sp.location.getLatitude()) + ", "; sp.objectType = ObjectType.PARTIAL_LOCATION; @@ -824,7 +1121,7 @@ public class SearchCoreFactory { } - private void parseUrl(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + private boolean parseUrl(SearchPhrase phrase, SearchResultMatcher resultMatcher) { String text = phrase.getLastWord(); GeoParsedPoint pnt = GeoPointParserUtil.parse(text); int wordsSpan= 1; @@ -853,24 +1150,15 @@ public class SearchCoreFactory { } sp.objectType = ObjectType.LOCATION; resultMatcher.publish(sp); + return true; } + return false; } @Override public int getSearchPriority(SearchPhrase p) { - return 1; + return SEARCH_LOCATION_PRIORITY; } } - public static void main(String[] args) throws IOException { - testSearchLocationAndUrlAPI(new SearchLocationAndUrlAPI()); - } - - private static void testSearchLocationAndUrlAPI(SearchLocationAndUrlAPI api) throws IOException { - SearchResultMatcher srm = new SearchResultMatcher(null, 0, null, 10); - api.search(new SearchPhrase(null).generateNewPhrase("17R 419230 2714967", null), srm); - System.out.println(srm.getRequestResults()); - - } - } diff --git a/OsmAnd-java/src/net/osmand/search/core/SearchPhrase.java b/OsmAnd-java/src/net/osmand/search/core/SearchPhrase.java index ccd2febb24..33928cc96d 100644 --- a/OsmAnd-java/src/net/osmand/search/core/SearchPhrase.java +++ b/OsmAnd-java/src/net/osmand/search/core/SearchPhrase.java @@ -383,6 +383,6 @@ public class SearchPhrase { } public int getRadiusSearch(int meters) { - return getRadiusLevel() * meters; + return (1 << (getRadiusLevel() - 1)) * meters; } } diff --git a/OsmAnd-java/test/java/net/osmand/search/LocationSearchTest.java b/OsmAnd-java/test/java/net/osmand/search/LocationSearchTest.java index 523ad201f5..5394b2b77a 100644 --- a/OsmAnd-java/test/java/net/osmand/search/LocationSearchTest.java +++ b/OsmAnd-java/test/java/net/osmand/search/LocationSearchTest.java @@ -17,8 +17,8 @@ public class LocationSearchTest { SearchResultMatcher srm = new SearchUICore.SearchResultMatcher(null, 0, null, 100); new SearchCoreFactory.SearchLocationAndUrlAPI(). search(new SearchPhrase(null).generateNewPhrase(string, null), srm); - Assert.assertEquals(srm.getRequestResults().size(), 1); - Assert.assertEquals(srm.getRequestResults().get(0).location, latLon); + Assert.assertEquals(1, srm.getRequestResults().size()); + Assert.assertEquals(latLon, srm.getRequestResults().get(0).location); } @Test @@ -33,33 +33,51 @@ public class LocationSearchTest { search("5.445,3.523", new LatLon(5.445, 3.523)); search("5:1:1,3:1", new LatLon(5 + 1/60f + 1/3600f, 3 + 1/60f)); } - + + @Test + public void testUTMSearch() throws IOException { + search("17N6734294749123", new LatLon(42.875017, -78.87659050764749)); + search("17 N 673429 4749123", new LatLon(42.875017, -78.87659050764749)); + } + @Test public void testBasicSpaceSearch() throws IOException { search("5.0 3.0", new LatLon(5, 3)); + search("-5.0 -3.0", new LatLon(-5, -3)); + search("-45.5 3.0S", new LatLon(-45.5, -3)); + search("45.5S 3.0 W", new LatLon(-45.5, -3)); search("5.445 3.523", new LatLon(5.445, 3.523)); search("5:1:1 3:1", new LatLon(5 + 1/60f + 1/3600f, 3 + 1/60f)); + search("5:1#1 3#1", new LatLon(5 + 1/60f + 1/3600f, 3 + 1/60f)); + search("5'1'1 3'1", new LatLon(5 + 1/60f + 1/3600f, 3 + 1/60f)); + search("5#1#1 3#1", new LatLon(5 + 1/60f + 1/3600f, 3 + 1/60f)); } - // TODO 17R 419230 2714967 -// 17N6734294749123 -// 45° 30'30"W -// -45 -// 45 W -// 45 S -// 45.50W -// 45.50S -// W45 -// S45 -// 45 30.5W -// 44 30.5S -// 45 30 30 W -// 45 30 30 N -// -45 30 30 -// 45 30 30 -// 45 30.50W -// 45 30.50 - + @Test + public void testSimpleURLSearch() throws IOException { + search("ftp://simpleurl?lat=34.23&lon=-53.2&z=15", new LatLon(34.23, -53.2)); + search("ftp://simpleurl?z=15&lat=34.23&lon=-53.2", new LatLon(34.23, -53.2)); + } + + @Test + public void testAdvancedSpaceSearch() throws IOException { + search("5 30 30 N 4 30 W", new LatLon(5.5 + 30 / 3600f, -4.5)); + search("5 30 -4 30", new LatLon(5.5, -4.5)); + search("S 5 30 4 30 W", new LatLon(-5.5, -4.5)); + search("S5.4232 4.30W", new LatLon(-5.4232, -4.3)); + search("S5.4232 W4.30", new LatLon(-5.4232, -4.3)); + search("5.4232, W4.30", new LatLon(5.4232, -4.3)); + search("5.4232N, 45 30.5W", new LatLon(5.4232, -(45 + 30.5 / 60f))); + } + + @Test + public void testArcgisSpaceSearch() throws IOException { + search("43°38′33.24″N 79°23′13.7″W", new LatLon(43 + 38/60f + 33.24/3600f,-(79 + 23/60f + 13.7/3600f))); + search("45° 30'30\"W 3.0", new LatLon(45 + 0.5 + 1 / 120f, -3)); + search("43°S 79°23′13.7″W", new LatLon(-43,-(79 + 23/60f + 13.7/3600f))); + search("43° 79°23′13.7″E", new LatLon(43,79 + 23/60f + 13.7/3600f)); + search("43°38′ 79°23′13.7″E", new LatLon(43 + 38/60f,79 + 23/60f + 13.7/3600f)); + }