Benutzer:Erik/Extension:Häfen in der Nähe

aus SkipperGuide, dem Online-Revierführer über die Segelreviere der Welt.
Zur Navigation springen Zur Suche springen

Hier befindet sich ein Vorschlag für eine Extension zur Anzeige von Häfen in der Nähe eines gebenen Punkts. Kommentare, Bugs und Verbesserungsvorschläge bitte auf der Diskussionsseite

Nach dem Erstellen der Navigationsleisten, habe ich überlegt, wie man sinnvolle Navigationsleisten auch automatisch erstellen kann. Eine Möglichkeit habe ich einmal in der folgenden Extension ausprobiert. Sie arbeitet als Parser Hook und listet alle Häfen in der Nähe einer gegebenen geografischen Position auf. Die Position, sowie die maximale Distanz und die maximale Anzahl der anzuzeigenden Häfen kann als Parameter übergeben werden. Die bekannten Häfen werden auf das Zutreffen der Bedingungen untersucht und dann nach Entfernung und Alphabet geordnet angezeigt. Die Extension greift auf das kml-File zurück, welches von der GoogleEarthExport Extension erzeugt wird.

Beispiel

Im folgenden ein Beispiel, wie man die Extension in Verbindung mit der Vorlage Navigationsleiste nutzen kann:

{{Navigationsleiste
|TITEL=Häfen in der Umgebung
|INHALT=
<nearbyplaces lat="54.333333" lon="10.133333" dist="100" max="10"/>
}}

Das Ergebnis des oben aufgeführten Codes

Bedienung

Das Aufrufen der Orte geschieht mit dem Tag <nearbyplaces/> oder auch <nearbyplaces></nearbyplaces>. Ohne die Angabe weiterer Parameter werden die zehn am nächsten an Kiel gelegenen Häfen, die maximal 100sm von Kiel entfernt sein dürfen. Diese Darstellung kann durch die folgenden Parameter verändert werden:

lon
Angabe der geografischen Länge des Ausgangspunkts
lat
Angabe der geografischen Breit des Ausgsngspunkts
dist
Der maximal Abstand vom Ausgangspunkt, den ein Hafen haben darf
showdist
Mit showdist="yes" wird die Anzeige der Distanzen aktiviert
max
Die maximale Anzahl von angezeigten Häfen

Diese Werte werden als Attribute des Tags gesetzt. Beispiel:

<nearbyplaces lat="54.333333" lon="10.133333" dist="100" max="10"/>

Die Reihenfolge der Parameter ist dabei egal. Der Wert sollte immer in Anführungsstriche gesetzt sein. Wird ein Parameter ausgelassen, so wird der Default-Wert (s.o.) genommen.

Eine lauffähige Installation des Scripts gibt es hier. Dort kann auch mit den Einstellungen experimentiert werden.

Versionen

2007-03-04
Die Distanzanzeige ist nun per default deaktiviert. Sie kann über das Attribut showdist aktiviert werden.
2007-03-03
Fix für PHP4. Die benutzen DOM-Funktionen von PHP5 waren nicht rückwärtskompatibel. Die Umstellung auf DOM XML war zu aufwendig. Daher die 'saubere' Lösung: Erstellen eines Parsers.
2007-03-02
Initiale Version

Installation

Das folgende Script (NearbyPlacesExtension.php) muss im Extensions-Verzeichnis abgelegt und die Zeile require_once("./extensions/NearbyPlacesExtension.php"); muss zu der LocalSettings.php hinzugefügt werden.

<?php

// HISTORY
// 2007-03-04 : The display of the distance is no deactivated by default.
//              It can be activated using the showdist attribute.
// 2007-03-04 : Fix for PHP 4
//              Instead of DOM or DOM XML, the xml parser functions are used
// 2007-03-02 : Initial implemtation

// TODO
// - debug output
// - testing
// - lat and lon in degrees and minutes
// - improved retrival of places
// - error handling
// - config file
// - documentation
// - output data to be selected by attributes
// - error handling

// register the extension
$wgExtensionFunctions[] = "wfNearbyPlacesExtension";

// set the credit information
$wgExtensionCredits['parserhook'][] = array(

        'name' => 'Nearby Places',
        'author' => 'Erik Hansen',
        'url' => 'http://www.skipperguide.de/wiki/Benutzer:Erik/Extension:H%C3%A4fen_in_der_N%C3%A4he',
        'description' => 'Shows a list of places near a given place',

);

// initialize this extension
// register nearbyplaces as parser hook.
function wfNearbyPlacesExtension() {

        global $wgParser;
        $wgParser->setHook( "nearbyplaces", "npeShowNearbyPlaces" );

}

// the comparation function for sorting the places array
// this sort all places first by distance then by their name
function npeCmpPlacesByDist($a, $b) {

	if ($a["dist"] == $b["dist"]) {
		return strcmp($a["name"], $b["name"]);
	}
	return ($a["dist"] < $b["dist"]) ? -1 : 1;

}

