Deprecated: Joomla\Input\Input implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /home/adatpcom/public_html/libraries/vendor/joomla/input/src/Input.php on line 41

Deprecated: Return type of Joomla\Input\Input::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/adatpcom/public_html/libraries/vendor/joomla/input/src/Input.php on line 170

Deprecated: Joomla\CMS\Input\Input implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /home/adatpcom/public_html/libraries/src/Input/Input.php on line 31

Deprecated: Joomla\CMS\Input\Cookie implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /home/adatpcom/public_html/libraries/src/Input/Cookie.php on line 0

Deprecated: str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in /home/adatpcom/public_html/libraries/src/Uri/Uri.php on line 141
Webes térinformatika - PaciTrip 5. rész (Új funkció: információ ablak)

Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /home/adatpcom/public_html/templates/shaper_helix3/html/modules.php on line 21
Webes térinformatika - PaciTrip 5. rész (Új funkció: információ ablak)

Webes térinformatika - PaciTrip 5. rész (Új funkció: információ ablak)

Webes térképes alkalmazás készítéséről szóló sorozatunk előző részében sikerült elérnünk, hogy téradataink egy alaptérképi réteggel együtt megjelenjenek, s ezáltal a korábban, a koncepció megalkotásakor megfogalmazott funkcionális követelmények felét sikerült teljesítenünk. Azonban az alkalmazás ebben a formában nem kínál sok interakciót a felhasználó számára: mindössze nagyítani és kicsinyíteni tud a térképen. Nem tud váltani az egyes utazások között, nem tudja időcsúszka segítségével vizsgálni az utazások egyes napjait, de még információt sem kaphat az állomásokról és útvonalszakaszokról. Következő lépésben ezt az utóbbi funkciót szeretném beépíteni az alkalmazásba: legyen képes az állomásokra és az útvonalak szakaszaira kattintva azokról adatokat megjeleníteni egy kis információ ablakban.

Első lépésben fogalmazzuk meg a feladatot egy (ha szükséges, összetett) mondatban!

Azt szeretnénk megvalósítani, hogy a webes térképes alkalmazásban az állomásokra vagy az útvonalszakaszokra kattintva jelenjen meg egy képregényekből ismert beszédbuborékra emlékeztető információ ablak, amelynek "hegye" a kattintott elemre mutat, benne pedig a kattintott elem alfanumerikus leíró adataiból (attribútumaiból) összeállított tartalom jelenjen meg.

PaciTrip - Információ ablak minta

Az ilyen típusú információ ablakokat a webes térképes alkalmazásokon gyakran nevezik popup-nak, amely azonban nem összekeverendő a böngészők popup/alert üzeneteivel vagy a popup reklámokkal!

Ha ez így egyszerre túl soknak, túl bonyolultnak tűnik, szedjük kisebb részekre, és egyszerre mindig csak a probléma egy kis részével foglalkozzunk! A sok kicsi sokra megy elvét követve a részfeladatok megoldásából előbb-utóbb aztán összeállítjuk az egész nagy feladat megoldását!

  1. "az állomásokra vagy az útvonalszakaszokra kattintva"
  2. "jelenjen meg egy (...) információ ablak"
  3. "képregényekből ismert beszédbuborékra emlékeztető"
  4. "amelynek "hegye" a kattintott elemre mutat"
  5. "benne pedig a kattintott elem alfanumerikus leíró adataiból (attribútumaiból) összeállított tartalom jelenjen meg"

