• Ei tuloksia

Avoin palvelu- ja kohderajapinta Kanta-Hämeeseen

N/A
N/A
Info
Lataa
Protected

Academic year: 2022

Jaa "Avoin palvelu- ja kohderajapinta Kanta-Hämeeseen"

Copied!
54
0
0

Kokoteksti

(1)

AVOIN PALVELU- JA KOHDERAJAPINTA KANTA-HÄMEESEEN

Ammattikorkeakoulututkinnon opinnäytetyö Riihimäki, tieto- ja viestintätekniikka

Kevät, 2019

Yuxiu Guo

(2)

Tieto- ja viestintätekniikan koulutusohjelma Riihimäki

Tekijä Yuxiu Guo Vuosi 2019

Työn nimi Avoin palvelu- ja kohderajapinta Kanta-Hämeeseen Työn ohjaaja Toni Laitinen

TIIVISTELMÄ

Opinnäytetyössä tavoitteena oli tehdä paikkatietokanta Kanta-Hämeeseen.

Hankkeen taustalla oli nykyiset ongelmat paikkatietojen kanssa. Tietoja oli pal- jon, mutta ne olivat hajallaan. Niiden löydettävyys oli huono ja niihin oli vaikea päästä käsiksi. Tietojen löytäminen oli myös vaikeaa. Paikkatiedoilla tässä opin- näytetyössä ovat kahvilat, nähtävyydet, ulkoilureitit, jne. Niihin liittyy yleensä au- kioloajat ja tavoitteena oli että lähellä olevia avoinna olevia kohteita voisi hel- posti hakea.

Työn tilaaja on Hämeen ammattikorkeakoulu Oy, Älykkäät palvelut -tutkimusyk- sikkö. Mukana oli myös Hämeenlinnan kaupungin edustajia. Tilaajan tavoitteena oli saada helppokäyttöinen ja avoin paikkatietojärjestelmä, johon olisi mahdol- lista koostaa kaikki Kanta-Hämeen paikat. Erityisesti tärkeää oli, että paikkatie- toja voisi hakea helposti erilaisilla laitteilla. Tärkeimpänä oli avoin rajapinta, jonka avulla kannan tietojen ylläpitäminen onnistuisi helposti ja jonka päälle olisi mahdollista tehdä helposti erilaisia käyttöliittymiä.

Keskeiseksi tavoitteeksi tässä työssä tuli avoimet RESTful-palvelut. Lisäksi halut- tiin toteuttaa niiden päälle perus ylläpitokäyttöliittymät, joilla voidaan hallin- noida tietoja. Yksinkertainen karttakäyttöliittymä haluttiin, jotta tiedot saatiin vi- sualisoitua.

Tuloksena syntyi kaikki yllämainitut toiminnallisuudet. Varsinaista toteutustyötä helpotti asiakkaan antamat viitekehykset. Ohjelmointikieleksi haluttiin PHP ja tietokannaksi PostgreSQL. Arkkitehtuuriksi valittiin MVC-malli, joka helpotti koo- din jäsentämistä ja hyödynnettävyyttä.

Yhteenvetona voisi todeta, että opinnäytetyön tavoitteet saavutettiin. Sovellusta on kuitenkin tarkoitus jatkokehittää, jotta siitä saadaan vielä toimivampi. Selkeitä kehittämisehdotuksia ovat käyttöoikeushallinta ja mobiilisovellus.

Avainsanat REST, RESTful-palvelu, Paikkatieto

Sivut 49 sivua, joista liitteitä 24 sivua

(3)

Information and Communication Technology Riihimäki

Author Yuxiu Guo Year 2019

Subject Open Service and Destination interface to Kanta-Häme Supervisors Toni Laitinen

ABSTRACT

The goal of the thesis project was to create a spatial database in the Kanta-Häme area. At the background of the project there were problems with the current spatial data. There was a lot of information, but it was scattered. It was difficult to find and access this information. Spatial data in this project included cafes, tourist sights, outdoor tours, etc. These locations are usually associated with opening hours and customers want to easily find nearby open attractions.

The work was commissioned by Häme University of Applied Sciences, the Intelli- gent Services Research Unit. There were also representatives from the city of Hämeenlinna. The commissioner wanted to get an open and user-friendly loca- tion information system, a system that would contain all the interesting locations in Kanta-Häme. It was especially important, that the location data could be easily retrieved using different devices. The most important issue was the open inter- face, which would make possible to easily create many different user interfaces.

The key objective of this work was open RESTful services. In addition, the com- missioner wanted to acquire basic maintenance user interfaces to manage the data. A simple map interface was required to visualize the data.

As a result of the project, all the above functionalities were created. The actual implementation work was facilitated by the customer reference frameworks.

The programming language was PHP and PostgreSQL was used as the database.

An MVC model was chosen as the architecture here which facilitated code struc- turing and usability.

In summary, the objectives of the thesis were achieved. However, the application is intended to be further developed to make it even more functional. Clear de- velopment suggestions include user management and developing a mobile ap- plication.

Keywords REST, RESTful service, Spatial database.

Pages 49 pages including appendices 24 pages

(4)

1 JOHDANTO ... 1

2 PAIKKATIETOKANNAN SUUNNITTELU ... 2

2.1 Käytetty tietokanta ja sen laajennukset ... 2

2.1.1 PostgreSQL ... 2

2.1.2 PostGIS-laajennus ... 2

2.2 Tietokannan tietomallin suunnittelu ... 2

3 ARKKITEHTUURIN SUUNNITTELU ... 3

3.1 Operatiivinen arkkitehtuuri ... 3

3.1.1 Käytetty sovelluspalvelin ... 3

3.1.2 Tietokantapalvelin ... 4

3.2 Sovellusarkkitehtuuri ... 4

3.2.1 MVC ... 6

4 TIETOKANTAKERROS (ENGLANNIKSI DATA ACCESS LAYER) ... 7

4.1 Käytetyt valmiit kirjastot ... 7

4.1.1 PHP-DS (englanniksi Data Structures) ... 7

4.1.2 Doctrine ORM ... 8

4.2 Malli luokat (englanniksi Model) ... 8

4.3 DAO-luokat ... 9

5 LIIKETOIMINTAKERROS (ENGLANNIKSI BUSINESS LAYER)... 10

5.1 RESTFul API ... 11

5.2 Käytetyt valmiit kirjastot ... 13

5.2.1 PHP Composer ... 13

5.2.2 PHP-DS ... 13

5.2.3 GeoPHP ... 14

5.2.4 JSON SCHEMA ... 14

5.3 Paikkaapi.php ... 15

5.4 Controller-luokka... 15

5.5 API-luokat ... 16

5.6 Skeemat ... 16

6 REST API KÄYTTÖ ... 17

6.1 Osoitteet ... 17

6.2 Haku... 18

6.2.1 Kaikkien tietojen haku ... 18

6.2.2 Suodatettu haku ... 18

6.2.3 Haku id:llä ... 19

6.2.4 Haku paikan id:llä ... 19

6.2.5 Muut APIt... 19

6.3 Lisäys ... 19

6.4 Päivitys ... 20

(5)

7 KÄYTTÖLIITTYMÄKERROS (ENGLANNIKSI PRESENTATION LAYER) ... 20

7.1 Arkkitehtuuri ... 20

7.2 Käytetyt valmiit kirjastot ... 21

7.2.1 jQuery ... 21

7.2.2 jQuery UI ... 21

7.2.3 jsGrid ... 22

7.2.4 Serialize ... 22

7.2.5 GoogleApi ... 22

7.2.6 Leaflet ... 22

7.2.7 OpenStreeMap ... 22

7.3 Tietojen syöttäminen ... 23

7.4 Kartta ... 23

8 YHTEENVETO ... 24

LÄHTEET ... 25

Liitteet

Liite 1 TERMIT

Liite 2 RESTAPI LÄHDEKOODIT

Liite 3 YLLÄPITOKÄYTTÖLIITTYMÄN LÄHDEKOODIT

Liite 4 KARTTAKÄYTTÖLIITTYMÄN LÄHDEKOODIT

(6)

1 JOHDANTO

Tämän opinnäytetyön tarkoituksena oli toteuttaa Kanta-Hämeeseen pal- velu- ja kohdetietokanta, sekä avoin rajapinta siihen. Lisäksi opinnäyte- työssä oli tarkoitus toteuttaa yksinkertainen karttakäyttöliittymä ja tieto- jen ylläpitoon tarvittavat peruskäyttöliittymät. Tarve opinnäytetyölle oli selkeä. Kanta-Hämeessä oli paljon paikkatietoa, mutta se oli hajallaan. Sen saatavuus ja löytäminen oli vaikeaa. Tämän vuoksi haluttiin yksi paikka, mistä tarvittavat tiedot saisi helposti ulos.