// XML Parser Class
class npeCParser {

	var $places;
	var $tag;
	var $description;
	var $placename;
	var $coordinates;
	var $parser;

	// constructor	
	function npeCParser() {
		$this->places = array();
		$this->tag = "";
		$this->description = "";
		$this->placename = "";
		$this->coordinates = "";
	}

	// initialize variables for every new placemark
	// store the current tag for use within die characterData function
	function startElement($parser, $name, $attrs) {

		if ($name == "PLACEMARK") {
			$this->description = "";
			$this->placename = "";
			$this->coordinates = "";
		}
		$this->tag = $name;

	}

	// store the data from the closed placemark
	function endElement($parser, $name) {

		if ($name == "PLACEMARK") {
			$this->places[] = array ("name"        => trim($this->placename),
			                         "description" => trim($this->description),
			                         "coordinates" => trim($this->coordinates));
		}

	}

	// store the data of the tags
	function characterData($parser, $data) {

		switch ($this->tag) {
			case "NAME":
				$this->placename .= $data;
				break;
			case "DESCRIPTION":
				$this->description .= $data;
				break;
			case "COORDINATES":
				$this->coordinates .= $data;
				break;
		}

	}

	// parse the xml data
	function parse($xml) {

		$this->parser = xml_parser_create();
		xml_set_object($this->parser, $this);
		xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
		xml_set_element_handler($this->parser, "startElement", "endElement");
		xml_set_character_data_handler($this->parser, "characterData");
		xml_parse($this->parser, $xml);
		xml_parser_free($this->parser);

		return $this->places;

	}

}	

// this function 
function npeShowNearbyPlaces( $input, $argv, &$parser ) {

	// this is needed to use wiki syntax in the output
	global $wgOut;

	// get the supplied arguments
	if (isset($argv["lat"])) {
		$req_lat = $argv["lat"];
	} else {
		$req_lat = 54.333333;
	}

	if (isset($argv["lon"])) {
		$req_lon = $argv["lon"];
	} else {
		$req_lon = 10.133333;
	}

	if (isset($argv["dist"])) {
		$max_dist = $argv["dist"];
	} else {
		$max_dist = 100;
	}

	if (isset($argv["max"])) {
		$max_cnt = $argv["max"];
	} else {
		$max_cnt = 10;
	}

       $show_distance = false;
       if (isset($argv["showdist"])) {
                if ($argv["showdist"] == "yes") {
                        $show_distance = true;
                }
       }

	// initialize the array for the found places
	$nearby_places = array();

	// create a new dom instance and load the kml file into it
	$xml = file_get_contents("http://www.skipperguide.de/extension/GoogleEarthExport.php");

	// create a new Parser and parse the retrieved kml file
	$parser = new npeCParser();
	$places = $parser->parse($xml);

	// pick all placemarks from the kml-File
	// In case none is found we are done already.
	if (count($places) == 0)
		return ("Es wurden leider keine Häfen gefunden (0).");

	// parse through all palces from the kml file
	foreach ($places as $place) {

		list($lon, $lat) = sscanf($place["coordinates"], "%f,%f");
		// in case the difference in nm between the latitudes is 
	        // lager than the distance, we do not hat to proceed any
	        // further with this place
		if (abs($lat - $req_lat) >> ($max_dist / 3600.0)) {
			continue;
		}

		// calculate the distance between the two places
		// this is done by calculating the loxodromic distance
		// using the (Besteckrechnung nach Mittelbreite).
		// This should be sufficient for distances shorter than
		// 500nm.
		// In case the distance is larger than max_dist,
		// this place will be discarded.

		$b = $lat - $req_lat;
		$l = $lon - $req_lon;
		$pm = ($lat + $req_lat) / 2;
		$a = $l * cos($pm);
		$al = atan ($a / $b);
		$dist = abs(60 * $b / cos ($al));
		if ($dist > $max_dist) {
			continue;
		}

		// in case we have come that far, the place will be added
		// to the list of places within the requested range
		list($link) = sscanf($place["description"], "http://www.skipperguide.de/wiki/%s");
		$nearby_places[] = array ("lat" => $lat,
		                          "lon" => $lon,
		                          "dist" => $dist,
		                          "name" => $place["name"],
		                          "link" => $link);


	}

	// check wether any places have been found.
	if (count($nearby_places) == 0)
		return ("Es wurden leider keine Häfen gefunden (1).");

	// sort all places using npeCmpPlacesByDist for comparation
	// this sorts all places by distance. Places with the same
	// distancs are sorted by their name
	usort($nearby_places, "npeCmpPlacesByDist");

	// initialize the output string
	$output = "";

	// output all places up to max_cnt places ordered by distance an name
	$cnt = 0;
	foreach ($nearby_places as $place) {
 	
               $output .= "[[" . $place["link"] . "|" . $place["name"] . "]]";
               if ($show_distance == true) {
                       $output .= " (" . number_format($place["dist"], 0) . "sm)";
               }
               $output .= " |\n";

		$cnt++;
		if ($cnt >= $max_cnt) {
			break;
		}
	}

	// parse the output with the wiki parser, then return the result
	return ($wgOut->parse($output));

}

