Skip to content

Latest commit

 

History

History

05_city_map

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Mapa měst

V tomto díle přidáme k seznamu měst z minulého dílu jejich mapu a zobrazení dalších informací o aktuálně zvoleném městu. Navíc při zvolení města v seznamu se mapa automaticky posune tak, aby zvolené město bylo uprostřed.

Enum

Pokud potřebujeme vyjádřit nějaký výčet, obvykle číselných, hodnot, kde každá hodnota má nějaký význam, je vhodné si hodnoty pojmenovat. Pro pojmenovávání výčtů je v Pythonu k dispozici třída Enum, která umožňuje snadno jednotlivé hodnoty výčtu pojmenovat a použít.

Enum vytvoříme snadno tak, že vytvoříme třídu, která dědí od Enum. Jako atributy třídy (tedy přímo ve třídě, nikoli v inicializátoru) pak popíšeme jednotlivé prvky výčtu a přiřadíme k nim číselné hodnoty. Jednotlivé prvky výčtu je vhodné pojmenovávat velkými písmeny.

Příklad:

from enum import Enum

class Roles(Enum):
	LOCATION = QtCore.Qt.UserRole+0
	AREA = QtCore.Qt.UserRole+1
	POPULATION = QtCore.Qt.UserRole+2

K prvkům takto vytvořeného enumu můžeme přistupovat pomocí jména, například Roles.LOCATION, pokud chceme získat číselnou hodnotu, použijeme atribut .value, například Roles.AREA.value.

Pokud nám na přesných číselných hodnotách nezáleží, můžeme použít místo číselné hodnoty funkci auto() z modulu Enum a ta čísla přiřadí automaticky.

Příklad:

from enum import Enum, auto

class Fruit(Enum):
	APPLE = auto()
	PEAR = auto()
	GRAPEFRUIT = auto()

Role

Jak jsme si již v minulém díle ukázali, k jednotlivým vlastnostem prvků v seznamu přistupujeme pomocí rolí. Zatím jsme si ukazovali pouze výchozí Qt.DisplayRole, ale nyní budeme chtít kromě jména města umět získat i počet obyvatel, rozlohu a pro umístění v mapě i souřadnice. Pro každou tuto informaci si tedy vytvoříme vlastní roli.

Qt podporuje uživatelsky vytvářené role. Aby nedocházelo ke kolizi rolí definovaných v Qt a uživatelských rolí, je existuje konstanta Qt.UserRole, která nám říká, od kterého čísla máme začít vytvářené role číslovat. Protože budeme vytvářet rolí více, vytvoříme si pro naše role enum. Tento enum můžeme vytvořit přímo uvnitř třídy CityListModel, protože jen uvnitř této třídy pro nás mají hodnoty význam.

Abychom mohli přistupovat k nově vytvořeným rolím, musíme dát všem, kteří naši třídu používají, najevo, že naše třída tyto role podporuje. Ke zjištění, jaké role jsou pro danou třídu dostupné slouží metoda roleNames. Tato metoda vrací slovník, jehož klíči jsou čísla rolí a hodnotami pojmenování rolí, tedy to, jak budeme k rolím přistupovat z QML. Pokud metodu nepředefinujeme, pak se předávají výchozí role. My k nim chceme přidat i role vlastní, proto nejprve získáme slovík rolí od předka a následně k němu přidáme nové klíče pro námi vytvořené role.

Hodnotami nemohou být obyčejné řetězce, ale kvůli vazbám do Qt musí jít o QByteArray. Ten můžeme snadno vytvořit předáním bytes objektu pri vytváření QByteArray. bytes objekt vytvoříme jako normální řetězec, akorát před první uvozovky předřadíme b, tedy například b'location'. Takovýto objekt pak není řetězcem, ale dokud v něm používáme jen základní znaky (pro nás převážně písmena anglické abecedy a čísla), chovají se tyto objekty obdobně. V našem případě s ním nepotřebujeme nijak pracovat, stačí nám ho jen vytvořit a předat ho do QByteArray, tedy například QByteArray(b'location').

Pokud máme metodu roleNames() takto předefinovanou, můžeme kdekoli, kde pracujeme s rolemi našeho modelu, používat i nově přidané role.

Souřadnice

Pro práci se zeměpisnými souřadnicemi se v Qt používá třída QGeoCoordinate. Tato třída umožňuje uchovávat jak 2D, tak 3D souřadnice, souřadnice musí být v systému WGS84. Třída má i metody na výpočet vzdálenosti nebo azimutu mezi dvěma body. Při vytváření QGeoCoordinate se jako první parametr zadává zeměpisná šířka, jako druhý zeměpisná délka a jako třetí volitelný nadmořská výška.

Popis programu

Program zobrazuje seznam všech měst v ČR a při zvolení nějakého města ze seznamu program ukáže v prostředním sloupci jeho index (pořadí v seznamu), rozlohu a počet obyvatel a zároveň vystředí mapu v pravé části tak, aby bylo zvolené město uprostřed.

Třída CityListModel slouží jako model pro seznam i mapu a jsou z ní získávány i rozšiřující informace o městech.

Popis grafického rozhraní

Rozhraní je rozděleno do tří sloupců. V prvním je seznam měst, ve druhém rozšiřující informace o zvoleném městě a ve třetím sloupci je zobrazená mapa s městy (reprezentovány svými popisky).

