[QUIZ#10] zeja (Java)

zeja

Erfahrenes Mitglied
Ganz zufrieden bin ich nicht aber naja.... es läuft zumindest ;)

Java:
package de.tutorials.quiz10;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author zeja
 */
public class WorldTour {

	/**
	 * Liste eine Datei mit den Koordinaten für die Weltreise ein.
	 * 
	 * @param pointList
	 * @return
	 */
	public static List<WorldCoordinates> read(final File pointList) {
		final List<WorldCoordinates> coordinateList = new ArrayList<WorldCoordinates>( );

		try {
			final BufferedReader br = new BufferedReader(new FileReader(
					pointList));
			try {
				String line = null;
				while ((line = br.readLine( )) != null) {
					// Auskommentieren mit #
					if (!line.trim( ).equals("") && !line.startsWith("#")) {
						try {
							final WorldCoordinates coordinates = parseLine(line);
							coordinateList.add(coordinates);
						}
						catch (ParseException e) {
							e.printStackTrace( );
						}
					}
				}
			}
			finally {
				br.close( );
			}
		}
		catch (IOException e) {
			e.printStackTrace( );
		}

		return coordinateList;

	}

	/**
	 * Breite Länge Ortsname<br>
	 * G°M'S"<br>
	 * G°M'<br>
	 * G°<br>
	 * z.B: 40°43'N 74°W New York
	 * 
	 * @param line
	 * @throws ParseException
	 *             Wenn Koordinatenpaar nicht geparst werden kann.
	 */
	private static WorldCoordinates parseLine(final String line)
			throws ParseException {
		// Splitten bei Mindestens einem Whitespace
		final String[] parts = line.split("\\s+");
		if (parts != null) {
			if (parts.length > 2) {
				// Breitengrad
				final String latitudeString = parts[0].trim( );
				// Längengrad
				final String longitudeString = parts[1].trim( );

				// Ortsname zusammenbauen
				String cityName = parts[2];
				for (int i = 3; i < parts.length; i++) {
					cityName += " " + parts[i];
				}

				// Längengrad und Breitengrad parsen
				final Coordinate latitude = CoordinateParser
						.parseLatitude(latitudeString);
				final Coordinate longitude = CoordinateParser
						.parseLongitude(longitudeString);

				return new WorldCoordinates(latitude, longitude, cityName
						.trim( ));
			}
			else if (parts.length >= 1) {
				// Ortsname zusammenbauen
				String cityName = parts[0];
				for (int i = 1; i < parts.length; i++) {
					cityName += " " + parts[i];
				}
				// Nur die Stadt angegeben
				return WorldCoordinates.getCoordinatesForCityName(cityName);
			}
			else {
				throw new ParseException("Keine Koordinaten angegeben", -1);
			}
		}
		else {
			throw new ParseException("Koordinaten sind fehlerhaft.", -1);
		}
	}

	/**
	 * Erstellt aus den Wegpunkten eine Liste in Dezimaler Darstellung
	 * zum einzeichnen in das SVG
	 * 
	 * @param from
	 * @param to
	 * @param svg
	 */
	private static void waypoints2Polylist(final WorldCoordinates from,
			final WorldCoordinates to, final SVGEmitter svg) {

		List<Point2D.Double> polyList = new ArrayList<Point2D.Double>( );

		// Wegpunkte im Abstand von 100km erstellen
		final List<WorldCoordinates> calc = from.calculateWaypoints(to, 100);

		double lastLongitude = 0;
		for (WorldCoordinates coord : calc) {
			double longitude = coord.getLongitude( ).asDecimalDegree( );
			double latitude = -coord.getLatitude( ).asDecimalDegree( );

			if (longitude < -180) {
				longitude = 360 + longitude;
			}

			// Vorzeichenwechsel beachten die nicht im Bereich um 0
			// stattfinden
			// Liste wechseln, damit kein Strich quer über die Karte
			// gezeichnet
			// wird.
			if (((longitude < -1 && lastLongitude > 1) || (lastLongitude < -1 && longitude > 1))
					&& Math.signum(longitude) + Math.signum(lastLongitude) == 0) {
				svg.addPolyList(polyList);
				polyList = new ArrayList<Point2D.Double>( );
			}

			polyList.add(new Point2D.Double(longitude, latitude));
			lastLongitude = longitude;
		}
		svg.addPolyList(polyList);
	}

