Project

General

Profile

Download (8.42 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2020 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9

    
10
package eu.etaxonomy.cdm.ext.geo.kml;
11

    
12
import java.awt.geom.Point2D;
13

    
14
import javax.measure.Quantity;
15
import javax.measure.Unit;
16
import javax.measure.UnitConverter;
17
import javax.measure.quantity.Length;
18

    
19
import org.apache.log4j.Logger;
20
import org.geotools.data.DataUtilities;
21
import org.geotools.feature.SchemaException;
22
import org.geotools.feature.simple.SimpleFeatureBuilder;
23
import org.geotools.geometry.jts.JTS;
24
import org.geotools.geometry.jts.JTSFactoryFinder;
25
import org.geotools.referencing.CRS;
26
import org.geotools.referencing.GeodeticCalculator;
27
import org.geotools.referencing.crs.DefaultGeographicCRS;
28
import org.locationtech.jts.geom.Coordinate;
29
import org.locationtech.jts.geom.CoordinateSequence;
30
import org.locationtech.jts.geom.Geometry;
31
import org.locationtech.jts.geom.GeometryFactory;
32
import org.locationtech.jts.geom.Point;
33
import org.locationtech.jts.geom.Polygon;
34
import org.locationtech.jts.util.GeometricShapeFactory;
35
import org.opengis.feature.GeometryAttribute;
36
import org.opengis.feature.simple.SimpleFeature;
37
import org.opengis.feature.simple.SimpleFeatureType;
38
import org.opengis.geometry.MismatchedDimensionException;
39
import org.opengis.referencing.FactoryException;
40
import org.opengis.referencing.NoSuchAuthorityCodeException;
41
import org.opengis.referencing.crs.CoordinateReferenceSystem;
42
import org.opengis.referencing.crs.ProjectedCRS;
43
import org.opengis.referencing.operation.MathTransform;
44
import org.opengis.referencing.operation.TransformException;
45

    
46
import si.uom.SI;
47
import tec.uom.se.quantity.Quantities;
48

    
49
/**
50
 * See :
51
 * 
52
 * https://gis.stackexchange.com/questions/311272/create-dynamic-circle-polygon-from-specific-lat-long-using-geotools
53
 * https://gis.stackexchange.com/questions/283183/using-geometricshapefactory-to-create-circle-with-radius-in-miles
54
 * 
55
 * https://stackoverflow.com/questions/36481651/how-do-i-create-a-circle-with-latitude-longitude-and-radius-with-geotools#36528805
56
 * 
57
 * 
58
 * Another great library for creating shapes is https://github.com/locationtech/spatial4j
59
 * 
60
 * @author Andreas Kohlbecker
61
 * @since Apr 21, 2020
62
 */
63
public class GeometryBuilder {
64

    
65
	private final static Logger logger = Logger.getLogger(GeometryBuilder.class);
66

    
67
	public enum CircleMethod {
68
		circle, simpleCircleSmall, simpleCircle, reprojectedCircle;
69
	}
70

    
71
	/**
72
	 * Fails with javax.measure.IncommensurableException: m is not compatible with
73
	 * deg
74
	 * 
75
	 * see
76
	 * https://gis.stackexchange.com/questions/283183/using-geometricshapefactory-to-create-circle-with-radius-in-miles
77
	 * 
78
	 * @param radius
79
	 * @param latitude
80
	 * @param longitude
81
	 * @return
82
	 * @throws NoSuchAuthorityCodeException
83
	 * @throws FactoryException
84
	 */
85
	public Polygon circle(Double radius, Double latitude, Double longitude)
86
			throws NoSuchAuthorityCodeException, FactoryException {
87

    
88
		Quantity<Length> radiusMeter = Quantities.getQuantity(radius, SI.METRE);
89
		Unit<Length> origUnit = (Unit<Length>) DefaultGeographicCRS.WGS84.getCoordinateSystem().getAxis(0).getUnit(); //// (Unit<Length>)
90
																														//// ////
91
																														//// origCRS.getCoordinateSystem().getAxis(0).getUnit();
92

    
93
		GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
94
		shapeFactory.setNumPoints(32);
95
		shapeFactory.setCentre(new Coordinate(latitude, longitude));
96
		shapeFactory.setSize(radiusMeter.to(origUnit).getValue().doubleValue() * 2);
97

    
98
		Polygon circlePolygon = shapeFactory.createCircle();
99
		circlePolygon.setSRID(4326);
100

    
101
		return circlePolygon;
102
	}
103

    
104
	/**
105
	 * Only suitable for small radius (> 1000 m) as the circles are heavily
106
	 * distorted otherwise.
107
	 * 
108
	 * @param radius
109
	 * @param latitude
110
	 * @param longitude
111
	 * @return
112
	 */
113
	public Geometry simpleCircleSmall(Double radius, Double latitude, Double longitude) {
114

    
115
		double diameterInMeters = radius * 2.0d;
116
		GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
117
		shapeFactory.setNumPoints(64); // adjustable
118
		shapeFactory.setCentre(new Coordinate(longitude, latitude));
119
		// Length in meters of 1° of latitude = always 111.32 km
120
		shapeFactory.setWidth(diameterInMeters / 111320d);
121
		// Length in meters of 1° of longitude = 40075 km * cos( latitude ) / 360
122
		shapeFactory.setHeight(diameterInMeters / (40075000 * Math.cos(Math.toRadians(latitude)) / 360));
123

    
124
		Polygon circle = shapeFactory.createEllipse();
125
		return circle;
126
	}
127

    
128
	/**
129
	 * Creates perfect circles which are looking good but might be projected
130
	 * incorrectly for the resulting map
131
	 * 
132
	 * @param distance
133
	 * @param latitude
134
	 * @param longitude
135
	 * @return
136
	 */
137
	public Geometry simpleCircle(Quantity<Length> distance, Double latitude, Double longitude) {
138

    
139
		GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
140
		CoordinateSequence coordinateSequence = geometryFactory.getCoordinateSequenceFactory()
141
				.create(new Coordinate[] { new Coordinate(longitude, latitude) });
142
		Point point = new Point(coordinateSequence, geometryFactory);
143

    
144
		GeodeticCalculator calc = new GeodeticCalculator(DefaultGeographicCRS.WGS84);
145
		calc.setStartingGeographicPoint(longitude, latitude);
146
		UnitConverter converter = distance.getUnit().getConverterTo(SI.METRE);
147
		double d = converter.convert(distance.getValue()).doubleValue();
148
		calc.setDirection(0.0, d);
149
		Point2D p2 = calc.getDestinationGeographicPoint();
150
		calc.setDirection(90.0, d);
151
		Point2D p3 = calc.getDestinationGeographicPoint();
152

    
153
		double dy = p2.getY() - latitude;
154
		double dx = p3.getX() - longitude;
155
		double dist = (dy + dx) / 2.0;
156
		Polygon p1 = (Polygon) point.buffer(dist);
157
		return p1;
158
	}
159

    
160
	/**
161
	 * This Method should produces the best circles.
162
	 * 
163
	 * The code is based on an example published by Ian Turton on stackoverflow:
164
	 * 
165
	 * https://stackoverflow.com/questions/36481651/how-do-i-create-a-circle-with-latitude-longitude-and-radius-with-geotools#36528805
166
	 * 
167
	 * see https://gist.github.com/ianturton/973563fe5004985ba35a6e2247f7d823 and
168
	 * https://gitlab.com/snippets/17558
169
	 * 
170
	 * @param feature
171
	 * @param distance
172
	 * @return
173
	 */
174
	public Geometry bufferFeature(SimpleFeature feature, Double radius) {
175

    
176
		// replacment for Measure<Double, Length> distance:
177
		Quantity<Length> radiusMeter = Quantities.getQuantity(radius, SI.METRE);
178

    
179
		// extract the geometry
180
		GeometryAttribute gProp = feature.getDefaultGeometryProperty();
181
		CoordinateReferenceSystem origCRS = gProp.getDescriptor().getCoordinateReferenceSystem();
182

    
183
		Geometry geom = (Geometry) feature.getDefaultGeometry();
184
		Geometry pGeom = geom;
185
		MathTransform toTransform, fromTransform = null;
186

    
187
		// reproject the geometry to a local projection
188
		if (!(origCRS instanceof ProjectedCRS)) {
189

    
190
			double x = geom.getCoordinate().x;
191
			double y = geom.getCoordinate().y;
192

    
193
			String code = "AUTO:42001," + x + "," + y;
194
			// System.out.println(code);
195
			CoordinateReferenceSystem auto;
196
			try {
197
				auto = CRS.decode(code);
198
				toTransform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
199
				fromTransform = CRS.findMathTransform(auto, DefaultGeographicCRS.WGS84);
200
				pGeom = JTS.transform(geom, toTransform);
201

    
202
			} catch (MismatchedDimensionException | TransformException | FactoryException e) {
203
				logger.error(e);
204
			}
205

    
206
		}
207

    
208
		// create a buffer around the geometry, assumes the geometry is in the same
209
		// units as the distance variable.
210
		Geometry out = pGeom.buffer(radiusMeter.getValue().doubleValue());
211
		Geometry retGeom = out;
212
		// reproject the geometry to the original projection
213
		if (!(origCRS instanceof ProjectedCRS)) {
214
			try {
215
				retGeom = JTS.transform(out, fromTransform);
216

    
217
			} catch (MismatchedDimensionException | TransformException e) {
218
				logger.error(e);
219
			}
220
		}
221

    
222
		return retGeom;
223
	}
224

    
225
	public static SimpleFeature createSimplePointFeature(Double longitude, Double latitude) throws SchemaException {
226
		SimpleFeatureType schema = DataUtilities.createType("", "Location", "locations:Point:srid=4326,id:Integer" // a
227
																													// number
228
																													// attribute
229
		);
230
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(schema);
231

    
232
		GeometryFactory geometryFactory = new GeometryFactory();
233
		/* Longitude (= x coord) first ! */
234
		Point point = geometryFactory.createPoint(new Coordinate(longitude, latitude));
235
		featureBuilder.add(point);
236
		SimpleFeature feature = featureBuilder.buildFeature(null);
237
		return feature;
238
	}
239

    
240
}
(1-1/3)