art-d-sign
-


Hinweise


Antwort
 
LinkBack (4) Themen-Optionen Thema durchsuchen
Alt 15.07.2005, 01:15   4 links from elsewhere to this Post. Click to view. #1
TP-Special Mod
 
Benutzerbild von steffenk
 
Registriert seit: Feb 2005
Ort: Haan / NRW
steffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine User

Die Technik der TP-Karte


Auf mehrfachen Wunsch stelle ich hier die Technik vor, wie die TP-Karte generiert wird.

Dabei beschränke ich mich auf den PHP-Teil der Berechnung und Darstellung.
Die dynamischen Teile wie Bewegung der Karte kann man ja dem Quelltext entnehmen.

Karten mit Geodaten zeichnen

Ziel dieses Projektes ist es, alle Mitglieder eines Forums in einer Karte anzuzeigen.


Was brauchen wir denn alles, um das umsetzen zu können ?

- eine Karte
- eine Datenbank mit GeoDaten
- PLZ-Angaben der User
- Umrechnungsroutinen für die Geo-Koordinaten

Natürlich stösst man bei einem solchen Projekt unweigerlich auf die openGeoDB[1], ein OpenSource-Projekt für Geodaten. Nach anfänglichem Stöbern gefielen mir ein paar Sachen nicht:
- die Klasse setzt PEAR vorraus
- die Struktur ist sehr verschachtelt und nicht gut kommentiert, es erfordert einiges an Einarbeitung
- die Datenbank hat einen sehr komplexen Aufbau
- die Pins werden in das Bild kopiert, der Vorgang dauert bei vielen Pins sehr lange, da jeder Pin einzeln das Bild kopiert werden muss

Aber für das Projekt braucht man ja lediglich ein paar Angaben - Stadt, PLZ, Land, Koordinaten, und ein paar Kleinigkeiten.

Glücklicherweise gibt es noch eine kleinere Datenbank[2]. Man benötigt lediglich eine Tabelle, die geodb_locations. Hier sind alle benötigten Information enthalten.

Eine Karte kann man sich unter [3] downloaden und entsprechend grafisch bearbeiten. Sie beinhaltet Landes-/Bundeslandgrenzen, Städte, Flüsse und Strassen, es bleibt jedem überlassen, wie die Karte aussehen soll.
Das wichtigste aber ist die Range-Angabe, die die Koordinaten der Eckpunkte der Karte angibt. Spätestens hier kann man sich den Frust holen, da diese Angaben nirgends stehen - die openGeoDBClass nutzt nur eine Deutschlandkarte und hat demzufolge eine andere Range.

Berechnung
Hat man diese Angaben zusammen, kann es auch schon losgehen.
Ich benutze eine Tabelle, in die die Userdaten teinkommen. Die Tabelle heisst `geodb_user` und hat folgende Struktur:

Code:
userid | plz | land | geoid | x | y | raster_x | raster_y | combi | valid
geoid ist die id des Datensatzes in der geodb_locations, x/y die absoluten Bildkoordinaten, raster_x/raster_y die Koordinaten des Raster-Mittelpunktes, und combi gibt an, ob es mehrere mit dieser Koordinate gibt. valid gibt an, ob die Daten gültig sind.

Das Verfahren der Umrechnung ist schon fast trivial:
PHP-Code:
//Range-Bereich 
$range_min['x']=5.8
$range_max['x']=17.2
$range_min['y']=45.8
$range_max['y']=55.1

// Größe der Karte in Pixel 
$karte_groesse_x=1400
$karte_groesse_y=1800

