RegionChooserUtils.java 6.53 KB
Newer Older
1
package eu.simstadt.regionchooser;
2

Eric Duminil's avatar
Eric Duminil committed
3
import java.io.BufferedWriter;
4
5
6
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Eric Duminil's avatar
Eric Duminil committed
7
8
import java.time.LocalDate;
import java.util.Objects;
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;


22
public class RegionChooserUtils
23
24
25
26
{
	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
27
	private static final int CITYGML_HEADER_LENGTH = 50;
Eric Duminil's avatar
Eric Duminil committed
28
	private static final String EPSG = "EPSG:";
Eric Duminil's avatar
Eric Duminil committed
29
	private static final int BUFFER = 1024;
30

31
	private RegionChooserUtils() {
32
33
34
35
36
37
38
		// 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
39
	 *
40
41
42
	 * 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
43
	 *
44
45
	 * 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
46
	 *
47
48
49
	 * @param srsName
	 * @return CoordinateReferenceSystem
	 */
Eric Duminil's avatar
Eric Duminil committed
50
	public static CoordinateReferenceSystem crsFromSrsName(String srsName) {
51
52
53
54
55
56
57
58
59
60
61
62
63
		// 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
64
			return CRS_FACTORY.createFromName(EPSG + mOGC.group(1));
65
66
67
68
69
70
		}
		// 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
			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));
85
86
87
88
89
90
91
			}
		}
		throw new IllegalArgumentException("Unknown srsName format: " + srsName);
	}

	/**
	 * Converts a jts.geom.Polygon from one CoordinateReferenceSystem to another.
Eric Duminil's avatar
Eric Duminil committed
92
	 *
93
	 * NOTE: It would be easier with org.geotools.referencing.CRS instead of Proj4J
Eric Duminil's avatar
Eric Duminil committed
94
	 *
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
	 * @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
115
	 *
116
117
118
	 * 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
119
	 *
120
121
122
123
124
	 * @param citygmlPath
	 * @return
	 * @throws IOException
	 */
	public static CoordinateReferenceSystem crsFromCityGMLHeader(Path citygmlPath) throws IOException {
Eric Duminil's avatar
Eric Duminil committed
125
126
127
128
129
130
131
132
133
		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);
			}
134
135
136
137
138
		}
	}

	/**
	 * Finds every CityGML in every .proj folder in a repository.
Eric Duminil's avatar
Eric Duminil committed
139
	 *
140
141
142
143
144
145
146
147
148
149
150
151
152
	 * @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
153
154
155
156
157
158
159
160
161
162
163
164
165

	public static void writeStringBuilderToFile(StringBuilder sb, Path outputFile) throws IOException {
		if (outputFile != null) {
			try (BufferedWriter writer = Files.newBufferedWriter(outputFile)) {
				char[] chars = new char[BUFFER];
				for (int aPosStart = 0; aPosStart < sb.length(); aPosStart += BUFFER) {
					int chunk = Math.min(BUFFER, sb.length() - aPosStart);
					sb.getChars(aPosStart, aPosStart + chunk, chars, 0);
					writer.write(chars, 0, chunk);
				}
			}
		}
	}
Eric Duminil's avatar
Eric Duminil committed
166
167
168
169
170
171
172
173
174
175
176

	/**
	 * 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()));
	}
177
}