?>

ToDo

Verbesserte Abfrage der Orte
Bisher ruft das Script über http die KML-Datei des GoogleEarthExports ab. Da die Daten dafür allerdings ja auch auf dem Server liegen, ist es wohl besser direkt auf diese Daten zuzugreifen. Ist irgendwo dokumentiert, wie die Daten abgelegt sind?
Parameter
Die Usability bei den Parametern könnte deutlich erhöht werden:
  • Lon und Lat in Grad und Minuten übergeben
  • Plasibilitätstests auf die Parameter
Test mit verschiedenen Wiki-Versionen
Bisher wurde die Extension nur mit 1.6.0, 1.7.1, 1.9.3 getestet. Andere Versionen müssen noch getestet werden. Auch wurde bei den Tests nur PHP 5 verwendet. Läuft das Script auch mit PHP 4?
m.E. uninteressant. php7 ist eher die Frage.--Argonaut (Diskussion) 17:00, 8. Nov. 2017 (UTC)
Bisherige Testumgebungen:
  • MediaWiki: 1.6.0, PHP: 5.1.2 (apache2handler), MySQL: 5.0.18
  • MediaWiki: 1.7.1, PHP: 5.1.6 (cgi), MySQL: 4.0.24
  • MediaWiki: 1.9.3, PHP: 5.1.6 (apache2handler), MySQL: 5.0.27-log (Läuft noch nicht. Statt des Texts erscheinen wirre Zeichen beginnend mit UINQ)
Debugging
Zum besseren Debuggen müssten Debug-Ausgaben eingebaut werden.
Internationalisierung
Die Bildschirmausgaben im Fehlerfall, sowie die 'sm' sollten internationalisiert werden. Entsprechende Übersetzungen müssten eingefügt werden.
Config-File
Bisher ist alles hart rein gecodet. Veränderliche Einstellungen (z.B. der Linkt zum Downloaden der KML-Datei) sollten über Variablen in der LocalConfig.php oder über eine eigene Config-Datei einstellbar sein.
Dokumentation
Wie immer könnte die besser sein ;)
Fehlerbehandlungen
Müssen noch hinzu gefügt werden. Sind bisher noch nicht vorhanden.

Mathematisches

Verfahren

Breitenunterschied (b) in nm sind zunächst recht einfach zu berechnen. Er ergibt sich aus der Differenz (Δφ) von der Breiten von Ausgangspunkt zu Endpunkt. Der Längenunterschied (a) ist nicht mehr ganz so einfach zu berechnen, da eine Bogenminute nur am Äquator einer sm entpricht. Hier muss der Abweitung (Δλ) zwischen den beiden Punkten je nach Breite skaliert werden. Als Skalierungsfaktor ergibt sich der Cosinus der Breite. Da die beiden Punkte auf verschiedenen Breiten liegen können wird die mittlere Breite (Mittelbreite φM) dafür genommen. Somit ergibt sich aus der Abweitung (Δλ) der Längenunterschied (a). Längen- (a) und Breitenunterschied (b) bilden Gegenkathete (a) und Ankathete (b) eines rechtwinkligen Dreiecks. Der zu steuernde Kurs (α) ist der arctan von Längenunterschied (a) geteilt durch den Breitenunterschied (b). Die Distanz (Länge der Hypothenuse) ergibt sich dann aus dem Breitenunterschied (b) geteilt durch den cosinus der mittleren Breite (φM) skaliert mit 60, um von Grad auf sm umzurechnen.

Dieses Verfahren funktioniert nur bei kleineren Distanzen, bzw. Breitenunterschieden.

Formeln

Breitenunterschied
b = Δφ = φB - φA
Abweitung
Δλ = λB - λA
Mittelbreite
φM = ½ (φA + φB)
Längenunterschied
a = Δλ * cos(φM)
Winkel
α = arctan(a/b) = arctan((Δλ/Δφ) * cos(φM))
Distanz
d = 60 * b / cos(α)

Zum Nachlesen

  • Gunter Herdam: Astronomische Navigation, 3. Auflage September 2001, Eingenverlag, S. 101ff [1]
  • Werner Kumm, Hans-Dieter Lübbers, Harald Schultz: Sporthochseeschifferschein, 2. Auflage 1999, Delius Klasing, S. 16ff [2]
  • Karl-Richard Albrand, Hermann Junge: Formelsammlung Navigation, DSV Verlag, S. 14 [3]
  • Karl-Richard Albrand, Walter Stein: Nautische Tafeln und Formeln, DSV Verlag, S. 73 [4]