Opinnäytetyö alkaa tietokannan suunnittelulla. Tietokantaan haluttiin tal- lentaa Kanta-Hämeen palvelut ja kiinnostavat kohteet. Niistä haluttiin tal- lentaa perustietojen lisäksi myös sijaintitiedot, jotta niitä voitaisiin näyttää kartalla. Perustietoina haluttiin tallentaa kohteen luokittelu, nimi, kuvaus ja osoite. Kohteeseen haluttiin lisäksi liittää kuvia, yhteyshenkilöitä, auki- olotiedot ja www-linkit. Luokittelun avulla voidaan karttaa määritellä ha- luttaessa omia tasoja, joita voi näyttää tai piilotta. Näiden pohjatietojen perusteella valittiin sopiva tietokanta ja tehtiin tietomalli.

Tämän jälkeen suunniteltiin järjestelmän kokonaisarkkitehtuuri. Sovellus- palvelimeksi valittiin Apache 2.4 HTTP -palvelin ja palvelinpuolen ohjel- mointikieleksi valittiin PHP-ohjelmointikieli. Käyttöliittymä halutiin isoloida palvelimesta. Tämän vuoksi otettiin käyttöön MVC-arkkitehtuurimalli (eng- lanniksi Model-View-Controller).

MVC-mallin mallikerros toteutettiin palvelinpuolella PHP- ohjelmointikielellä. MVC-mallin osista lähinnä tietokantaa on malli (englan- niksi Model). Jäljempänä siitä käytetään myös nimitystä Data Access Layer eli tietokantakerros. Tietokantakerros toteutettiin Doctrine ORM -kirjaston avulla. Jokaista taulua varten toteutettiin oma Model-luokka ja Doctrine- kirjaston avulla se sidottiin tietokantatauluun ja sen kenttiin.

MVC-mallin ohjainkerros (englanniksi Controller) toteutettiin palvelin puo- lella PHP-ohjelmointikielellä. Käytännössä siinä toteutettiin avoimet REST- rajapinnat tietokannan tietojen päivittämiseen. Tiedonvälitykseen valittiin JSON-formaatti. Ohjainkerros käyttää tietokantakerrosta tietokannan ope- roimiseen. Ohjainkerrokseen rakennettiin oma reititysmekanismi REST- kutsujen välittämiseen oikealla API-luokalle. API-luokkiin toteutettiin si- säänpäin tulevien tietojen validointi skeemaa vastaan ennen varsinaista operaatiota.

MVC-mallin näkymäkerros eli käyttöliittymäkerros toteutettiin HTML5, CSS

ja Javascript -tekniikoilla. Peruskäyttöliittymien toteutuksessa hyödynnet-

tiin useita valmiita javascript-kirjastoja. Peruskirjastoksi valittiin jQuery ja

sitä laajennettiin jQuery UI ja jsGrid -kirjastoilla. jQuery-kirjaston avulla hoi-

detaan REST-kutsut palvelimelle. jQuery UI -kirjastoa käytetään joihinkin

(7)

käyttöliittymä toiminnallisuuksiin. jsGrid-kirjastoa puolestaan käytetään syöttölomakkeilla tietojen näyttämiseen, päivittämiseen ja poistamiseen.

Käyttöliittymäkerrokseen tehtiin kaksi erillistä yhden sivun HTML- sovellusta. Ylläpitokäyttöliittymän avulla voidaan hallinnoida tietokannan tietoja. Karttakäyttöliittymä, jonka avulla voidaan visuaalisesti nähdä tieto- kannan kohteet kartalla ja hakea niitä. Myös karttakäyttöliittymän toteu- tuksessa käytettiin valmiskirjastoja. Leaflet-kirjaston avulla haetaan kartan tiilet OpenStreetMap-palvelusta ja näytetään kohteet kartalla. GoogleApia käytetään osoitehakuihin.

Opinnäytetyössä keskityttiin MVC-mallin M- ja C-kerrosten ja niistä muo- dostuvan REST APIn kehittämiseen ja dokumentointiin. Niiden päälle ra- kennettiin peruskäyttöliittymät ja kartta, mutta vain perustoiminnallisuuk- sien osalta.

2 PAIKKATIETOKANNAN SUUNNITTELU

2.1 Käytetty tietokanta ja sen laajennukset

2.1.1 PostgreSQL

Tietokannaksi valittiin PostgreSQL, koska PostGIS-laajennuksen avulla se tukee hyvin paikkatietojen tallennusta. Se mahdollistaa spatiaalisten ja maantieteellisten kohteiden tallentamisen PostgreSQL-tietokantaan.

PostgreSQL on avoimeen lähdekoodiin perustuva relaatiotietokanta. Se kuitenkin poikkeaa hiukan perinteisestä relaatiotietokannasta, koska siinä tietokannan riveistä säilytetään useita versioita. Perinteisessä relaatiokan- nassa versiotietoja ei säilytetä.

2.1.2 PostGIS-laajennus

PostGIS-laajennus sisältää geometry-tietotyypin, johon voidaan tallentaa erityyppisiä geometrioita. Se mahdollistaa esim. yksittäisten pisteiden, vektoreiden tai monikulmioiden tallentamisen tietokantaan.

PostGIS-laajennuksessa on myös paljon geometrioiden käsittelyyn tarkoi- tettuja tietokantafunktioita, mutta tässä sovelluksessa niitä ei tarvita.

2.2 Tietokannan tietomallin suunnittelu

(8)

Tietokanta mallinnettiin käyttämällä Microsoft Visio Professional -tuotetta.

Aluksi käytiin asiakkaan kanssa keskustelu, jossa selvisi käsitemalli. Sen pe- rusteella päädyttiin alla olevan kuvan mukaiseen tietomalliin. Jokaisella paikalla on luokittelu (hotelli, kahvila, majoitus) ja luokittelulla on kategoria (palvelu tai kohde). Paikalla puolestaan voi olla useita paikkatietoja (sijain- teja), yhteystietoja, kuvia, linkkejä ja aukioloaikoja. Aukioloaikatyyppi ker- too onko kyseessä erikoispäivä, maanantai, tiistai, jne.

Kuva 1. Tietokannan ER-malli

3 ARKKITEHTUURIN SUUNNITTELU

3.1 Operatiivinen arkkitehtuuri

3.1.1 Käytetty sovelluspalvelin

Sovellus tarvitsee ajoympäristöksi Linux-palvelimen, joko fyysisen palveli- men tai virtuaalipalvelimen.

Itse sovellus rakennettiin Apache2 www-palvelimen päälle, koska siinä on hyvä tuki PHP-ohjelmointikielelle. Apache2 perusasennuksen lisäksi palve- lin koneelle asennettiin myös PHP-tulkki ja PostgreSQL-klientti.

Asennuskomennot:

apt-get update (1)

apt-get -y upgrade (2)

(9)

apt-get -y install apache2 curl apache2-utils cron php li-

bapache2-mod-php php-mysql php-pgsql (3)

apt-get install -y php-cli (4)

apt-get -y install postgresql-client-10

a2enmod proxy rewrite proxy_http proxy_wstunnel headers proxy_balancer proxy_html deflate rewrite authz_groupfile

(5) (6)

3.1.2 Tietokantapalvelin

Tietokantapalvelin tarvitsee myös ajoympäristöksi Linux-palvelimen. Vaih- toehtoisesti myös tietokanta voidaan ostaa pilvipalveluna, kunhan PostGIS- laajennus on tuettuna. Ainakin Amazon RDS tukee PostGIS-laajennusta.

Tietokantana PostgreSQL, johon lisättynä PostGIS-laajennus.

Asennuskomennot:

apt-get update (1)

apt-get install -y postgresql-10 postgresql-client-10 post-

gresql-contrib-10 (2)

apt-get install -y postgresql-10-postgis-2.4 (3) Tietokannan luonti palvelimella psql-komennolla tai työasemalta pgAdmin työkalulla.

3.2 Sovellusarkkitehtuuri

Sovellusarkkitehtuurissa päädyttiin hyödyntämään MVC-mallia. Siinä so- vellus on jaettu kolmeen kerrokseen: malli (englanniksi model), ohjain (englanniksi controller) ja näkymä (englanniksi view). Näkymä vastaa käyt- töliittymäkerrosta, ohjain liiketoimintakerrosta ja malli tietokantakerrosta.

Alla olevassa kuvassa on esitetty opinnäytetyössä käytetty MVC-mallin mu-

kainen arkkitehtuuri.

(10)

Kuva 2. MVC-mallin mukainen arkkitehtuuri

Alla olevassa kuvassa on kuvattu tietojen välittyminen opinnäytetyön MVC-mallin mukaisessa toteutuksessa.

