public static GeoDistanceSortBuilder randomGeoDistanceSortBuilder() {
    String fieldName = randomAsciiOfLengthBetween(1, 10);
    GeoDistanceSortBuilder result = null;

    int id = randomIntBetween(0, 2);
    switch (id) {
      case 0:
        int count = randomIntBetween(1, 10);
        String[] geohashes = new String[count];
        for (int i = 0; i < count; i++) {
          geohashes[i] = RandomGeoGenerator.randomPoint(random()).geohash();
        }

        result = new GeoDistanceSortBuilder(fieldName, geohashes);
        break;
      case 1:
        GeoPoint pt = RandomGeoGenerator.randomPoint(random());
        result = new GeoDistanceSortBuilder(fieldName, pt.getLat(), pt.getLon());
        break;
      case 2:
        result = new GeoDistanceSortBuilder(fieldName, points(new GeoPoint[0]));
        break;
      default:
        throw new IllegalStateException("one of three geo initialisation strategies must be used");
    }
    if (randomBoolean()) {
      result.geoDistance(geoDistance(result.geoDistance()));
    }
    if (randomBoolean()) {
      result.unit(randomValueOtherThan(result.unit(), () -> randomFrom(DistanceUnit.values())));
    }
    if (randomBoolean()) {
      result.order(randomFrom(SortOrder.values()));
    }
    if (randomBoolean()) {
      result.sortMode(randomValueOtherThan(SortMode.SUM, () -> randomFrom(SortMode.values())));
    }
    if (randomBoolean()) {
      result.setNestedFilter(randomNestedFilter());
    }
    if (randomBoolean()) {
      result.setNestedPath(
          randomValueOtherThan(result.getNestedPath(), () -> randomAsciiOfLengthBetween(1, 10)));
    }
    if (randomBoolean()) {
      result.validation(
          randomValueOtherThan(
              result.validation(), () -> randomFrom(GeoValidationMethod.values())));
    }

    return result;
  }
 @Override
 public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext)
     throws MapperParsingException {
   Builder builder = geoShapeField(name);
   for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
       iterator.hasNext(); ) {
     Map.Entry<String, Object> entry = iterator.next();
     String fieldName = Strings.toUnderscoreCase(entry.getKey());
     Object fieldNode = entry.getValue();
     if (Names.TREE.equals(fieldName)) {
       builder.fieldType().setTree(fieldNode.toString());
       iterator.remove();
     } else if (Names.TREE_LEVELS.equals(fieldName)) {
       builder.fieldType().setTreeLevels(Integer.parseInt(fieldNode.toString()));
       iterator.remove();
     } else if (Names.TREE_PRESISION.equals(fieldName)) {
       builder
           .fieldType()
           .setPrecisionInMeters(
               DistanceUnit.parse(
                   fieldNode.toString(), DistanceUnit.DEFAULT, DistanceUnit.DEFAULT));
       iterator.remove();
     } else if (Names.DISTANCE_ERROR_PCT.equals(fieldName)) {
       builder.fieldType().setDistanceErrorPct(Double.parseDouble(fieldNode.toString()));
       iterator.remove();
     } else if (Names.ORIENTATION.equals(fieldName)) {
       builder
           .fieldType()
           .setOrientation(ShapeBuilder.orientationFromString(fieldNode.toString()));
       iterator.remove();
     } else if (Names.STRATEGY.equals(fieldName)) {
       builder.fieldType().setStrategyName(fieldNode.toString());
       iterator.remove();
     } else if (Names.COERCE.equals(fieldName)) {
       builder.coerce(nodeBooleanValue(fieldNode));
       iterator.remove();
     } else if (Names.STRATEGY_POINTS_ONLY.equals(fieldName)
         && builder.fieldType().strategyName.equals(SpatialStrategy.TERM.getStrategyName())
             == false) {
       builder.fieldType().setPointsOnly(XContentMapValues.nodeBooleanValue(fieldNode));
       iterator.remove();
     }
   }
   return builder;
 }
 @Override
 protected GeoDistanceSortBuilder mutate(GeoDistanceSortBuilder original) throws IOException {
   GeoDistanceSortBuilder result = new GeoDistanceSortBuilder(original);
   int parameter = randomIntBetween(0, 8);
   switch (parameter) {
     case 0:
       while (Arrays.deepEquals(original.points(), result.points())) {
         GeoPoint pt = RandomGeoGenerator.randomPoint(random());
         result.point(pt.getLat(), pt.getLon());
       }
       break;
     case 1:
       result.points(points(original.points()));
       break;
     case 2:
       result.geoDistance(geoDistance(original.geoDistance()));
       break;
     case 3:
       result.unit(randomValueOtherThan(result.unit(), () -> randomFrom(DistanceUnit.values())));
       break;
     case 4:
       result.order(randomValueOtherThan(original.order(), () -> randomFrom(SortOrder.values())));
       break;
     case 5:
       result.sortMode(
           randomValueOtherThanMany(
               Arrays.asList(SortMode.SUM, result.sortMode())::contains,
               () -> randomFrom(SortMode.values())));
       break;
     case 6:
       result.setNestedFilter(
           randomValueOtherThan(original.getNestedFilter(), () -> randomNestedFilter()));
       break;
     case 7:
       result.setNestedPath(
           randomValueOtherThan(result.getNestedPath(), () -> randomAsciiOfLengthBetween(1, 10)));
       break;
     case 8:
       result.validation(
           randomValueOtherThan(
               result.validation(), () -> randomFrom(GeoValidationMethod.values())));
       break;
   }
   return result;
 }
  @Override
  protected GeoDistanceRangeQueryBuilder doCreateTestQueryBuilder() {
    GeoDistanceRangeQueryBuilder builder;
    GeoPoint randomPoint = RandomGeoGenerator.randomPointIn(random(), -180.0, -89.9, 180.0, 89.9);
    if (randomBoolean()) {
      builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME, randomPoint.geohash());
    } else {
      if (randomBoolean()) {
        builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME, randomPoint);
      } else {
        builder =
            new GeoDistanceRangeQueryBuilder(
                GEO_POINT_FIELD_NAME, randomPoint.lat(), randomPoint.lon());
      }
    }
    GeoPoint point = builder.point();
    final double maxRadius = GeoUtils.maxRadialDistanceMeters(point.lat(), point.lon());
    final int fromValueMeters = randomInt((int) (maxRadius * 0.5));
    final int toValueMeters = randomIntBetween(fromValueMeters + 1, (int) maxRadius);
    DistanceUnit fromToUnits = randomFrom(DistanceUnit.values());
    final String fromToUnitsStr = fromToUnits.toString();
    final double fromValue =
        DistanceUnit.convert(fromValueMeters, DistanceUnit.DEFAULT, fromToUnits);
    final double toValue = DistanceUnit.convert(toValueMeters, DistanceUnit.DEFAULT, fromToUnits);

    if (randomBoolean()) {
      int branch = randomInt(2);
      fromToUnits = DistanceUnit.DEFAULT;
      switch (branch) {
        case 0:
          builder.from(fromValueMeters);
          break;
        case 1:
          builder.to(toValueMeters);
          break;
        case 2:
          builder.from(fromValueMeters);
          builder.to(toValueMeters);
          break;
      }
    } else {
      int branch = randomInt(2);
      switch (branch) {
        case 0:
          builder.from(fromValue + fromToUnitsStr);
          break;
        case 1:
          builder.to(toValue + fromToUnitsStr);
          break;
        case 2:
          builder.from(fromValue + fromToUnitsStr);
          builder.to(toValue + fromToUnitsStr);
          break;
      }
    }
    if (randomBoolean()) {
      builder.includeLower(randomBoolean());
    }
    if (randomBoolean()) {
      builder.includeUpper(randomBoolean());
    }
    if (randomBoolean()) {
      builder.geoDistance(randomFrom(GeoDistance.values()));
    }
    builder.unit(fromToUnits);
    if (randomBoolean()) {
      builder.setValidationMethod(randomFrom(GeoValidationMethod.values()));
    }

    if (randomBoolean()) {
      builder.ignoreUnmapped(randomBoolean());
    }
    return builder;
  }
  public void testDuelGeoPoints() throws Exception {
    final String mapping =
        XContentFactory.jsonBuilder()
            .startObject()
            .startObject("type")
            .startObject("properties")
            .startObject("geopoint")
            .field("type", "geo_point")
            .startObject("fielddata")
            .field("format", "doc_values")
            .endObject()
            .endObject()
            .endObject()
            .endObject()
            .endObject()
            .string();

    final DocumentMapper mapper = mapperService.documentMapperParser().parse(mapping);

    Random random = getRandom();
    int atLeast = scaledRandomIntBetween(1000, 1500);
    int maxValuesPerDoc = randomBoolean() ? 1 : randomIntBetween(2, 40);
    // to test deduplication
    double defaultLat = randomDouble() * 180 - 90;
    double defaultLon = randomDouble() * 360 - 180;
    for (int i = 0; i < atLeast; i++) {
      final int numValues = randomInt(maxValuesPerDoc);
      XContentBuilder doc = XContentFactory.jsonBuilder().startObject().startArray("geopoint");
      for (int j = 0; j < numValues; ++j) {
        if (randomBoolean()) {
          doc.startObject().field("lat", defaultLat).field("lon", defaultLon).endObject();
        } else {
          doc.startObject()
              .field("lat", randomDouble() * 180 - 90)
              .field("lon", randomDouble() * 360 - 180)
              .endObject();
        }
      }
      doc = doc.endArray().endObject();
      final ParsedDocument d = mapper.parse("type", Integer.toString(i), doc.bytes());

      writer.addDocument(d.rootDoc());
      if (random.nextInt(10) == 0) {
        refreshReader();
      }
    }
    AtomicReaderContext context = refreshReader();
    Map<FieldDataType, Type> typeMap = new HashMap<>();
    final Distance precision = new Distance(1, randomFrom(DistanceUnit.values()));
    typeMap.put(
        new FieldDataType("geo_point", ImmutableSettings.builder().put("format", "array")),
        Type.GeoPoint);
    typeMap.put(
        new FieldDataType(
            "geo_point",
            ImmutableSettings.builder().put("format", "compressed").put("precision", precision)),
        Type.GeoPoint);
    typeMap.put(
        new FieldDataType("geo_point", ImmutableSettings.builder().put("format", "doc_values")),
        Type.GeoPoint);

    ArrayList<Entry<FieldDataType, Type>> list = new ArrayList<>(typeMap.entrySet());
    while (!list.isEmpty()) {
      Entry<FieldDataType, Type> left;
      Entry<FieldDataType, Type> right;
      if (list.size() > 1) {
        left = list.remove(random.nextInt(list.size()));
        right = list.remove(random.nextInt(list.size()));
      } else {
        right = left = list.remove(0);
      }
      ifdService.clear();
      IndexGeoPointFieldData leftFieldData =
          getForField(left.getKey(), left.getValue().name().toLowerCase(Locale.ROOT));

      ifdService.clear();
      IndexGeoPointFieldData rightFieldData =
          getForField(right.getKey(), right.getValue().name().toLowerCase(Locale.ROOT));

      duelFieldDataGeoPoint(random, context, leftFieldData, rightFieldData, precision);
      duelFieldDataGeoPoint(random, context, rightFieldData, leftFieldData, precision);

      DirectoryReader perSegment = DirectoryReader.open(writer, true);
      CompositeReaderContext composite = perSegment.getContext();
      List<AtomicReaderContext> leaves = composite.leaves();
      for (AtomicReaderContext atomicReaderContext : leaves) {
        duelFieldDataGeoPoint(
            random, atomicReaderContext, leftFieldData, rightFieldData, precision);
      }
      perSegment.close();
    }
  }
  @Override
  public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    XContentParser.Token token;

    boolean cache = false;
    CacheKeyFilter.Key cacheKey = null;
    String filterName = null;
    String currentFieldName = null;
    double lat = 0;
    double lon = 0;
    String fieldName = null;
    Object vFrom = null;
    Object vTo = null;
    boolean includeLower = true;
    boolean includeUpper = true;
    DistanceUnit unit = DistanceUnit.KILOMETERS; // default unit
    GeoDistance geoDistance = GeoDistance.ARC;
    String optimizeBbox = "memory";
    boolean normalizeLon = true;
    boolean normalizeLat = true;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_ARRAY) {
        token = parser.nextToken();
        lon = parser.doubleValue();
        token = parser.nextToken();
        lat = parser.doubleValue();
        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {}

        fieldName = currentFieldName;
      } else if (token == XContentParser.Token.START_OBJECT) {
        // the json in the format of -> field : { lat : 30, lon : 12 }
        String currentName = parser.currentName();
        fieldName = currentFieldName;
        while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
          if (token == XContentParser.Token.FIELD_NAME) {
            currentName = parser.currentName();
          } else if (token.isValue()) {
            if (currentName.equals(GeoPointFieldMapper.Names.LAT)) {
              lat = parser.doubleValue();
            } else if (currentName.equals(GeoPointFieldMapper.Names.LON)) {
              lon = parser.doubleValue();
            } else if (currentName.equals(GeoPointFieldMapper.Names.GEOHASH)) {
              double[] values = GeoHashUtils.decode(parser.text());
              lat = values[0];
              lon = values[1];
            }
          }
        }
      } else if (token.isValue()) {
        if (currentFieldName.equals("from")) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vFrom = parser.text(); // a String
          } else {
            vFrom = parser.numberValue(); // a Number
          }
        } else if (currentFieldName.equals("to")) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vTo = parser.text(); // a String
          } else {
            vTo = parser.numberValue(); // a Number
          }
        } else if ("include_lower".equals(currentFieldName)
            || "includeLower".equals(currentFieldName)) {
          includeLower = parser.booleanValue();
        } else if ("include_upper".equals(currentFieldName)
            || "includeUpper".equals(currentFieldName)) {
          includeUpper = parser.booleanValue();
        } else if ("gt".equals(currentFieldName)) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vFrom = parser.text(); // a String
          } else {
            vFrom = parser.numberValue(); // a Number
          }
          includeLower = false;
        } else if ("gte".equals(currentFieldName) || "ge".equals(currentFieldName)) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vFrom = parser.text(); // a String
          } else {
            vFrom = parser.numberValue(); // a Number
          }
          includeLower = true;
        } else if ("lt".equals(currentFieldName)) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vTo = parser.text(); // a String
          } else {
            vTo = parser.numberValue(); // a Number
          }
          includeUpper = false;
        } else if ("lte".equals(currentFieldName) || "le".equals(currentFieldName)) {
          if (token == XContentParser.Token.VALUE_NULL) {
          } else if (token == XContentParser.Token.VALUE_STRING) {
            vTo = parser.text(); // a String
          } else {
            vTo = parser.numberValue(); // a Number
          }
          includeUpper = true;
        } else if (currentFieldName.equals("unit")) {
          unit = DistanceUnit.fromString(parser.text());
        } else if (currentFieldName.equals("distance_type")
            || currentFieldName.equals("distanceType")) {
          geoDistance = GeoDistance.fromString(parser.text());
        } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LAT_SUFFIX)) {
          lat = parser.doubleValue();
          fieldName =
              currentFieldName.substring(
                  0, currentFieldName.length() - GeoPointFieldMapper.Names.LAT_SUFFIX.length());
        } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LON_SUFFIX)) {
          lon = parser.doubleValue();
          fieldName =
              currentFieldName.substring(
                  0, currentFieldName.length() - GeoPointFieldMapper.Names.LON_SUFFIX.length());
        } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.GEOHASH_SUFFIX)) {
          double[] values = GeoHashUtils.decode(parser.text());
          lat = values[0];
          lon = values[1];
          fieldName =
              currentFieldName.substring(
                  0, currentFieldName.length() - GeoPointFieldMapper.Names.GEOHASH_SUFFIX.length());
        } else if ("_name".equals(currentFieldName)) {
          filterName = parser.text();
        } else if ("_cache".equals(currentFieldName)) {
          cache = parser.booleanValue();
        } else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
          cacheKey = new CacheKeyFilter.Key(parser.text());
        } else if ("optimize_bbox".equals(currentFieldName)
            || "optimizeBbox".equals(currentFieldName)) {
          optimizeBbox = parser.textOrNull();
        } else if ("normalize".equals(currentFieldName)) {
          normalizeLat = parser.booleanValue();
          normalizeLon = parser.booleanValue();
        } else {
          // assume the value is the actual value
          String value = parser.text();
          int comma = value.indexOf(',');
          if (comma != -1) {
            lat = Double.parseDouble(value.substring(0, comma).trim());
            lon = Double.parseDouble(value.substring(comma + 1).trim());
          } else {
            double[] values = GeoHashUtils.decode(value);
            lat = values[0];
            lon = values[1];
          }
          fieldName = currentFieldName;
        }
      }
    }

    Double from = null;
    Double to = null;
    if (vFrom != null) {
      if (vFrom instanceof Number) {
        from = unit.toMiles(((Number) vFrom).doubleValue());
      } else {
        from = DistanceUnit.parse((String) vFrom, unit, DistanceUnit.MILES);
      }
      from = geoDistance.normalize(from, DistanceUnit.MILES);
    }
    if (vTo != null) {
      if (vTo instanceof Number) {
        to = unit.toMiles(((Number) vTo).doubleValue());
      } else {
        to = DistanceUnit.parse((String) vTo, unit, DistanceUnit.MILES);
      }
      to = geoDistance.normalize(to, DistanceUnit.MILES);
    }

    if (normalizeLat) {
      lat = GeoUtils.normalizeLat(lat);
    }
    if (normalizeLon) {
      lon = GeoUtils.normalizeLon(lon);
    }

    MapperService.SmartNameFieldMappers smartMappers = parseContext.smartFieldMappers(fieldName);
    if (smartMappers == null || !smartMappers.hasMapper()) {
      throw new QueryParsingException(
          parseContext.index(), "failed to find geo_point field [" + fieldName + "]");
    }
    FieldMapper mapper = smartMappers.mapper();
    if (mapper.fieldDataType() != GeoPointFieldDataType.TYPE) {
      throw new QueryParsingException(
          parseContext.index(), "field [" + fieldName + "] is not a geo_point field");
    }
    GeoPointFieldMapper geoMapper = ((GeoPointFieldMapper.GeoStringFieldMapper) mapper).geoMapper();
    fieldName = mapper.names().indexName();

    Filter filter =
        new GeoDistanceRangeFilter(
            lat,
            lon,
            from,
            to,
            includeLower,
            includeUpper,
            geoDistance,
            fieldName,
            geoMapper,
            parseContext.indexCache().fieldData(),
            optimizeBbox);
    if (cache) {
      filter = parseContext.cacheFilter(filter, cacheKey);
    }
    filter = wrapSmartNameFilter(filter, smartMappers, parseContext);
    if (filterName != null) {
      parseContext.addNamedFilter(filterName, filter);
    }
    return filter;
  }
    @Override
    public Builder fromXContent(QueryParseContext parseContext) throws IOException {
      XContentParser parser = parseContext.parser();

      String fieldName = null;
      String geohash = null;
      Integer levels = null;
      Boolean neighbors = null;
      String queryName = null;
      Float boost = null;

      XContentParser.Token token;
      if ((token = parser.currentToken()) != Token.START_OBJECT) {
        throw new ElasticsearchParseException(
            "failed to parse [{}] query. expected an object but found [{}] instead", NAME, token);
      }

      while ((token = parser.nextToken()) != Token.END_OBJECT) {
        if (token == Token.FIELD_NAME) {
          String field = parser.currentName();

          if (parseContext.isDeprecatedSetting(field)) {
            // skip
          } else if (parseContext.parseFieldMatcher().match(field, PRECISION_FIELD)) {
            token = parser.nextToken();
            if (token == Token.VALUE_NUMBER) {
              levels = parser.intValue();
            } else if (token == Token.VALUE_STRING) {
              double meters =
                  DistanceUnit.parse(parser.text(), DistanceUnit.DEFAULT, DistanceUnit.METERS);
              levels = GeoUtils.geoHashLevelsForPrecision(meters);
            }
          } else if (parseContext.parseFieldMatcher().match(field, NEIGHBORS_FIELD)) {
            parser.nextToken();
            neighbors = parser.booleanValue();
          } else if (parseContext
              .parseFieldMatcher()
              .match(field, AbstractQueryBuilder.NAME_FIELD)) {
            parser.nextToken();
            queryName = parser.text();
          } else if (parseContext
              .parseFieldMatcher()
              .match(field, AbstractQueryBuilder.BOOST_FIELD)) {
            parser.nextToken();
            boost = parser.floatValue();
          } else {
            if (fieldName == null) {
              fieldName = field;
              token = parser.nextToken();
              if (token == Token.VALUE_STRING) {
                // A string indicates either a geohash or a
                // lat/lon
                // string
                String location = parser.text();
                if (location.indexOf(",") > 0) {
                  geohash = GeoUtils.parseGeoPoint(parser).geohash();
                } else {
                  geohash = location;
                }
              } else {
                geohash = GeoUtils.parseGeoPoint(parser).geohash();
              }
            } else {
              throw new ParsingException(
                  parser.getTokenLocation(),
                  "["
                      + NAME
                      + "] field name already set to ["
                      + fieldName
                      + "] but found ["
                      + field
                      + "]");
            }
          }
        } else {
          throw new ElasticsearchParseException(
              "failed to parse [{}] query. unexpected token [{}]", NAME, token);
        }
      }
      Builder builder = new Builder(fieldName, geohash);
      if (levels != null) {
        builder.precision(levels);
      }
      if (neighbors != null) {
        builder.neighbors(neighbors);
      }
      if (queryName != null) {
        builder.queryName(queryName);
      }
      if (boost != null) {
        builder.boost(boost);
      }
      return builder;
    }
 public Builder precision(String precision) {
   double meters = DistanceUnit.parse(precision, DistanceUnit.DEFAULT, DistanceUnit.METERS);
   return precision(GeoUtils.geoHashLevelsForPrecision(meters));
 }