//Userdaten und Geodaten in ein Array packen 
$res=mysql_query("SELECT * FROM `user`"); 
$i=0
while(
$row=mysql_fetch_array($res)) { 
    
$members[$i++]=$row

$anz=sizeof($members); 
for (
$i=0;$i<$anz;$i++) 
{     
    
$sql="SELECT * FROM `geodb_locations` WHERE `plz` like '%".$members[$i]['plz']."%' AND adm0='".$members[$i]['land']."'  ORDER BY LENGTH(plz)"
    
$res=mysql_query($sql); 
    
//gibt es den angegebenen Ort ? 
    
if ($res
    { 
        
$r=mysql_fetch_object($res); 
        if (
$r
        { 
            
// Dann Geokoordinaten auf Bildgröße skalieren und absolute X,Y-Koordinaten speichern 
            
$members[$i]['x']= floor( ($r->laenge $range_min['x']) * ($karte_groesse_x / ($range_max['x'] - $range_min['x']))); 
            
$members[$i]['y']= floor( ($r->breite $range_max['y']) * ($karte_groesse_y / ($range_min['y'] - $range_max['y']))); 
            
$members[$i]['valid']=1
        } else { 
            
$members[$i]['x']=$members[$i]['y']=$members[$i]['valid']=0
        } 
    } 

Ihr werdet lachen - das wars schon. Die wichtigste Hürde ist geschafft, man liest eine PLZ aus der Datenbank und berechnet mit der Formel die Koordinate auf dem Bild.
Für unsere Karte lesen wir alle relevanten Userdaten und legen sie in einem Array ab Um alle relevanten Daten zur Laufzeit zur Verfügung zu haben, habe ich eine neue Tabelle, in der die User mit ihren Koordinaten eingetragen werden.

Jetzt besteht das Problem, das es u.U. zu einer PLZ mehrere User gibt oder Standorte so nah beieinander liegen, das die Pins sich überlappen würden. Aus diesem Grund habe ich die Karte in ein Raster eingeteilt. Die Grösse des Rasters richtet sich nach der Grösse der Pins.
Sind mehrere User in einem Raster, werden sie zusammengefasst und der Pin in den Mittelpunkt des Rasters verlegt. Diese Raster-Koordinaten werden ebenfalls für jeden User berechnet, so das man nachher leicht erkennen kann, ob es mehrere User im gleichen Raster gibt.

PHP-Code:
//Rastergrösse: 24 x 24 Pixel 
$rastergruppierung_x=24
$rastergruppierung_y=24

//Für jeden User aus den Koordinaten das Raster berechnen 

for ($i=0;$i<$anz;$i++) 
    if(
$members[$i]['valid']==1) { 
        
// Mittelpunkt des zugehörigen Rasterquadrats ermitteln 
        
$rest_x=$members[$i]['x'] % $rastergruppierung_x
        
$rest_y=$members[$i]['y'] % $rastergruppierung_y
        
$members[$i]['raster_x']=$members[$i]['x']-$rest_x+floor($rastergruppierung_x/2); 
        
$members[$i]['raster_y']=$members[$i]['y']-$rest_y+floor($rastergruppierung_y/2); 
    } 

Karte ausgeben
Hier hab ich mich für eine dynamische Variante entschieden, die die Pins zur Laufzeit auf der Karte absolut positioniert. Die Karte bleibt in ihrem Originalzustand und es werden keine Imagefunktionen benötigt, was die Performance deutlich erhöht.

Für die Karte brauchen folgenden Aufbau:

HTML-Code:
<div id="originalImgBox" style="position:absolute;top:0px;left:0px">
<img id="originalImg" style="position:relative" src="karte.gif" width="1400" height="1800" />
<!-- Anfang Sektion Pins -->
... dynamischer Code ...
<!-- Ende Sektion Pins -->
</div>
Pins ausgeben
Jetzt geht es an den dynamischen Teil, in dem die Pins ausgegeben werden.
Als erstes bauen wir uns die Abfrage mit allen relevanten Daten:

PHP-Code:
$sql="SELECT `user`.`userid`,`user`.`username`,`user`.`homepage`, `user`.`avatar`,`geodb_user`.*, 
IF(`geodb_locations`.`ortsteil`!='',CONCAT(`geodb_locations`.`ort`,' / ',`geodb_locations`.`ortsteil`),`geodb_locations`.`ort`) as `stadt`, `geodb_locations`.`adm1` as `bundesland`,`geodb_locations`.`kfz`,`geodb_locations`.`ort`,`geodb_locations`.`name_int` as `urlort`, 
CONCAT(`raster_y`,'_',`raster_x`) as `raster`, 
FROM `user` 
LEFT JOIN `geodb_user` ON `geodb_user`.`userid`=`user`.`userid` 
LEFT JOIN `geodb_locations` ON `geodb_locations`.`id`=`geodb_user`.`geoid` 
WHERE `geodb_user`.`valid`=1 ORDER BY `raster` ASC; 
Jetzt haben wir die Userdaten nach Rasterkoordinaten sortiert, können die Schleife durchlaufen und die Pins setzen. Die Einblendung der Informationen besorgt die Klasse overlib [4], die das div automatisch bei mouseover einblendet und bei mouseout ausblendet.

Zusätzlich habe ich für x und y Offsetwerte benutzt, die die Lage des Pins verschieben. Diese Offset hängen mit der Grafik des Pins zusammen, schliesslich soll eine Stelle des Pins auf die Koordinate zeigen, und das ist nicht zwingend eine Ecke des gifs.

PHP-Code:
//Query ausführen 
$res=mysql_query($sql); 
$num=mysql_num_rows($res); 

//Variablen initialisieren 
$oldraster=$html=""
$pin='images/p.gif'
//Offsets 
$offset_x=-4
$offset_y=-8

//Jetzt die Daten durchlaufen 
for($i=0;$i<$num;$i++) 
//Datensatz holen 
    
$a=mysql_fetch_array($res); 
    
$combi=$a['combi']; 

    
//Gruppierung 
    
$raster=$a['raster']; 
    
//Neues Raster ? dann html löschen und alte Gruppierung ausgeben 
    
if($oldraster!=$raster) { 
        if(
$i>&& $html!="") { 
            
//nicht beim ersten mal, daher muss $i grösser als 0 sein 
            
Pin($x+$offset_x,$y+$offset_y,$html,$pin); //Pin zeichnen 
        

        
$html=""//Ausgabe löschen 
    

     
    
//Wenn combi=1 gibt es mehrere im Planquadrat 
    
$x=($combi==0) ? $a['x']:$a['raster_x']; 
    
$y=($combi==0) ? $a['y']:$a['raster_y']; 
     
    
//PopUp-Informationen sammeln 
    
$stadturl='<a class="s" href="http://www.'.strtolower(str_replace(" ","-",$a['urlort'])).'.'.strtolower($a['land']).'" target="_blank">'.$a['plz'].'&nbsp;&nbsp;'.$a['stadt'].'</a>'
    
$avatar='<img src="/forum/customavatars/avatar'.$a['userid'].'_'.$a['avatarrevision'].'.gif" width="46" height="46" alt="">'
    
$homepage=($a['homepage']!="") ? '<a href="'.$a['homepage'].'" target="_blank">[HP]</a>&nbsp;&nbsp;' ''
    
// usw. Hier kann man ja beliebige Informationen für den User sammeln 
     
    //Infos zu einem Div zusammensetzen 
    
$html.='<div class="p">'.$avatar.$ulink.$stadturl.'&nbsp;('.$a['bundesland']; 
    
$html.=($a['kfz']!="") ? '  KFZ: '.$a['kfz'].')<br>' ')<br>'.$homepage
    
$html.='</div>'
     
    
//schauen, ob ausgegeben werden soll (Einzelpin), sonst aufsparen (Combi) 
    
if($combi==|| $i==($num-1)) {     
        
Pin($x+$offset_x,$y+$offset_y,$html,$pin); 
        
$html==""
    } 
     
    
//Raster merken 
    
$oldraster=$raster


function 
Pin($x,$y,$html,$pin) { 
    echo 
'<div style="position:absolute;top:'.$y.'px;left:'.$x.'px;"' 
        
.' onmouseover="overlib(\''.str_replace('"','&quot;',$html).'\',STICKY, MOUSEOFF);" onmouseout="nd();">' 
        
.'<img src="'.$pin.'" alt=""></div>'."\n"
     

Und schon ist alles fertig. Beim Überfahren der Pins mit der Maus sorgt die overlib-Klasse für die Einblendung der Informationen.



Der Grund für die Umhängetabelle begründet sich in der Tatsache, das sehr viele User zusammenkommen und der Aufruf der Seite entsprechend lange dauern würde. hat man weniger User / Objekte, könnte man die relevanten Informationen auch direkt auskesen.

Datenpflege
Leider wird man immer feststellen, das Daten ungenau oder falsch sind oder gänzlich fehlen.
Für diesen Fall muss man in der Lage sein, die Daten zu ergänzen / korrigieren. Hierzu bediene ich mich der PLZ-Suche [5], um den/die genauen Ortsnamen zu ermitteln. Unter GNS Search kann ich mit diesen Angaben die GeoKoordinaten ermitteln.Die Koordinaten sind hier in Breiten- und Längengrad angegeben, so das wir noch eine Umrechnungsfunktion brauchen:

PHP-Code:
function dms2deg($dms) { 
    
$cfgStrings = array('N''NO''O''SO''S''SW''W''NW'); 
    
$negativeSigns = array($cfgStrings[4], $cfgStrings[6], "-"); 
    
$negativeSignsString $cfgStrings[4].$cfgStrings[6]; 
    if (
strlen($dms) == 6) { 
    
$dms "0".$dms
    } elseif (
strlen($dms) == 5) { 
    
$dms "00".$dms
    } 
    
$searchPattern "|\s*([$negativeSignsString\-\+]?)\s*(\d{1,3})[\°\s]*(\d{1,2})[\'\s]*(\d{1,2})([\,\.]*)(\d*)[\'\"\s]*([$negativeSignsString\-\+]?)|i"
    if(
preg_match($searchPattern$dms$result)) { 
    if (
in_array(strtoupper($result[1]), $negativeSigns) || in_array(strtoupper($result[7]), $negativeSigns)) { 
    
$algSign = -1.
    } else { 
    
$algSign 1.
    } 
    if (((
1. $result[2]) > 360) || ($result[3] >= 60) || ($result[4] >= 60)) { 
    return 
'Values out of range'
    } 
    return 
$algSign * ($result[2] +(($result[3] + (($result[4].".".$result[6]) * 10/6)/100)*10/6)/100); 
    } else { 
    return 
'No DMS-Format (Like 51° 24\' 32.123\'\' W)'
    } 

Damit ist das Grundprinzip erklärt. Es sollte jetzt nicht schwerfallen, eigene Projekte mit Kartenunterstützung zu erstellen.

Ich habe bewusst darauf verzichtet, komplette Quelltexte anzugeben, da das individuell verschieden benötigt wir, vielmehr ging es mir um die Erklärung des Prinzips.

... und so schnell wird man zum Geografen :-)


Quellen
[1] openGeoDB-Homepage
[2] openGeoDB-Datenbank (Download)
[3] openGeoDB Karten
[4] Overlib
[5] GeoNet Names Server (GNS)
[6] PLZ-Suche Europa

Das Tutorial findet ihr auch im dislabs-Labor
__________________

Typo3 · MySQLDumper · dislabs
·
manche Mühlen mahlen schneller ...
"Ich habe Rücken"
Horst Schlämmer

Geändert von steffenk (15.07.2005 um 01:17 Uhr).
steffenk ist gerade online   Mit Zitat antworten


Alt 15.07.2005, 19:57   #2
TP-Specialist
 
Benutzerbild von Schneeschaufel
 
Registriert seit: Mar 2002
Ort: Ö
Schneeschaufel hilft, wo's gehtSchneeschaufel hilft, wo's gehtSchneeschaufel hilft, wo's geht
Vielen Dank für die ausführliche Erklärung. Mal schauen, was geht...
Schneeschaufel ist offline   Mit Zitat antworten
Alt 09.08.2005, 09:53   #3
TP-Junior
 
Registriert seit: Feb 2005
rigo2 macht alles soweit korrekt
Gibt's auch einen Link zur TP-Karte?
rigo2 ist offline   Mit Zitat antworten
Alt 09.08.2005, 10:14   #4
TP-Special Mod
 
Benutzerbild von steffenk
 
Registriert seit: Feb 2005
Ort: Haan / NRW
steffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine User
Schau mal oben auf dieser Seite, links neben "Willkommen rigo2"
__________________

Typo3 · MySQLDumper · dislabs
·
manche Mühlen mahlen schneller ...
"Ich habe Rücken"
Horst Schlämmer
steffenk ist gerade online   Mit Zitat antworten
Alt 19.08.2005, 21:01   #5
TP-Newbie
 
Benutzerbild von kWER
 
Registriert seit: Aug 2005
kWER macht alles soweit korrekt
Hallo St@eff.en,

erstmal herzlichen Dank für Deine Anleitung, nun habe ich einen ungefähren Plan, wie ich mein Vorhaben angehen kann.

Ich bin nicht gerade routiniert im Programmieren aber sehe das als eine großartige und vor allem sinnvolle Aufgabe, hierdurch etwas zu lernen.

Ich hätte auch schon eine ganz simple Frage ganz zu Beginn - und zwar was für Datentypen Du für die 10 Felder in der Tabelle geodb_user benutzt hast. Wäre schön, wenn ich das erfahren könnte um gleich zu Beginn Fehler auszuschließen.

Danke im Voraus, mfg, kWER

PS: Hauptsächlich ist frage ich mich, ob x,y,raster_x, raster_y und valid INTs sein sollten.

Geändert von kWER (19.08.2005 um 21:07 Uhr).
kWER ist offline   Mit Zitat antworten
Alt 19.08.2005, 21:05   #6
TP-Special Mod
 
Benutzerbild von steffenk
 
Registriert seit: Feb 2005
Ort: Haan / NRW
steffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine User
np, hier ist sie:

Code:
CREATE TABLE `geodb_user` ( 
`userid` int(11) unsigned NOT NULL default '0', 
`gruppe` tinyint(4) NOT NULL default '0', 
`plz` varchar(5) NOT NULL default '', 
`land` char(3) NOT NULL default '', 
`geoid` int(11) NOT NULL default '0', 
`x` int(4) NOT NULL default '0', 
`y` int(11) NOT NULL default '0', 
`raster_x` int(4) NOT NULL default '0', 
`raster_y` int(4) NOT NULL default '0', 
`combi` tinyint(4) NOT NULL default '0', 
`combi1` tinyint(4) NOT NULL default '0', 
PRIMARY KEY (`userid`) ) TYPE=MyISAM
aber das ist nicht bindend, hier kann man auch Infos eintragen wie man möchte.
__________________

Typo3 · MySQLDumper · dislabs
·
manche Mühlen mahlen schneller ...
"Ich habe Rücken"
Horst Schlämmer
steffenk ist gerade online   Mit Zitat antworten
Alt 19.08.2005, 21:08   #7
TP-Newbie
 
Benutzerbild von kWER
 
Registriert seit: Aug 2005
kWER macht alles soweit korrekt
Hossa, vielen Dank, das hab ich ja Glück gehabt
kWER ist offline   Mit Zitat antworten
Alt 05.09.2005, 20:59   #8
TP-Newbie
 
Benutzerbild von kWER
 
Registriert seit: Aug 2005
kWER macht alles soweit korrekt
Ich bin's nochmal.
Hatte heute endlich mal Zeit, mich wieder an diese Herausforderung zu setzen und bin gewissermaßen verzweifelt

Ich hätte ein paar grundsätzliche Fragen, meine Tabellen verwirren mich nur noch.

Wo ist der Unterschied zwischen den Tabellen 'user' und 'geolib_user'?
Beide müssen scheinbar relevante Geodaten sowie PLZ beinhalten, nur dass die 'user' noch mehr spezielle Daten (wie HP, Avatar) etc. hat.
Anfangs nahm ich an, dass 'user' eben mein vorhandener Userstamm (zB von nem Forum) ist, die große SELECT Abfrage kurz vor Schluss nahm mir aber diesen Glauben.

Ich habe mir die 'geolib_user' wie empfohlen angelegt und zusätzlich eine Tabelle 'user' gemacht, wo eben zusätzliche Infos über Kontaktmöglichkeiten/Schlafplätze etc. gespeichert werden (und ein paar Testwerte eingefügt) - muss ich nun auch noch Felder für die Geodatens schaffen? Wird geolib_user damit nicht überflüssig?
Irgendwie fehlt mir das Verständnis, wie ich den Zusammenhang zwischen PLZ und Koordinaten speichere - beziehungsweise wohin.

Sorry, ich stehe komplett auf dem Schlauch
Würde mich über Hilfe oder evtl. auch größere Quelltextausschnitte sehr freuen.

MfG kWER
kWER ist offline   Mit Zitat antworten
Alt 07.09.2005, 22:08   #9
TP-Newbie
 
Registriert seit: Sep 2005
TheToast macht alles soweit korrekt
Wunderbar, schöne Erklärung. Ist nur noch anzumerken, dass diese Methode in der Nähe der Pole ungenau wird Reicht aber vollkommen, würde ich sagen
TheToast ist offline   Mit Zitat antworten
Alt 07.09.2005, 22:21   #10
TP-Special Mod
 
Benutzerbild von steffenk
 
Registriert seit: Feb 2005
Ort: Haan / NRW
steffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine Usersteffenk lebt für das TP und seine User
@TheToast Danke. Könntest Du das mit der Ungenauigkeit konkretisieren ? Das versteh ich nicht

@kWer - sry, hab erst grad gemerkt, das Du erneut gepostet hast.

Da das ganze mit dem Forum verknüpft ist, ist die Tabelle user die Forumstabelle der User mit userid für die Links zum Profil, die Avatar, Homepage etc.
Das ist eigentlich gar nicht von Belang, man kann da was völlig beliebiges nehmen.

Du kannst ja am vorhandenen Quelltext sehen, welche Felder überhaupt benötigt werden.

Alles was die Koordinaten angeht steht in der Tabelle geodb_user, und eigentlich reicht die auch völlig aus für die Darstellung der Pins.

Wenn Du ein Problem hast, dann poste doch mal Deinen Source und ich kann Dir detaillierte Infos geben, mehr Quelltext möchte ich aus datenschutztechnischen Gründen nicht geben, da es um Internas dieses Forums geht, bitte hab dafür Verständnis.
__________________

Typo3 · MySQLDumper · dislabs
·
manche Mühlen mahlen schneller ...
"Ich habe Rücken"
Horst Schlämmer
steffenk ist gerade online   Mit Zitat antworten
Alt 04.01.2006, 13:51   #11
TP-Newbie
 
Registriert seit: Jan 2006
FireGlow macht alles soweit korrekt

Diskussion aufleben lassen.


Moin!

Erstmal Danke für die Mühe deinen Code vorzustellen.

Nun mal eine Frage, was genau meinst du mit "Range" und mit welcher Methode man das rauskriegen kann? Wenn man die TP-Karte nutzt, kann man dann auch deine Range benutzen und muss sich darum keine sorgen machen? ;-)

Gruß

Anton
FireGlow ist offline   Mit Zitat antworten