Kuva 3. Toteutuksen sekvenssikaavio

(11)

3.2.1 MVC

MVC-malli keksittiin 1970-80-luvun taitteessa. Ajatus lähti Trygve Reens- kaugin vuonna 1978 kirjoittamasta muistiosta (Trygve Mikkjel Heyerdahl Reenskaug, 2003). Muistiossa kuvattiin mallin kolme kerrosta. Alkuperäi- sen idean mukaan Malli-kerros (englanniksi Model) edustaa tietoa ja malli voi olla yksittäinen olio tai olioiden hierarkia. Näkymät (englanniksi View) kuvattiin alkuperäisessä dokumentissa mallin visuaaliseksi kuvaukseksi.

Näkymä sidotaan malliin ja se saa näytettävän tiedon kysymällä ne mallilta.

Ohjain (englanniksi Controller) on alkuperäisessä muistiossa kuvattu käyt- täjän ja järjestelmän yhdistäväksi linkiksi. Alkuperäisessä ajatuksessa mu- kana oli myös muokkaaja kerros (englanniksi Editors). Sen tarkoituksena on laajentaa ohjainta, siten että käyttäjä voi muuttaa näkymässä olevaa tie- toa. (Reenskaug, 1979)

Ensimmäinen julkinen toteutus tehtiin vuonna 80-luvun alussa Smalltalk- 80 luokkakirjastoon. Alkuvaiheessa MVC:stä ei kuitenkaan ollut mitään jul- kista dokumentaatiota. Ensimmäinen julkaisu oli “A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk -80" ja se julkaistiin vasta 1988. (Wiki.c2, 2014)

MVC-mallin läpimurto alkoi 2000-luvulla, kun se tuli Java, Python ja Ruby ympäristöihin. Vuonna Ruby ja Python versiot mahdollistivat MVC-mallin hyödyntämisen myös pienissä sovelluksissa, koska kyseisten työvälineiden avulla voitiin kehittää nopeasti. (Wikipedia, 2019a)

Kuva 4. MVC-mallin kerrokset ja niiden vuorovaikutus, (Wikipedia, 2019a)

Ylläolevassa kuvassa on esitetty mallin kerrokset ja niiden keskinäinen vuo-

rovaikutus. Käyttäjän syöte menee ohjainkerrokseen. Ohjainkerros käyttää

mallikerrosta ja mallikerros päivittää näkymäkerrosta. Lopulta käyttäjä nä-

kee päivitetyn näkymän. (Wikipedia, 2019a)

(12)

MVC on nykyisin suosittu, koska sen avulla koodi jäsentyy paremmin. Koodi organisoidaan tehtävien mukaan ja jokaisella kerroksella on selkeä oma tehtävä. (codecademy, n.d.)

4 TIETOKANTAKERROS (ENGLANNIKSI DATA ACCESS LAYER)

Tietokanta kerros toteutettiin hyödyntämällä Doctrine ORM -kirjastoa. Jo- kaista tietokannan taulua varten tehtiin oma malliluokka ja oma dao- luokka. Jokainen malliluokka periytyy basemodel-luokasta, jossa on perus- toiminnallisuudet. Samoin jokainen dao-luokka periytyy basedao-luokasta, jossa on dao-luokkien perustoiminnallisuudet.

Kuva 5. Tietokantakerroksen luokkakaavio

4.1 Käytetyt valmiit kirjastot

4.1.1 PHP-DS (englanniksi Data Structures)

Tämä kirjasto sisältää sovelluksen tarvitsemia tietorakenteita. PHP:n perus

array ei ollut sovelluksen kannalta riittävä, vaan sovellus tarvitsee DS\Map

tietorakennetta. Sen avulla voidaan tallentaa avain-arvo pareja ja tarvitta-

essa hakea arvoa avaimella.

(13)

Esimerkki DS\Map käytöstä

$map = new \Ds\Map();

$map->put('a', 1);

$map->put('b', 2);

$map->get('b');

4.1.2 Doctrine ORM

Doctrine-kirjaston avulla tietokanta voidaan sitoa PHP-luokkiin. Käytän- nössä malliluokan alkuun määritellään annotaatioilla käytettävä tietokan- tataulu ja jokaisen kentän kommenttiin määritellään tietokannan kentän tietotyyppi. Tämän jälkeen kirjaston avulla voidaan, käyttämällä DQL:ää (Doctrine Query Language), hakea tietokannan tiedot suoraan PHP- malliluokan olioihin. Myös päivitys onnistuu helposti. Tarvitsee vain vaih- taa malli olion sisältöä ja kutsua käskeä Doctrine-kirjastoa persistoimaan kyseinen olio.

Toiminnot menevät EntityManager-luokan kautta. Ensin pitää luoda Enti- tyManager-olio:

$isDevMode = true;

$ormconfig = Setup:: createAnnotationMetadataConfigu- ration (array( __DIR__ ."/src"), $isDevMode);

$ormconn = array(

'driver' => 'pdo_pgsql', 'user' => 'paikkatieto', 'password' => 'paikkatieto', 'host' => '127.0.0.1',

'port' => '5435',

'dbname' => 'paikkatieto'

$this->entityManager = EntityManager:: ); cre- ate ($ormconn, $ormconfig);

4.2 Malli luokat (englanniksi Model)

Jokaisesta tietokantataulusta tehtiin oma malliluokka. Jokaisesta tietokan- tataulun kentästä tehtiin malliluokan muuttuja ja jokaiselle muuttujalle tehtiin getter- ja setter -funktiot. Annotaatioilla määriteltiin tietokantatau- lun nimi ja tietokantakenttien tietotyypit.

Malliluokissa on funktio fromJson, jonka avulla JSON-olio voidaan lukea

malliluokan kenttiin. Malliluokka periytyy BaseModel-luokasta. BaseMo-

del-luokka toteuttaa JsonSerializable rajapinnan eli käytännössä jsonSe-

rialize funktion. Kyseinen funktio lukee kaikki model-luokan muuttujat ja

(14)

palauttaa ne, jotta kyseinen malli voidaan muuntaa JSON-muotoon. Base- Model-luokka sisältää myös getGeometry funktion, joka muuntaa GeoJSON muotoisen olion merkkijonoksi, joka voidaan tallentaa tietokan- nan geometry tietotyyppiin.

Esimerkkimalli alla:

/** * @Entity @Table(name="paikkatieto.linkki") **/

class Linkki extends BaseModel {

/** @Id @Column(type="integer") @GeneratedValue

**/ protected $linkki_id;

/** @Column(type="integer") **/

protected $paikka_id;

/** @Column(type="string") **/

protected $linkki;

function fromJson($json) { $this->linkki_id=$this->get- Value($json,'linkki_id');

$this->paikka_id=$this->get- Value($json,'paikka_id');

$this->linkki=$this->getValue($json,'link- ki');

} /**

* @return mixed */

public function getLinkki_id() {

return $this->linkki_id;

… } }

4.3 DAO-luokat

Jokaista tietokantataulua varten tehtiin myös oma DAO-luokka. DAO- luokkien avulla tietokantataulusta voidaan hakea, päivittää, lisätä tai pois- taa tietoja. DAO-luokat kytkeytyvät kantaan Doctrine-kirjaston Entity- Manager luokan avulla.

Alla esimerkkejä Doctrine-kirjaston käytöstä:

// kuvan haku

public function haeKuva($id) {

$kuvaEntity = $this->getEntityManager()-

>find("Kuva", $id);

return $kuvaEntity;

}

// kuvan luonti

(15)

public function luoKuva($kuva){

$kuvaEntity = new Kuva();

$kuvaEntity->fromJson($kuva);

$this->getEntityManager()->per- sist($kuvaEntity);

$this->getEntityManager()->flush();

return $kuvaEntity->getKuva_id();

}

// paikan kuvien haku

public function haePaikanKuvat($paikka_id) {

$query = $this->getEntityManager()->create- Query("SELECT k FROM Kuva k WHERE k.paikka_id = " .

$paikka_id);

$kuvat = $query->getResult();

return $kuvat;

}

5 LIIKETOIMINTAKERROS (ENGLANNIKSI BUSINESS LAYER)

Liiketoimintakerros sisältää varsinaisen rajapinnan toteutuksen. Liiketoi- mintakerros tarjoaa ulospäin REST API-rajapinnan. Tietojen siirtoon palve- limen ja asiakkaiden välillä käytetään JSON-formaattia.

Kaikki API-kutsut ohjataan yhden PHP-sivun (paikkaapi.php) kautta. Siinä päätellään mihin API-luokkaan (paikka, yhteystieto, aukiolo, kuva, linkki) kutsu välitetään. Tämä on tarkemmin kuvattu luvussa 5.3.

