<?php
#-------------------------------------------------------------------------------
#	$Id: gen-karte-mit-markern.php,v 1.11 2024/08/15 01:43:53 wolf Exp $
#-------------------------------------------------------------------------------
#	Generiere Karte aus heruntergeladenen Kacheln und füge zwei Marker hinzu
#-------------------------------------------------------------------------------

# Zeige die Schritte der Berechnung

$VERBOSE = 1;

# Wenn $WEB_SERVER_SCRIPT = 1 gesetzt ist, werden die Koordinaten aus
# der Variable $_GET entnommen und die generierte Graphik mit passendem
# header() ausgegeben.

$WEB_SERVER_SCRIPT = 1;

# Wenn $IGNORE_MISSING_TILES = 0 gesetzt ist, wird bei Fehlern beim Tile-
# Download das Skript beendet.

$IGNORE_MISSING_TILES = 0;

# Bei Aufruf als Skript im Webserver werden die Koordinaten aus $_GET
# gelesen und das Karten-PNG mit einem passenden Header auf STDOUT geschrieben.
# Das verträgt sich natürlich *nicht* mit $VERBOSE.

if( $WEB_SERVER_SCRIPT ) $VERBOSE = 0;

#-------------------------------------------------------------------------------
#	Parameter (in Produktion von Kommandozeile oder aus Query-String)
#-------------------------------------------------------------------------------

$lon1 =  7;
$lat1 = 51;

$lon2 =  7.1;
$lat2 = 51.1;

$imagePath = "map.png";

#-------------------------------------------------------------------------------
#	Wenn Server-Script, nutze Query-Paramter
#-------------------------------------------------------------------------------
#	Da keine Fremdnutzung, ohne Sanity-Checks
#-------------------------------------------------------------------------------

if( $WEB_SERVER_SCRIPT ){

	$lon1 = @$_GET[ 'lon1' ] * 1;
	$lat1 = @$_GET[ 'lat1' ] * 1;

	$lon2 = @$_GET[ 'lon2' ] * 1;
	$lat2 = @$_GET[ 'lat2' ] * 1;
}

#-------------------------------------------------------------------------------
#	Wenn Kommandozeile, nutze Kommandozeilenattribute
#-------------------------------------------------------------------------------
#	Da keine Fremdnutzung, ohne Sanity-Checks
#-------------------------------------------------------------------------------

if( !$WEB_SERVER_SCRIPT ){

	$lon1 = str_replace( ',', '.', @$argv[ 1 ] ) * 1;
	$lat1 = str_replace( ',', '.', @$argv[ 2 ] ) * 1;

	$lon2 = str_replace( ',', '.', @$argv[ 3 ] ) * 1;
	$lat2 = str_replace( ',', '.', @$argv[ 4 ] ) * 1;
}

#-------------------------------------------------------------------------------
#	Der OSM-Tileserver lehnt Requests von PHP mit default-User-Agent ab.
#	Die Betreiber erwarten eine Identifikation mit Kontaktdaten.
#-------------------------------------------------------------------------------

ini_set( 'user_agent', 
	"Kartengenerator https://www.netzwolf.info/bastelkiste/gen-karte-mit-markern.php" );

#-------------------------------------------------------------------------------
#	Errorhandler
#-------------------------------------------------------------------------------

if( $VERBOSE ){

	header( "Status: 203 Non Autoritative" );
	header( "Content-Type: text/plain; charset=utf-8" );
}

function fatal( $message, $status = "400 Bad Request" ){

	global $VERBOSE;

	if( !$VERBOSE ){

		header( "Status: $status" );
		header( "Content-Type: text/plain; charset=utf-8" );
	}

	echo "$status\n";
	echo "Fataler Fehler: $message\n";
	exit( 1 );
}

#-------------------------------------------------------------------------------
#	Sanity: Koordinaten dürfen nicht gleich sein
#-------------------------------------------------------------------------------

if( abs( $lon1 - $lon2 ) < 1e-8 && abs( $lat1 - $lat2 ) < 1e-8 ){

	fatal( "Markerkoordinaten ($lon1, $lat1) und ($lon2, $lat2) dürfen nicht identisch sein." );
	exit( 1 );
}