Én első nekifutásra így osztanám fel a feladatot. Persze ha később úgy látom, hogy valamelyik még mindig túl összetett ebben a formában, akkor majd további részekre bontom, hogy egyszerre mindig csak egy jól megfogható, egyszerű dologgal kelljen foglalkoznom. Most nézzük meg, hogy mit is kell csinálni az egyes részfeladatok teljesítéséhez és azonosítsuk, melyik fájlban kell majd a kódon változtatnunk ehhez!

  1. A kattintás eseményt kell megragadnunk és ha ez bekövetkezik, akkor kell végrehajtani az információs ablakot megjelenítő logikát. A gyakorlatban ezt úgy tudjuk megvalósítani, hogy a Javascript fájlunkban a kattintás eseményhez (event) társítunk egy eseménykezelő (event handler) funkciót.
  2. Az információ ablak egy új eleme lesz a webes alkalmazásnak, azaz végeredményben egy HTML element lesz. Többféle megoldás is létezik ennek az element-nek a létrehozására, kezelésére, én azt választom, hogy először üresen hozom létre a HTML fájlban, s az információ ablakot megjelenítő logika csak a láthatóságát és a tartalmát fogja változtatni. Mivel a térképhez tartozó element-ről van szó, a '#map' div-en belül hozzuk majd létre.
  3. Az információ ablak megjelenését, stílusát a CSS fájlon keresztül tudjuk vezérelni, ezért ebbe kell majd írnunk néhány szabályt, hogy az információs ablak úgy nézzen ki, ahogy szeretnénk.
  4. Ezt a kattintás (vagyis az 1. számú részfeladat) után közvetlenül tudjuk megvalósítani, amikor az alkalmazás elkezdi végrehajtani a kattintás Javascript fájlban leírt eseménykezelő funkcióját. Ebben a funkciónak tehát tudnia kell, hogy
    1. melyik elemre kattintott a felhasználó,
    2. aztán oda kell elhelyeznie az információ ablak egyik karakteres pontját.
  5. Ezt szintén az 1. számú részfeladat folytatásaként tudjuk megvalósítani. Az ekkor elinduló az eseménykezelő funkciónak
    1. ki kell nyernie a kattintott elem attribútumait,
    2. össze kell fűznie az így kinyert adatokat,
    3. az összefűzött tartalmat be kell illeszteni az információ ablak HTML element-jébe és
    4. meg kell jelenítenie az információs ablak HTML element-jét.

Látható, hogy már a részfeladatok átgondolása során is további részfeladatokra bontottam egyik-másik összetett feladatot, mert így egyszerűbb lesz végrehajtani őket. Az is látszik, hogy egyik-másik részfeladatról már meg tudtam állapítani, hogy egymásra épülnek, ezért érdemes tisztázni, hogy milyen sorrendben hajtsuk végre a részfeladatokat. Nincs kőbe vésett, általánosan elfogadott sorrend. Van aki szigorú sorrendet követ, van aki ide-oda ugrálva a fájlok között beleír abba, amelyikbe éppen szükséges. Az átláthatóság kedvéért én egy szigorúbb sorrendet követek most: először a HTML fájlba írjuk bele, ami szükséges (2. számú részfeladat), majd a CSS-be vesszük fel az új stílusszabályokat (3. számú részfeladat), s végül a JS fájlba írjuk le az eseménykezelést és az eseménykezelő funkciót (1. számú, 4. számú és 5. számú részfeladatok).

Ha valamilyen újdonságot szeretnénk megvalósítani, támaszkodjunk a dokumentációra, tutorial-okra, példákra és mások megoldásaira! Az OpenLayers weboldalán a példák (Examples) között keresgélve ti is megtalálhatjátok a Popup névre keresztelt kis mintaalkalmazást, amely nagyjából azt valósítja meg, amit mi szeretnénk, ezért az információ ablak elkészítése során nagyban támaszkodom erre a példára!

HTML - Az információ ablak element-je