API-luokat on periytetty Controller-luokasta, joka sisältää REST-kutsujen reitityksen oikeille API-luokan funktioille. Controller-luokka on tarkemmin kuvattu luvussa 5.4.

Varsinaisen API-luokan funktiot kutsuvat tietokantakerrosta. Päivityksen ja

lisäyksen yhteydessä tarkistetaan lisäksi, että pyynnössä tuleva JSON-olio

on skeeman mukainen ja siinä on kaikki tarvittavat tiedot.

(16)

Kuva 6. Liiketoimintakerroksen luokkakaavio

5.1 RESTFul API

REST lyhenne tulee sanoista REpresentional State Transfer. Se on Roy Fiel- dingin kehittämä HTTP-protokollaan perustuva arkkitehtuuri tyyli. Hän ke- hitti sitä rinnakkain HTTP 1.1 protokollan kanssa ja se julkaistiin hänen väi- töskirjassaan vuonna 2000. (Wikipedia, 2019b). REST on arkkitehtuurinen tyyli hajautettujen www-sovelluksien tekoon.

REST määrittelee kuusi arkkitehtuurista rajoitetta. Järjestelmän on nouda- tettava näitä rajoitteita, jotta se täyttää määritelmän RESTful-järjestelmä tai API. Arkkitehtuuriset rajoitteet ovat: asiakas-palvelin-sovellus, tilaton, välimuistikelpoinen, kerrostettu järjestelmä, yhtenäinen rajapinta ja valin- naisesti koodin siirtäminen käyttöliittymälle. (RESTfulAPI.net, REST Architectural Constraints, 2017b)

Asiakas-palvelin sovelluksessa käyttöliittymä ja tietokanta eristetään toisis-

taan. Tällä mahdollistetaan helpompi siirrettävyys uusille käyttöliittymä-

alustoille. Web-sovelluksille suurin hyöty on kuitenkin, se että komponent-

teja voidaan kehittää samanaikaisesti ja toisistaan riippumatta. Samalla

(17)

palvelinkomponentit tulevat yksinkertaisimmiksi ja järjestelmästä tulee pa- remmin skaalattava. (R. Fielding, 2014)

Tilattomuus tarkoittaa, että palvelimella ei säilytetä asiakkaan tiloja, vaan että tarvittavat tilasidonnaiset tiedot pitää välittää jokaisen pyynnön mu- kana. Istunto tiedot säilötään asiakassovelluksen puolella. Tämä parantaa palvelin puolen suorituskykyä, koska palvelimen ei tarvitse säilöä tilatietoja pyyntöjen välillä, vaan ne voidaan tuhota heti pyynnön päätyttyä. Järjes- telmän valvonta helpottuu myös huomattavasti, koska pyyntö sisältää kaikki tiedot. Haittapuolena on kasvanut verkkoliikenne ja palvelimen riip- puvuus useista asiakassovellus versioista. (R. Fielding, 2014)

Välimuistikelpoisuudella tarkoitetaan, että palvelin voi merkitä vastauksen sellaiseksi, että asiakassovellus voi käyttää sitä vastauksena samanlaisiin pyyntöihin. Tällöin asiakassovelluksen ei tarvitse kysyä sitä palvelimelta vaan se voi käyttää paikallisessa muistissa olevaa vastausta. Välimuistira- joitteet vähentävät palvelimelle tulevaa kuormaa ja siten lisäävät suoritus- kykyä ja skaalautuvuutta. Haittapuolena välimuisti voi huonontaa luotetta- vuutta, koska välimuistin tieto voi olla vanhentunutta. (R. Fielding, 2014) Kerrostettu järjestelmä voi koostua useasta hierarkkisesta tasosta. Kom- ponentit eivät kuitenkaan voi nähdä välittömän kerroksen ulkopuolella.

(RESTfulAPI.net, What is REST, 2017a). Kerrostetussa järjestelmässä toi- minnot voidaan jakaa useille palvelimille. APIt voidaan laittaa yhdelle pal- velimelle, tietokanta toiselle ja autentikointi omalle palvelimelleen.

(RESTfulAPI.net, REST Architectural Constraints, 2017b). Asiakas ei tiedä kytkeytyykö se loppupalvelimelle vai välityspalvelimelle. Välityspalvelimien avulla järjestelmää voidaan skaalata ja tehdä yhteisiä välimuisteja.

(Wikipedia, 2019b).

RESTful web -palvelut tarjoavat yhtenäisen rajapinnan, jota on helppo käyt- tää. Järjestelmässä olevalla resurssilla tulisi olla vain yksi URI. Yksittäinen resurssi ei saisi olla liian suuri. Resurssi voi sisältää linkkejä, joista saadaan siihen liittyviä tietoja. Resurssien esitysten tulee noudattaa sovittuja oh- jeita, kuten nimeämiskäytännöt ja formaatti. Resurssien käyttö ja muutta- minen tapahtuu yhtenäisen tavan avulla. (RESTfulAPI.net, REST Architectural Constraints, 2017b)

On demand -koodin avulla voidaan laajentaa asiakassovelluksen toiminnal- lisuutta. Tämä tekee asiakassovelluksista yksinkertaisempia.

(RESTfulAPI.net, What is REST, 2017a). RESTful-palvelu voi palauttaa asia-

kassovellukselle käännettyjä komponentteja kuten Java appletteja tai Ja-

vaScriptiä. (Wikipedia, 2019b).

(18)

5.2 Käytetyt valmiit kirjastot

Palvelin puolella on käytetty koodin selkeyttämisen vuoksi valmiita avoi- men lähdekoodin kirjastoja. Kirjastot on asennettu PHP composer -työka- lun avulla.

5.2.1 PHP Composer

Composer on työväline, jolla voi hallinnoida sovelluksen tarvitsemia kirjas- toja. Se asentaa tarvittavat kirjastot vendor-hakemistoon ja tekee sinne au- toload.php tiedoston. Autoload.php tiedosto lisätään sovellukseen re- quire_once funktion avulla ja tämän jälkeen kaikki composerilla ladatut kir- jastot ovat sovelluksen käytössä. Composer määritykset tehdään com- poser.json tiedostoon.

Esimerkki määrityksistä:

{ "autoload" : { "psr-4" : {

"Hamk\\Paikkatietoapi\\" : "src"

},

"psr-0" : {

"\"php-ds/php-ds\" : \"~1.2\"" : ""

} },

"config" : {

"vendor-dir" : "src/vendor"

},

"name" : "hamk/paikkatietoapi", "require" : {

"php" : ">=7.3",

"php-ds/php-ds" : "^1.2", "doctrine/orm" : "^2.6.2", "symfony/yaml" : "2.*",

"justinrainbow/json-schema" : "~5.2"

},

"type" : "rest api",

"description" : "paikkatieto rest api"

}

Tämän jälkeen ajetaan ”composer update”-komento.

5.2.2 PHP-DS

PHP-ohjelmointikielessä on vakiona array-tietorakenne, mutta se ei ollut

sovelluksen kannalta riittävä. Sen takia käyttöön otettiin PHP Data Structu-

res -kirjasto, joka tarjoaa monipuolisemmat ja tietorakenteen sovelluksen

käyttöön.

(19)

Kirjastosta käytetään Map-luokkaa, jonka avulla voidaan säilö avain/arvo pareja. Se helpottaa käsittelyä, koska Map:stä voidaan hakea haluttu tietue suoraan avaimella. Sovellus käyttää myös Sequence-luokkaa, joka järjestää arvot yhteen lineaariseen ulottuvuuteen.

5.2.3 GeoPHP

GeoPHP-kirjastoa käytetään GeoJSON muotoisten olioiden käsittelyyn. Tie- tokannassa paikkatiedot ovat geometry-tyyppisessä tietotyypissä. Geo- metry tietotyyppi ei sovellu käyttöliittymäkerrokselle ja se on muutettava toiseen muotoon. Muodoksi valittiin GeoJSON, koska kartat tukevat GeoJSON-olioita. Lisäksi kartan pisteistä voidaan helposti luoda GeoJSON- olioita, jotka voidaan lähettää tietokantaan tallennettaviksi. Lisäksi GeoJSON-olion sisällä voidaan välittää properties-kentässä sovelluskohtai- sia tietoja (esim paikan nimi, paikan kuvaus, jne).

Esimerkki kartan pisteestä:

{ "type": "Feature", "geometry": { "type": "Point",

"coordinates": [125.6, 10.1]

},

"properties": {

"nimi": "Dinagat Islands"

} }

5.2.4 JSON SCHEMA

JSON Schema on PHP-kirjasto, jonka avulla voidaan validoida JSON-olioita skeemaa vasten. Kirjaston avulla voidaan helposti tarkistaa, että sisäänpäin tulevassa datassa on kaikki tarvittavat kentät ja ne ovat oikean muotoisia.