	/**
	 * Berechnen die Fakultät für den angegebenen Wert.
	 * 
	 * @param value
	 *            n
	 * @return n!
	 */
	private static int fakultät(int value) {
		if (value == 0 || value == 1) {
			return value;
		}
		return value * fakultät(value - 1);
	}

	private static List<WorldCoordinates> getShortestRoute(
			final WorldCoordinates src, final WorldCoordinates target,
			final Set<List<WorldCoordinates>> permutationSet) {
		List<WorldCoordinates> bestList = null;
		double shortestDistance = -1;
		for (List<WorldCoordinates> currentList : permutationSet) {
			double distance = src.distanceTo(currentList.get(0));
			for (int j = 0; j < currentList.size( ) - 1; j++) {
				final WorldCoordinates first = currentList.get(j);
				final WorldCoordinates second = currentList.get(j + 1);
				distance += first.distanceTo(second);
			}
			distance += currentList.get(currentList.size( ) - 1).distanceTo(
					target);

			if (shortestDistance == -1 || distance < shortestDistance) {
				shortestDistance = distance;
				bestList = currentList;
			}
		}
		return bestList;
	}

	private static Set<List<WorldCoordinates>> createPermutations(
			final List<WorldCoordinates> list) {
		final List<WorldCoordinates> permutationList = new ArrayList<WorldCoordinates>(
				list.subList(2, list.size( )));

		// Alle Permutationen erstellen
		final Set<List<WorldCoordinates>> permutationSet = new HashSet<List<WorldCoordinates>>( );
		int destSize = fakultät(permutationList.size( ));

		// Eigentlich müßte hier ein ordentlicher Algorithmus rein ;)
		while (permutationSet.size( ) < destSize) {
			Collections.shuffle(permutationList);
			permutationSet
					.add(new ArrayList<WorldCoordinates>(permutationList));
		}
		return permutationSet;
	}

	public static void main(String[] args) throws IOException {
		final File f = new File("city.list");
		final List<WorldCoordinates> list = read(f);

		if (list == null || list.size( ) < 2) {
			System.out
					.println("Es müssen mindestens 2 Koordinaten oder Städte angegeben werden.");
		}

		final WorldCoordinates src = list.get(0);
		final WorldCoordinates target = list.get(1);

		List<WorldCoordinates> bestList = list;

		// Wenn mehr als zwei Koordinaten gegeben sind,
		// dann kürzeste Route ermitteln, die von den Start- zu den
		// Endkoordinaten über die übrigen Koordinaten führt.
		if (list.size( ) > 2) {
			// Permutationen der Liste (ohne Start und Ende)
			// erstellen.
			final Set<List<WorldCoordinates>> permutationSet = createPermutations(list);

			// Kürzeste Route ermitteln
			final List<WorldCoordinates> tempList = getShortestRoute(src,
					target, permutationSet);
			if (tempList != null) {
				// Start und Endpunkt einfügen
				tempList.add(0, src);
				tempList.add(target);

				bestList = tempList;
			}
		}

		SVGEmitter svg = new SVGEmitter( );
		// Route in das SVG Bild einzeichnen
		for (int j = 0; j < bestList.size( ) - 1; j++) {
			final WorldCoordinates first = bestList.get(j);
			final WorldCoordinates second = bestList.get(j + 1);

			// Start und Endpunkt einzeichnen
			boolean isFirst = (j == 0);
			boolean isLast = (j == (bestList.size( ) - 2));
			if (isFirst) {
				svg.addStart(first);
			}
			else {
				svg.addCross(first);
			}
			if (isLast) {
				svg.addEnd(second);
			}
			else {
				svg.addCross(second);
			}

			// Beste Strecke zwischen den Punkte einzeichnen
			waypoints2Polylist(first, second, svg);
		}

		svg.save(new File("city.svg"));
	}

}