A példától annyiban eltérek, hogy a  '#map' div-en belül helyezem el az újabb div-et, amelynek a '#popup' id-t adom. Ezen belül további két div-et hozok létre:

  • egyet ('#popup-closer') az információ ablakot bezáró gombnak, ami egy X-et ábrázoló kép (img) lesz (41. sor a lenti kódban),
  • egyet pedig az információ ablak majdani tartalmának ("#popup-content'), ez egyelőre üresen marad (42. sor a lenti kódban).
<!DOCTYPE html> 
<html>
    <head>
        <meta charset = "utf-8"></meta>
        <title>PaciTrip</title>
        <!-- Open Sans betűtípus -->
        <link rel = "stylesheet" type = "text/css" href = "libs/open_sans_font.css"/>
        <!-- jQuery -->
        <script src = "libs/jquery.js"></script>
        <!-- OpenLayers -->
        <script src = "libs/ol.js"></script>
        <link rel = "stylesheet" href = "libs/ol.css" type = "text/css"/> 
        <!-- Saját JS -->
        <script src = "js/map.js"></script>
        <!-- Saját CSS -->
        <link rel = "stylesheet" href = "css/style.css" type = "text/css"/>
    </head>
    <body>
 
        <!-- Header -->
        <div id = "header" class = "box">
            <span class = "mainTitle">PaciTrip</span>
            <span class = "subTitle">NordTrip - 2018.06.15-29.</span>
            <span class = "logo"><a href = "https:\\adatterkep.com"><img src = "img/logo.png" alt = "AdatTérKép logó" title = "AdatTérKép"/></a>
        </div>
     
        <!-- Map -->
        <div id = "map" class = "box">
     
            <!-- Trip Selector --> 
            <div id = "tripSelector">
                <button class = "tripSelectorButton" value = "nordtrip">NordTrip</button>
                <div class = "tripSelectorDropdownItem" value = "nordtrip">NordTrip</div>
                <div class = "tripSelectorDropdownItem" value = "swisstrip">SwissTrip</div>
                <div class = "tripSelectorDropdownItem" value = "iretrip">IreTrip</div>
                <div class = "tripSelectorDropdownItem" value = "eurotrip">EuroTrip</div>
            </div>
         
            <!-- Popup -->
            <div id = "popup" class = "ol-popup">
                <div id = "popup-closer" class = "ol-popup-closer"><img src = "img/closer.png"/></div>
                <div id = "popup-content"></div>
            </div>
         
            <!-- Time slider -->
            <div id = "timeSlider" class = "box">
            </div>

        </div>

    </body>
</html>

PaciTrip - Az információ ablak HTML element-jei

Ez a módosítás egyelőre nem eredményez látványos változást az alkalmazásban. Mivel a legtöbb element, amit létrehoztunk üres, egyedül a kép jelenik meg, s ráadásul lejjebb tolja a térképet, ami így kilóg a képernyőből. De aggodalomra semmi ok, egyelőre jó ez így, a feladat akkor lesz teljes, ha a stílusokat és a Javascript logikát is megírtuk!

CSS - Az információ ablak megjelenítése

A stílus szabályokat először egy az egyben átveszem a korábban már említett példából, majd néhány apróságot változtatok rajta, hogy jobban illeszkedjen az alkalmazás eddigi koncepciójába:

  • a pixelben (px) megadott méreteket relatív mértékegységre (em) cserélem,
  • színek értékeit változók segítségével adom meg,
  • a '.ol-popup-closer' szabályból eltávolítom a text-decoration property-t, mivel az információ ablak bezárásakor nem szöveget, hanem képet használok, helyette hozzáadom a cursor property-t 'pointer' értékkel,
  • a '.ol-popup-closer:after' szabályt eltávolítom, mivel ez csak azért felel, hogy a bezárásért felelős div-ben megjelenjen egy 'X' karakter, de nálam ott egy kép van,
  • hozzáadom a popupImage szabályt, amely az információ ablakban megjelenő kép méretét és igazítását határozza meg,
  • hozzáadom a popupLink szabályt, amely az információ ablakban elhelyezett (blog bejegyzésre mutató) link megjelenését határozza meg.

Mindezen módosítások után így néz ki a CSS fájlhoz hozzáadott kódrészlet:

/* Korábbi CSS kód */

/* Popup */

.ol-popup {
    position: absolute;
    background-color: var(--color-v03);
    -webkit-filter: drop-shadow(0 .0625em .25em rgba(0,0,0,0.2));
    filter: drop-shadow(0 .0625em .25em rgba(0,0,0,0.2));
    padding: 1em;
    border-radius: .625em;
    border: .0625em solid #cccccc;
    bottom: .75em;
    left: -3.125em;
    min-width: 17.5em;
}

.ol-popup:after, .ol-popup:before {
    top: 100%;
    border: solid transparent;
    content: " ";
    height: 0;
    width: 0;
    position: absolute;
    pointer-events: none;
}

.ol-popup:after {
    border-top-color: var(--color-v03);
    border-width: .625em;
    left: 3em;
    margin-left: -.625em;
}

.ol-popup:before {
    border-top-color: #cccccc;
    border-width: .6875em;
    left: 3em;
    margin-left: -.6875em;
}

.ol-popup-closer {
    position: absolute;
    cursor: pointer;
    top: .3125em;
    right: .3125em;
}

.popupImage{
    max-width: 90%;
    margin: .5em 5%;
}

.popupLink{
    color: var(--color-v01);
    text-decoration: none;
    font-weight: bold;
    margin: 0 auto;
}

Ezzel a módosítással egy pillanatra már láthatóvá válik az információ ablak, persze egyelőre üresen, aztán előfordulhat, hogy a térkép kitakarja, csak a böngésző ablak bal szélén lehet látni, hogy azért ott van. (Az alábbi képen átmenetileg felülre helyezem, hogy látható legyen, hol kell keresni.)

PaciTrip - A stílusokkal ellátott információ ablak

Javascript -  Eseménykezelő rendelése a kattintás eseményhez

Következő feladatunk elkapni a kattintás pillanatát és elindítani az információ ablakot kirajzoló és tartalommal megtöltő logikát. Az OpenLayers példában látható módon a 'map' változóban tárolt Map objektum on() metódusát fogom ehhez használni, amely két paramétert vár: az esemény típusát és egy funkciót, amelyet az esemény bekövetkezésekor végre kell hajtania.

Ha egészen precízek akarunk lenni, akkor az on() a PluggableMap objektum metódusa, amely azonban a Map objektum szülőobjektuma. Az objektum-orientált logika ökölszabálya szerint a gyerek (Map) objektum rendelkezik mindazon tulajdonságokkal és funkciókkal, mint a szülője (PluggableMap), így bár a dokumentációban a Map-nél nincs feltüntetve az on() metódus, mégis rendelkezik vele, hiszen a szülőobjektumának van ilyen funkciója.

A Map objektum dokumentációjában látható, hogy milyen eseményekkel tud dolgozni: van 'click', 'dblclick' és 'singleclick' is (sok egyéb mellett), ezekből én a 'singleclick'-et választom. A végrehajtandó funkciónak pedig adok egy paramétert, amit 'evt'-nek nevezem el. Ez valójában egy objektum, amely a konkrét esemény bekövetkezésekor jön létre, s tartalmaz számos fontos adatot, kattintás esetében például azt, hogy a képernyő melyik pontjára kattintottunk. A funkció magjában egyelőre csak jelzem, hogy oda kerül majd az információ ablakot megjelenítő és tartalommal megtöltő logika, illetve ellenőrzési célból beleteszek egy console.log() parancsot, hogy lássuk, valóban történik-e valami a kattintáskor.

// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {

        // Az információ ablak elhelyezését és tartalommal való megtöltését végző logika
        console.log("Ellenőrizzük, hogy valóban történik-e valami, ha kattintunk!");

    });
 
});

