|
@@ -0,0 +1,434 @@
|
|
|
|
+package cn.com.taiji.tile.util;
|
|
|
|
+
|
|
|
|
+import com.github.davidmoten.geo.GeoHash;
|
|
|
|
+import com.github.davidmoten.geo.LatLong;
|
|
|
|
+import org.geotools.referencing.GeodeticCalculator;
|
|
|
|
+import org.geotools.referencing.datum.DefaultEllipsoid;
|
|
|
|
+import org.geotools.util.logging.Logging;
|
|
|
|
+import org.locationtech.jts.geom.*;
|
|
|
|
+import org.locationtech.jts.io.ParseException;
|
|
|
|
+import org.locationtech.jts.io.WKTReader;
|
|
|
|
+
|
|
|
|
+import java.awt.geom.Point2D;
|
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
|
+import java.net.URLDecoder;
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
+import java.util.*;
|
|
|
|
+import java.util.logging.Logger;
|
|
|
|
+import java.util.regex.Matcher;
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
+
|
|
|
|
+public class GeoUtils {
|
|
|
|
+ private static final Logger LOGGER = Logging.getLogger(GeoUtils.class);
|
|
|
|
+ private static final Pattern GEO_POINT_PATTERN = Pattern.compile("\\s*([-+]?\\d*\\.?\\d*)[^-+\\d.]+([-+]?\\d*\\.?\\d*)\\s*");
|
|
|
|
+ private static final Pattern GEO_HASH_PATTERN = Pattern.compile("[0123456789bcdefghjkmnpqrstuvwxyz]+");
|
|
|
|
+ private static final Pattern ELASTIC_DISTANCE_PATTERN = Pattern.compile("([0-9]+(\\.[0-9]+)?)([a-zA-Z]*)");
|
|
|
|
+ private static final double CIRCLE_INTERPOLATION_INTERVAL = 500.0;
|
|
|
|
+ private static final int MAX_CIRCLE_POINTS = 500;
|
|
|
|
+ private static final int MIN_CIRCLE_POINTS = 40;
|
|
|
|
+ private static final double MIN_CIRCLE_RADIUS_M = 0.001;
|
|
|
|
+ private final GeodeticCalculator geodeticCalculator;
|
|
|
|
+ private static final Pattern WKT_PATTERN = Pattern.compile("POINT.*|LINESTRING.*|POLYGON.*|MULTIPOINT.*|MULTILINESTRING.*|MULTIPOLYGON.*|GEOMETRYCOLLECTION.*");
|
|
|
|
+ private final GeometryFactory geometryFactory = new GeometryFactory();
|
|
|
|
+ private final WKTReader wktReader;
|
|
|
|
+ private static String s;
|
|
|
|
+ static final Map<String, Double> UNITS_MAP = new HashMap<String, Double>() {
|
|
|
|
+ private static final long serialVersionUID = 1L;
|
|
|
|
+
|
|
|
|
+ {
|
|
|
|
+ this.put("millimeter", 0.001);
|
|
|
|
+ this.put("mm", 0.001);
|
|
|
|
+ this.put("cm", 0.01);
|
|
|
|
+ this.put("m", 1.0);
|
|
|
|
+ this.put("kilometers", 1000.0);
|
|
|
|
+ this.put("kilometer", 1000.0);
|
|
|
|
+ this.put("km", 1000.0);
|
|
|
|
+ this.put("in", 0.0254);
|
|
|
|
+ this.put("ft", 0.3048);
|
|
|
|
+ this.put("feet", 0.3048);
|
|
|
|
+ this.put("yd", 0.9144);
|
|
|
|
+ this.put("mi", 1609.344);
|
|
|
|
+ this.put("miles", 1609.344);
|
|
|
|
+ this.put("NM", 1852.0);
|
|
|
|
+ this.put("nmi", 1852.0);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ private static GeoUtils _this;
|
|
|
|
+
|
|
|
|
+ private GeoUtils() {
|
|
|
|
+ this.geodeticCalculator = new GeodeticCalculator(DefaultEllipsoid.WGS84);
|
|
|
|
+ this.wktReader = new WKTReader();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static GeoUtils getGeoUtils() {
|
|
|
|
+ if (_this == null) {
|
|
|
|
+ _this = new GeoUtils();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return _this;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Point createPoint(double x, double y) {
|
|
|
|
+ return this.geometryFactory.createPoint(new Coordinate(x, y));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Geometry createGeometry(Object obj) {
|
|
|
|
+ Object geometry;
|
|
|
|
+ double x;
|
|
|
|
+ double y;
|
|
|
|
+ if (obj instanceof String) {
|
|
|
|
+ String ms = obj + "";
|
|
|
|
+ if (ms.length() > 65536) {
|
|
|
|
+ ms = ms.substring(0, 65536);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Matcher listMatcher = GEO_POINT_PATTERN.matcher(ms);
|
|
|
|
+ if (listMatcher.matches()) {
|
|
|
|
+ x = Double.valueOf(listMatcher.group(1));
|
|
|
|
+ y = Double.valueOf(listMatcher.group(2));
|
|
|
|
+ geometry = this.geometryFactory.createPoint(new Coordinate(y, x));
|
|
|
|
+ } else if (GEO_HASH_PATTERN.matcher(ms).matches()) {
|
|
|
|
+ LatLong latLon = GeoHash.decodeHash(ms);
|
|
|
|
+ Coordinate geoPoint = new Coordinate(latLon.getLon(), latLon.getLat());
|
|
|
|
+ y = geoPoint.y;
|
|
|
|
+ double lon = geoPoint.x;
|
|
|
|
+ geometry = this.geometryFactory.createPoint(new Coordinate(lon, y));
|
|
|
|
+ } else if (WKT_PATTERN.matcher(ms).matches()) {
|
|
|
|
+ Geometry geom;
|
|
|
|
+ try {
|
|
|
|
+ geom = this.wktReader.read((String)obj);
|
|
|
|
+ } catch (ParseException var13) {
|
|
|
|
+ geom = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ geometry = geom;
|
|
|
|
+ } else {
|
|
|
|
+ geometry = null;
|
|
|
|
+ }
|
|
|
|
+ } else if (obj instanceof List && ((List)obj).size() == 2) {
|
|
|
|
+ List<?> values = (List)obj;
|
|
|
|
+ if (Number.class.isAssignableFrom(values.get(0).getClass())) {
|
|
|
|
+ x = ((Number)values.get(0)).doubleValue();
|
|
|
|
+ y = ((Number)values.get(1)).doubleValue();
|
|
|
|
+ geometry = this.geometryFactory.createPoint(new Coordinate(x, y));
|
|
|
|
+ } else if (values.get(0) instanceof String) {
|
|
|
|
+ x = Double.valueOf((String)values.get(0));
|
|
|
|
+ y = Double.valueOf((String)values.get(1));
|
|
|
|
+ geometry = this.geometryFactory.createPoint(new Coordinate(x, y));
|
|
|
|
+ } else {
|
|
|
|
+ geometry = null;
|
|
|
|
+ }
|
|
|
|
+ } else if (obj instanceof Map) {
|
|
|
|
+ geometry = this.createGeometry((Map)obj);
|
|
|
|
+ } else {
|
|
|
|
+ geometry = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (Geometry)geometry;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Geometry createGeometry(String geometryType, Coordinate[][] coordinates, String radius) {
|
|
|
|
+ switch (geometryType.toUpperCase()) {
|
|
|
|
+ case "MULTIPOLYGON":
|
|
|
|
+ Polygon[] polygons = new Polygon[coordinates.length];
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < coordinates.length; ++i) {
|
|
|
|
+ polygons[i] = this.geometryFactory.createPolygon(coordinates[i]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this.geometryFactory.createMultiPolygon(polygons);
|
|
|
|
+ case "ENVELOPE":
|
|
|
|
+ Envelope envelope = new Envelope(coordinates[0][0], coordinates[0][1]);
|
|
|
|
+ return this.geometryFactory.toGeometry(envelope);
|
|
|
|
+ case "POINT":
|
|
|
|
+ return this.geometryFactory.createPoint(coordinates[0][0]);
|
|
|
|
+ case "POLYGON":
|
|
|
|
+ return this.geometryFactory.createPolygon(coordinates[0]);
|
|
|
|
+ case "MULTILINESTRING":
|
|
|
|
+ LineString[] lineStrings = new LineString[coordinates.length];
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < coordinates.length; ++i) {
|
|
|
|
+ lineStrings[i] = this.geometryFactory.createLineString(coordinates[i]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this.geometryFactory.createMultiLineString(lineStrings);
|
|
|
|
+ case "LINESTRING":
|
|
|
|
+ return this.geometryFactory.createLineString(coordinates[0]);
|
|
|
|
+ case "MULTIPOINT":
|
|
|
|
+ this.geometryFactory.createMultiPointFromCoords(coordinates[0]);
|
|
|
|
+ case "CIRCLE":
|
|
|
|
+ return this.createCircle(coordinates[0][0], radius);
|
|
|
|
+ default:
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Geometry createGeometry(Map<String, Object> properties) {
|
|
|
|
+ Object geometry;
|
|
|
|
+ List list;
|
|
|
|
+ Coordinate[] coords;
|
|
|
|
+ Coordinate coordinate;
|
|
|
|
+ int i;
|
|
|
|
+ switch (String.valueOf(properties.get("type")).toUpperCase()) {
|
|
|
|
+ case "POINT":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ coordinate = this.createCoordinate(list);
|
|
|
|
+ geometry = this.geometryFactory.createPoint(coordinate);
|
|
|
|
+ break;
|
|
|
|
+ case "LINESTRING":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ coords = this.createCoordinates(list);
|
|
|
|
+ geometry = this.geometryFactory.createLineString(coords);
|
|
|
|
+ break;
|
|
|
|
+ case "POLYGON":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ geometry = this.createPolygon(list);
|
|
|
|
+ break;
|
|
|
|
+ case "MULTIPOINT":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ coords = this.createCoordinates(list);
|
|
|
|
+ geometry = this.geometryFactory.createMultiPointFromCoords(coords);
|
|
|
|
+ break;
|
|
|
|
+ case "MULTILINESTRING":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ LineString[] lineStrings = new LineString[list.size()];
|
|
|
|
+
|
|
|
|
+ for(i = 0; i < list.size(); ++i) {
|
|
|
|
+ Coordinate[] coordinates = this.createCoordinates((List)list.get(i));
|
|
|
|
+ lineStrings[i] = this.geometryFactory.createLineString(coordinates);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ geometry = this.geometryFactory.createMultiLineString(lineStrings);
|
|
|
|
+ break;
|
|
|
|
+ case "MULTIPOLYGON":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ Polygon[] polygons = new Polygon[list.size()];
|
|
|
|
+
|
|
|
|
+ for(i = 0; i < list.size(); ++i) {
|
|
|
|
+ polygons[i] = this.createPolygon((List)list.get(i));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ geometry = this.geometryFactory.createMultiPolygon(polygons);
|
|
|
|
+ break;
|
|
|
|
+ case "GEOMETRYCOLLECTION":
|
|
|
|
+ list = (List)properties.get("geometries");
|
|
|
|
+ Geometry[] geometries = new Geometry[list.size()];
|
|
|
|
+
|
|
|
|
+ for(i = 0; i < geometries.length; ++i) {
|
|
|
|
+ geometries[i] = this.createGeometry((Map)list.get(i));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ geometry = this.geometryFactory.createGeometryCollection(geometries);
|
|
|
|
+ break;
|
|
|
|
+ case "ENVELOPE":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ coords = this.createCoordinates(list);
|
|
|
|
+ Envelope envelope = new Envelope(coords[0], coords[1]);
|
|
|
|
+ geometry = this.geometryFactory.toGeometry(envelope);
|
|
|
|
+ break;
|
|
|
|
+ case "CIRCLE":
|
|
|
|
+ list = (List)properties.get("coordinates");
|
|
|
|
+ String radius = (String)properties.get("radius");
|
|
|
|
+ coordinate = this.createCoordinate(list);
|
|
|
|
+ geometry = this.createCircle(coordinate, radius);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ Object latObj = properties.get("lat");
|
|
|
|
+ Object lonObj = properties.get("lon");
|
|
|
|
+ if (latObj != null && lonObj != null) {
|
|
|
|
+ Double lat;
|
|
|
|
+ if (latObj instanceof Number) {
|
|
|
|
+ lat = ((Number)latObj).doubleValue();
|
|
|
|
+ } else if (latObj instanceof String) {
|
|
|
|
+ lat = new Double((String)latObj);
|
|
|
|
+ } else {
|
|
|
|
+ lat = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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 = this.geometryFactory.createPoint(new Coordinate(lon, lat));
|
|
|
|
+ } else {
|
|
|
|
+ geometry = null;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ geometry = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (Geometry)geometry;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Polygon createPolygon(List<List<Object>> posList) {
|
|
|
|
+ Coordinate[] shellCoordinates = this.createCoordinates((List)posList.get(0));
|
|
|
|
+ LinearRing shell = this.geometryFactory.createLinearRing(shellCoordinates);
|
|
|
|
+ LinearRing[] holes = new LinearRing[posList.size() - 1];
|
|
|
|
+
|
|
|
|
+ for(int i = 1; i < posList.size(); ++i) {
|
|
|
|
+ Coordinate[] coordinates = this.createCoordinates((List)posList.get(i));
|
|
|
|
+ holes[i - 1] = this.geometryFactory.createLinearRing(coordinates);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this.geometryFactory.createPolygon(shell, holes);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Coordinate[] createCoordinates(List<List<Object>> posList) {
|
|
|
|
+ Coordinate[] coordinates = new Coordinate[posList.size()];
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < posList.size(); ++i) {
|
|
|
|
+ coordinates[i] = this.createCoordinate((List)posList.get(i));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return coordinates;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Coordinate createCoordinate(List<Object> posList) {
|
|
|
|
+ if (posList == null) {
|
|
|
|
+ return null;
|
|
|
|
+ } else {
|
|
|
|
+ double x;
|
|
|
|
+ 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);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public List<Object> readField(Map<String, Object> source, String name) {
|
|
|
|
+ List<String> keys = Arrays.asList(name.split("\\."));
|
|
|
|
+ List<Object> values = new ArrayList();
|
|
|
|
+ if (!keys.isEmpty()) {
|
|
|
|
+ Object entry = source.get(keys.get(0));
|
|
|
|
+ if (entry == null) {
|
|
|
|
+ this.readField(source.get(name), new ArrayList(), values);
|
|
|
|
+ } else {
|
|
|
|
+ this.readField(entry, keys.subList(1, keys.size()), values);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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 && List.class.isAssignableFrom(entry.getClass())) {
|
|
|
|
+ Iterator var6 = ((List)entry).iterator();
|
|
|
|
+
|
|
|
|
+ while(var6.hasNext()) {
|
|
|
|
+ Object object = var6.next();
|
|
|
|
+ this.readField(object, keys, values);
|
|
|
|
+ }
|
|
|
|
+ } else if (entry != null && !keys.isEmpty() && Map.class.isAssignableFrom(entry.getClass())) {
|
|
|
|
+ Object nextEntry = ((Map)entry).get(keys.get(0));
|
|
|
|
+ List<String> newKeys = keys.subList(1, keys.size());
|
|
|
|
+ this.readField(nextEntry, newKeys, values);
|
|
|
|
+ } else if (entry != null) {
|
|
|
|
+ values.add(entry);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static boolean isGeoPointFeature(Map<String, Object> map) {
|
|
|
|
+ boolean result = false;
|
|
|
|
+ if (map.size() == 2 && map.containsKey("coordinates")) {
|
|
|
|
+ try {
|
|
|
|
+ result = "geo_point".equals(((Map)map.get("coordinates")).get("type"));
|
|
|
|
+ } catch (Exception var3) {
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static String urlDecode(String value) {
|
|
|
|
+ try {
|
|
|
|
+ value = URLDecoder.decode(value, StandardCharsets.UTF_8.toString());
|
|
|
|
+ } catch (UnsupportedEncodingException var2) {
|
|
|
|
+ LOGGER.warning("Unable to encode value(s): " + var2);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Geometry createCircle(Coordinate centreCoord, String radius) {
|
|
|
|
+ if (centreCoord == null) {
|
|
|
|
+ return null;
|
|
|
|
+ } else {
|
|
|
|
+ double radM;
|
|
|
|
+ try {
|
|
|
|
+ radM = convertToMeters(radius);
|
|
|
|
+ } catch (Exception var15) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (radM < 0.001) {
|
|
|
|
+ return null;
|
|
|
|
+ } else {
|
|
|
|
+ double circumferance = radM * 2.0 * Math.PI;
|
|
|
|
+ int numPoints = (int)(circumferance / 500.0);
|
|
|
|
+ numPoints = Math.max(40, numPoints);
|
|
|
|
+ numPoints = Math.min(500, numPoints);
|
|
|
|
+ double angularIncrement = 360.0 / (double)numPoints;
|
|
|
|
+ this.geodeticCalculator.setStartingGeographicPoint(centreCoord.x, centreCoord.y);
|
|
|
|
+ Coordinate[] linearRingCoords = new Coordinate[numPoints + 1];
|
|
|
|
+ double angle = 0.0;
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < numPoints; ++i) {
|
|
|
|
+ this.geodeticCalculator.setDirection(angle, radM);
|
|
|
|
+ Point2D point2D = this.geodeticCalculator.getDestinationGeographicPoint();
|
|
|
|
+ linearRingCoords[i] = new Coordinate(point2D.getX(), point2D.getY());
|
|
|
|
+ angle += angularIncrement;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ linearRingCoords[numPoints] = linearRingCoords[0];
|
|
|
|
+ LinearRing linearRing = this.geometryFactory.createLinearRing(linearRingCoords);
|
|
|
|
+ return this.geometryFactory.createPolygon(linearRing);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static double convertToMeters(String distanceWithUnit) throws IllegalArgumentException {
|
|
|
|
+ if (distanceWithUnit != null && !distanceWithUnit.isEmpty()) {
|
|
|
|
+ Matcher matcher = ELASTIC_DISTANCE_PATTERN.matcher(distanceWithUnit);
|
|
|
|
+ if (matcher.matches()) {
|
|
|
|
+ double distance = Double.valueOf(matcher.group(1));
|
|
|
|
+ String unit = matcher.group(3);
|
|
|
|
+ Double conversion = (Double)UNITS_MAP.get(unit);
|
|
|
|
+ if (conversion == null) {
|
|
|
|
+ if (unit != null && !unit.isEmpty()) {
|
|
|
|
+ throw new IllegalArgumentException("Illegal unit: " + unit);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ conversion = 1.0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return distance * conversion;
|
|
|
|
+ } else {
|
|
|
|
+ throw new IllegalArgumentException("Distance string argument has incorrect format");
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ throw new IllegalArgumentException("Null of zero length distance string argument");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|