/**
* This file is hereby placed into the Public Domain. This means anyone is
* free to do whatever they wish with this file.
*/
package mil.nga.giat.data.elasticsearch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.davidmoten.geo.GeoHash;
import com.github.davidmoten.geo.LatLong;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
/**
* Utilities for parsing Elasticsearch document source and field content to
* extract values and create geometries.
*
*/
public class ElasticParserUtil {
private static final Pattern GEO_POINT_PATTERN;
static {
GEO_POINT_PATTERN = Pattern.compile("(-*\\d*\\.*\\d*?),(-*\\d*\\.*\\d*?)");
}
private final GeometryFactory geometryFactory;
public ElasticParserUtil() {
this.geometryFactory = new GeometryFactory();
}
/**
* Create point geometry given geo_point or geo_shape definition. GeoPoint
* can be defined by string, geohash, coordinate array or properties map.
* GeoShape is defined by properties map.
* @param obj GeoPoint or GeoShape definition
* @return Geometry
*/
public Geometry createGeometry(Object obj) {
final Geometry geometry;
if (obj instanceof String) {
// geo_point by string
final Matcher listMatcher = GEO_POINT_PATTERN.matcher((String) obj);
if (listMatcher.matches()) {
// coordinates
final double y = Double.valueOf(listMatcher.group(1));
final double x = Double.valueOf(listMatcher.group(2));
geometry = geometryFactory.createPoint(new Coordinate(x,y));
} else {
// try geohash
final LatLong latLon = GeoHash.decodeHash((String) obj);
Coordinate geoPoint = new Coordinate(latLon.getLon(), latLon.getLat());
if (geoPoint != null) {
final double lat = geoPoint.y;
final double lon = geoPoint.x;
geometry = geometryFactory.createPoint(new Coordinate(lon,lat));
} else {
geometry = null;
}
}
} else if (obj instanceof List && ((List<?>) obj).size()==2) {
// geo_point by coordinate array
final List<?> values = (List<?>) obj;
if (Number.class.isAssignableFrom(values.get(0).getClass())) {
final double x = ((Number) values.get(0)).doubleValue();
final double y = ((Number) values.get(1)).doubleValue();
geometry = geometryFactory.createPoint(new Coordinate(x,y));
} else if (values.get(0) instanceof String) {
final double x = Double.valueOf((String) values.get(0));
final double y = Double.valueOf((String) values.get(1));
geometry = geometryFactory.createPoint(new Coordinate(x,y));
} else {
geometry = null;
}
} else if (obj instanceof Map) {
// geo_shape or geo_point by properties
geometry = createGeometry((Map<String, Object>) obj);
} else {
geometry = null;
}
return geometry;
}
/**
* Create geometry given property map defining geo_shape type and coordinates
* or geo_point lat and lon.
* @param properties Properties
* @return Geometry
*/
public Geometry createGeometry(final Map<String, Object> properties) {
final Geometry geometry;
switch (String.valueOf(properties.get("type")).toUpperCase()) {
case "POINT": {
final List posList;
posList = (List) properties.get("coordinates");
final Coordinate coordinate = createCoordinate(posList);
geometry = geometryFactory.createPoint(coordinate);
break;
} case "LINESTRING": {
final List<List> posList;
posList = (List) properties.get("coordinates");
final Coordinate[] coordinates = createCoordinates(posList);
geometry = geometryFactory.createLineString(coordinates);
break;
} case "POLYGON": {
final List<List<List>> posList;
posList = (List) properties.get("coordinates");
geometry = createPolygon(posList);
break;
} case "MULTIPOINT": {
final List<List> posList;
posList = (List) properties.get("coordinates");
final Coordinate[] coordinates = createCoordinates(posList);
geometry = geometryFactory.createMultiPoint(coordinates);
break;
} case "MULTILINESTRING": {
final List<List<List>> posList;
posList = (List) properties.get("coordinates");
final LineString[] lineStrings = new LineString[posList.size()];
for (int i=0; i<posList.size(); i++) {
final Coordinate[] coordinates = createCoordinates(posList.get(i));
lineStrings[i] = geometryFactory.createLineString(coordinates);
}
geometry = geometryFactory.createMultiLineString(lineStrings);
break;
} case "MULTIPOLYGON": {
final List<List<List<List>>> posList;
posList = (List) properties.get("coordinates");
final Polygon[] polygons = new Polygon[posList.size()];
for (int i=0; i<posList.size(); i++) {
polygons[i] = createPolygon(posList.get(i));
}
geometry = geometryFactory.createMultiPolygon(polygons);
break;
} case "GEOMETRYCOLLECTION": {
final List<Map<String,Object>> list;
list = (List) properties.get("geometries");
final Geometry[] geometries = new Geometry[list.size()];
for (int i=0; i<geometries.length; i++) {
geometries[i] = createGeometry(list.get(i));
}
geometry = geometryFactory.createGeometryCollection(geometries);
break;
} case "ENVELOPE": {
final List<List> posList;
posList = (List) properties.get("coordinates");
final Coordinate[] coords = createCoordinates(posList);
final Envelope envelope = new Envelope(coords[0], coords[1]);
geometry = geometryFactory.toGeometry(envelope);
break;
} default:
// check if this is a geo_point
final Object latObj = properties.get("lat");
final Object lonObj = properties.get("lon");
if (latObj != null && lonObj != null) {
final Double lat;
if (latObj instanceof Number) {
lat = ((Number)latObj).doubleValue();
} else if (latObj instanceof String) {
lat = new Double((String)latObj);
} else {
lat = null;
}
final Double lon;
if (lonObj instanceof Number) {
lon = ((Number)lonObj).doubleValue();
} else if (lonObj instanceof String) {
lon = new Double((String)lonObj);
} else {
lon = null;
}
if (lat != null && lon != null) {
geometry = geometryFactory.createPoint(new Coordinate(lon,lat));
} else {
geometry = null;
}
} else {
geometry = null;
}
break;
}
return geometry;
}
private Polygon createPolygon(final List<List<List>> posList) {
final Coordinate[] shellCoordinates = createCoordinates(posList.get(0));
final LinearRing shell = geometryFactory.createLinearRing(shellCoordinates);
final LinearRing[] holes = new LinearRing[posList.size()-1];
for (int i=1; i<posList.size(); i++) {
final Coordinate[] coordinates = createCoordinates(posList.get(i));
holes[i-1] = geometryFactory.createLinearRing(coordinates);
}
return geometryFactory.createPolygon(shell, holes);
}
private Coordinate[] createCoordinates(final List<List> posList) {
final Coordinate[] coordinates = new Coordinate[posList.size()];
for (int i=0; i<posList.size(); i++) {
coordinates[i] = createCoordinate(posList.get(i));
}
return coordinates;
}
private Coordinate createCoordinate(final List posList) {
final double x;
final double y;
if (Number.class.isAssignableFrom(posList.get(0).getClass())) {
x = ((Number) posList.get(0)).doubleValue();
y = ((Number) posList.get(1)).doubleValue();
} else {
x = Double.valueOf(posList.get(0).toString());
y = Double.valueOf(posList.get(1).toString());
}
return new Coordinate(x,y);
}
/**
* Read field from document source.
* @param source Source
* @param name Field to extract.
* @return List of values or empty list if not found
*/
public List<Object> readField(Map<String, Object> source, String name) {
final List<String> keys = Arrays.asList(name.split("\\."));
List<Object> values = new ArrayList<>();
if (!keys.isEmpty()) {
readField(source.get(keys.get(0)), keys.subList(1, keys.size()), values);
}
final List<Object> result;
if (!values.isEmpty()) {
result = values;
} else {
result = null;
}
return result;
}
private void readField(Object entry, List<String> keys, List<Object> values) {
if (entry == null) {
} else if (List.class.isAssignableFrom(entry.getClass())) {
for (Object object : (List<?>) entry) {
readField(object, keys, values);
}
} else if (!keys.isEmpty() && Map.class.isAssignableFrom(entry.getClass())) {
final Object nextEntry = ((Map<?,?>) entry).get(keys.get(0));
final List<String> newKeys = keys.subList(1, keys.size());
readField(nextEntry, newKeys, values);
} else if (entry != null) {
values.add(entry);
}
}
public static boolean isGeoPointFeature(Map map) {
boolean result = false;
if (map.size() == 2 && map.containsKey("coordinates")) {
try {
result = "geo_point".equals(((Map) map.get("coordinates")).get("type"));
} catch (Exception e) { }
}
return result;
}
}