Kirjaston käyttö:

function validoi($schemajson,$jsonobject) { $validator = new Validator;

$realpath = real-

path ( __DIR__ .'/schema/'.$schemajson);

$schema = 'file://'.$realpath;

$validator->validate(

$jsonobject,

(object)['$ref' => $schema],

Constraint:: CHECK_MODE_APPLY_DEFAULTS );

if (!$validator->isValid()) {

(20)

header ('Content-type: application/json;

charset=iso-8859-1');

echo json_encode ($validator->getErrors());

return false;

}

return true;

}

5.3 Paikkaapi.php

Kaikki REST API -rajapinnan kutsut ohjataan paikkaapi.php tiedoston kautta. Siinä pilkotaan pyynnön uri osiin ’/’-merkin kohdalta ja sijoitetaan osat array-olioon. Elementtien avulla päätellään kutsuttava API (paikka, linkki, jne), haluttu alitoiminto ja id. Päättely riippuu elementtien lukumäärästä.

− Jos taulukossa on 2 elementtiä niin o toinen elementti on api

o Esim. GET /paikkaapi.php/paikka o Hakee kaikki paikat

− Jos taulukossa on 3 elementtiä niin o toinen elementti on api o kolmas elementti on id

o Esim. GET /paikkaapi.php/paikka/15 o Hakee paikan, jonka id on 15

− Jos taulukossa on 4 elementtiä niin o toinen elementti on alitoiminto o kolmas elementti on id

o neljäs elementti on haluttu api

o Esim. GET /paikkaapi.php/paikka/15/yhteystieto

o Hakee yhteystiedot, jotka liittyvät paikkaan, jonka id on 15.

5.4 Controller-luokka

Jokainen API-luokka periytyy Controller-luokasta. Controller-luokan teh-

tävä on tarkistaa minkä tyyppinen REST-kutsu on kyseessä (GET, POST,

PUT, DELETE, PATCH) ja välittää se oikealle funktiolle. Oletustoteutuksena

kaikki kutsut palauttavat virheviestin ja ne toimivat vasta kun ne on toteu-

tettu varsinaisessa API-luokassa.

(21)

Kutsut menevät seuraavasti:

Taulukko 1. API-kutsut

HTTP VERB Funktio Parametrit Selitys

GET haeKaikki - Hakee kaikki rivit

GET haePaikantiedot paikka_id Hakee yksittäiseen paikkaan liittyvät ri- vit.

GET haeAvaimella id Hakee avaimella yh-

den rivin.

GET getjson -

Hakee kaikki rivit, mutta palauttaa paikkatiedon

GeoJSON muodossa.

POST luo - Tekee uuden rivin.

PUT paivita - Päivittää olemassa

olevan rivin (kaikki tiedot)

DELETE poista id Poistaa olemassa

olevan rivin.

PATCH Tätä ei toteutettu, koska ei tarvetta

5.5 API-luokat

API-luokat periytyvät Controller-luokasta ja ne ovat varsinainen työ suorit- taja. Ne kuormittavat halutut funktiot ja toteuttavat ne halutulla tavalla.

Ne alustavat tarvittavat DAO-luokat ja käyttävät niitä tietojen hakemiseen, päivittämiseen ja poistamiseen. Lisäksi niissä toteutetaan tietojen validointi ennen tietokantaan vientiä.

5.6 Skeemat

Skeemat määrittävät millaisia JSON-olioita REST API -rajapinnat voivat ot-

taa vastaan. JSON-oliot validoidaan tarvittaessa skeemoja vastaan ja jos

olio on puutteellinen, niin toimintoa ei jatketa. Skeemassa määritellään tie-

torakenteen, niiden tietotyypit, pakollisuudet ja rajoitukset, esim. pituu-

det, muodot, jne.

(22)

Alla on esimerkki skeemasta:

{ "$schema": "http://json-schema.org/draft- 04/schema#",

"title": "linkki",

"description": "paikkaan liityvat www linkit", "type": "array",

"properties": { "linkki_id": {

"description": "Linkin uniikki id", "type": "integer"

},

"paikka_id": {

"type": "integer"

}, "linkki": {

"description": "Linkin kuvaus", "type": "string",

"minLength": 12, "maxLength": 255 }

},

"required": ["paikka_id", "linkki"]

}

6 REST API KÄYTTÖ

6.1 Osoitteet

Osoite muodostuu URLin alkuosasta /api/paikkaapi.php ja URLin loppu-

osasta, joka on alla olevassa taulukossa. Loppuosa kuvaa käytettävän APIn.

(23)

Taulukko 2. RESTful-palveluiden osoitteet

URL Api Selitys Tuetut toimin-

not /paikka/ paikka api Paikka ja paikkatie-

totaulujen hallin- nointiin käytetty api

lisäys, päivitys, poisto, haku ja GeoJSON haku

/kuva/ kuva api Paikkojen kuvien hallinnointiin käy- tetty api.

lisäys, poisto, haku

binaarihaku (image) /linkki/ linkki api Paikkojen www-link-

kien hallinnointiin käytetty api.

lisäys, päivitys, poisto ja haku /luokittelu/ luokittelu

api Luokittelujen ha-

kuun käytetty api. haku

/yhteystieto/ yhteystieto api

Paikkojen yhteys- henkilötietojen hal- linnointiin käytetty api.

lisäys, päivitys, poisto, ja haku

/aukiolo/ aukioloaika api

Paikkojen aukioloai- kojen hallinnointiin käytetty api

lisäys, päivitys, poisto ja haku

6.2 Haku

Kaikki ylläkuvatut APIt tukevat hakua id:llä. Paikka API tukee kaikkien paik- katietojen hakua ja suodatettua hakua, joka palauttaa hakukriteerit täyttä- vät paikat. Luokittelu API puolestaan tukee ainoastaan kaikkien kannassa olevien luokittelutietojen hakua. Muut APIt tukevat yksittäisen paikan tietojen hakua.

6.2.1 Kaikkien tietojen haku

Toimii nykyisellään vain paikka APIn kanssa. Urlin loppuosaan laitetaan sana ”kaikki”. Tällöin palautetaan kaikki paikkatiedot. Kaikki paikkatiedot voidaan hakea myös geojson muotoisena, jolloin urlin loppuosaan laite- taan sana ”geojson”.

6.2.2 Suodatettu haku

Suodatettu haku toimii nykyisellään vain paikka APIn kanssa. Halutut haku-

kriteerit laitetaan urlin loppuun. esim. /paikka/?osoite=Hämeenl. Tieto-

kannasta haetaan LIKE tai sama kuin-haulla hakukriteerejä vastaavat rivit

(24)

ja palautetaan ne kutsujalle. Sallitut hakukriteerit ovat alla olevassa taulu- kossa:

Taulukko 3. Sallitut hakukriteerit

Parametri Tietotyyppi Selitys

paikka_id numero Hakee paikka taulusta paikka_id kentän perusteella eli palauttaa yhden rivin id:llä haettuna.

luokittelu_id numero Palauttaa ne rivit, joilla luokit- telu_id on sama kuin parametrin arvot.

nimi merkkijono Paikka taulun nimi kentän mu- kaan tapahtuva LIKE-haku.

kuvaus merkkijono Paikka taulun kuvaus kentän mukaan tapahtuva LIKE-haku.

paikkatieto[osoite] merkkijono Paikkatietotaulun osoite kentän mukaan tapahtuva LIKE-haku.

6.2.3 Haku id:llä

Id:llä hakeminen onnistuu, kun URLin loppuun lisätään halutun rivin id. Eli esim. /linkki/15 palauttaa tietokannassa linkki-taulussa olevan rivin, jonka id on 15.

6.2.4 Haku paikan id:llä

Tällä haulla voidaan hakea paikkaan liittyviä yhteystietoja, linkkejä, kuvia ja aukioloja. URL muodostuu seuraavasti: paikka/<id>/<api>. Missä <id> on paikan id ja <api> on halutun APIn nimi (yhteystieto, kuva, linkki, aukiolo).

6.2.5 Muut APIt

Luokittelu APIn avulla voidaan hakea tietokannassa olevat luokittelu. Se tu- kee vain hakua ja urlin loppuun on laitettava ”kaikki”.

6.3 Lisäys

Lisäykset menevät normaalisti paikkaapi.php kautta ja kutsu urlissa on ha-

luttu API. HTTP-verbi on tällöin POST ja JSON data tulee pyynnön request-

payload:ssa.

(25)

6.4 Päivitys

Päivitykset menevät normaalisti paikkaapi.php kautta ja kutsu urlissa on haluttu API. HTTP-verbi on tällöin PUT ja JSON data tulee pyynnön request- payload:ssa.

6.5 Poisto

Päivitykset menevät normaalisti paikkaapi.php kautta ja kutsu urlissa on haluttu API. HTTP-verbi on tällöin DELETE ja poistettavan tietueen id on ur- lin loppuosassa.

7 KÄYTTÖLIITTYMÄKERROS (ENGLANNIKSI PRESENTATION LAYER)

7.1 Arkkitehtuuri

Käyttöliittymäkerrokseen toteutettiin kaksi yhden html-sivun sovellusta:

karttasovellus ja ylläpitosovellus. Käyttöliittymäpuolelle otettiin mukaan kuvassa näkyvät avoimeen lähdekoodiin perustuvat kirjastot. Kirjastojen käyttötarkoitus on selitetty luvussa 7.2.

Kuva 7. Käyttöliittymäkerroksen kirjastot

(26)

7.2 Käytetyt valmiit kirjastot

7.2.1 jQuery

jQuery-kirjasto on nopea ja pieni JavaScript-kirjasto, jonka helpottaa HTML dokumenttien (englanniksi view) käsittelyä ja Ajax-kutsujen tekemistä. Se toimii useimmissa markkinoilla olevissa selaimissa.

Se on hyvä pohjaratkaisu REST-palvelukutsujen tekemiseen. Sen avulla voi- daan helposti tehdä tarvittavat REST-kutsut palvelimelle. Lisäksi siihen on saatavilla useita kirjastoja jotka laajentavat sen toimintaa.

Alla on esimerkki REST-palvelun kutsumisesta jQuery-kirjastolla:

return $.ajax({

type: "POST",

url: "../api/paikkaapi.php/paikka, contentType: 'application/json', dataType: "JSON",

data: JSON .stringify(item) });

7.2.2 jQuery UI

jQuery UI on jQuery-kirjaston päälle rakennettu kevyt käyttöliittymäkir- jasto. Se sisältää useita käteviä käyttöliittymäkomponentteja. Tässä työssä kuitenkin käytetään vain Dialog-komponenttia, jonka avulla voidaan hel- posti avata HTML div omaan modaaliin ikkunaan. Dialogin avaus onnistuu alla olevalla JavaScript-koodilla:

$( "#lisaakuvaDialog" ).dialog();

Tämä avaa uuden ikkunan, jonka sisällä on div, jonka id on ”lisaaKuva- Dialog”. Eli alla oleva div:

<div class="hiddenform" id="lisaakuvaDialog"

title="Lisää kuva">

<form id="lisaaKuvaForm">

<fieldset>

<input type="hidden" name="paikka_id"

id="kuva_paikka_id" value="15"/>

<label class="label" for="kuva">Tallenna kuva</label>

<input type="file" name="kuva" id="kuva">

<input class="button" type="button" on- click=" submitLisaaKuva ();" value="Lisää" />

</fieldset>

</form>

</div>

(27)

7.2.3 jsGrid

jsGrid-kirjasto on myös jQuery-kirjaston päälle kirjoitettu kirjasto. Sen avulla voidaan käyttöliittymän puolella näyttää tietoja taulukkomuodossa.

Se mahdollistaa myös tietojen muokkaamisen, suodattamisen, poistami- sen, sivutuksen ja järjestämisen.

Tässä työssä sitä käytetään näytöillä, joissa pitää näyttää useita tietokan- nan rivejä. Taulukoissa näytettävät tiedot haetaan palvelimelta REST API kautta. Myös päivitykset, lisäykset ja poistot tehdään taulukko komponen- tin ja REST APIn avulla.

Taulukko komponentin ja palvelimen väliin on tehty oma JavaScript luokka (RestController), jonka kautta kaikki taulukko komponentin palvelukutsut kulkevat. RestController sisältää funktiokutsut kaikille operaatiolle: tieto- jen haku, lisäys, poisto, päivitys.

7.2.4 Serialize

Tämän kirjaston avulla HTML lomake voidaan muuttaa JSON-muotoon. Se helpottaa paljon ajax-kutsujen tekemistä, koska lomaketta ei tarvitse itse käydä kenttä kentältä läpi, vaan kirjasto tekee siitä JSON-olion.

7.2.5 GoogleApi

GoogleApi:sta käytetään Googlen geokoodaus-palvelua. Sen avulla voi- daan selvittää karttapisteen koordinaattien avulla katuosoite. Osoitteen selvittämistä tarvitaan ylläpitokäyttöliittymissä. Paikan osoittavaa ikonia halutaan siirtää kartalla ja uuden paikan osoite halutaan tallentaa tietokan- taan.

7.2.6 Leaflet

LeafLet-kirjaston avulla ladataan karttapohja OpenStreetMap-palvelusta.

Lisäksi sen avulla näytetään geojson muotoiset karttakohteet omana taso- naan kartalla. Kirjastoa käytetään sekä ylläpitokäyttöliittymissä, että kart- takäyttöliittymässä. Alun perin ajatuksena oli käyttää OpenLayers-kirjas- toa, mutta LeafLet korvasi sen. OpenLayers olisi ollut turhan raskas ja Leaf- Let:ssä oli kaikki tarvittavat toiminnallisuudet.

7.2.7 OpenStreeMap

Pohjakartta haetaan OpenStreetMap-palvelusta. Alun perin ajateltiin käyt-

tää GoogleMaps-palvelun kartta-aineistoja, mutta ilmaisessa OpenStreet-

Map:ssa oli tarvittavat ominaisuudet, joten se valittiin. Karttapohjan vaih-

taminen on tosin helppoa koska ne ladataan LeafLet-kirjaston kautta.

(28)

7.3 Tietojen syöttäminen

Ylläpitokäyttöliittymä toteutettiin välilehtiratkaisuna. Näytöllä on viisi väli- lehteä, yksi kutakin syötettävää tietoa kohden. Ensimmäisellä välilehdellä on lisäksi kartta, jonka avulla karttakohdetta voi visuaalisesti siirtää oike- aan kohtaan. Tietojen syöttäminen hoidetaan taulukon avulla.

Kuva 8. Ylläpitokäyttöliittymä

7.4 Kartta

Karttakäyttöliittymä yksinkertaisuudessaan näyttää kohteet kartalta.

Kohdetta klikattaessa käyttäjälle näytetään tarkemmat tiedot kohteesta.

Kuva 9. Karttakäyttöliittymä

(29)

8 YHTEENVETO

Opinnäytetyön tarkoituksena oli toteuttaa paikkatietokanta Kanta-Hämee- seen. Tuloksena syntyi avoin rajapinta ja tarvittavat peruskäyttöliittymät.

Toteutuksessa käytettiin paljon avoimen lähdekoodin kirjastoja ja niiden yhteensovittaminen oli välillä haastavaa.

Opinnäytetyön aikana osa suunnitelluista teknologioista vaihdettiin.

GoogleMaps vaihdettiin OpenStreetMap-palveluun, koska se oli ilmainen ja käyttötarkoitukseen riittävä. Alun perin ajateltiin käyttää GeoServer-pal- velua karttatasojen esittämiseen. Sovelluksessa ei kuitenkaan ollut varsi- naisia karttatasoja, esim. omia pohjakarttoja, vaan vain pistemäisiä koh- teita. Pistemäisten kohteiden näyttämiseen GeoServer olisi ollut turhan raskas vaihtoehto. Tämän vuoksi sovelluksessa päädyttiin käyttämään LeafLet-kirjaston geojson tasoja. GeoJSON on muutenkin yleistymässä ja hyvin tuettuna mobiililaitteissa ja se voi sisältää myös omia tietoja, kuten tässä sovelluksessa haluttiin.

MVC-malli ja RESTful-arkkitehtuurimallit helpottivat tekemistä. Ne saneli- vat yhtenäisen tavan tehdä asioita. Myös jatkokehitys on helpompaa, kun asiat on tehty samalla tavalla. Aluksi tietokanta operaatiot tehtiin suoraan PHP:n tietokantaluokkien avulla. Tämä kuitenkin osoittautui työlääksi ja sen vuoksi käyttöön otettiin Doctrine-kirjasto. Se helpotti tietokantaohjel- mointia huomattavasti.

Lopputuloksena syntyi jatkokehitettävä paikkatietokanta, johon on mah-

dollista säilöä paljon hyödyllistä tietoa. Avoimet rajapinnat mahdollistavat

uusien käyttöliittymien tekemisen. Käyttäjähallinta ja mobiilikäyttöliittymä

ovat varmasti seuraavia jatkokehityskohteita tälle sovellukselle.

(30)

LÄHTEET

codecademy. (ei pvm). MVC: Model, View, Controller. Haettu 25. 03 2019 osoitteesta https://www.codecademy.com/articles/mvc

R. Fielding, J. R. (2014). Hypertext Transfer Protocol (HTTP/1.1): Semantics

and Content. Haettu 08. 04 2019 osoitteesta

https://tools.ietf.org/html/rfc7231#section-4.2.3

Reenskaug, T. (1979). MODELS - VIEWS - CONTROLLERS. Haettu 25. 03 2019 osoitteesta http://heim.ifi.uio.no/~trygver/1979/mvc-2/1979-12-MVC.pdf RESTfulAPI.net, REST Architectural Constraints. (2017b). REST Architectural Constraints. Haettu 07. 04 2019 osoitteesta https://restfulapi.net/rest- architectural-constraints/

RESTfulAPI.net, What is REST. (2017a). What is REST. Haettu 07. 04 2019 osoitteesta https://restfulapi.net/

Trygve Mikkjel Heyerdahl Reenskaug. (2003). MVC: Model-View-

Controller. Haettu 20. 03 2019 osoitteesta

http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html

Wiki.c2. (2014). Model View Controller History. Haettu 02. 04 2019 osoitteesta http://wiki.c2.com/?ModelViewControllerHistory

Wikipedia. (2019a). Model–view–controller. Haettu 06. 04 2019 osoitteesta

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93control ler

Wikipedia. (2019b). Representational state transfer. Haettu 06. 04 2019 osoitteesta

https://en.wikipedia.org/wiki/Representational_state_transfer

(31)

Liite 1 TERMIT

HTTP

HyperText Transfer Protocol. Selaimen ja palvelimen väliseen tiedonsiir- toon käytettävä protokolla.

JAVASCRIPT

Ohjelmointikieli, jota käytetään lisäämään dynaamista toiminnallisuutta www-sivuille.

JSON

JavaScript Object Notation. Yksikertainen tiedonvälitykseen käytetty for- maatti.

MVC MVC-arkkitehtuurimalli on ohjelmistoarkkitehtuurimalli, joka jakaa ohjel- miston kolmeen osaan: malli (englanniksi model), näkymä (englanniksi view) ja ohjain (englanniksi controller).

ORM Object Relational Mapping. Tekniikka, jota käytetään olio-ohjelmointikie- lissä tiedon muuntamiseen yhteensopimattomien järjestelmien kanssa.

PHP PHP on palvelinpuolen ohjelmointikieli web kehitykseen.

(32)

Liite 2 RESTAPI LÄHDEKOODIT

./api/api/aukioloapi.class.php

<?php

include_once 'controller.class.php';

class AukioloApi extends Controller { private $aukiolodao;

function __construct() {

$this->aukiolodao = new AukioloDAO();

}

function luo($aukiolo) {

$aukiolo_id = $this->aukiolodao->luoAukiolo($aukiolo);

error_log ("aukioloid1 " . $aukiolo_id);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['maanantai'], 1);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['tiistai'], 2);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['keskiviikko'], 3);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['torstai'], 4);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['perjantai'], 5);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['lauantai'], 6);

