View Javadoc
1   package net.sumaris.core.util;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Core shared
6    * %%
7    * Copyright (C) 2018 - 2019 SUMARiS Consortium
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.base.Preconditions;
26  import com.vividsolutions.jts.geom.*;
27  import com.vividsolutions.jts.io.ParseException;
28  import com.vividsolutions.jts.io.WKTReader;
29  import com.vividsolutions.jts.io.WKTWriter;
30  import net.sumaris.core.dao.referential.location.Locations;
31  import org.apache.commons.lang3.StringUtils;
32  
33  import java.text.NumberFormat;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  public class Geometries {
38  
39  	protected static final double EARTH_RADIUS = 6378288.0;
40  	protected static Pattern COORDINATES_PATTERN = Pattern.compile("([0-9]{4})([NS])[\\s\\xA0]+([0-9]{5})([EW])[\\s\\xA0]*");
41  	protected final static String WKT_POINT = "POINT(%s %s)";
42  	protected final static String WKT_MULTIPOINT_BASE = "MULTIPOINT(%s)";
43  	protected final static String WKT_MULTIPOINT_SUBPART = "(%s %s)";
44  	protected final static String WKT_POLYGON = "POLYGON((%s))";
45  	protected final static String WKT_POLYGON_SUBPART = "%s %s";
46  	protected final static String WKT_MULTIPOLYGON = "MULTIPOLYGON((%s))";
47  	protected final static String WKT_MULTIPOLYGON_SUBPART = "(%s)";
48  
49  	private static final GeometryFactory geometryFactory = new GeometryFactory();
50  
51  	/**
52  	 * <p>createPoint.</p>
53  	 *
54  	 * @param x a {@link Double} object.
55  	 * @param y a {@link Double} object.
56  	 * @return a {@link Point} object.
57  	 */
58  	public static Point createPoint(Double x, Double y) {
59  		return geometryFactory.createPoint(new Coordinate(x, y));
60  	}
61  
62  
63  	public static Point createPoint(Number longitudeX, Number latitudeY) {
64  		String wktString = String.format(WKT_POINT, longitudeX, latitudeY);
65  		return (Point) getGeometry(wktString);
66  	}
67  
68  	/**
69  	 * <p>createLine.</p>
70  	 *
71  	 * @param minX a {@link Double} object.
72  	 * @param minY a {@link Double} object.
73  	 * @param maxX a {@link Double} object.
74  	 * @param maxY a {@link Double} object.
75  	 * @return a {@link LineString} object.
76  	 */
77  	public static LineString createLine(Double minX, Double minY, Double maxX, Double maxY) {
78  		return geometryFactory.createLineString(new Coordinate[]{
79  				new Coordinate(minX, minY),
80  				new Coordinate(maxX, maxY)});
81  	}
82  
83  	/**
84  	 * <p>createPolygon.</p>
85  	 *
86  	 * @param minX a {@link Double} object.
87  	 * @param minY a {@link Double} object.
88  	 * @param maxX a {@link Double} object.
89  	 * @param maxY a {@link Double} object.
90  	 * @return a {@link Polygon} object.
91  	 */
92  	public static Polygon createPolygon(Double minX, Double minY, Double maxX, Double maxY) {
93  		return geometryFactory.createPolygon(new Coordinate[]{
94  				new Coordinate(minX, minY),
95  				new Coordinate(minX, maxY),
96  				new Coordinate(maxX, maxY),
97  				new Coordinate(maxX, minY),
98  				new Coordinate(minX, minY)});
99  	}
100 
101 	/**
102 	 * <p>getGeometry.</p>
103 	 *
104 	 * @param wktString a {@link String} object.
105 	 * @return a {@link Geometry} object.
106 	 */
107 	public static Geometry getGeometry(String wktString) {
108 		try {
109 			return new WKTReader(geometryFactory).read(wktString);
110 		} catch (ParseException e) {
111 			throw new RuntimeException(e);
112 		}
113 	}
114 
115 	/**
116 	 * <p>getWKTString.</p>
117 	 *
118 	 * @param geometry a {@link Geometry} object.
119 	 * @return a {@link String} object.
120 	 */
121 	public static String getWKTString(Geometry geometry) {
122 		return new WKTWriter().write(geometry);
123 	}
124 
125 
126 	/**
127 	 * @deprecated  use getGeometry() instead
128 	 * @param wktGeometry
129 	 * @return
130 	 */
131 	@Deprecated
132 	public static Geometry wktToGeometry(String wktGeometry) {
133 		return getGeometry(wktGeometry);
134 	}
135 
136 
137 	public static MultiPoint createMultiPoint(Number... coords) {
138 		StringBuilder sb = new StringBuilder();
139 		for (int i = 0; i < coords.length; i = i + 2) {
140 			Number longitudeX = coords[i];
141 			Number latitudeY = coords[i];
142 			sb.append(", ");
143 			sb.append(String.format(WKT_MULTIPOINT_SUBPART, longitudeX, latitudeY));
144 		}
145 
146 		String wktString = String.format(WKT_MULTIPOINT_BASE, sb.substring(2));
147 		Geometry geom = getGeometry(wktString);
148 		return (MultiPoint) geom;
149 	}
150 
151 	public static Number getLatitudeFromCoordinates(String coordinates) throws java.text.ParseException {
152 		Preconditions.checkNotNull(coordinates);
153 		Matcher matcher = COORDINATES_PATTERN.matcher(StringUtils.trim(coordinates));
154 		if (!matcher.matches()) {
155 			throw new RuntimeException("Not a coordinate string: " + coordinates);
156 		}
157 		String latitudeString = matcher.group(1);
158 		String latitudeLetter = matcher.group(2);
159 		try {
160 			Number latitudeDegrees = NumberFormat.getInstance().parse(latitudeString.substring(0, 2));
161 			Number latitudeMinutes = NumberFormat.getInstance().parse(latitudeString.substring(2));
162 			double latitude = ("S".equals(latitudeLetter) ? -1 : 1) * ((double) (latitudeMinutes.doubleValue() / 60) + latitudeDegrees.doubleValue());
163 			return latitude;
164 		} catch (java.text.ParseException e) {
165 			throw new java.text.ParseException("Unable to convert coordinates in lat/long.", 0);
166 		}
167 	}
168 
169 	public static Number getLongitudeFromCoordinates(String coordinates) throws java.text.ParseException {
170 		Preconditions.checkNotNull(coordinates);
171 		Matcher matcher = COORDINATES_PATTERN.matcher(coordinates.trim());
172 		if (!matcher.matches()) {
173 			throw new RuntimeException("Not a coordinate string:" + coordinates);
174 		}
175 		String longitudeString = matcher.group(3);
176 		String longitudeLetter = matcher.group(4);
177 		try {
178 			Number longitudeDegrees = NumberFormat.getInstance().parse(longitudeString.substring(0, 3));
179 			Number longitudeMinutes = NumberFormat.getInstance().parse(longitudeString.substring(3));
180 			double longitude = ("W".equals(longitudeLetter) ? -1 : 1)
181 					* ((double) (longitudeMinutes.doubleValue() / 60) + longitudeDegrees.doubleValue());
182 			return longitude;
183 		} catch (java.text.ParseException e) {
184 			throw new java.text.ParseException("Unable to convert coordinates in lat/long.", 0);
185 		}
186 	}
187 
188 	/**
189 	 * Create a polygon from 2 points : bottom left, and top right
190 	 * 
191 	 * @param bottomLeftX
192 	 * @param bottomLeftY
193 	 * @param topRightX
194 	 * @param topRightY
195 	 * @return
196 	 */
197 	public static Geometry createRectangleGeometry(Number bottomLeftX, Number bottomLeftY, Number topRightX, Number topRightY,
198 			boolean returnHasMultiPolygon) {
199 		String wtkPoint1 = String.format(WKT_POLYGON_SUBPART, bottomLeftX, bottomLeftY);
200 		String wtkPoint2 = String.format(WKT_POLYGON_SUBPART, topRightX, bottomLeftY);
201 		String wtkPoint3 = String.format(WKT_POLYGON_SUBPART, topRightX, topRightY);
202 		String wtkPoint4 = String.format(WKT_POLYGON_SUBPART, bottomLeftX, topRightY);
203 		String wtkPolygon = wtkPoint1 + "," + wtkPoint2 + "," + wtkPoint3 + "," + wtkPoint4 + "," + wtkPoint1;
204 		String wktString;
205 		if (!returnHasMultiPolygon) {
206 			wktString = String.format(WKT_POLYGON, wtkPolygon);
207 		} else {
208 			wktString = String.format(WKT_MULTIPOLYGON, String.format(WKT_MULTIPOLYGON_SUBPART, wtkPolygon));
209 		}
210 
211 		return getGeometry(wktString);
212 	}
213 
214 	/**
215 	 * <p>getDistanceInMeters.</p>
216 	 *
217 	 * @param startLatitude a {@link Float} object.
218 	 * @param startLongitude a {@link Float} object.
219 	 * @param endLatitude a {@link Float} object.
220 	 * @param endLongitude a {@link Float} object.
221 	 * @return a int.
222 	 */
223 	public static int getDistanceInMeters(Number startLatitude,
224 										  Number startLongitude,
225 										  Number endLatitude,
226 										  Number endLongitude) {
227 		Preconditions.checkNotNull(startLatitude);
228 		Preconditions.checkNotNull(startLongitude);
229 		Preconditions.checkNotNull(endLatitude);
230 		Preconditions.checkNotNull(endLongitude);
231 
232 		double sLat = startLatitude.doubleValue() * Math.PI / 180.0;
233 		double sLong = startLongitude.doubleValue() * Math.PI / 180.0;
234 		double eLat = endLatitude.doubleValue() * Math.PI / 180.0;
235 		double eLong = endLongitude.doubleValue() * Math.PI / 180.0;
236 
237 		Double d = EARTH_RADIUS *
238 				(Math.PI / 2 - Math.asin(Math.sin(eLat) * Math.sin(sLat)
239 						+ Math.cos(eLong - sLong) * Math.cos(eLat) * Math.cos(sLat)));
240 		return d.intValue();
241 	}
242 
243 	/**
244 	 * <p>getDistanceInMilles.</p>
245 	 *
246 	 * @param distance a {@link Float} object.
247 	 * @return a {@link String} object.
248 	 */
249 	public static String getDistanceInMilles(Number distance) {
250 		String distanceText;
251 		if (distance != null) {
252 			double distanceInMilles = distance.doubleValue() / 1852;
253 			distanceText = String.format("%.3d", distanceInMilles);
254 
255 		} else {
256 			distanceText = "";
257 		}
258 		return distanceText;
259 	}
260 }