Java:
package de.tutorials.quiz10;

/**
 * Himmelsrichtungen.
 * 
 * @author zeja
 */
public enum Direction {
	WEST {

		@Override
		public String toString() {
			return "W";
		}
	},
	EAST {

		@Override
		public String toString() {
			return "E";
		}
	},
	NORTH {

		@Override
		public String toString() {
			return "N";
		}
	},
	SOUTH {

		@Override
		public String toString() {
			return "S";
		}
	};
}

Java:
package de.tutorials.quiz10;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

/**
 * Koordinaten auf der Erde in Dezimaler und Sexagesimaler
 * Repräsentation.
 * 
 * @author zeja
 */
public class Coordinate {
	
	/**
	 * Keine Angabe für eine Komponente in Sexagesimaler Darstellung
	 */
	public static final int NONE = 0;
	/**
	 * Eine halbe Sekunde in Sexagesimaler Darstellung
	 */
	private static final double HALF_SECOND = 1. / 7200.;

	/**
	 * 60 als BigDecimal
	 */
	private static final BigDecimal SIXTY = BigDecimal.valueOf(60.0);
	/**
	 * Maximale Genauigkeit beim Runden
	 */
	private static final MathContext MC = new MathContext(34,
			RoundingMode.HALF_UP);

	/**
	 * Sexagesimal Grad °
	 */
	private final int degree;
	/**
	 * Sexagesimal Minuten '
	 */
	private final int minutes;
	/**
	 * Sexagesimal Sekunden "
	 */
	private final int seconds;
	/**
	 * Sexagesimal Richtung
	 */
	private final Direction direction;
	/**
	 * Dezimale Darstellung in Grad
	 */
	private final double decimalDegree;
	/**
	 * Dezimale Darstellung in Radian
	 */
	private final double decimalRadian;

	/**
	 * Erstellt eine Koordinate durch Angabe der Position in Dezimaler
	 * Darstellung.
	 * 
	 * @param decimal
	 *            Dezimale Darstellung der Koordinaten. Für
	 *            Breitengrade steht eine Negative Zahl für die
	 *            Richtung West und für einen Längengrad für Süd.
	 * @param latitude
	 *            <code>true</code> Wenn es sich um die Koordinaten
	 *            für einen Breitengrad handelt.
	 */
	public Coordinate(double decimal, boolean latitude) {
		this.decimalDegree = decimal;
		if (latitude) {
			if (this.decimalDegree < 0) {
				this.direction = Direction.WEST;
			}
			else {
				this.direction = Direction.EAST;
			}
		}
		else {
			if (this.decimalDegree < 0) {
				this.direction = Direction.SOUTH;
			}
			else {
				this.direction = Direction.NORTH;
			}
		}

		// Decimal in °'" umrechnen
		double d = Math.abs(this.decimalDegree);
		d += HALF_SECOND; // add a second for rounding
		this.degree = (int) Math.floor(d);
		this.minutes = (int) Math.floor((d - this.degree) * 60.0);
		this.seconds = (int) Math
				.floor((d - this.degree - this.minutes / 60.0) * 3600.0);

		this.decimalRadian = Math.toRadians(this.decimalDegree);
	}

	/**
	 * Erstellt Koordinaten über die Sexagesimale Darstellung.
	 * @param degree
	 *            Sexagesimal Grad
	 * @param minutes
	 *            Sexagesimal Minuten
	 * @param seconds
	 *            Sexagesimal Sekunden
	 * @param direction
	 *            Richtung (Norden, Osten, Westen, Süden)
	 */
	public Coordinate(int degree, int minutes, int seconds, Direction direction) {
		this.degree = degree;
		this.minutes = minutes;
		this.seconds = seconds;
		this.direction = direction;

		this.decimalDegree = sexagesimalToDecimalDegree( );
		this.decimalRadian = Math.toRadians(this.decimalDegree);
	}