Ezzel sikeresen megragadtuk a kattintás eseményt, a böngésző fejlesztői eszköztárát megnyitva (F12) a Console felületen látható, ahogy a térképen kattintva megjelenik a console.log() parancsba írt szöveg.

PaciTrip - A kattintás eseménykezelő működésének ellenőrzése

Javascript -  Információ ablak tartalmának összeállítása

Az eseménykezelő funkció paramétereként kapott objektum tehát tartalmazza a kattintás helyét, következő lépésben tehát ennek segítségével nyerjük ki, hogy melyik pontra/pontokra vagy szakaszra/szakaszokra esik ez a kattintás! Bár logikailag a kattintás egy pont, a technológiai sajátosságok miatt ez valójában egy pixel lesz, s azon a pixelen a térkép aktuális léptékétől függően több pont vagy szakasz is elhelyezkedhet. Tovább színesíti a helyzetet, hogy a kattintás jellegű események webes térképes alkalmazások során többnyire rendelkeznek valamekkora tolerancia értékkel is, azaz nem csak a kattintás pontos helyét, hanem valamekkora sugarú környezetét is figyelembe veszi majd az alkalmazás.

Ezt a helyzetet a logika kialakítása során kezelnünk kell, hiszen egyszerre csak egy információ ablak látszódhat, így el kell döntenünk, hogy ha a kattintás például 3 állomást és 2 szakaszt is érint, akkor melyikhez tartozó információ ablak jelenjen meg? Én úgy döntöttem, hogy ha a kattintás állomást és szakaszt is érint, akkor az állomáshoz tartozó információ ablak jelenjen meg, ha pedig több állomást is érint a kattintás, akkor rögtön az elsőt adja vissza, így a kódot is ennek megfelelően alakítottam.

