diff options
Diffstat (limited to 'container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java')
-rw-r--r-- | container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java new file mode 100644 index 00000000000..03e212fc854 --- /dev/null +++ b/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java @@ -0,0 +1,174 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.prelude.searcher; + +import com.yahoo.component.chain.dependencies.After; +import com.yahoo.component.chain.dependencies.Before; +import com.yahoo.component.chain.dependencies.Provides; +import com.yahoo.geo.DegreesParser; +import com.yahoo.geo.BoundingBoxParser; +import com.yahoo.yolean.Exceptions; +import com.yahoo.search.Query; +import com.yahoo.search.Result; +import com.yahoo.search.Searcher; +import com.yahoo.processing.request.CompoundName; +import com.yahoo.search.result.ErrorMessage; +import com.yahoo.search.searchchain.Execution; +import com.yahoo.search.searchchain.PhaseNames; +import com.yahoo.prelude.Location; + +/** + * A searcher converting human-readable position parameters + * into internal format. + * <br> + * Reads the following query properties: + * <ul> + * <li> pos.ll (geographical latitude and longitude) + * <li> pos.xy (alternate to pos.ll - direct x and y in internal units) + * <li> pos.radius (distance in one of: + * internal units (no suffix), meter (m), kilometer (km) or miles (mi) + * </ul> + * + * @author Arne J + */ +@After(PhaseNames.RAW_QUERY) +@Before(PhaseNames.TRANSFORMED_QUERY) +@Provides(PosSearcher.POSITION_PARSING) +public class PosSearcher extends Searcher { + public static final String POSITION_PARSING = "PositionParsing"; + + private static final CompoundName posBb = new CompoundName("pos.bb"); + private static final CompoundName posLl = new CompoundName("pos.ll"); + private static final CompoundName posXy = new CompoundName("pos.xy"); + private static final CompoundName posAttributeName = new CompoundName("pos.attribute"); + private static final CompoundName posRadius = new CompoundName("pos.radius"); + private static final CompoundName posUnits = new CompoundName("pos.units"); + + // according to wikipedia: + // Earth's equatorial radius = 6378137 meter - not used + // meters per mile = 1609.344 + // 180 degrees equals one half diameter equals PI*r + // Earth's polar radius = 6356752 meter + + public final static double km2deg = 1000.000 * 180.0 / (Math.PI * 6356752.0); + public final static double mi2deg = 1609.344 * 180.0 / (Math.PI * 6356752.0); + + + public Result search(Query query, Execution execution) { + String bb = query.properties().getString(posBb); + String ll = query.properties().getString(posLl); + String xy = query.properties().getString(posXy); + + if (ll == null && xy == null && bb == null) { + return execution.search(query); // Nothing to do + } + if (query.getRanking().getLocation() != null) { + // this searcher is a NOP if there is already a location + // in the query + query.trace("query already has a location set, not processing 'pos' params", false, 1); + return execution.search(query); + } + + Location loc = new Location(); + loc.setDimensions(2); + String posAttribute = query.properties().getString(posAttributeName); + loc.setAttribute(posAttribute); + + try { + if (ll == null && xy == null && bb != null) { + parseBoundingBox(bb, loc); + } else { + if (ll != null && xy != null) { + throw new IllegalArgumentException("Cannot handle both lat/long and xy coords at the same time"); + } + if (ll != null) { + handleGeoCircle(query, ll, loc); + } + if (xy != null) { + handleXyCircle(query, xy, loc); + } + if (bb != null) { + parseBoundingBox(bb, loc); + } + } + } + catch (IllegalArgumentException e) { + // System.err.println("error: "+e); + return new Result(query, ErrorMessage.createInvalidQueryParameter( + "Error in pos parameters: " + Exceptions.toMessageString(e))); + } + // and finally: + query.getRanking().setLocation(loc); + return execution.search(query); + } + + private void handleGeoCircle(Query query, String ll, Location target) { + double ewCoord = 0; + double nsCoord = 0; + try { + DegreesParser parsed = new DegreesParser(ll); + ewCoord = parsed.longitude; + nsCoord = parsed.latitude; + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unable to parse lat/long string '" +ll + "'", e); + } + String radius = query.properties().getString(posRadius); + double radiusdegrees = 0.0; + + if (radius == null) { + radiusdegrees = 50.0 * km2deg; + } else if (radius.endsWith("km")) { + double radiuskm = Double.valueOf(radius.substring(0, radius.length()-2)); + radiusdegrees = radiuskm * km2deg; + } else if (radius.endsWith("m")) { + double radiusm = Double.valueOf(radius.substring(0, radius.length()-1)); + radiusdegrees = radiusm * km2deg / 1000.0; + } else if (radius.endsWith("mi")) { + double radiusmiles = Double.valueOf(radius.substring(0, radius.length()-2)); + radiusdegrees = radiusmiles * mi2deg; + } else { + radiusdegrees = Integer.parseInt(radius) * 0.000001; + } + target.setGeoCircle(nsCoord, ewCoord, radiusdegrees); + } + + + private void handleXyCircle(Query query, String xy, Location target) { + int xcoord = 0; + int ycoord = 0; + // parse xy + int semipos = xy.indexOf(';'); + if (semipos > 0 && semipos < xy.length()) { + xcoord = Integer.parseInt(xy.substring(0, semipos)); + ycoord = Integer.parseInt(xy.substring(semipos+1, xy.length())); + } else { + throw new IllegalArgumentException("pos.xy must be in the format 'digits;digits' but was: '"+xy+"'"); + } + String radius = query.properties().getString(posRadius); + int radiusUnits = 0; + if (radius == null) { + radiusUnits = 5000; + } else if (radius.endsWith("km")) { + double radiuskm = Double.valueOf(radius.substring(0, radius.length()-2)); + double radiusdegrees = radiuskm * km2deg; + radiusUnits = (int)(radiusdegrees * 1000000); + } else if (radius.endsWith("m")) { + double radiusm = Double.valueOf(radius.substring(0, radius.length()-1)); + double radiusdegrees = radiusm * km2deg / 1000.0; + radiusUnits = (int)(radiusdegrees * 1000000); + } else if (radius.endsWith("mi")) { + double radiusmiles = Double.valueOf(radius.substring(0, radius.length()-2)); + double radiusdegrees = radiusmiles * mi2deg; + radiusUnits = (int)(radiusdegrees * 1000000); + } else { + radiusUnits = Integer.parseInt(radius); + } + target.setXyCircle(xcoord, ycoord, radiusUnits); + } + + + private static void parseBoundingBox(String bb, Location target) { + BoundingBoxParser parser = new BoundingBoxParser(bb); + target.setBoundingBox(parser.n, parser.s, parser.e, parser.w); + } + +} |