$id = $this->tallennaPaiva($aukiolo_id, $aukiolo, $aukiolo['sunnuntai'], 7);

if($id != null) {

return $this->haeAvaimella($id);

} return null;

}

function tallennaPaiva($aukiolo_id, $aukiolo, $paiva, $tyyppi_id) { if(!isset($paiva['alkuaika']) || !isset($paiva['loppuaika'])) return -1;

error_log ("aukioloid " . $aukiolo_id);

$paiva['alkupaiva'] = $aukiolo['alkupaiva'];

$paiva['loppupaiva'] = $aukiolo['loppupaiva'];

$paiva['paikka_id'] = $aukiolo['paikka_id'];

$paiva['tyyppi_id'] = $tyyppi_id;

$paiva['aukiolo_id'] = $aukiolo_id;

$id = $this->aukiolodao->luoAukioloaika($paiva);

}

function haePaikantiedot($paikka_id) {

$linki = $this->aukiolodao->haePaikanAukiolot($paikka_id);

return $linki;

}

function haeAvaimella($id) {

$aukiolo = $this->aukiolodao->haeAukiolo($id);

return $aukiolo;

}

function poista($id) {

return $this->aukiolodao->poistaAukiolo($id);

} }

?>

./api/api/controller.class.php

<?php

use JsonSchema\Validator;