Protože na mnoha místech potřebujeme pracovat s aktuálně zvolenou položkou ze seznamu, bylo by nepraktické ve všech místech, kde s ní potřebujeme pracovat, ji získávat ze seznamu. Také by to nebylo vhodné z pohledu rozšiřitelnosti, například pokud bychom umožnili výběr města i kliknutím do mapy, tak bychom museli celý systém navázání složitě upravovat. Proto si vytvoříme novou property currentModelItem, ve které budeme uchovávat aktuálně zvolené město ze seznamu. Přesněji model aktuálně zvoleného města, kterého se můžeme ptát na všechny role, které jsme si předtím v Pythonu deklarovali. Tato property je viditelná z celého QML a použijeme ji všude tam, kde chceme zobrazovat informace o aktuálně vybraném prvku. Pokud bychom v budoucnu umožnili vybrat město jiným způsobem, bude stačit jen nastavit tuto proměnnou a vše ostatní bude fungovat stejně bez potřeby zásahu.

Pro zobrazení seznamu používáme ListView jako minule, delegát a highlight zůstaly nezměněny. Místo přímého použití property cityListModel jako modelu je nyní použit DelegateModel, který nám umožní relativně snadno získat zvolenou položku. DelegateModel má vlastnost model, která udává, z jakého modelu bude brát data, v našem případě to bude cityListModel a vlastnost delegate, kam přiřadíme (nezměněného) delegáta z minule. Když je delegát nastavený u DelegateModel, již se u ListView nenastavuje. Dále je potřeba při změně vybraného města v seznamu nastavit property currentModelItem.

Získání modelu aktuálně zvoleného prvku není úplně přímočaré, protože narážíme na univerzálnost jednotlivých komponent. Nejprve musíme získat DelegateModelGroup se všemi prvky z DelegateModelu, což uděláme pomocí vlastnosti items. Následně můžeme získat pomocí .get(<index>) objekt reprezentující prvek modelu na daném indexu. Index zjistíme stejně jako v minulém případě pomocí vlastnosti currentIndex. Ze získaného objektu ale ještě potřebujeme vlastnost model, abychom získali model zvoleného prvku a mohli se ptát na jeho jednotlivé role. Výše zmíněné kroky můžeme zapsat za sebe do řádku a získáme výsledný tvar cityListDelegateModel.items.get(cityList.currentIndex).model.

Sloupec s rozšířenými informacemi o vybraném městě tvoří sloupec s několika komponentami Text, ve kterých se z currentModelItem pomocí rolí získává rozloha a počet obyvatel. Abychom mohli uvádět km2 s horním indexem, musíme u komponenty, která je zobrazuje, nastavit vlastnost textFormat na Text.RichText a následně můžeme použít HTML značku <sup> a </sup> k ohraničení horního indexu.

Poslední sloupec obsahuje mapu. Pro práci s mapou a se souřadnicemi, musíme nejprve importovat QtLocation a QtPositioning. Mapu v QML reprezentujeme pomocí komponenty Map, která ale tvoří jen skořápku zajišťující interakci mezi zobrazovanou mapou, dalšími mapovými prvky a uživatelem.

K zobrazení podkladní mapy v komponentě Map je třeba vytvořit komponentu Plugin, ve které nastavíme, jakého typu mapa je a případné další parametry. V našem případě chceme zobrazit mapové dlaždice z projektu OpenStreetMap a to ve variantě bez popisků, aby se naše popisky měst nepletly s popisky na mapě. Takové dlaždice poskytuje například Wikimedia cloud services. Plugin nám pomocí vlastnosti name umožňuje vybrat z několika poskytovatelů dlaždic, my zvolíme osm, protože chceme zobrazovat data projektu OpenStreetMap. Tím vybereme Qt Location Open Street Map Plugin, ve kterém můžeme pomocí komponent PluginParameter upravovat jednotlivá nastavení. My si pomocí parametru osm.mapping.custom.host zvolíme vlastního poskytovatele dlaždic.

V komponentě Map nastavíme plugin pomocí vlastnosti plugin a aby byla zobrazena mapa od poskytovatele, kterého jsme nastavili v pluginu, musíme nastavit vlastnost activeMapType dle dokumentace na supportedMapTypes[supportedMapTypes.length - 1]. Tímto máme nastavenou podkladní mapu.

Abychom na mapě mohli zobrazovat názvy měst z našeho modelu, musíme do mapy přidat komponentu MapItemView. Obdobně jako ListView nastavíme MapItemView dvě vlastnosti - model, ze kterého máme brát data a delegate, ve kterém určíme, jak mají data vypadat. Komponenta delegáta musí obsahovat právě jednu komponentu dědící od MapItem, což v našem případě je MapQuickItem.

MapQuickItem má několik vlastností, které je vhodné nastavit:

  • coordinate - souřadnice, na kterých se má zobrazovaná komponenta zobrazit
  • sourceItem - zobrazovaná komponenta - QML komponenta, která se má zobrazit
  • anchorPoint - který bod ze zobrazované komponenty má být ten, který se zobrazí na zadaných souřadnicích. Udává se v pixelech, ve výchozím stavu se na zadaných souřadnicích zobrazí levý horní roh zobrazované komponenty

V našem případě je zobrazovaná komponenta jednoduchá komponenta Text, která obsahuje jméno daného města a je umístěna na souřadnice daného města.

Nyní máme podkladní mapu, popisky s názvy měst a zbývá jen nastavit, jak má být mapa přiblížená a jak má být vystředěná. Přiblížení zvolíme pomocí atributu zoomLevel a střed mapy určený vlastnotí center svážeme se souřadnicemi vybraného města ze seznamu, čímž zajistíme, aby se mapa při změně vybraného města automaticky vystředila na toto město. Samozřejmě dále můžeme mapou přibližovat, oddalovat a posouvat, ale když dojde ke změně vybraného města, mapa se automaticky vystředí na toto město.

Zdroje