Az eseménykezelő funkción belül először definiálok egy változót. A popupdrawn egy logikai változó, alapértelmezetten hamis (false) értékkel, amely azt mutatja, hogy megjelent-e már információ ablak az adott kattintás hatására. Ezután a Map objektum forEachFeatureAtPixel() metódusát használom, amely két paramétert vár: egy pixelt és egy ún. callback functiont. A pixelt az esemény objektumból (evt) szerzem meg, amelyről korábban azt állítottam, hogy tartalmazza a kattintás helyét. Ezt az információt egészen pontosan a pixel tulajdonsága hordozza, így a paraméter értéke 'evt.pixel' lesz. A forEachFeatureAtPixel() funkció ezt felhasználva megvizsgálja, hogy a paraméterként kapott pixelt melyik vektoros téradatok (feature-k) érintik, s mindegyiken végrehajtja a callback function-t. A forEachFeatureAtPixel() funkciónak lehetnek további opciói is: egyrészt korlátozható, hogy mely rétegek elemeivel foglalkozzon (layerFilter), másrészt meghatározható a korábban említett tolerancia (hitTolerance). (Utóbbi értékét 2-re állítom.)

A callback function segítségével először is ellenőrzöm, hogy az éppen vizsgált vektoros téradat (feature) melyik rétegen található. Mivel mind az állomások, mind az útvonalszakaszok rétegének adtam korábban title tulajdonságot, ennek értékét meg is tudom vizsgálni (layer.getProperties().title). Elsőként azt ellenőrzöm, hogy állomásról van-e szó (layer.getProperties().title == 'Állomások'), összekötve azzal a vizsgálattal, hogy az információ ablak megjelent-e már. Ha a geometria az állomások rétegéhez tartozik és még nincs információ ablak, akkor futtatok egy funkciót, amely összeállítja és elhelyezi az állomásokra igazított információ ablakot (buildStopsPopup). Másodikként pedig az előző analógiájára ellenőrzöm, hogy a geometria az útvonal rétegen van-e (és megjelent-e már információ ablak) és ha igen, akkor futtatom azt a funkciót, amely összeállítja és elhelyezi az útvonalakra igazított információ ablakot (buildRoutesPopup). Bármelyik funkció fut is le, a popupdrawn változó értékét igazra (true) állítom, hiszen megjelent egy információ ablak. Ezzel tudom biztosítani azt, hogy akárhány állomás és szakasz esik is a vizsgált pixelre, csak egy információ ablak jelenik meg.

// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {
        var popupdrawn = false;
        map.forEachFeatureAtPixel(evt.pixel, function(feature, layer){
            if(layer.getProperties().title == 'Állomások' && !popupdrawn){
                buildStopsPopup(feature);
                popupdrawn = true;
            }else if(layer.getProperties().title == 'Útvonal' && !popupdrawn){
                buildRoutesPopup(feature, evt.coordinate);
                popupdrawn = true;
            }
        }, {hitTolerance : 2});
    });
 
});

Az előbbi gondolatmenetnek egy (vagy kettő, nézőpont kérdése) hibája van csupán: egyelőre nem létezik olyan funkció, amely az állomásokra/útvonalakra igazított információ ablakot elkészítené. Ezért a kód tesztelése előtt még készítsük el ezt a két funkciót! Hasonló logika mentén működik mindkettő, ám néhány lényeges pontban el térnek. Mindkettő esetben az alábbi lépésekre lesz szükség:

  1. paraméterek meghatározása,
  2. koordináták kinyerése,
  3. attribútumok kinyerése,
  4. tartalom összefűzése az attribútumok alapján,
  5. tartalom beillesztése a '#popup-content' element-be.

Az állomások információ ablakát felépítő és megjelenítő funkció ezek után így néz ki:

  1. Egy paramétert kér (feature), a kiválasztott állomás objektumát.
  2. A feature objektumból lekérdezzük a geometriát (ami egy pont lesz) a getGeometry() metódus segítségével, amiből kiolvassuk a pont koordinátáit a getCoordinates() metódussal. Ezt eltároljuk a coordinate változóban.
  3. A feature objektumból lekérdezzük az alfanumerikus adatokat a getProperties() metódus segítségével. Ezt eltároljuk a props változóban. A változóban tárolt objektum olyan kulcsokkal rendelkezik, mint amilyen oszlopokat létrehoztunk a stops.geojson készítése során.
  4. A contentHtml változóban összefűzünk egy HTML tag-eket is tartalmazó szöveget, amelynek bizonyos helyeire a props változóban tárolt értékeket illesztjük be. Például azt, hogy a kattintott állomást hányadik napon érintettük, a props.day segítségével tudjuk beilleszteni.
  5. Egy jQuery kifejezés segítségével megváltoztatjuk a '#popup-content element' HTML tartalmát, az új tartalom a contentHtml-ben összefűzött szöveg lesz: $('#popup-content').html(contentHtml).
// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {
        var popupdrawn = false;
        map.forEachFeatureAtPixel(evt.pixel, function(feature, layer){
            if(layer.getProperties().title == 'Állomások' && !popupdrawn){
                buildStopsPopup(feature);
                popupdrawn = true;
            }else if(layer.getProperties().title == 'Útvonal' && !popupdrawn){
                buildRoutesPopup(feature, evt.coordinate);
                popupdrawn = true;
            }
        }, {hitTolerance : 2});
    });

    // Az állomások információ ablakát összeállító és megjelenítő funkció
    function buildStopsPopup(feature){
        var coordinate = feature.getGeometry().getCoordinates();
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                       props.day + '. nap ' + props.stop + '. állomás: ' + '<br/>' + 
                       props.place + '<br/>' +
                       '<img class = "popupImage" href = "img/nordtrip/' + props.pic + '"/><br/>' +
                       props.desc + '<br/>' +
                       '</div>';

        $('#popup-content').html(contentHtml);
    }
 
});

Az útvonalszakaszok információ ablakát felépítő és megjelenítő funkció sok esetben hasonló lesz az előzőhöz, de vannak fontos különbségek:

  1. Két paramétert kér: a kiválasztott állomás objektumát (feature) és a kattintás térképi helyét (clickedMapPoint).
  2. A feature objektumból lekérdezzük a geometriát (ami egy vonalas elem lesz) a getGeometry() metódus segítségével. Mivel a vonalnak szinte bármelyik pontját választhatnánk, a kattintás térképi helyéhez legközelebb esőt keressük meg a getClosestPoint() metódus segítségével, amelynek paraméterként adjuk a clickedMapPoint-ban tárolt pontot. Ezt eltároljuk a coordinate változóban.
  3. A feature objektumból lekérdezzük az alfanumerikus adatokatgetProperties() metódus segítségével. Ezt eltároljuk a props változóban. A változóban tárolt objektum olyan kulcsokkal rendelkezik, mint amilyen oszlopokat létrehoztunk a routes.geojson készítése során.
  4. contentHtml változóban összefűzünk egy HTML tag-eket is tartalmazó szöveget, amelynek bizonyos helyeire a props változóban tárolt értékeket illesztjük be. Például azt, hogy a kattintott állomást hányadik napon érintettük, a props.day segítségével tudjuk beilleszteni.
  5. Egy jQuery kifejezés segítségével megváltoztatjuk a '#popup-content element' HTML tartalmát, az új tartalom a contentHtml-ben összefűzött szöveg lesz: $('#popup-content').html(contentHtml).
// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {
        var popupdrawn = false;
        map.forEachFeatureAtPixel(evt.pixel, function(feature, layer){
            if(layer.getProperties().title == 'Állomások' && !popupdrawn){
                buildStopsPopup(feature);
                popupdrawn = true;
            }else if(layer.getProperties().title == 'Útvonal' && !popupdrawn){
                buildRoutesPopup(feature, evt.coordinate);
                popupdrawn = true;
            }
        }, {hitTolerance : 2});
    });

    // Az állomások információ ablakát összeállító és megjelenítő funkció
    function buildStopsPopup(feature){
        var coordinate = feature.getGeometry().getCoordinates();
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                           props.day + '. nap ' + props.stop + '. állomás: ' + '<br/>' + 
                           props.place + '<br/>' +
                           '<img class = "popupImage" href = "img/nordtrip/' + props.pic + '"/><br/>' +
                           props.desc + '<br/>' +
                           '</div>';

        $('#popup-content').html(contentHtml);
    }

    // Az útvonalak információ ablakát összeállító és megjelenítő funkció
    function buildRoutesPopup(feature, clickedMapPoint){
        var coordinate = feature.getGeometry().getClosestPoint(clickedMapPoint);
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                          '<a class = "popupLink" href = "' + props.blog + '" target = "blank">NordTrip ' + props.day + '. nap (' + props.date + ')</a>' + 
                          '</div>';

        $('#popup-content').html(contentHtml);
    }

});

