Principy API
- O událostech
- Souřadné systémy a parsování WGS84 (GPS)
- Vlastní ovládací prvky - map party
- Stahování souborů, práce s XML a JSON
- Mapové vrstvy, aneb jak jsou definované podklady
- Překryvné mapové vrstvy
- Kontextové menu
O událostech
AMapy Api vyhazuje řadu událostí, a umožňuje tak ostatním objektům udržovat si přehled o aktuálním stavu map. Pro většinu mashupů jsou nejdůležitější události objektů AMarker a AMap, nicméně události vyhazují i jiné objekty. Událost má jako prefix vždy 'on' (onClick, onViewportChanged), a to proto aby byly odlišeny události DOM a syntetické události mapy. Existují dva způsoby jak zaregistrovat odběr události.
Registrace události pomocí options
Objekt options je object literal pro předávání většinou nepovinných parametrů. Viz. následující příklad.
marker1.showBubble("Klik na ikonku maximalizace nafoukne bublinu.", {
'maxSize' : new ASize(400, 300),
'onMaximized' : function(bubble) { alert('Bublina maximalizována.'); }
});
Pokud je klíčem název existující události a hodnotou funkce (reference na funkci), je událost zaregistrována.
Registrace události pomocí addEvent
Druhým způsobem je metoda addEvent, kterou mají všechny objekty vyhazující události. Prvním parametrem je název události a druhým funkce nebo reference na ni.
mainMap.addEvent("onClick", function(marker, point) {
if (marker)
mainMap.removeOverlay(marker);
else
mainMap.addOverlay(new AMarker(point));
});
V dokumentaci k API jsou všechny události popsané v options objektu konkrétní metody nebo konstruktoru.
Pro odstranění handleru existuje metoda removeEvent se stejnými parametry. Aby byl handler opravdu odstraněn, musí mít obě metody předanou referenci se stejnou signaturou.
Souřadné systémy a parsování WGS84 (GPS)
AMapy Api podporují v současnosti tři nejdůležitější kartografické souřadné systémy - S42, JTSK a WGS84 (GPS). Další mohou být doplněny dle potřeby. Při vytvoření pozice je třeba jako třetí parametr určit konkrétní souřadný systém. Vytvořenou pozici lze převést na libovolný další souřadný systém pomocí metody AGeoPoint.convertTo.
var brno_S42 = new AGeoPoint(3618170, 5452697, ACoordinateSystem.S42); var brno_JTSK = brno_S42.convertTo(ACoordinateSystem.JTSK);[příklad na přepočet souřadných systémů]
Při konverzi je nutno počítat s možnou, až několikametrovou odchylkou danou rozdílnými projekcemi souřadných systémů.
Parsování WGS84 (GPS)
Nejpoužívanější souřadný systém WGS84 (GPS) lze zapsat mnoha různými způsoby. Proto AMapy API podporují mnoho různých formátů. Oddělovačem zeměpisné šířky a délky (fi, lambda) může být:
- mezera
- čárka
- středník
Podporovány jsou tyto zápisy a libovolné jejich kombinace:
- 15
- +15.347534
- +15.347534°
- 15.3434°N
- 15d30.344'N
- 15° 30.344'N
- 15° 30' 3.34"N
- bez mezery i s mezerou
- za desetinný oddělovač je považována pouze tečka "."
Kontroluje přítomnost definice šířky/délky a případně koordináty prohodí.
- fi = N/S(-)
- lambda = E/W(-)
var someUserInput = "50°04\'45.78\"N; 14°24\'19.58\"E";
var point = new AGeoPoint(someUserInput);
[příklad na parsování GPS v textovém
řetězci]
Vlastní ovládací prvky - map party
Map party jsou DIV elementy nacházející se nad mapou. Svou pozici mají pevně definovanou vůči mapovému výřezu. AMapy API obsahuje předpřipravené třídy pro jejich snadnou tvorbu. Map party jsou určeny nejen pro ovládání mapy, ale i pro libovolnou další interakci uživatele s mapou.
Jednoduchý map part odvozený od AMapPartBase
Následující příklad popisuje, jak vlastní map part napsat. Nejprve je třeba vytvořit vlastní třídu, která dědí od AMapPartBase. Vytvořená třída od svého předka zdědí několik užitečných metod, jako například getDefaultPosition a další. getDefaultPosition lze v odvozené třídě překrýt jednoduše tak, že ji implementujeme znova, jak je vidět v příkladu níže.
V odvozené třídy je nutné implementovat metodu mapPartInit. V ní probíhá logika konstrukce vlastního map partu. Parametry map a wrapper předává mapa sama, první parametr je odkaz na mapu do které byl part přidán a umožňuje tak získat referenci pro interakci s mapou. Druhým parametrem je wrapper, mapovým api vytvořený a napozicovaný element DIV, do kterého lze vložit libovolné HTML, případně k němu připojit handlery událostí.
var AZoomInOutPart = AMapPartBase.extend({
options: {
duration : 1000,
transition: Fx.Transitions.quadInOut
},
// tuhle metodu je nutné implementovat.
mapPartInit: function(map, wrapper){
this.map = map;
this.wrapper = wrapper;
this.createButtons();
},
createButtons: function() {
this.btns = {};
this.btns.zoomInOut = new Element("div")
.setStyles('width:96px; height:96px; background-image:url(../../design/examples/zoomInOut.png)')
.injectInside(this.wrapper)
.fixPng();
this.btns.zoomIn = new Element("div")
.setStyles('position: absolute; left: 20px; top: 26px; width:29px; height:28px; cursor:pointer;')
.addEvent('mouseover', this.doZoom.pass(-1, this))
.injectInside(this.btns.zoomInOut);
this.btns.zoomOut = new Element("div")
.setStyles('position: absolute; left: 62px; top: 30px; width:20px; height:20px; cursor:pointer;')
.addEvent('mouseover', this.doZoom.pass(1, this))
.injectInside(this.btns.zoomInOut);
},
doZoom: function(direction) {
var idx = this.map.getCurrentScaleIdx();
this.map.zoomTo(idx + direction, null, null, this.options);
},
// přepsaná metoda, vrací novou výchozí pozici web partu
getDefaultPosition: function(){
return new APosition(ACorner.LEFT_BOTTOM, new ASize(7,0));
}
});
[jednoduchý příklad pro vlastní mapový
part]
Složitější map part odvozený od AMapPartDraggable
AMapPartDraggable je sofistikovanější abstraktní třída určená pro rychlý vývoj formulářů, a opět přidává některé další šikovné funkce, které vývoj vlastních map partů usnadní.
- připravený prázdný dragovatelný formulář
- připravené styly a elementy
- možnost map part schovat a zpět zobrazit
- s tím spojené odpalování událostí
Následující příklad představuje formulář pro lokalizaci bodu na mapě. Bod lze nějak pojmenovat, popsat, a následně získaná data odeslat na server. Krom ukázky schopností třídy AMapPartDraggable, je příklad i ukázkou mnoha užitečných metod, viz. komentáře v příkladu.
var SmartPart = AMapPartDraggable.extend({
// init
mapPartInit: function(map, wrapper, options) {
// zavoláme metodu předka, která dragovací part tvoří
this.parent(map, wrapper, {
width: 225,
title: 'Lokalizovat pozici',
onShow: this.onShow.bind(this),
onHide: this.onHide.bind(this)
});
this.map = map;
this.createContent();
// vytvořim si dopředu obálku kolem metody addOrRemoveMarker, bind říká kdo bude vystupovat pod this.
// pokud chci mít možnost event handlery nejenom přidat, ale i odebrat (třeba na zavření partu),
// musím vytvořit referenci přesně tímto způsobem. Metoda removeEvent jinak nebude fungovat.
this.onMapClick = {bound: this.addOrRemoveMarker.bind(this)};
},
onShow: function() {
this.map.addEvent('onClick', this.onMapClick.bound);
},
onHide: function() {
this.clearForm();
this.map.removeEvent('onClick', this.onMapClick.bound);
},
// part methods
createContent: function() {
// getContent vrací div do kterého vložím HTML string (můžu použít i appendChild)
this.getContent()
// nemusím registrovat každé tlačítko zvlášť,
// událost kliku probublá a pomocí targetu zjistím na které bylo kliknuto
.addEvent('click', this.onClick.bindWithEvent(this))
// pakliže chci využít integrované styly, musím contentu nsatavit třídu .mappart
.addClass('mappart')
.setHTML(
"<h2>Jméno bodu</h2>" +
"<input id='pointname' class='field' maxlength='100' name='name' type='text' />" +
"<h2>GPS</h2>" +
"<input id='gpsposition' class='field' maxlength='100' name='gps' type='text' />" +
"<p class='note'>Klikem na mapě vyberte bod</p>" +
"<h2>Popis</h2>" +
"<textarea class='field' name='description' rows='4' cols='4'></textarea>" +
"<p class='note'>Tento popis bude přidán k vašemu bodu.</p>" +
"<div class='footer'><div class='button send'>odeslat</div><div class='button clear'>smazat</div></div>"+
"<p class='message'></p>");
},
onClick: function(e) {
// nactu target, coz je element ktery udalost vyvolal (na ktery sem klik)
var t = $(e.target);
if (!t) return;
// podle class se rozhodnu co mam delat
if (t.hasClass('clear')) this.clearForm();
else if (t.hasClass('send')) this.sendMail();
},
addOrRemoveMarker: function(overlay, geopoint) {
// vymažu předchozí značku
this.removeMarker();
// vytvořím novou značku
var marker = new AMarker(geopoint, { draggable: true });
marker.addEvent('onDragEnd', function() {
$('gpsposition').value = marker.getGeoPoint().toDisplayGPS();
});
this.map.addOverlay(marker);
this.cMarker = marker;
// aktualizuju GPS pozici
$('gpsposition').value = geopoint.toDisplayGPS();
},
sendMail: function() {
// nactu co budu validovat
var name = $('pointname').value.trim();
var gps = new AGeoPoint($('gpsposition').value).isValid();
// validuji a skladam odpoved
var message = [];
if (!name) message.push('jméno prosím');
if (!gps) message.push(' pozici prosím');
if (message.length) {
this.showMessage(message.join(','));
return;
}
// metoda toQueryString nacte vsechny hodnoty
var params = this.getContent().toQueryString();
alert(params);
//new Ajax.request('someUrl.php', params, this.showResponse.bind(this));
},
showResponse: function(text, xml) {
},
clearForm: function() {
var c = this.getContent();
c.getElements('input').each( function(elm) { elm.value = '' });
c.getElement("textarea").value = '';
c.getElement('.message').innerHTML = '';
this.removeMarker();
},
removeMarker: function() {
if (this.cMarker) {
this.cMarker.remove();
this.cMarker = null;
}
},
showMessage: function(message) {
this.getContent().getElement('.message').innerHTML = message;
}
});
[příklad na AMapPartDraggable]
Stahování souborů, práce s XML a JSON
Aby API mashup měl z čeho být živ, potřebuje data. Z prohlížeče lze JavaScriptem vyvolat požadavek na soubor jednoduše, pomocí třídy Ajax frameworku MooTools, který je součástí API. Framework programátora odstiňuje od implementačních detailů crossbrowser naturelu. Požadovaný soubor se musí nacházet na stejné doméně.
var myAjax = new Ajax('/ajax.php', {
method: 'get',
onComplete: function(text, xml) {
// kód
}
}).request();
Jak je vidět, stažený soubor lze přečíst pouze jako text nebo XML dokument, viz. předané parametry text, xml.
XML nebo JSON?
Vědět jak vyvolat požadavek na stažení souboru nestačí. Při návrhu sofistikované
mapové aplikace je také třeba zvolit vhodný formát pro výměnu dat
mezi serverem a klientem (prohlížečem). Nejjednoduší mashupy mohou s klidem použít
jako datovou strukturu například string hodnoty oddělené čárkou. V případě větší
složitosti, však může být zpracování takovýchto dat velmi nemotorné.
Lepším řešením se zdá být XML, což je rozšířený a dobře podporovaný
standard. Většinou také není problém na serveru XML dokumenty vytvářet. Obtíže ovšem
mohou nastat při zpracování XML dokumentu v prohlížeči. Pro procházení XML dokumentu
a čtení dat je k dispozici pouze pár DOM metod, které je nutné dobře znát.
Zdaleka nejlepším řešením je evaluovaný JSON, živoucí JavaScript objekt, který může reprezentovat komplexní datové struktury. Objekt lze na serveru vytvořit pomocí mnoha JSON serializérů, dostupných pro všechny hlavní vývojové platformy.
Výhody evaluovaného JSON oproti XML jsou dané prostředím Javascriptu. JSON je datově menší a lépe se s ním pracuje (v prohlížeči). V případě, že přesto preferujete XML, nebo nemáte možnost volbu formátu ovlivnit (např. webové služby), lze využít do API vestavěný konvertor XML na JSON.
Pojmy JSON a object literal jsou často zaměňovány, přestože definují něco úplně jiného. Object literal můžeme chápat jako jiný způsob zápisu Javascriptového kódu. JSON je odlehčený výměnný datový formát na object literal syntax založený. Datový znamená, že například nemůže obsahovat funkce. Pro pochopení JSON je dobré si jej představit, jako statickou datovou strukturu popisující objektový datový model uloženou jako string.
Načtení dat z XML pomocí DOM metod
Pro potřeby API byla vytvořena třída
AXml, která obsahuje šikovné metody pro práci s XML dokumenty.
Příklady jak zpracovat XML dokument, viz.
http://www.w3schools.com/dom/dom_parser.asp, nebo zde: http://www.quirksmode.org/dom/importxml.html
Následující příklad ukazuje, jak stažený XML dokument procházet klasickými DOM metodami.
Tento způsob zpracování XML je trochu nešikovný. Možné problémy jsou dva, Internet
Explorer nepovažuje whitespace za node, a jednoduše jej ignoruje. Nelze tedy spoléhat,
že childNodes[2] vrátí vždy stejný node (v různých
prohlížečích). Tento problém řeší metoda AXml.getElements
Další pastí může být načítání textové hodnoty vybraného node, proto byla vytvořena
metoda AXml.value.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Načtení a zpracování XML pomocí DOM | AMapy API</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="/api/api.php5?guid=VAS_GUID"></script>
<script type="text/javascript">
var Page = {
load: function() {
this.map = new AMap("map");
this.map.loadMaps();
var myAjax = new Ajax('stuff/chovneStanice.xml', {
method: 'get',
onComplete: this.onXmlLoaded.bind(this),
onFailure: function() {
alert('Chyba, server neodpověděl.');
}
}).request();
},
onXmlLoaded: function(text, xml) {
// nejprve nactu vsechny tagy stanice
var stations = xml.getElementsByTagName('stanice');
for(var i=0; i < stations.length; i++) {
var stanice = stations[i];
// nad kazdym node mohu opet zavolat getElementsByTagName, AXml.value mi prečte obsah node
var nazev = AXml.value(stanice.getElementsByTagName('nazev')[0]);
var gps = AXml.value(stanice.getElementsByTagName('gps')[0]);
// paklize jsou nazev i gps definovany - vytvorime znacku
if (nazev && gps) this.createMarker(nazev, gps);
}
},
createMarker: function(title, gps) {
var marker = new AMarker(new AGeoPoint(gps));
marker.addEvent('onClick', function() { marker.showBubble(title); });
this.map.addOverlay(marker);
}
}
window.addEvent('domready', Page.load.bind(Page));
</script>
</head>
<body>
<div id="map" style="width: 500px; height: 300px;">
</div>
</body>
</html>
[příklad na zpracování XML pomocí DOM]
Nečtení dat z XML pomocí JSON
Elegatnějším způsobem jak zpracovat XML je, převést jej na JSON. Je zřejmé, že převod XML do JSON nemůže být úplně přesný, což je dané rozdílnou strukturou XML a JSON formátů. Přesto je metoda AXml.toJson velmi užitečná, a to všude kde je zdrojem dat XML nepříliš rozsáhlé XML s komplikovanou strukturou. Parsování rozsáhlých XML dokumentů je třeba se vyvarovat. Má-li XML víc jak několik stovek uzlů, je daleko rychlejší načítání dat pomocí DOM metod, které ukázal předchozí příklad.
Logika převodu XML na JSON je popsána v tomto článku. Následuje variace předchozího příkladu.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Načtení a zpracování XML pomocí JSON | AMapy API</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="/api/api.php5?guid=VAS_GUID"></script>
<script type="text/javascript">
var Page = {
load: function() {
this.map = new AMap("map");
this.map.loadMaps();
var myAjax = new Ajax('stuff/chovneStanice.xml', {
method: 'get',
onComplete: this.onXmlLoaded.bind(this),
onFailure: function() {
alert('Chyba, server neodpověděl.');
}
}).request();
},
onXmlLoaded: function(text, xml) {
var xmlJson = AXml.toJson(xml);
var stations = xmlJson.chovneStanice.stanice;
for(var i=0; i < stations.length; i++) {
var stanice = stations[i];
if (stanice.nazev && stanice.gps) this.createMarker(stanice.nazev, stanice.gps);
}
},
createMarker: function(title, gps) {
var marker = new AMarker(new AGeoPoint(gps));
marker.addEvent('onClick', function() { marker.showBubble(title); });
this.map.addOverlay(marker);
}
}
window.addEvent('domready',Page.load.bind(Page));
</script>
</head>
<body>
<div id="map" style="width: 500px; height: 300px;">
</div>
</body>
</html>
[příklad na transformaci XML do JSON]
Úplně ideální je vrátit klientu rovnou JSON. XML má své využití na mnoha místech, ale faktem zůstává, že jeho podpora v nejrozšířenějších prohlížečích zatím není dostačující.
Mapové vrstvy, aneb jak jsou definované podklady
Ve snaze o maximální otevřenost, AMapy API umožňuje nahradit mapové podklady podklady vlastními. Podklady jsou tvořeny čtvercovými obrázky typu jpg, gif nebo png.
Pro vložení vlastních podkladů je třeba znát třídy AMapType a AScaleInfo. Stručně řečeno, AScaleInfo popisuje jedno měřítko, tedy počet čtverců na osách x a y, cestu k obrázkům a jejich typ, kartografické umístění. AMapType popisuje jednu mapovou aplikaci, tj. typ mapy. Ta se skládá z jednotlivých měřítek (AScaleInfo), má nějaký název a pár dalších vlastností.
Jak vytvořit vlastní podklady?
Pro vytvoření vlastních podkladů musíte nejprve nařezat jednotlivé čtverce. Na internetu existuje řada utilit. Následující příklad byl vytvořen pomocí Google Maps Image Cutter.
Tento software nařeže vložený obrázek, a vytvoří tedy řadu maličkých obrázků. Navíc k tomu vygeneruje kompletní stránku s Google Mapou. Aby šli vytvořené obrázky použít i na AMapy API, poskytuje API (navíc) možnost definovat cestu k obrázku stejně, jako to dělá Google Maps, a to pomocí vlastní funkce getTileURL.
[Vlastní podklady]Překryvné mapové vrstvy
Překryvné mapové vrstvy jsou definovány úplně stejně jako vlastní mapové podklady, liší se pouze způsobem přidání. Překryvná vrstva se nazývá layer. Přidat lze maximálně tři překryvné vrstvy.
[Překryvné vrstvy]Kontextové menu
Kontextové menu slouží k různým akcím spojeným s konkrétním místem. Jak ukazuje následující příklad, je menu plně konfigurovatelné.
var Page = {
load: function() {
// definice obsahu i akcí kontextového menu
var menu = [
new AContextMenuItem('Označit místo', (function() {
this.map.addOverlay(new AMarker(this.menuPosition, {'draggable': true}));
}).bind(this), AMAPY_API_IMAGE_ROOT + 'cmenu/spendlik.png', 21),
new AContextMenuItem('Vycentrovat', (function() {
this.map.loadMaps(this.menuPosition);
}).bind(this)),
new AContextMenuItem("<span style='font-size: 10px' id='contextmenugps'><span>", null, AMAPY_API_IMAGE_ROOT + 'cmenu/gps.png', 21)
];
this.map = new AMap("map", {
// definice položek
'contextMenuItems': menu,
// nadefinování akce, která se provede po otevření menu, zde si například uložím pozici myšky a zobrazím ji i do menu
'onContextMenuShowed': (function() {
this.menuPosition = this.map.getGeoMousePos();
$('contextmenugps').innerHTML = this.menuPosition.toDisplayGPS();
}).bind(this)
});
this.map.loadMaps();
}
}
window.addEvent('domready', Page.load.bind(Page));
[Kontextové menu]