Efringen-Kirchen ausbalanciert

Inspiriert von Flopps Beitrag „Am Schwerpunkt von Freiburg“ hab ich mich gestern hingesetzt und den Schwerpunkt von Efringen-Kirchen berechnet. Da ich als Geodät beruflich vorbelastet bin, ging die Sache recht zügig über die Bühne. Doch wozu braucht man den Schwerpunkt von Efringen-Kirchen?

Auf der offenen Geocaching-Plattform Opencaching.de gibt es so genannte Safari-Caches. Bei diesem Cachetyp handelt es sich um einen virtuellen Cache. Kurz gesagt, es befindet sich keine physikalische Box und kein Logbuch vor Ort. Die Aufgabenstellung beinhaltet, zunächst diesen Ort zu finden und dann den Besuch mit einem Foto zu dokumentieren. Auf dem Foto sollen das GPS-Gerät und der besuchte Ort abgebildet sein.

Der Cache „Am Mittelpunkt“ von following ist genau so ein  Safari-Cache. Die Aufgabenstellung beschreibt mehrere Varianten zum Bestimmen des Schwerpunktes. Ich habe mich für die Berechnungsvariante entschieden. Die Daten stammen aus dem OpenStreetMap-Projekt und wurden mit JOSM, GPS-Babel oder GPSies, LibreOffice.org und Tincta bearbeitet.

Bild des Schwerpunktes von Efringen-Kirchen
Schwerpunkt von Efringen-Kirchen

Das Vorgehen ist wie folgt:

  1. Finden der Stadtgrenze von Efringen-Kirchen (Relation: Efringen-Kirchen (1130296))
  2. Laden der Relation mit JOSM und Speichern als GPX-Datei
  3. Konvertieren der GPX-Datei ins ASCII-Format (XML-Datei ginge in Java auch) mit GPSies oder GPSBabel
  4. Extrahieren der Koordinaten und Einfügen der ersten Zeile zusätzlich als letzte Zeile mit LibreOffice.org
  5. Speichern der Datei mit dem Texteditor
  6. Programmieren des Java-Codes mit Eclipse
  7. Cachen vor Ort

Da mir Python noch nicht so zügig von der Hand geht, hier mein Lösungsansatz in Java.

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * Das Programm Schwerpunkt berechnet den geometrischen Schwerpunkt eines Polygons
 * aus Koordinaten (x y). Die Punkte müssen der Reihe nach vorliegen und der erste 
 * und der letzte Punkt in der Liste sind identisch.
 * 
 * Siehe auch: <a href="https://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt#Polygon">Wikipedia-Artikel</a>.
 * 
 * @author Sebastian aka Azimut400gon
 */
public class Schwerpunkt {

	/**
	 * Konstruktor ohne Parameter. Steuert den Programmablauf.
	 */
	public Schwerpunkt() {

		String dateiName = "Efringen-Kirchen.txt";

		/* Dateiaufbau
		 * ===========
		 * 
		 * 47.64511840	7.594074100 // Erster Punkt
		 * 47.64459570	7.593142100
		 * 47.64312320	7.594131000
		 * ...
		 * 47.64511840	7.594074100 // Erster Punkt wiederholt
		 */

		ArrayList<String> geleseneZeilen = datenLesen(dateiName); 

		double[] schwerPunkt = schwerpunktBerechnen(geleseneZeilen);

		System.out.println("Der berechnete Schwerpunkt hat die Koordinaten: \n");
		System.out.println(schwerPunkt[0] + " " + schwerPunkt[1]);

	}

	/**
	 * Liest die Koordinaten aus der Textdatei zeilenweise ein und gibt sie als 
	 * <code>ArrayList<String></code>-Objekt zurück.
	 * @return ArrayList<String> mit den gelesenen Zeilen als String
	 */
	private ArrayList<String> datenLesen(String dateiName) {

		String zeile = null;
		ArrayList<String> zeilen = new ArrayList<String>();

		try {
			FileInputStream fis = new FileInputStream(dateiName);
			BufferedReader leser = new BufferedReader(new InputStreamReader(fis));

			try {
				while ((zeile = leser.readLine()) != null) {
					zeilen.add(zeile);
				}
			} catch (Exception e) {
				System.err.println("Datei konnten nicht geschlossen werden.");
			} finally {
				fis.close();
			}

		} catch (Exception e) {
			System.err.println("Fehler beim Lesen der Datei: "+ dateiName);
		}

		return zeilen;

	}

	/**
	 * Berechnet den geometrischen Schwerpunkt der eingelesenen Datei und gibt 
	 * diesen als double-Feld zurück.
	 * @return Berechneter Schwerpunkt als double [x, y]
	 */
	private double[] schwerpunktBerechnen(ArrayList<String> zeilen) {

		double[][] pkt = new double[zeilen.size()][2];

		StringTokenizer st = null;

		for (int i = 0; i < zeilen.size(); i++) {

			st = new StringTokenizer(zeilen.get(i));

			if (st.countTokens() == 2) {
				pkt[i][0] = Double.parseDouble(st.nextToken());
				pkt[i][1] = Double.parseDouble(st.nextToken());
			} else {
				System.err.println("Falsches Format in der Eingabedatei.");
			}

		}

		// Flächenberechung  mit Gauß'scher Trapezformel
		// https://de.wikipedia.org/wiki/Gaußsche_Trapezformel
		double a = 0d;

		for (int i = 0; i < pkt.length-1; i++) {
			a += (pkt[i][0] * pkt[i+1][1]) - (pkt[i+1][0] * pkt[i][1]);
		}

		a += (pkt[pkt.length-1][0] * pkt[0][1]) - (pkt[0][0] * pkt[pkt.length-1][1]);

		a /= 2.0;

		// Berechnung des Schwerpunktes
		// https://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt#Polygon
		double x_S = 0d, y_S = 0d, hilf = 0d;

		for (int i = 0; i < pkt.length-1; i++) {
			hilf = (pkt[i][0] * pkt[i+1][1] - pkt[i+1][0] * pkt[i][1]);
			x_S += (pkt[i][0] + pkt[i+1][0]) * hilf;
			y_S += (pkt[i][1] + pkt[i+1][1]) * hilf;
		}

		hilf = (pkt[pkt.length-1][0] * pkt[0][1] - pkt[0][0] * pkt[pkt.length-1][1]);
		x_S += (pkt[pkt.length-1][0] + pkt[0][0]) * hilf;
		y_S += (pkt[pkt.length-1][1] + pkt[0][1]) * hilf;

		x_S /= 6d * a;
		y_S /= 6d * a;

		double[] schwerpunkt = {x_S, y_S};

		return schwerpunkt;

	}

	/**
	 * main()-Methode - Einstiegspunkt des Programms.
	 */
	public static void main(String[] args) {

		//Schedule a job for the event-dispatching thread:
		//creating and showing this application's GUI.
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new Schwerpunkt();
			}
		});
	}

}