use JsonSchema\Constraints\Constraint;

class Controller {

private function getJsonObject() {

$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';

if(strcasecmp($contentType, 'application/json') != 0){

throw new Exception('Content type must be: application/json');

}

$content = trim(file_get_contents("php://input"));

$jsonobject = json_decode($content, true);

return $jsonobject;

}

public function dispatch($subAction,$id) { if ($this->customdispatch($subAction,$id)) { return;

}

$httpverb = strtoupper($_SERVER['REQUEST_METHOD']);

$result = null;

switch($httpverb) { case 'GET':

if(isset($subAction)) { if($subAction === "paikka") {

$result = $this->haePaikantiedot($id);

if(!isset($result)) {

return $this->virhe("no data found for id ".$id);

} } }

if(isset($id) && $result === null) { if($id === "kaikki") { $result = $this->haeKaikki();

} else if($id === "geojson") { $result = $this->geojson();

} else {

$result = $this->haeAvaimella($id);

} } else {

if($result === null) { $result = $this->haeKaikki();

} } break;

case 'POST':

$newobject = $this->getJsonObject();

$result = $this->luo($newobject);

break;

case 'PUT':

$newobject = $this->getJsonObject();

$result = $this->paivita($newobject);

break;

case 'DELETE':

if(isset($id)) { $this->poista($id);

} else {

throw new Exception("please give id!");

}

(33)

break;

case 'PATCH':

throw new Exception("not supported by this api. contact joni....");

break;

default:

$result = "{ \"error\": \"not implemented\"";

break;

}

if($result != null) {

header('Content-type: application/json; charset=iso-8859-1');

echo json_encode($result);

return;

} }

function customdispatch($subAction,$id) { return false;

}

function virhe($teksti) {

header('Content-type: application/json; charset=iso-8859-1');

echo "{ \"error\": \"" .$teksti. "\" }";

}

function geojson() {

return "{ \"error\": \"not implemented\"";

}

function haePaikantiedot($paikka_id) {

return "{ \"error\": \"haePaikantiedot not implemented\"";

}

function haeKaikki() {

return "{ \"error\": \"haeKaikki not implemented\"";

}

function luo($newobject) {

return "{ \"error\": \"luo not implemented\"";

}

function paivita($newobject) {

return "{ \"error\": \"paivita not implemented\"";

}

function haeAvaimella($id) {

return "{ \"error\": \"haeAvaimella not implemented\"";

}

function poista($id) {

return "{ \"error\": \"poista not implemented\"";

}

function validoi($schemajson,$newobject) { $validator = new Validator;

$realpath = realpath(__DIR__.'/schema/'.$schemajson);

$schema = 'file://'.$realpath;

$validator->validate(

$newobject,

(object)['$ref' => $schema], Constraint::CHECK_MODE_APPLY_DEFAULTS );

if (!$validator->isValid()) {

header('Content-type: application/json; charset=iso-8859-1');

echo json_encode($validator->getErrors());

return false;

} return true;

} }

?>

./api/api/kuvaapi.class.php

<?php

class KuvaApi extends Controller { private $kuvadao;

function customdispatch($subAction,$id) { if($subAction === "getimage") { $kuva = $this->getImage($id);

header('Content-type: ' . $kuva->getTyyppi());

$binary = base64_decode(substr($kuva->getKuva(),37));

echo $binary;

return true;

}

return false;

}

function __construct() { $this->kuvadao = new KuvaDAO();

}

function getImage($id) {

$kuva = $this->kuvadao->haeKuva($id);

return $kuva;

}

function haeAvaimella($id) {

$kuva = $this->kuvadao->haeKuva($id);

return $kuva;

}

function luo($newobject) {

$kuva_id = $this->kuvadao->luoKuva($newobject);

if($kuva_id != null) {

return $this->haeAvaimella($kuva_id);

} return null;

}

function haePaikantiedot($paikka_id) {

return $this->kuvadao->haePaikanKuvat($paikka_id);

}

function poista($id) {

$this->kuvadao->poistaKuva($id);

} }

?>

./api/api/linkkiapi.class.php

<?php

include_once 'controller.class.php';

