Po čase teď mám trošku volna a nápady co dělat. Jedním z nich je zpřehlednění staré funkce pro parsování GPS souřadnic.
Nová verze neopravuje žádné nedostatky, ani nepřidává žádnou funkčnost - jednoduše z toho důvodu, že jsem (a ani nikdo jiný) nenalezl v původní funkci žádné chyby, které by bylo třeba opravovat, a rozšiřovat funkčnost dost dobře nejde.
Nová verze je tedy funkčně i algoritmicky zcela totožná s tou původní. Jedinou změnou je výrazné zpřehlednění zdrojového kódu.
Do budoucna ale vylepšení chystám - z funkce udělám třídu, rozdělím jí do více metod (což povede k dalšímu zpřehlednění kódu) a doplním jí o možnost nějaké rozumné a praktické práce s GPS souřadnicemi. Např. převod formátů, získávání jednotlivých složek či nějaké základní výpočty.
A tady je konečně slibovaný zdrojový kód:
/**
* prevod GPS souradnic od uzivatele na souradnice ve stupnich
*
* @param string $gps souradnice zadane uzivatelem
* @param boolean $toString vratit vysledek jako string misto pole floatu
* @param boolean $strict prevest jen presny format (jen zakladni korekce chyb - bile znaky navic, jiny format uvozovek; retezec nesmi obsahovat znaky nepatrici do GPS formatu)
* @param string $encoding kodovani vstupniho retezce
*
* @return array|string [sirka;delka] souradnice ve stupnich, nebo false pokud se souradnice nepodarilo prevest
*/
function gpsToFloat($gps,$toString=false,$strict=false,$encoding='utf-8'){
$ret = false;
//prevod na upper zjednodusi manipulaci se specifikaci polokoule
$gps = mb_strtoupper($gps,$encoding);
//generovani regularu - vyrazy pro sirku a delku jsou ekvivalentni
if( ! function_exists('gpsToFloatRegExp')){ function gpsToFloatRegExp($first=true){
return '(['.($first?'NS':'EW').'-])?
\s*
(?P<'.($first ? 'latSt' : 'longSt').'>\d{1,3}(\s*[\.,]\s*\d+)?)
\s*
(?('.($first? '3' : '9').')
°?
|
(?:
°\s*
(?:
(?(?<!\d)
(?P<'.($first ? 'latMin' : 'longMin').'>\d{1,3} (\s*[\.,]\s*\d+)?)
\s*
)
(?('.($first ? '5' : '11').')
[\'`´]?
|
(?:
[\'`´]\s*
(?:
(?(?<!\d)
(?P<'.($first ? 'latSec' : 'longSec').'>\d{1,3} (?:\s*[\.,]\s*\d+)?)
\s*(?:["“”]|(?:[\'`´]\s*[\'`´]))?
)
)?
)?
)
)?
)?
)
(?('.($first ? '1' : '7').') | \s*['.($first ? 'NS' : 'EW').']?)';
}}
$matches = false;
//pokus o precteni souradnic podle regularniho vyrazu - povede se, jestlize vstup je v nejakem "rozumnem" tvaru pripominajicim platny format
if(preg_match('#^\s*'.gpsToFloatRegExp().'[^\d]+'.gpsToFloatRegExp(false).'\s*$#xu', $gps, $matches)){
//pretypovani na float
foreach (array('latSt','latMin','latSec','longSt','longMin','longSec') as $item){
$matches[$item] = $matches[$item] ? floatval(preg_replace('#[^\d\.]+#', '', str_replace(',', '.', $matches[$item]))) : 0;
}
//prepocitani na stupne
$ret = array(
$matches['latSt'] + $matches['latMin']/60 + $matches['latSec']/3600,
$matches['longSt'] + $matches['longMin']/60 + $matches['longSec']/3600
);
//upraveni znamenek souradnic podle znaku "-" nebo znaku oznacujiciho polokouli
$mFirstPos = mb_strpos($gps,'-',null,$encoding);
if($mFirstPos === 0 ||
$mFirstPos > 0 && $mFirstPos < mb_strpos($gps, $matches['latSt'], null, $encoding)
|| mb_strpos($gps, 'S', null, $encoding) !== false
){
$ret[0] = -$ret[0];
}
if($mFirstPos > 0 && $mFirstPos > mb_strpos($gps, $matches['latSt'], null ,$encoding)
|| mb_strpos($gps, '-', $mFirstPos+1, $encoding) > 0
|| mb_strpos($gps, 'W', null, $encoding) !== false
){
$ret[1] = -$ret[1];
}
// Jinak je-li povoleno dalsi prevadeni se pokusime vstup prevest na rozumnejsi tvar a preparsovat ho jeste jednou
} elseif( ! $strict) {
//vyhazeni znaku ktere v GPS souradnicich nemaji co delat
$gps = trim(preg_replace(array('#[^\d\.\,SW-]#u','# +#u','# *\. *#u'), array(' ',' ','.'), $gps));
preg_match_all('#[\d]+(?:[\.,][\d]+)?#u', $gps, $matches);
$count = count($matches[0]);
//vstup lze jednoznacne prevest jen pokud obsahuje lichy pocet cisel nebo desetinnou tecku v jine skupine nez posledni
if($count==2 || $count==4 || $count==6 ||
( mb_strpos($gps, '.', null, $encoding) < mb_strrpos($gps,$matches[0][count($matches[0])-1], null, $encoding)
&& mb_substr_count($gps, '.', $encoding) <= 2 )
){
//escapovani tecek pro pouziti v regularu a nahrazeni carek pouzitych jako oddelovac desetinnych mist teckami
$matches2 = array();
for($i=0; $i<count($matches[0]); ++$i){
$matches2[0][$i] = str_replace('.', '\.', $matches[0][$i]);
$matches[0][$i] = str_replace(',', '.', $matches[0][$i]);
}
//sestaveni regularu pro transformaci vstupu - z vstupu ponechame nalezena cisla, bile znaky a specifikaci polokoule v miste kde se muze nachazet a doplnime znaky jednotek, vse ostatni bude vyhazeno (po predchozi uprave zbyly znaky ktere mohou oznacovat polokouly nebo byt oddelovacem desetinnych mist)
$dels = array('°',"'",'"');
$pattern = '';
$replace = '';
if(mb_strpos($gps, '.', null, $encoding) || mb_strpos($gps, ',', null, $encoding)){
$j = 0;
for($i=0; $i < $count; ++ $i){
$replace .= $matches[0][$i].$dels[$j%3].' \\'.($i+2).' ';
$pattern .= '([ '.( $i==0 ? 'S-' : ($j==0 ? 'SW-' : '') ).']*).*?'.$matches2[0][$i];
if(mb_strpos($matches[0][$i], '.', null, $encoding)){
$j = 0;
} else {
++ $j;
}
}
} else {
for($i=0; $i<$count; ++$i){
$replace .= $matches[0][$i].$dels[$i%($count/2)].' \\'.($i+2).' ';
$pattern .= '([ '.( $i==0 ? 'S-' : ($i==$count/2 ? 'SW-' : '') ).']*).*?'.$matches2[0][$i];
}
}
//pokusime se preparsovat upraveny retezec ve strikt modu (byl-li platny, prevedl se na standardni format a pujde prevest)
$ret = gpsToFloat(
$count==2 ? $gps : preg_replace('#^.*?'.$pattern.'.*?([ W]*).*?$#u','\\1 '.$replace,$gps),
false,
true,
$encoding
);
}
}
// vratime vysledne pole, nebo false pokud se retezec nepodarilo prevest nebo jsou souradnice mimo povoleny rozsah
return ( ! $ret || $ret[0]>90 || $ret[0]<-90 || $ret[1]>180 || $ret[1]<-180)
?
false
:
($toString ? $ret[0].' '.$ret[1] : $ret);
}
Opravil jsem chyby, které reportoval Saman (viz komentáře).
Do budoucna chystám obsáhlejší třídu pro práci s GPS zahrnující kromě této parsovací funkce spoustu užitečných věcí navíc. Nemohu však zaručit termín zveřejnění (je to téměř hotové, ale chce to ještě doladit a doplnit), takže máte-li o třídu zájem, napište mi e-mail a budu vás informovat (případně nabídnu aktuální betaverzi).
Publikováno 10.08.2009 22:02 v sekci Webdesign
Trvalý odkaz
Komentářů: 7 (Zobrazit komentáře)
Napsal Ajax 16.02.2010 16:41:52
Ahoj!
Delam geocaching a nebavi me porad lovit po netu ruzne prevody na souradnice a chtel jsem si to napsat sam. Ale po chvili jsem zjistil, ze to asi nebude easy a zacal jsem googlit. Dostal jsem se sem a rikam si: jeste, ze jsem to nepsal. :) Jsem amater v PHP, takze se mi nejak nedari ten tvuj skript rozchodit. Podle me tam prebyva | chybi slozena zavorka. Hlavni funkce konci nad poslednim returnem. delam neco blbe ja, nebo je to blbe na strankach?
Diky za odpoved!
Ajax
Napsal Ajax 03.03.2010 09:19:03
Nic? Skoda.. :(
Napsal Black Wolf www.blackwolf.cz 23.03.2010 00:04:13
Opraveno.
Napsal Michal 12.08.2010 20:19:13
Ahoj, možná že jsem to dobře nepochopil, ale nějak mi nejde vytáhnout druhá návratová hodnota, první tahám: vysledek[0] a druhou vysledek[1] ale ta je po probehnuti funkce nulova. Sry, ale nastudovat tu funkci by mi zabralo hafec času, tak to zkouším, jestli mi poradíte rychleji zde, díky :)
Napsal Michal 12.08.2010 20:29:12
omlouvám se, chyba byla u mě. Poučení pro příště, čím déle hledám chybu, tím více je triviální :D
Napsal Saman 15.09.2010 13:05:50
Ahoj, pěkná funkce, ale pár broučků jsem našel:
a) souřadnice šířky (ta první) musí být mezi -90 a 90
b) při vnitřním volání fce $ret = gpsToFloat.. (asi 14.řádek od konce) musí být druhý parametr FALSE, jinak to nefunguje s $toString = TRUE
c) pokud oddělujeme souřadnice čárkou tak to zahodí i \'S\' před čárkou (50°15\'45\'\'S , 50°15\'45\'\'W) a první souřadnici vyhodnotí jako kladnou
Napsal Black Wolf www.blackwolf.cz 10.10.2010 15:17:28
Saman:
Velmi děkuji za reportování bugů. Sice mi to chvíli trvalo (spousta práce a děravá paměť :)), ale opravit to nebyl problém a dnes jsem zde publikovanou verzi nahradil opravenou verzí.