	/**
	 * Rechnet die Sexagesimale Darstellung in die Dezimale
	 * Darstellung um.
	 * 
	 * @return
	 */
	private double sexagesimalToDecimalDegree() {
		BigDecimal value = BigDecimal.valueOf(seconds).divide(SIXTY, MC).add(
				BigDecimal.valueOf(minutes)).divide(SIXTY, MC).add(
				BigDecimal.valueOf(degree));

		if (direction == Direction.WEST || direction == Direction.SOUTH) {
			value = value.negate( );
		}
		return value.doubleValue( );
	}

	/**
	 * Gibt die Koordinaten in Dezimal als Radian zurück.
	 * 
	 * @return
	 */
	public double asDecimalRadian() {
		return this.decimalRadian;
	}

	/**
	 * Gibt die Koordinaten in Dezimal als Grad zurück.
	 * 
	 * @return
	 */
	public double asDecimalDegree() {
		return decimalDegree;
	}

	@Override
	public String toString() {
		final StringBuilder b = new StringBuilder( );
		b.append(degree).append("°");
		b.append(minutes).append("'");
		b.append(seconds).append("\"");
		b.append(direction);

		return b.toString( );
	}

}

Java:
package de.tutorials.quiz10;

import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parser für Koordinaten (Längengrad oder Breitengrad) aus einem
 * String in Sexagesimaler Darstellung.
 * 
 * @author zeja
 */
public final class CoordinateParser {

	/**
	 * Pattern zum parsen von Sexagesimalen Grad: 145°
	 */
	private static final String DEGREE_REGEX = "([0-9]*)°";
	/**
	 * Pattern zum parsen von Sexagesimalen Minuten: 65'
	 */
	private static final String MINUTES_REGEX = "(?:([0-9]*)')?";
	/**
	 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
	 */
	private static final String SECONDS_REGEX = "(?:([0-9]*)(?:''|\"))?";
	/**
	 * Himmelsrichtung Längengrad
	 */
	private static final String LONGITUDE_DIRECTION_REGEX = "([OEW])";
	/**
	 * Himmelsrichtung Breitengrad
	 */
	private static final String LATITUDE_DIRECTION_REGEX = "([NS])";

	/**
	 * Pattern zum Parsen von Breitengraden.
	 */
	private static final Pattern LATITUDE_PATTERN = Pattern
			.compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
					+ LATITUDE_DIRECTION_REGEX);

	/**
	 * Pattern zum Parsen von Längengraden.
	 */
	private static final Pattern LONGITUDE_PATTERN = Pattern
			.compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
					+ LONGITUDE_DIRECTION_REGEX);

	private CoordinateParser() {
		throw new AssertionError( );
	}

	/**
	 * Parst einen Breitengrad der Form<br>
	 * G°M'S""(OEW)<br>
	 * Die Formen<br>
	 * G°(OEW)<br>
	 * G°M'(OEW)<br>
	 * sind ebenfalls erlaubt.
	 * 
	 * @param strg
	 * @return Die geparsten Koordinaten
	 * @throws ParseException
	 *             Wenn eine Fehler beim Parsen aufgetreten ist.
	 */
	public static Coordinate parseLatitude(final String strg)
			throws ParseException {
		return parseCoordinates(strg, LATITUDE_PATTERN);
	}

	/**
	 * Parst einen Längengrad der Form<br>
	 * G°M'S"(NS)<br>
	 * Die Formen<br>
	 * G°(NS)<br>
	 * G°M'(NS)<br>
	 * sind ebenfalls erlaubt.
	 * 
	 * @param strg
	 * @return Die geparsten Koordinaten
	 * @throws ParseException
	 *             Wenn eine Fehler beim Parsen aufgetreten ist.
	 */
	public static Coordinate parseLongitude(final String strg)
			throws ParseException {
		return parseCoordinates(strg, LONGITUDE_PATTERN);
	}

	private static Coordinate parseCoordinates(final String strg,
			final Pattern pattern) throws ParseException {
		if (strg == null) {
			throw new ParseException("Keine Koordinaten gegeben.", -1);
		}
		final Matcher matcher = pattern.matcher(strg);
		if (matcher.matches( )) {
			if (matcher.groupCount( ) == 4) {
				// Grad
				String tmp = matcher.group(1);
				int degree = Integer.parseInt(tmp);

				// Optional Minuten
				tmp = matcher.group(2);
				int minutes = Coordinate.NONE;
				if (tmp != null) {
					minutes = Integer.parseInt(tmp);
				}

				// Optinal Sekunden
				tmp = matcher.group(3);
				int seconds = Coordinate.NONE;
				if (tmp != null) {
					seconds = Integer.parseInt(tmp);
				}

				// Richtung
				tmp = matcher.group(4);
				final Direction direction;
				if (tmp.equals("N")) {
					direction = Direction.NORTH;
				}
				else if (tmp.equals("S")) {
					direction = Direction.SOUTH;
				}
				else if (tmp.equals("E") || tmp.equals("O")) {
					direction = Direction.EAST;
				}
				else if (tmp.equals("W")) {
					direction = Direction.WEST;
				}
				else {
					direction = null;
				}
				return new Coordinate(degree, minutes, seconds, direction);
			}
			else {
				throw new ParseException(
						"Die Koordinaten-Darstellung ist fehlerhaft: " + strg,
						-1);
			}
		}
		else {
			throw new ParseException(
					"Die Koordinaten-Darstellung ist fehlerhaft: " + strg, -1);
		}
	}

}