#-------------------------------------------------------------------------------
#	Karten-Konfiguration
#-------------------------------------------------------------------------------

$MAP_WIDTH  = 600;
$MAP_HEIGHT = 600;

$MARGIN_X   = 20;	# Mindestabstand der Marker vom Rand der Karte
$MARGIN_Y   = 20;	# darf 0 sein.

#-------------------------------------------------------------------------------
#	Automatische Höhenkonfiguration
#-------------------------------------------------------------------------------
#	Wenn 0 oder FALSE, deaktiviert.
#-------------------------------------------------------------------------------

$MAP_MIN_HEIGHT = 300;

#-------------------------------------------------------------------------------
#	Tile-Konfiguration
#-------------------------------------------------------------------------------

$TILE_BASE_URL = "https://a.tile.openstreetmap.org/";

$TILE_WIDTH  = 256;
$TILE_HEIGHT = 256;

#-------------------------------------------------------------------------------
#	Marker-Konfiguration
#-------------------------------------------------------------------------------

$MARKER_PATH  = "./nadel-blau-26x30.png";

$MARKER_WIDTH  = 26;
$MARKER_HEIGHT = 30;

$HOTSPOT_X   =  0;	# Hotspot des Markers von links gezählt
$HOTSPOT_Y   = 30;	# Hotspot des Markers von oben gezählt

#-------------------------------------------------------------------------------
#	Für Marker verfügbarer Bereich in der Karte
#-------------------------------------------------------------------------------

$MAP_NETTO_WIDTH  = $MAP_WIDTH  - $MARKER_WIDTH  - 2 * $MARGIN_X;
$MAP_NETTO_HEIGHT = $MAP_HEIGHT - $MARKER_HEIGHT - 2 * $MARGIN_Y;

#-------------------------------------------------------------------------------
#	Mathematik: von WGS/Geo über Merkator-Projektion nach 0...1
#-------------------------------------------------------------------------------

function lonGeoToX( $lon ){

	$mlon = $lon * 3.1415926 / 180;

	return (1 + $mlon / 3.1415926 ) / 2;
}

function latGeoToY( $lat ){

	$phi  = $lat * 3.1415926 / 180;
	$mlat = log( tan( $phi ) + 1 / cos( $phi ));

	return ( 1 - $mlat / 3.1415926 ) / 2;
}

function log2( $x ){

	return log( $x ) / log( 2 );
}

function exp2( $x ){

	return exp( $x * log( 2 ));
}

#-------------------------------------------------------------------------------
#	Konvertiere Koordinaten von Geo nach Merkator-Projektion und 0...1 Unit
#-------------------------------------------------------------------------------

$x1 = lonGeoToX( $lon1 );
$y1 = latGeoToY( $lat1 );

$x2 = lonGeoToX( $lon2 );
$y2 = latGeoToY( $lat2 );

#-------------------------------------------------------------------------------
#	Zur Überprüfung vergleiche mit Abschnitt 4 von:
#	https://www.netzwolf.info/osm/tilebrowser.html?lat=51&lon=7&zoom=9
#-------------------------------------------------------------------------------

if( $VERBOSE ){

	echo "lon1:$lon1\n";
	echo "lat1:$lat1\n";
	echo "\n";

	echo "x1:$x1\n";
	echo "y1:$y1\n";
	echo "\n";

	echo "lon2:$lon2\n";
	echo "lat2:$lat2\n";
	echo "\n";

	echo "x2:$x2\n";
	echo "y2:$y2\n";
	echo "\n";
}

#-------------------------------------------------------------------------------
#	Abstand der Marker
#-------------------------------------------------------------------------------

$dx = abs( $x2 - $x1 );
$dy = abs( $y2 - $y1 );

#-------------------------------------------------------------------------------
#	Anpassung der Kartenhöhe
#-------------------------------------------------------------------------------

if( $MAP_MIN_HEIGHT ){

	if( $dx > 0 ){

		$reducedNettoHeight = max(
			floor( $MAP_NETTO_WIDTH * $dy / $dx ),
			$MAP_MIN_HEIGHT - $MARKER_HEIGHT - 2 * $MARGIN_Y );

		$reducedHeight =
			$reducedNettoHeight + $MARKER_HEIGHT + 2 * $MARGIN_Y;

		if( $reducedHeight < $MAP_HEIGHT ) {

			$MAP_HEIGHT       = $reducedHeight;
			$MAP_NETTO_HEIGHT = $reducedNettoHeight;
		}
	}
}