A webes térképes alkalmazásban egy állomásra vagy útvonalszakaszra kattintva már megtelik tartalommal az eddig üres buborék, sajnos azonban a térkép még most is rátakarhat és továbbra is a bal alsó sarokban marad az információ ablak, nem a kattintás helyén jelenik meg. Ez azért van, mert bár a coordinate változóba kiszámoltuk, hol is kellene lennie, ezt az információt még nem használjuk fel. Legyen tehát ez a következő lépés és a kitűzött feladatokkal készen is leszünk!

PaciTrip - Információ ablak jó tartalommal, de rossz helyen

Javascript -  Információ ablak megfelelő elhelyezése

Az információ ablak két okból nincs megfelelő helyen:

  • nem kapcsoltuk össze a térképpel, attól egyelőre teljesen függetlenül létezik az alkalmazásban,
  • nem igazítottuk a megfelelő térképi koordinátákra.

Az előbbi probléma megoldásához először egy Overlay objektumot kell létrehoznunk, amely a térkép felett megjelenő, egy térképi ponthoz kapcsolt fedvény. Létrehozásakor megadható több paraméter is, amelyekből most csak néhányat használok:

  • element: a HTML element, amelyben a fedvényként megjelenítendő tartalom található,
  • autoPan: ha értéke igaz (true), új pozíció megadásakor automatikusan az új helyre mozgatja a térképi nézetet,
  • autoPanAnimation: értéke egy objektum, amelyen belül csak a duration kulcsnak adtam értéket, hogy a nézet váltás ne ugrásszerű legyen.

Az Overlay objektumot eltároltam az overlay változóban (a lenti kód 7-13. sorai), amit aztán a Map objektumhoz kapcsoltam olyan módon, hogy az eddigi paraméterei mellé felvettem még az overlays-t is (a lenti kód 21. sora).

A második problémát így már nagyon könnyen tudjuk orvosolni. Az Overlay objektum rendelkezik egy setPosition() metódussal, amely a fedvényt egy új térképi pontra illeszti. A kattintás utáni új térképi pontokat pedig már kiszámoltuk a buildStopsPopup() és buildRoutesPopup() funkciókon belül (a coordinate változó tartalmazza az új helyzetet). Az információ ablak megfelelő helyre igazításához tehát csak annyit kell tennünk, hogy ezt a két funkciót kiegészítjük az overlay.setPosition(coordinate) sorral (a lenti kód 50. és 64. sorai).

// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy fedvényt (Overlay) a térképhez
    var overlay = new ol.Overlay({
        element : $("#popup")[0],
        autoPan: true,
        autoPanAnimation: {
            duration: 250
        }
    }); 

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar],
        overlays : [overlay]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {
        var popupdrawn = false;
        map.forEachFeatureAtPixel(evt.pixel, function(feature, layer){
            if(layer.getProperties().title == 'Állomások' && !popupdrawn){
                buildStopsPopup(feature);
                popupdrawn = true;
            }else if(layer.getProperties().title == 'Útvonal' && !popupdrawn){
                buildRoutesPopup(feature, evt.coordinate);
                popupdrawn = true;
            }
        }, {hitTolerance : 2});
    });
    
    // Az állomások információ ablakát összeállító és megjelenítő funkció
    function buildStopsPopup(feature){
        var coordinate = feature.getGeometry().getCoordinates();
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                           props.day + '. nap ' + props.stop + '. állomás: ' + '<br/>' + 
                           props.place + '<br/>' +
                           '<img class = "popupImage" href = "img/nordtrip/' + props.pic + '"/><br/>' +
                           props.desc + '<br/>' +
                           '</div>';

        overlay.setPosition(coordinate);

        $('#popup-content').html(contentHtml);
    }
    
    // Az útvonalak információ ablakát összeállító és megjelenítő funkció
    function buildRoutesPopup(feature, clickedMapPoint){
        var coordinate = feature.getGeometry().getClosestPoint(clickedMapPoint);
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                          '<a class = "popupLink" href = "' + props.blog + '" target = "blank">NordTrip ' + props.day + '. nap (' + props.date + ')</a>' + 
                          '</div>';
        
        overlay.setPosition(coordinate);
        
        $('#popup-content').html(contentHtml);
    }

});

Kész vagyunk?

