RegionChooserUtils.java 6.59 KB
Newer Older
1
package eu.simstadt.regionchooser;
2
3
4
5

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Eric Duminil's avatar
Eric Duminil committed
6
7
import java.time.LocalDate;
import java.util.Objects;
8
9
10
11
12
13
14
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
Eric Duminil's avatar
Eric Duminil committed
15
16
17
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
18
19
20
21
22
23
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;


24
public class RegionChooserUtils
25
26
27
28
{
	private static final CRSFactory CRS_FACTORY = new CRSFactory();
	public static final CoordinateReferenceSystem WGS84 = CRS_FACTORY.createFromName("EPSG:4326");
	private static final Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=[\"'])[^\"']+(?=[\"'])");
Eric Duminil's avatar
Eric Duminil committed
29
	private static final int CITYGML_HEADER_LENGTH = 50;
Eric Duminil's avatar
Eric Duminil committed
30
	private static final String EPSG = "EPSG:";
Eric Duminil's avatar
Eric Duminil committed
31
	private static final int BUFFER = 1024;
32

33
	private RegionChooserUtils() {
34
35
36
37
38
39
40
		// only static use
	}

	/**
	 * The srsName (The name by which this reference system is identified) inside the CityGML file can have multiple
	 * formats. This method tries to parse the string and detect the corresponding reference system. If it is found, it
	 * returns a proj4j.CoordinateReferenceSystem. It throws an IllegalArgumentException otherwise.
Eric Duminil's avatar
Eric Duminil committed
41
	 *
42
43
44
	 * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German Citygmls might also have "DE_DHDN_3GK3"
	 * or "ETRS89_UTM32" as srsName, so those are also included. It isn't guaranteed that those formats are correctly
	 * parsed, though.
Eric Duminil's avatar
Eric Duminil committed
45
	 *
46
47
	 * The EPSG ids and parameters are defined in resources ('nad/epsg') inside proj4j-0.1.0.jar. Some EPSG ids are
	 * missing though, e.g. 7415
Eric Duminil's avatar
Eric Duminil committed
48
	 *
49
50
51
	 * @param srsName
	 * @return CoordinateReferenceSystem
	 */
Eric Duminil's avatar
Eric Duminil committed
52
	public static CoordinateReferenceSystem crsFromSrsName(String srsName) {
53
54
55
56
57
58
59
60
61
62
63
64
65
		// EPSG:31467
		Pattern pEPSG = Pattern.compile("^(EPSG:\\d+)$");
		Matcher mEPSG = pEPSG.matcher(srsName);
		if (mEPSG.find()) {
			return CRS_FACTORY.createFromName(srsName);
		}

		// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
		//   or
		// urn:ogc:def:crs,crs:EPSG::28992
		Pattern pOGC = Pattern.compile("urn:ogc:def:crs(?:,crs)?:EPSG:[\\d\\.]*:([\\d]+)\\D*");
		Matcher mOGC = pOGC.matcher(srsName);
		if (mOGC.find()) {
Eric Duminil's avatar
Eric Duminil committed
66
			return CRS_FACTORY.createFromName(EPSG + mOGC.group(1));
67
68
69
70
71
72
		}
		// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
		// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
		Pattern pURN = Pattern.compile("urn:adv:crs:([^\\*]+)");
		Matcher mURN = pURN.matcher(srsName);
		if (mURN.find()) {
Eric Duminil's avatar
Eric Duminil committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
			String shortSrsName = mURN.group(1);

			// Gauss Krueger:
			if (shortSrsName.startsWith("DE_DHDN_3GK")) {
				int gaussKruegerBaseEPSG = 31464;
				int gaussKruegerId = Integer.parseInt(shortSrsName.substring(11));
				return CRS_FACTORY.createFromName(EPSG + (gaussKruegerBaseEPSG + gaussKruegerId));
			}

			// UTM North:
			if (shortSrsName.startsWith("ETRS89_UTM")) {
				int utmBaseEPSG = 25800;
				int utmId = Integer.parseInt(shortSrsName.substring(10));
				return CRS_FACTORY.createFromName(EPSG + (utmBaseEPSG + utmId));
87
88
89
90
91
92
93
			}
		}
		throw new IllegalArgumentException("Unknown srsName format: " + srsName);
	}

	/**
	 * Converts a jts.geom.Polygon from one CoordinateReferenceSystem to another.
Eric Duminil's avatar
Eric Duminil committed
94
	 *
95
	 * NOTE: It would be easier with org.geotools.referencing.CRS instead of Proj4J
Eric Duminil's avatar
Eric Duminil committed
96
	 *
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
	 * @param polygonInOriginalCRS
	 * @param originalCRS
	 * @param newCRS
	 * @return
	 */
	public static Polygon changePolygonCRS(Polygon polygonInOriginalCRS, CoordinateReferenceSystem originalCRS,
			CoordinateReferenceSystem newCRS) {
		GeometryFactory geometryFactory = new GeometryFactory();
		Coordinate[] convexHullcoordinates = polygonInOriginalCRS.getCoordinates();
		BasicCoordinateTransform transformToWgs84 = new BasicCoordinateTransform(originalCRS, newCRS);
		for (int i = 0; i < convexHullcoordinates.length; i++) {
			ProjCoordinate wgs84Coordinate = transformToWgs84.transform(
					new ProjCoordinate(convexHullcoordinates[i].x, convexHullcoordinates[i].y), new ProjCoordinate());
			convexHullcoordinates[i] = new Coordinate(wgs84Coordinate.x, wgs84Coordinate.y);
		}

		return geometryFactory.createPolygon(convexHullcoordinates);
	}

Eric Duminil's avatar
Eric Duminil committed
116
117
118
119
120
121
122
123
124
	public static String wktPolygonToLocalCRS(String wktPolygonInWGS84, CoordinateReferenceSystem localCRS)
			throws ParseException {
		final WKTReader wktReader = new WKTReader();
		final WKTWriter wktWriter = new WKTWriter();
		// WKT coordinates are in WGS84, so should be first converted to srsName
		Polygon wgs84Polygon = (Polygon) wktReader.read(wktPolygonInWGS84);
		return wktWriter.write(changePolygonCRS(wgs84Polygon, WGS84, localCRS));
	}

125
	/**
Eric Duminil's avatar
Eric Duminil committed
126
	 *
127
128
129
	 * Fast scan of the 50 first lines of a Citygml file to look for srsName. It might not be as reliable as parsing the
	 * whole CityGML, but it should be much faster and use much less memory. For a more reliable solution, use
	 * GeoCoordinatesAccessor. This solution can be convenient for Web services, RegionChooser or HullExtractor.
Eric Duminil's avatar
Eric Duminil committed
130
	 *
131
132
133
134
135
	 * @param citygmlPath
	 * @return
	 * @throws IOException
	 */
	public static CoordinateReferenceSystem crsFromCityGMLHeader(Path citygmlPath) throws IOException {
Eric Duminil's avatar
Eric Duminil committed
136
137
138
139
140
141
142
143
144
		try (Stream<String> lines = Files.lines(citygmlPath).limit(CITYGML_HEADER_LENGTH)) {
			Optional<String> line = lines.filter(srsNamePattern.asPredicate()).findFirst();
			if (line.isPresent()) {
				Matcher matcher = srsNamePattern.matcher(line.get());
				matcher.find();
				return crsFromSrsName(matcher.group());
			} else {
				throw new IllegalArgumentException("No srsName found in the header of " + citygmlPath);
			}
145
146
147
148
149
		}
	}

	/**
	 * Finds every CityGML in every .proj folder in a repository.
Eric Duminil's avatar
Eric Duminil committed
150
	 *
151
152
153
154
155
156
157
158
159
160
161
162
163
	 * @param repository
	 * @return a stream of CityGML Paths
	 * @throws IOException
	 */
	public static Stream<Path> everyCityGML(Path repository) throws IOException {
		return Files.walk(repository, 2)
				.sorted()
				.filter(
						p -> Files.isRegularFile(p) &&
								p.getParent().toString().endsWith(".proj") &&
								p.toString().toLowerCase().endsWith(".gml"));
	}

Eric Duminil's avatar
Eric Duminil committed
164
165
166
167
168
169
170
171
172
173
	/**
	 * Returns application version, if it has been written in the JAR file during deployment.
	 *
	 * e.g. "0.9.1-SNAPSHOT (rev. 73cbe48e, 2018-07-20)"
	 */
	public static String getApplicationVersion() {
		Package regionChooserJar = RegionChooserFX.class.getPackage();
		return Objects.toString(regionChooserJar.getImplementationVersion(),
				String.format("development version (%s)", LocalDate.now()));
	}
174
}