#-------------------------------------------------------------------------------
#	Bestimme den Zoom-Wert
#-------------------------------------------------------------------------------

$xscale = $MAP_NETTO_WIDTH  / $TILE_WIDTH  / ($dx + 1e-12);
$yscale = $MAP_NETTO_HEIGHT / $TILE_HEIGHT / ($dy + 1e-12);

$zoom = min( 19, floor( log2( min( $xscale, $yscale ))));

if( $VERBOSE ){

	echo "zoom:$zoom\n";
	echo "\n";
}

#-------------------------------------------------------------------------------
#	Bestimme die Position der Marker in Pixel vom linken und oberen Weltrand
#-------------------------------------------------------------------------------

$px1 = exp2( $zoom ) * $TILE_WIDTH  * $x1;
$py1 = exp2( $zoom ) * $TILE_HEIGHT * $y1;

$px2 = exp2( $zoom ) * $TILE_WIDTH  * $x2;
$py2 = exp2( $zoom ) * $TILE_HEIGHT * $y2;

#-------------------------------------------------------------------------------
#	Bestimme die Position der Karte in Pixel vom linken und oberen Weltrand
#-------------------------------------------------------------------------------

$map_center_x = ( $px1 + $px2 ) / 2;
$map_center_y = ( $py1 + $py2 ) / 2;

$map_left     = floor( $map_center_x - $MAP_WIDTH  / 2 );
$map_top      = floor( $map_center_y - $MAP_HEIGHT / 2 );

#-------------------------------------------------------------------------------
#	Erzeuge das Karten-Image
#-------------------------------------------------------------------------------

$mapImage = imagecreatetruecolor( $MAP_WIDTH, $MAP_HEIGHT );

