/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.common.geo; import org.opensearch.OpenSearchParseException; import org.opensearch.common.xcontent.LoggingDeprecationHandler; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.core.xcontent.MapXContentParser; import org.opensearch.geometry.Geometry; import org.opensearch.geometry.GeometryCollection; import org.opensearch.geometry.Point; import org.opensearch.geometry.utils.GeometryValidator; import org.opensearch.geometry.utils.StandardValidator; import org.opensearch.geometry.utils.WellKnownText; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * An utility class with a geometry parser methods supporting different shape representation formats * * @opensearch.internal */ public final class GeometryParser { private final GeoJson geoJsonParser; private final WellKnownText wellKnownTextParser; private final boolean ignoreZValue; public GeometryParser(boolean rightOrientation, boolean coerce, boolean ignoreZValue) { GeometryValidator validator = new StandardValidator(ignoreZValue); geoJsonParser = new GeoJson(rightOrientation, coerce, validator); wellKnownTextParser = new WellKnownText(coerce, validator); this.ignoreZValue = ignoreZValue; } /** * Parses supplied XContent into Geometry */ public Geometry parse(XContentParser parser) throws IOException, ParseException { return geometryFormat(parser).fromXContent(parser); } /** * Returns a geometry format object that can parse and then serialize the object back to the same format. */ public GeometryFormat geometryFormat(String format) { if (format.equals(GeoJsonGeometryFormat.NAME)) { return new GeoJsonGeometryFormat(geoJsonParser); } else if (format.equals(WKTGeometryFormat.NAME)) { return new WKTGeometryFormat(wellKnownTextParser); } else { throw new IllegalArgumentException("Unrecognized geometry format [" + format + "]."); } } /** * Returns a geometry format object that can parse and then serialize the object back to the same format. * This method automatically recognizes the format by examining the provided {@link XContentParser}. */ public GeometryFormat geometryFormat(XContentParser parser) { if (parser.currentToken() == XContentParser.Token.START_OBJECT) { return new GeoJsonGeometryFormat(geoJsonParser); } else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) { return new WKTGeometryFormat(wellKnownTextParser); } else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { // We don't know the format of the original geometry - so going with default return new GeoJsonGeometryFormat(geoJsonParser); } else { throw new OpenSearchParseException("shape must be an object consisting of type and coordinates"); } } /** * Parses the value as a {@link Geometry}. The following types of values are supported: *

* Object: has to contain either lat and lon or geohash fields *

* String: expected to be in "latitude, longitude" format, a geohash or WKT *

* Array: two or more elements, the first element is longitude, the second is latitude, the rest is ignored if ignoreZValue is true *

* Json structure: valid geojson definition */ public Geometry parseGeometry(Object value) throws OpenSearchParseException { if (value instanceof List) { List values = (List) value; if (values.size() == 2 && values.get(0) instanceof Number) { GeoPoint point = GeoUtils.parseGeoPoint(values, ignoreZValue); return new Point(point.lon(), point.lat()); } else { List geometries = new ArrayList<>(values.size()); for (Object object : values) { geometries.add(parseGeometry(object)); } return new GeometryCollection<>(geometries); } } try ( XContentParser parser = new MapXContentParser( NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, Collections.singletonMap("null_value", value), null ) ) { parser.nextToken(); // start object parser.nextToken(); // field name parser.nextToken(); // field value if (isPoint(value)) { GeoPoint point = GeoUtils.parseGeoPoint(parser, new GeoPoint(), ignoreZValue); return new Point(point.lon(), point.lat()); } else { return parse(parser); } } catch (IOException | ParseException ex) { throw new OpenSearchParseException("error parsing geometry ", ex); } } private boolean isPoint(Object value) { // can we do this better? if (value instanceof Map) { Map map = (Map) value; return map.containsKey("lat") && map.containsKey("lon"); } else if (value instanceof String) { String string = (String) value; return Character.isDigit(string.charAt(0)) || string.indexOf('(') == -1; } return false; } }