Java:
package de.tutorials.quiz10;

import static java.lang.Math.PI;
import static java.lang.Math.asin;
import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

/**
 * http://www.movable-type.co.uk/scripts/latlong.html
 * 
 * @author zeja
 */
public class WorldCoordinates {

	private static final double EQUATORIAL_RADIUS_KM = 6378.137;// km
	private static final double TWO_PI = 2.0 * PI;

	private final Coordinate latitude;
	private final Coordinate longitude;
	private final String city;

	public WorldCoordinates(Coordinate latitude, Coordinate longitude,
			String city) {
		this.latitude = latitude;
		this.longitude = longitude;
		this.city = city;
	}

	public double distanceTo(WorldCoordinates other) {
		final double radLat1 = getLatitude( ).asDecimalRadian( );
		final double radLat2 = other.getLatitude( ).asDecimalRadian( );

		final double radLong1 = getLongitude( ).asDecimalRadian( );
		final double radLong2 = other.getLongitude( ).asDecimalRadian( );

		final double R = EQUATORIAL_RADIUS_KM; // km
		final double dLat = radLat2 - radLat1;
		final double dLon = radLong2 - radLong1;
		final double dLatHalf = dLat / 2;
		final double dLonHalf = dLon / 2;
		final double a = sin(dLatHalf) * sin(dLatHalf) + cos(radLat1)
				* cos(radLat2) * sin(dLonHalf) * sin(dLonHalf);
		final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
		final double d = R * c;

		return d;
	}

	private double toBearing(double value) {
		return (toDegrees(value) + 360) % 360;
	}

	private double getBearing(WorldCoordinates other) {
		final double radLat1 = getLatitude( ).asDecimalRadian( );
		final double radLat2 = other.getLatitude( ).asDecimalRadian( );

		final double radLong1 = getLongitude( ).asDecimalRadian( );
		final double radLong2 = other.getLongitude( ).asDecimalRadian( );

		final double dLon = radLong2 - radLong1;

		final double y = sin(dLon) * cos(radLat2);
		final double x = cos(radLat1) * sin(radLat2) - sin(radLat1)
				* cos(radLat2) * cos(dLon);
		final double phi = toBearing(atan2(y, x));

		return phi;
	}

