package org.wikibrain.spatial.util; import com.google.gson.JsonObject; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Created by bjhecht on 5/21/14. */ public class WikiBrainSpatialUtils { /** * Radius of earth, in meters. */ public static final double EARTH_RADIUS = 6372800; public static final double EARTH_CIRCUMFERENCE = 2 * Math.PI * EARTH_RADIUS; private static final String EARTH_ITEM_ID = "Q2"; private static final Logger LOG = LoggerFactory.getLogger(WikiBrainSpatialUtils.class); public static Geometry jsonToGeometry(JsonObject json){ try { Double latitude = json.get("latitude").getAsDouble(); Double longitude = json.get("longitude").getAsDouble(); if (json.has("globe") && json.get("globe").isJsonPrimitive()) { String globe = json.get("globe").getAsString(); if (!globe.endsWith(EARTH_ITEM_ID) && !globe.endsWith("earth")) { return null; // check to make sure these refer to the Earth } } return getPoint(latitude, longitude); }catch(Exception e){ LOG.warn("Parse error while reading Wikidata json value: " + json + " (" + e.getMessage() + ")"); return null; } } /** * Returns the effective centroid of the geometry. * This is (currently) the centroid of the largest polygon. * @param g * @return */ public static Point getCenter(Geometry g) { if (g instanceof Point) { return (Point)g; } Geometry largest = g; if (largest instanceof MultiPolygon) { double largestArea = -1; MultiPolygon mp = (MultiPolygon)g; for (int i = 0; i < mp.getNumGeometries(); i++) { Geometry g2 = mp.getGeometryN(i); double area = g2.getArea(); if (area > largestArea) { largestArea = area; largest = g2; } } } return largest.getCentroid(); } public static double[] get3DPoints(Point p) { double lng = FastMath.toRadians(p.getX()); double lat = FastMath.toRadians(p.getY()); return new double[] { FastMath.cos(lat) * FastMath.sin(-lng), FastMath.cos(lat) * FastMath.cos(-lng), FastMath.sin(-lat), }; } public static double haversine(Point p1, Point p2) { return haversine(p1.getX(), p1.getY(), p2.getX(), p2.getY()); } /** * Approximation of the distance between two geographic points that treats the * earth as a sphere. Fast, but can have 0.5% error because the Earth is closer * to an ellipsoid. * * From http://rosettacode.org/wiki/Haversine_formula#Java * * The use of FastMath below cuts the time by more than 50%. * * @param lon1 * @param lat1 * @param lon2 * @param lat2 * @return */ public static double haversine(double lon1, double lat1, double lon2, double lat2) { double dLat = Math.toRadians(lat2 - lat1); double dLon = Math.toRadians(lon2 - lon1); lat1 = Math.toRadians(lat1); lat2 = Math.toRadians(lat2); double a = FastMath.sin(dLat / 2) * FastMath.sin(dLat / 2) + FastMath.sin(dLon / 2) * FastMath.sin(dLon / 2) * FastMath.cos(lat1) * FastMath.cos(lat2); double c = 2 * FastMath.asin(FastMath.sqrt(a)); return EARTH_RADIUS * c; } public static Point getPoint(double lat, double lon) { Coordinate[] coords = new Coordinate[1]; coords[0] = new Coordinate(lon, lat); CoordinateArraySequence coordArraySeq = new CoordinateArraySequence(coords); return new Point(coordArraySeq, new GeometryFactory(new PrecisionModel(), 4326)); } }