if( !$mapImage ) fatal( "imagecreatetruecolor($MAP_WIDTH,$MAP_HEIGHT): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Lade das Marker-Image
#-------------------------------------------------------------------------------

$markerImage = imagecreatefrompng( $MARKER_PATH );

if( !$markerImage ) fatal( "imagecreatefrompng('$imagecreatefrompng'): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Abholen der Kacheln und Zeichnen in das Map-Image
#-------------------------------------------------------------------------------
#	XXX unvollständig, holt nur linke obere Karte und malt diese zentriert
#-------------------------------------------------------------------------------

$tile_base_x = floor( $map_left / $TILE_WIDTH  );
$tile_base_y = floor( $map_top  / $TILE_HEIGHT );

#-------------------------------------------------------------------------------
#	y-Schleife
#-------------------------------------------------------------------------------

for( $tile_y = $tile_base_y; $tile_y * $TILE_HEIGHT < $map_top + $MAP_HEIGHT; ++$tile_y ){

	for( $tile_x = $tile_base_x; $tile_x * $TILE_WIDTH < $map_left + $MAP_WIDTH; ++$tile_x ){

		#------------------------------------------------------
		#	Bestimme URL
		#------------------------------------------------------

		$selector = "{$zoom}/{$tile_x}/{$tile_y}";

		$tileUrl = "{$TILE_BASE_URL}{$selector}.png";

		if( $VERBOSE ){

			echo "selector: $selector\n";
			echo "tile-url: $tileUrl\n";
			echo "\n";
		}

		#------------------------------------------------------
		#	Download Tile
		#------------------------------------------------------

		$tileImage = imagecreatefrompng( $tileUrl );

		#------------------------------------------------------
		#	für den lokalen Test
		#------------------------------------------------------

		if( !$tileImage && $IGNORE_MISSING_TILES ) continue;

		#------------------------------------------------------
		#	Kachel nicht verfügbar
		#------------------------------------------------------

		if( !$tileImage ) fatal( "imagecreatefrompng('$tileUrl'): failed.", "502 Bad Gateway" );

		#------------------------------------------------------
		#	Zeichne Tile
		#------------------------------------------------------

		$tile_px = $tile_x * $TILE_WIDTH;
		$tile_py = $tile_y * $TILE_HEIGHT;

		$status = imagecopy( $mapImage, $tileImage,
			$tile_px - $map_left,
			$tile_py - $map_top,
			0, 0,
			$TILE_WIDTH,
			$TILE_HEIGHT );

		if( !$status ) fatal( "imagecopy(): failed.", "500 Internal Server Error" );
	}
}

#-------------------------------------------------------------------------------
#	Zeichne die Marker in die Karte
#-------------------------------------------------------------------------------

$destX = $px1 - $map_left - $HOTSPOT_X;
$destY = $py1 - $map_top  - $HOTSPOT_Y;

if( $VERBOSE ){

	echo "destX1: $destX\n";
	echo "destY1: $destY\n";
	echo "\n";
}

$status = imagecopy( $mapImage, $markerImage,
	floor( $destX ), floor( $destY ),
	0, 0,
	$MARKER_WIDTH,
	$MARKER_HEIGHT );

if( !$status ) fatal( "imagecopy(MARKER1): failed.", "500 Internal Server Error" );

$destX = $px2 - $map_left - $HOTSPOT_X;
$destY = $py2 - $map_top  - $HOTSPOT_Y;

if( $VERBOSE ){

	echo "destX2: $destX\n";
	echo "destY2: $destY\n";
	echo "\n";
}

$status = imagecopy( $mapImage, $markerImage,
	floor( $destX ), floor( $destY ),
	0, 0,
	$MARKER_WIDTH,
	$MARKER_HEIGHT );

if( !$status ) fatal( "imagecopy(MARKER2): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Copyright-Hinweis-Parameter
#-------------------------------------------------------------------------------

$copyrightText   = "Karte (c) OpenStreetMap-Mitwirkende";
$copyrightFont   = 4;
$copyrightWidth  = 282;
$copyrightHeight = 16;

$copyrightX = $MAP_WIDTH  - $copyrightWidth;
$copyrightY = $MAP_HEIGHT - $copyrightHeight;

#-------------------------------------------------------------------------------
#	Definiere die Copyright-Farben
#-------------------------------------------------------------------------------

$copyrightBgColor  = imagecolorallocate( $mapImage, 208, 208, 208 );

if( $copyrightBgColor === FALSE )
	fatal( "imagecolorallocate(copyrightBgColor): failed.", "500 Internal Server Error" );

$copyrightTextColor  = imagecolorallocate( $mapImage, 0, 0, 0 );

if( $copyrightTextColor === FALSE )
	fatal( "imagecolorallocate(copyrightTextColor): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Zeichne den Copyright-Hintergrund
#-------------------------------------------------------------------------------

$status = imagefilledrectangle( $mapImage,
		$copyrightX, $copyrightY,
		$MAP_WIDTH, $MAP_HEIGHT,
		$copyrightBgColor );

if( !$status ) fatal( "imagefilledrectangle(copyright): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Schreibe den Copyright-Text
#-------------------------------------------------------------------------------

$status = imagestring( $mapImage,
		$copyrightFont,
		$copyrightX, $copyrightY,
		$copyrightText,
		$copyrightTextColor );

if( !$status ) fatal( "imagestring(copyright): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Wenn Server-Skript, schreibe mit Header auf STDOUT
#-------------------------------------------------------------------------------

if( $WEB_SERVER_SCRIPT ){

	header( "Content-Type: image/png" );
	$result =  imagepng( $mapImage );
	exit( 0 );
}

#-------------------------------------------------------------------------------
#	Sonst: Öffne Datei für Imagefile zum Schreiben
#-------------------------------------------------------------------------------

$imageFile = fopen( $imagePath, "w" );

if( !$imageFile ) fatal( "fopen('$imagePath','w'): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Schreibe Map-Image als PNG in ImageFile (schließt automatisch die Datei)
#-------------------------------------------------------------------------------

$result =  imagepng( $mapImage, $imageFile );

if( !$result ) fatal( "imagepng(): failed.", "500 Internal Server Error" );

#-------------------------------------------------------------------------------
#	Done :-)
#-------------------------------------------------------------------------------

exit( 0 );

#-------------------------------------------------------------------------------
#	$Id: gen-karte-mit-markern.php,v 1.11 2024/08/15 01:43:53 wolf Exp $
#-------------------------------------------------------------------------------
?>