	public List<WorldCoordinates> calculateWaypoints(WorldCoordinates other,
			int steps) {
		final List<WorldCoordinates> waypoints = new ArrayList<WorldCoordinates>( );

		final double R = EQUATORIAL_RADIUS_KM;

		final double radLat1 = getLatitude( ).asDecimalRadian( );
		final double radLong1 = getLongitude( ).asDecimalRadian( );
		final double phi = toRadians(getBearing(other));

		final double d = distanceTo(other);

		waypoints.add(this);
		for (int i = 0; i < (int) d; i += steps) {
			final double dR = i / R;

			final double lat2 = asin(sin(radLat1) * cos(dR) + cos(radLat1)
					* sin(dR) * cos(phi));
			double lon2 = radLong1
					+ atan2(sin(phi) * sin(dR) * cos(radLat1), cos(dR)
							- sin(radLat1) * sin(lat2));
			// normalise to -180...+180
			lon2 = ((lon2 + PI) % TWO_PI) - PI;

			waypoints.add(new WorldCoordinates(new Coordinate(toDegrees(lat2),
					true), new Coordinate(toDegrees(lon2), false), ""));
		}
		waypoints.add(other);
		return waypoints;
	}

	/**
	 * Name der Stadt für diese Koordinaten.
	 * 
	 * @return
	 */
	public String getCity() {
		return city;
	}

	/**
	 * Breitengrad
	 * 
	 * @return
	 */
	public Coordinate getLatitude() {
		return latitude;
	}

	/**
	 * Längengrad.
	 * 
	 * @return
	 */
	public Coordinate getLongitude() {
		return longitude;
	}

	/**
	 * Benutzt die Google Maps API um für den Namen einer Stadt die
	 * Koordinaten abzurufen.
	 * 
	 * @param cityName
	 *            Der Name der Stadt
	 * @return Die Koordinaten für die Stadt
	 * @throws ParseException
	 *             Wenn die Koordinaten nicht ermittelt werden
	 *             konnten.
	 */
	public static WorldCoordinates getCoordinatesForCityName(
			final String cityName) throws ParseException {
		final String urlString = "http://maps.google.com/maps/geo?q="
				+ cityName + "&output=xml&sensor=true_or_false&key=abcdefg";

		try {
			final URL url = new URL(urlString);
			final URLConnection urlConnection = url.openConnection( );
			final InputStream in = urlConnection.getInputStream( );
			final BufferedReader br = new BufferedReader(new InputStreamReader(
					in));
			try {
				String line = null;
				while ((line = br.readLine( )) != null) {
					// Eigentlich mit nem XML Parser lesen. Aber so
					// gehts erstmal auch.
					if (line.contains("<coordinates>")) {
						final int start = line.indexOf("<coordinates>");
						final int end = line.indexOf("</coordinates>");

						if (start != -1 && end != -1) {
							// Koordinaten auslesen
							final String[] split = line.substring(
									start + "<coordinates>".length( ), end)
									.split(",");

							if (split != null && split.length > 1) {
								final Coordinate longitude = new Coordinate(
										Double.parseDouble(split[0]), false);
								final Coordinate latitude = new Coordinate(
										Double.parseDouble(split[1]), true);

								return new WorldCoordinates(latitude,
										longitude, cityName);
							}
						}
					}
				}
				throw new ParseException("Koordinaten für die Stadt: "
						+ cityName + " konnten nicht ermittelt werden.", -1);
			}
			finally {
				br.close( );
			}
		}
		catch (MalformedURLException e) {
			throw new ParseException(e.getMessage( ), -1);
		}
		catch (IOException e) {
			throw new ParseException(e.getMessage( ), -1);
		}
	}

	@Override
	public String toString() {
		return latitude + " " + longitude + " " + city;
	}

}

Java:
package de.tutorials.quiz10;

import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Emitter für die Erstellung von SVG-Dateien.
 * 
 * @author zeja
 */
public class SVGEmitter {

	/**
	 * Zeilenumbruch.
	 */
	private static final String LINE_SEP = System.getProperty("line.separator");

	/**
	 * Liste von Elementen für die SVG-Datei.
	 */
	private final List<SVGPart> svgList = new ArrayList<SVGPart>( );

