Zpracovat GPS souřadnice není tak lehké, jak se na první pohled může zdát. Mohou se totiž vyskytovat ve třech standardních formátech – ve stupních, ve stupních a minutách, nebo ve stupních, minutách a vteřinách. Používají-li se souřadnice ze vstupu jen pro zobrazení, řešit to nemusíme – stačí zobrazit to co uživatel zadal a jiný uživatel si s tím už nějak poradí (a nebo neporadí, ale to již není náš problém).
Horší je když chceme se souřadnicemi dále počítat. Pak je nezbytně nutný převod do jednotného formátu, nejlépe na stupně, abychom mohli uloženou hodnotu přímo použít v libovolném výpočtu a nemuseli ji pokaždé přepočítávat.
Nejjednodušší je vynutit si od uživatele zadání ve stupních. Jenže chudák uživatel který bude chtít zkopírovat souřadnice odněkud, kde jsou v jiném formátu. Přece po něm nebudeme chtít, aby vytáhl papír a kalkulačku a přepočítával to. Nehledě na to, že vůbec nemusí mít tušení o různých formátech a co který znamená – on to potřebuje používat, ne tomu rozumět a počítat s tím.
Tudíž je třeba umět korektně přečíst a zpracovat všechny formáty. A aby to nebylo tak jednoduché, je nutné počítat i s tím, že uživatel někde udělá nějakou chybu – jmenovitě jiný počet mezer či různé podoby znaku pro minuty a vteřiny (jednoduchých a dvojitých uvozovek, apostrofů atp. je více typů, dva apostrofy opticky vypadají stejně jako uvozovky atd.). Taktéž jsou dva možné způsoby zadání polokoule – plus (které se nepíše) a mínus, nebo počáteční písmeno anglického názvu příslušné světové strany, které může být před nebo za souřadnicí. No a když jsme u toho, uživatel může znaky pro jednotky vynechat úplně nebo může vstup obsahovat libovolné jiné nekorektní znaky.
Dále již nebudu napínat a předkládám své řešení problému:
Edit 10.8.2009: Zveřejnil jsem druhou, vylepřenou verzi tohoto kódu, která je výrazně přehlednější.
/**
* 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{1,3} (\s*[\.,]\s*\d+)?)
\s*
)
(?('.($first?'5':'11').')
[\'`´]?
|
(?:
[\'`´]\s*
(?:
(?(?\d{1,3} (?:\s*[\.,]\s*\d+)?)
\s*(?:["“”]|(?:[\'`´]\s*[\'`´]))?
)
)?
)?
)
)?
)?
)
(?('.($first?'1':'7').') | \s*['.($first?'NS':'EW').']?)';
}}
if(preg_match('# ^\s*'.gpsToFloatRegExp().'\s+'.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 && $mFirstPos0 && $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];
}
} 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]);
//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)<=2){
//escapovani tecek pro pouziti v regularu a nahrazeni carek pouzitych jako oddelovac desetinnych mist teckami
for($i=0,$matches2=array(); $i<$count; $replace.=$matches[0][$i].$dels[$j%3].' \\'.($i+2).' ',$pattern.='([ '.($i==0?'S-':($j==0?'SW-':'')).']*).*?'.$matches2[0][$i],(mb_strpos($matches[0][$i],'.',null,$encoding)?$j=0:++$j),++$i);
} else {
for($i=0,$pattern=$replace=''; $i<$count; $replace.=$matches[0][$i].$dels[$i%($count/2)].' \\'.($i+2).' ',$pattern.='([ '.($i==0?'S-':($i==$count/2?'SW-':'')).']*).*?'.$matches2[0][$i],++$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),$toString,true,$encoding);
}
}
// vratime vysledne pole, nebo false pokud se retezec nepodarilo prevest nebo jsou souradnice mimo povoleny rozsah
return (!$ret || $ret[0]>180 || $ret[0]<-180 || $ret[1]>180 || $ret[1]<-180) ? false : ($toString ? $ret[0].' '.$ret[1] : $ret);
}
Je založeno na odhadu toho, co uživatel může zadat a jak to upravit aby to šlo přečíst. Tudíž stoprocentně spolehlivé to není a ani být nemůže (nic blbuvzdorné udělat nelze, blbci jsou hrozně vynalézaví), ovšem výsledky jsou imho velmi dobré.
Jak to celé funguje nastíním jen zběžně, podrobně by to bylo na další dvě strany.
Základem je regulární výraz, který rozparsuje souřadnice do pole s jednotlivými složkami. Ten přečte pouze korektní formát, který může maximálně obsahovat nadbytečné bílé znaky nebo chyby v použitých znacích pro označení jednotek. Výraz generuje funkce gpsToFloatRegExp – části pro délku a šířku jsou obdobné jen s jinými názvy položek ve výstupním poli a indexy subvýrazů v podmínkách.
Výraz je založen na tom, že pokud stupně jsou celým číslem, může ale nemusí za nimi následovat minuty a obdobně pro vteřiny. Krom toho řeší možnost přítomnosti specifikace polokoule před nebo za souřadnicí.
Pokud se to nepodaří, pokusí se funkce upravit vstup tak, aby se pravděpodobnost shody s tímto výrazem zvýšila. Vyhází ze vstupu vše co není platnou součástí GPS souřadnic a zavolá s tímto vstupem funkci rekurzivně, tentokrát ve strikt módu (tzn. pokud ani tentokrát výraz nesedí, vrátí se false).
PS1: Jedná se o „pedagogickou“ implementaci, takže důraz byl krom funkčnosti kladen hlavně na kompaktnost kódu a jeho spektakulárnost, nikoli přehlednost. Psát tímto stylem cokoliv většího by byla profesní sebevražda, neb by se v tom nikdy nikdo nevyznal, autora nevyjímaje.
PS2: Pojem pedagogická (taktéž vzorová nebo ukázková) implementace se v akademickém prostředí obvykle používá pro několikařádkový kód balancující na hranici geniality a prasárny, jehož pochopení vyžaduje alespoň týden intenzivního zkoumání, přičemž většina lidí ho nepochopí nikdy. Skrytá motivace pro jeho psaní je ukázat studentům jaký machr autor řešení je:)
PS3: Kdo to první rozluští a pochopí má u mě lízátko a bublifuk…;)
Publikováno 22.06.2008 22:21 v sekci Webdesign
Trvalý odkaz
Komentářů: 0 (Zobrazit komentáře)
Článek ještě nikdo nekomentoval