class LinkkiApi extends Controller { private $linkkidao;

function __construct() {

$this->linkkidao = new LinkkiDAO();

}

function luo($newobject) {

if ($this->validoi("linkkischema.json", $newobject)) { $id = $this->linkkidao->luoLinkki($newobject);

if($id != null) {

return $this->haeAvaimella($id);

} }

(34)

return null;

}

function haePaikantiedot($paikka_id) {

$linki = $this->linkkidao->haePaikanLinkit($paikka_id);

return $linki;

}

function haeAvaimella($id) {

$linki = $this->linkkidao->haelinkki($id);

return $linki;

}

function poista($id) {

$linki = $this->linkkidao->poisLinkki($id);

return $linki;

}

function paivita($newobject) {

if ($this->validoi("linkkischema.json", $newobject)) { $linki = $this->linkkidao->paivitaLinkki($newobject);

return $linki;

} return null;

} }

?>

./api/api/luokitteluapi.class.php

<?php

class LuokitteluApi extends Controller { private $luokitteludao;

function __construct() {

$this->luokitteludao = new LuokitteluDAO();

}

function __destruct() { }

function haeKaikki() {

$luokittelut = $this->luokitteludao->haeLuokittelut();

return $luokittelut;

} } ?>

./api/api/paikkaapi.class.php

<?php

include_once 'controller.class.php';

class PaikkaApi extends Controller { private $paikkadao;

private $aukiolodao;

private $yhteystietodao;

private $kuvadao;

private $linkkidao;

function __construct() {

$this->paikkadao = new PaikkaDAO();

$this->aukiolodao = new AukioloDAO();

$this->yhteystietodao = new YhteystietoDAO();

$this->kuvadao = new KuvaDAO();

$this->linkkidao = new LinkkiDAO();

}

function __destruct() { }

function geojson() {

return $this->paikkadao->haeGeot()->values();

}

function haeKaikki() { $whereclause = "";

foreach($_GET as $key=>$val) { if(isset($val) && $val != null) { $condition = "";

if($key != "luokittelu_id" && $key != "paikkatieto") { $condition = "p." . $key . " like '" . $val . "%'";

} else if($key === "luokittelu_id" && $val != 0) { $condition = "p." . $key . " = " . $val;

} else if($key === "paikkatieto" && $val['osoite'] != null) { $condition = "t.osoite like '" . $val['osoite'] . "%'";

}

if($condition != "") {

$whereclause = $whereclause . " AND " . $condition;

} } }

$paikat = $this->paikkadao->haePaikat($whereclause);

return $paikat;

}

function luo($newobject) {

$id = $this->paikkadao->luoPaikka($newobject);

if($id != null) {

return $this->haeAvaimella($id);

} return null;

}

function paivita($newobject) {

return $this->paikkadao->paivitaPaikka($newobject);

}

function haeAvaimella($id) {

$paikka = $this->paikkadao->haePaikka($id);

return $paikka;

}

function poista($paikka_id) {

$this->yhteystietodao->poistaPaikanYhteystiedot($paikka_id);

$this->linkkidao->poistaPaikanLinkit($paikka_id);

$this->kuvadao->poistaPaikanKuvat($paikka_id);

$this->aukiolodao->poistaPaikanAukiolot($paikka_id);

return $this->paikkadao->poistaPaikka($paikka_id);

} }

?>

./api/api/schema/linkkischema.json

{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "linkki",

"description": "paikkaan liityvat www linkit", "type": "array",

"properties": { "linki_id": {

"description": "The unique identifier for a link", "type": "integer"

},

(35)

"paikka_id": { "type": "integer"

}, "linki": {

"description": "Name of the product", "type": "string",

"minLength": 12, "maxLength": 255 }

},

"required": ["paikka_id", "linki"]

}

./api/api/yhteystietoapi.class.php

<?php

include_once 'controller.class.php';

class YhteystietoApi extends Controller { private $yhteystietodao;

function __construct() {

$this->yhteystietodao = new YhteystietoDAO();

}

function luo($newobject) {

$id = $this->yhteystietodao->luoYhteystieto($newobject);

if($id != null) {

return $this->haeAvaimella($id);

} return null;

}

function haeAvaimella($id) {

$yhteystieto = $this->yhteystietodao->haeyhteystieto($id);

return $yhteystieto;

}

function poista($id) {

return $this->yhteystietodao->poistaYhteystieto($id);

}

function haePaikantiedot($paikka_id) {

return $this->yhteystietodao->haePaikanYhteystiedot($paikka_id);

}

function paivita($yhteystieto) {

return $this->yhteystietodao->paivitaYhteystieto($yhteystieto);

} }

?>

./api/dao/aukiolodao.class.php

<?php

class AukioloDAO extends BaseDao{

public function luoAukioloAika($aukioloaika){

$aukioloaikaEntity = new Aukioloaika(); //make new model $aukioloaikaEntity->fromJson($aukioloaika);

error_log ("aukioloid " . $aukioloaikaEntity->getAukioloId());

$this->getEntityManager()->persist($aukioloaikaEntity);

$this->getEntityManager()->flush();

return $aukioloaikaEntity->getAukioloaika_id();

}

public function luoAukiolo($aukiolo){

$aukiolo_id = $this->getSequenceNextValue(null, "paikkatieto.aukiolo_aukiolo_id_seq");

$aukioloEntity = new Aukiolo(); //make new model $aukioloEntity->fromJson($aukiolo);

$aukioloEntity->setAukioloId($aukiolo_id);

$this->getEntityManager()->persist($aukioloEntity);

$this->getEntityManager()->flush();

return $aukiolo_id;

}

public function haeAukiolo() { $aukiolo = new Aukiolo();

return $aukiolo;

}

public function poistaPaikanAukiolot($paikka_id) {

$query = $this->getEntityManager()->createQuery("SELECT a FROM aukioloaika a WHERE a.paikka_id = " .

$paikka_id);

$aukioloajat = $query->getResult();

foreach($aukioloajat as $aukioloaika) {

$this->getEntityManager()->remove($aukioloaika);

}

$query = $this->getEntityManager()->createQuery("SELECT a FROM aukiolo a WHERE a.paikka_id = " . $paikka_id);

$aukiolot = $query->getResult();

foreach($aukiolot as $aukiolo) {

$this->getEntityManager()->remove($aukiolo);

}

$this->getEntityManager()->flush();

return "success";

}

public function haePaikanAukiolot($paikka_id) {

$query = $this->getEntityManager()->createQuery("SELECT a FROM aukiolo a WHERE a.paikka_id = " . $paikka_id);

$aukiolot = $query->getResult();

return $aukiolot;

}

public function poistaAukiolo($id) { $conn = $this->getConnection();

$poistaPaikkaQuery = "delete from paikkatieto.aukioloaika where aukiolo_id = " . $id . ";";

$result = pg_query($conn, $poistaPaikkaQuery);

$paikkaEntity = $this->getEntityManager()->find("aukiolo", $id);

$this->getEntityManager()->remove($paikkaEntity);

$this->getEntityManager()->flush();

return "success";

} } ?>

./api/dao/basedao.class.php

<?php

use Doctrine\ORM\Tools\Setup;

use Doctrine\ORM\EntityManager;

class BaseDao { private $geojson;

public $FAILED = "failed";

public $SUCCESS = "success";

private $entityManager;

function __construct() { $this->geojson = new GeoJSON();

}

public function getConnection() {

Viittaukset

Outline

LIITTYVÄT TIEDOSTOT

Kuten tunnettua, Darwin tyytyi Lajien synnyssä vain lyhyesti huomauttamaan, että hänen esittämänsä luonnonvalinnan teoria toisi ennen pitkää valoa myös ihmisen alkuperään ja

Kirjoita paperiin nimesi ja muut tarvittavat tiedot.. Kuten aina, perustele vastauksesi

Asiakaspalveluhenkilö syöttää tarvittavat tiedot palvelupyynnölle ja kirjaa kuvaus kenttään palvelupyynnön tiedot, mitkä hän otti vastaan puheli- messa,

Hankkeen Rakennusten sähköisen energiamerkinnän ja -monitoroinnin avoin palvelu- alusta (eCertification) tarkoituksena on yhdistää tiedot, jotka koskevat rakennuskantaa,

− Hätäkeskusten tietojärjestelmää kehitettäessä tulisi huomioida, että järjestelmä ky- kenee käsittelemään myös liikenteen häiriötilanteista tarvittavat tiedot. Tiedot

Siltä osin kuin tutkinnon osassa vaadittavaa osaamista ei voida työtä tekemällä näytössä kattavasti osoittaa, sitä täydennetään muulla osaamisen

Niiden luonne vain on muuttunut: eleet ja kasvottainen puhe ovat vaihtuneet kirjoitukseksi ja ku- viksi sitä mukaa kuin kirjapainotaito on kehittynyt.. Sa- malla ilmaisu on

Mu- kana olo tässä komiteassa merkitsi sitä, että Gebhard tunsi sekä suomalaisen maaseudun so- siaaliset ongelmat yleensä että myös alueelliset erot, ja nämä tiedot hän