A kitűzött feladatokkal igen! Azonban ha most kicsit tesztelgetjük az alkalmazást, kiderül, hogy van egy "apróság", amiről az elején elfeledkeztem. Az információ ablak hibátlanul megjelenik, ha másik állomásra/útvonalszakaszra kattintok, megváltozik a tartalma, azonban semmilyen módon nem tudom eltüntetni! Hiába van ott a bezárást jelképező X, rákattintva nem történik semmi, ehhez a kattintás eseményhez ugyanis nem kapcsoltunk még semmilyen eseménykezelőt. Ezt a kapcsolatot a jQuery click() funkciójával hozzuk létre, amelynek paramétere az eseménykezelő funkció. Ezen belül pedig csak annyit kell tennünk, hogy az információ ablak fedvény (overlay) pozícióját meghatározotlanná (undefined) tesszük (a lenti kód 39-41. sorai).

// Várjuk meg, míg felépül a HTML struktúra (jQuery)
$(document).ready(function(){
 
    // ... Korábbi kód, amely létrehozta a stílusokat, adatforrásokat, rétegeket, stb. ...

    // Hozzunk létre egy fedvényt (Overlay) a térképhez
    var overlay = new ol.Overlay({
        element : $("#popup")[0],
        autoPan: true,
        autoPanAnimation: {
            duration: 250
        }
    }); 

    // Hozzunk létre egy térképet (OpenLayers)
        var map = new ol.Map({
        view : view,
        layers : [osm, routesLayer, stopsLayer],
        target : 'map',
        controls : [zoomButton, scaleBar],
        overlays : [overlay]
    });

    // Kattintás eseménykezelő hozzárendelése a térképhez
    map.on('singleclick', function(evt) {
        var popupdrawn = false;
        map.forEachFeatureAtPixel(evt.pixel, function(feature, layer){
            if(layer.getProperties().title == 'Állomások' && !popupdrawn){
                buildStopsPopup(feature);
                popupdrawn = true;
            }else if(layer.getProperties().title == 'Útvonal' && !popupdrawn){
                buildRoutesPopup(feature, evt.coordinate);
                popupdrawn = true;
            }
        }, {hitTolerance : 2});
    });

    // Kattintás eseménykezelő hozzárendelése az információ ablak bezáró gombjához
    $("#popup-closer").click(function() {
       overlay.setPosition(undefined);
    });
    
    // Az állomások információ ablakát összeállító és megjelenítő funkció
    function buildStopsPopup(feature){
        var coordinate = feature.getGeometry().getCoordinates();
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                           props.day + '. nap ' + props.stop + '. állomás: ' + '<br/>' + 
                           props.place + '<br/>' +
                           '<img class = "popupImage" href = "img/nordtrip/' + props.pic + '"/><br/>' +
                           props.desc + '<br/>' +
                           '</div>';

        overlay.setPosition(coordinate);

        $('#popup-content').html(contentHtml);
    }
    
    // Az útvonalak információ ablakát összeállító és megjelenítő funkció
    function buildRoutesPopup(feature, clickedMapPoint){
        var coordinate = feature.getGeometry().getClosestPoint(clickedMapPoint);
        var props = feature.getProperties();

        var contentHtml = '<div class = "popupHtml">' +
                          '<a class = "popupLink" href = "' + props.blog + '" target = "blank">NordTrip ' + props.day + '. nap (' + props.date + ')</a>' + 
                          '</div>';
        
        overlay.setPosition(coordinate);
        
        $('#popup-content').html(contentHtml);
    }

});

PaciTrip - Információ ablak jó tartalommal, jó helyen

Ahogy bővül és fejlődik a webalkalmazás, a HTML, a CSS és a JS is változni fog még. Elsősorban új elemeket, szabályokat és funkciókat adunk majd hozzá, de előfordulhat az is, hogy a most leírtakból kell majd törölnünk, vagy legalább egy részét megváltoztatnunk.

A PaciTrip webes térképes alkalmazás már elkészült állományait megtalálod GitHub oldalunkon!

Tekintsd meg a PaciTrip webes térképes alkalmazást aktuális állapotában! (Az aktuális állapot eltérhet a fent leírt változattól!)

Ha adatbázis-építés, adatelemzés, vizualizáció, térinformatika témában segítségre van szükséged, írj nekünk az Ez az e-mail-cím a szpemrobotok elleni védelem alatt áll. Megtekintéséhez engedélyeznie kell a JavaScript használatát. címre és megpróbálunk egy tippel, ötlettel segíteni!


Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /home/adatpcom/public_html/templates/shaper_helix3/html/modules.php on line 21