	/**
	 * Speicher des SVG in eine Datei.
	 * 
	 * @param file
	 *            Die Datei in die gespeichert werden soll.
	 * @throws IOException
	 *             Wenn ein Fehler beim Speichern auftritt.
	 */
	public void save(File file) throws IOException {
		final FileWriter w = new FileWriter(file);
		try {
			w.write(createSVGContent( ));
		}
		finally {
			w.close( );
		}
	}

	/**
	 * Erstellt den Inhalt der SVG Datei.
	 * 
	 * @return
	 */
	private String createSVGContent() {
		final StringBuilder b = new StringBuilder( );
		b.append("<?xml version=\"1.0\" ?>").append(LINE_SEP);
		b.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"").append(
				LINE_SEP);
		b.append("   \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">")
				.append(LINE_SEP);
		b.append("<svg version=\"1.1\"").append(LINE_SEP);
		b
				.append(
						"    xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"")
				.append(LINE_SEP);
		b
				.append(
						"    width=\"1024\" height=\"512\" stroke=\"red\" fill=\"none\"")
				.append(LINE_SEP);
		b.append("    viewBox=\"-180 -90 360 180\" stroke-width=\"0.3\">")
				.append(LINE_SEP);
		b.append("   ").append(LINE_SEP);
		b.append("    <defs>").append(LINE_SEP);
		b.append("        <g id='Kreuz'>").append(LINE_SEP);
		b.append("           <line x1=\"-1\" y1=\"-1\" x2=\"1\" y2=\"1\" />")
				.append(LINE_SEP);
		b.append("           <line x1=\"-1\" y1=\"1\" x2=\"1\" y2=\"-1\" />")
				.append(LINE_SEP);
		b.append("       </g>").append(LINE_SEP);

		b.append("        <g id='Start'>").append(LINE_SEP);
		b
				.append(
						"           <polygon fill=\"red\" stroke=\"red\" points=\"0 0, 0 -2, 1 -1, 0 -1\" />")
				.append(LINE_SEP);
		b.append("        </g>").append(LINE_SEP);

		b.append("        <g id='Ende'>").append(LINE_SEP);
		b
				.append(
						"           <polygon fill=\"yellow\" stroke=\"yellow\" points=\"0 0, 0 -2, -1 -1, 0 -1\" />")
				.append(LINE_SEP);
		b.append("        </g>").append(LINE_SEP);

		b.append("   </defs>").append(LINE_SEP);
		b.append("  ").append(LINE_SEP);
		b
				.append(
						"   <image x=\"-180\" y=\"-90\" width=\"360\" height=\"180\" xlink:href=\"http://veimages.gsfc.nasa.gov/2430/land_ocean_ice_2048.jpg\" />")
				.append(LINE_SEP);
		b.append("   ").append(LINE_SEP);

		for (SVGPart part : svgList) {
			b.append("    ").append(part.emit( )).append(LINE_SEP);
		}

		b.append("</svg>").append(LINE_SEP);

		return b.toString( );
	}

	/**
	 * Erstellt ein Kreuz an den gegebenen Koordinaten und eine
	 * Beschriftung.
	 * 
	 * @param coordinates
	 */
	public void addCross(final WorldCoordinates coordinates) {
		this.svgList.add(new Text(coordinates));
		this.svgList.add(new Cross(coordinates));
	}

	/**
	 * Erstellt eine Startfahne an den gegebenen Koordinaten und eine
	 * Beschriftung.
	 * 
	 * @param from
	 */
	public void addStart(final WorldCoordinates from) {
		svgList.add(new Text(from));
		svgList.add(new Start(from));
	}

	/**
	 * Erstellt eine Zielfahne an den gegebenen Koordinaten und eine
	 * Beschriftung.
	 * 
	 * @param to
	 */
	public void addEnd(final WorldCoordinates to) {
		svgList.add(new Text(to));
		svgList.add(new Ende(to));
	}

	/**
	 * Liste von Punkten, aus denen einen Linie erstellt wird.
	 * @param polyList
	 */
	public void addPolyList(final List<Point2D.Double> polyList) {
		this.svgList.add(new PolyList(polyList));
	}

	private static abstract class SVGPart {

		public abstract String emit();

	}

	private static class PolyList extends SVGPart {

		private final List<Point2D.Double> polyList;

		public PolyList(final List<Point2D.Double> polyList) {
			this.polyList = polyList;
		}

		public String emit() {
			final StringBuilder b = new StringBuilder( );

			b.append("<polyline stroke=\"red\" stroke-width=\"0.3px\" ");
			b.append("points=\"");

			for (final Iterator<Point2D.Double> it = polyList.iterator( ); it
					.hasNext( );) {
				final Double point = it.next( );
				b.append(point.x).append(" ").append(point.y);
				if (it.hasNext( )) {
					b.append(", ");
				}
			}

			b.append("\" />");

			return b.toString( );
		}

	}

	private static class XLink extends SVGPart {

		private final WorldCoordinates coordinates;
		private final String label;

		public XLink(final WorldCoordinates coordinates, final String label) {
			this.coordinates = coordinates;
			this.label = label;
		}

		@Override
		public String emit() {
			final StringBuilder b = new StringBuilder( );
			b.append("<use xlink:href=\"#").append(label).append(
					"\" transform=\"translate(").append(
					coordinates.getLongitude( ).asDecimalDegree( )).append(" ")
					.append(-coordinates.getLatitude( ).asDecimalDegree( ))
					.append(")\" />");
			return b.toString( );
		}
	}

	private static class Start extends XLink {

		public Start(final WorldCoordinates coordinates) {
			super(coordinates, "Start");
		}

	}

	private static class Ende extends XLink {

		public Ende(final WorldCoordinates coordinates) {
			super(coordinates, "Ende");
		}

	}

	private static class Cross extends XLink {

		public Cross(final WorldCoordinates coordinates) {
			super(coordinates, "Kreuz");
		}
	}

	private static class Text extends SVGPart {

		private final WorldCoordinates coordinates;

		public Text(final WorldCoordinates coordinates) {
			this.coordinates = coordinates;
		}

		@Override
		public String emit() {
			final StringBuilder b = new StringBuilder( );
			b.append("<text x=\""
					+ (coordinates.getLongitude( ).asDecimalDegree( ) - 0)
					+ "\" y=\""
					+ (-coordinates.getLatitude( ).asDecimalDegree( ) - 2)
					+ "\"");
			b.append(" font-family=\"verdana\" font-size=\"3px\">");
			b.append(coordinates.getCity( ));
			b.append("</text>");
			return b.toString( );
		}
	}

}
 
Hallo zeja,

ich hab mir heute mal deine Implementierung angeschaut und muss sagen: wow! :D Du hast dir ja ganz schön viel Mühe gegeben. Eine sehr schöne Demonstration von OOP. Du bist auch die einzige, die die Berechnung der kürzesten Route mit drin hat. Zwar mit Brute-Force, aber immerhin. Und die Fähnchen bei der SVG-Ausgabe sind auch nett :)

Auf die Schnelle ist mir nur ein Kritikpunkt aufgefallen:
Java:
    /**
     * Eine Sekunde in Sexagesimaler Darstellung
     */
    private static final double SECOND = 1. / 7200.;
Wäre eine Sekunde nicht eher 1/3600? Also entweder stimmt der Wert nicht oder der Kommentar und die Bezeichnung sind falsch.

Grüße,
Matthias
 
Zuletzt bearbeitet:
Thx :)

Das mit der Sekunde war mir den abend bei der Abgabe auch noch aufgefallen... dann dacht ich mir "wird schon keiner merken" ;) Nee beim Copy&Paste aus dem Javascript ist mir das 1/2 verloren gegangen. Es muss natürlich ne halbe Sekunde sein. Das macht fürs Runden ja auch Sinn. Habs mal korrigiert.

Das mit dem finden der kürzesten Route hatte ich noch keine Lust ordentlich zu machen ;)
 
Zurück