= Напредна тема: Имплементација на PostGIS = За имплементација на напредна тема за нашиот проект избравме PostGIS кој овозможува надградување на базата на податоци да управува со географски информации. Во реалните системи на светските авиокомпании овозможено е следење на летови, нивните рути како и самите авиони употребувајќи GPS технологии и калкулации на рути. Со цел пореалистично моделирање на проектот сметаме дека оваа тема би дала малку поголема прецизност на дел од податоците. Пред имплементаицја на оваа тема, во табелата !ScheduledFlight која претставува закажан лет (урнек), колоната Distance беше генерирана на рандомизиран начин односно според времето на полетување и слетување, што не претставува реален податок. Проблем се јавува при употреба на оваа вредност во калкулациите на цена на авионски билети како и додавање на поени за летање на патниците. Со воведување на PostGIS, податоците се реални и употребливи во бизнис логиката на апликацијата. Исто така овозможено е имплементирање на дополнителни функционалности присутни во реални системи. Со цен јасно прикажување на резултатите кои ги добивме со имплементација на PostGIS, за визуелизација на податоците ја употребивме алатката QGIS која директно се поврзува со нашата база на податоци во Postgres и во која може да се извршуваат и потребните прашалници. Во документацијата по имплементација на секој дел ја имаме прикажано и соодветната визуелизација. == Инсталација на PostGIS За воведување на PostGIS во нашата база на податоци потребно беше локална инсталација на алатката и нејзино вклучување во базата преку извршување на командата {{{ #!sql create extension postgis; }}} == Пормена на таблеата Airport За успешно имплементирање на PostGIS потребно беше да се направи промена во табелата Airport. За да можеме да пресметаме растојание помеѓу аеродромите (километража на лет) потрбено беше додавање на колони Longitude (географска должина) и Latitude (географска ширина). Со додавање на овие две колони овозможено е одредување на реалните координати на аеродромот. {{{ #!sql alter table airport add column longitude numeric; alter table airport add column latitude numeric; }}} По додавање на колоните тие беа пополнети со веќе постоечките податоци од OpenFlight CSV датотеката која исто така ја употребивме и за првобитното пополнување на табелата што ни овозможи одржување на конзистентност во податоците. Како следен чекор потребно беше претставување на координатите во форма на Point. За таа цел додадовме нова колона location од тип geography(Point, 4326). {{{ #!sql alter table airport add column location geography(Point, 4326); }}} За пополнување на колоната location потребно беше да се искористат податоците за longitude и latitude на секој аеродром. За креирање на објект Point потребно беше да се изврши функцијата ST_MakePoint, која создава Point од два аргументи - координати. {{{ #!sql update airport set location = ST_MakePoint(longitude, latitude)::geography; }}} При визуелизација ја искористивме колоната location во Airport и како лабела на секоја точка колоната Code. ''Сите аеродроми'' [[Image(World-Airports.png, 800px, align=center)]] ''Аеродромите на балканскиот полуостров'' [[Image(Balkan.png, 800px, align=center)]] == Промена во табелата !ScheduledFlight == При иницијално пополнување на податоците во табелата !ScheduledFlight, во колоната Distance беа поставени случајни податоци (искалкулирани според времето на полетување и слетување кои исто така се случајно пополнети). Со цен вршење на реални пресмети за цена на летовите како и поени за патниците потребно е да се знае вистинското растојание кое го поминува секој лет. За исполнување на таа цен потребно беше да се пресмета вистинското растојание употребувајќи ја новата колона Location во табелата Airport. PostGIS нуди функционалност пресметка на растојание помеѓу две точки ST_Distance. За промена на колоната Distance го извршивме следниот SQL код: {{{ #!sql update airport update scheduledflight as sf set distance = greatest(1, round(ST_Distance(dep.location, arr.location) / 1000)::int) from slot as ds join gate as dg on ds.gateid = dg.id join terminal as dt on dg.terminalid = dt.id join airport as dep on dep.id = dt.airportid, slot as asl join gate as ag on asl.gateid = ag.id join terminal as at on at.id = ag.terminalid join airport as arr on arr.id = at.airportid where sf.departure = ds.id and sf.arrival = asl.id and dep.location is not null and arr.location is not null; }}} Поради тоа што поставен е Check кој проверува дали растојанието е барем 1 километар, потребно беше да се избере поголемата вредност помеѓу 1 и ST_Distance(dep.location, arr.location) за да може успешно да се вметнат сите колони. === Тригер Calculate Distance === Иако кодот погоре ги корегира веќе постоечките податоци за Distance, при додавање на нов ScheduledFlight или промена на некој од слотовите за полетување и слетување не се врши повторна пресметка. За таа цел имплементиравме тригер кои при изведување на некои од овие активности врши повторна пресметка на растојанието. Вака се озовможува конзистентност на податоците. [attachment:CalculateDistance.sql] == Поглед Flight Routes == За излистување на сите рути на веќе постоечки летови потребно беше креирање на поглед - Flight Routes. Во овој поглед се прикажуваат податоците за број на лет, време и аеродром на полетување и слетување, рутата како објект Line во PostGIS и растојанието. За преставување на рутата како Line ја употребивме функцијата ST_MakeLine која како аргументи ги прима координатите на аеродромите. [attachment:FlightRoutes.sql] == 3. Каде е интегриран PostGIS? == PostGIS се протега низ повеќе компоненти на постоечкиот проект: || '''Компонента''' || '''Тип на интеграција''' || || Табела {{{Airport}}} || Додадени се колоните {{{latitude}}}, {{{longitude}}}, {{{location}}} (geography Point) || || Колона {{{Airport.location}}} || Просторен GIST индекс за брзи геопросторни прашалници || || Табела {{{ScheduledFlight}}} || Колоната {{{distance}}} е ажурирана со пресметани реални дистанци || || Тригер {{{SetDistance}}} || Автоматски пресметува дистанца при INSERT/UPDATE на !ScheduledFlight || || Поглед {{{FlightRoutes}}} || Нов поглед кој ги претставува летовите како !LineString геометрии || || Функција {{{FindNearestAirport}}} || Нова функција за наоѓање најблиски аеродроми || || Функција {{{FlightAtTime}}} || Нова функција за пресметка на локација на лет во даден момент || || Тригер {{{AwardMilePoints}}} (Фаза 4) || Сега ги пресметува миљните поени врз основа на реални растојанија || ---- == 4. Технички предуслови == Пред да може да се користи PostGIS, екстензијата мора да биде активирана во базата на податоци: {{{ #!sql CREATE EXTENSION postgis; }}} Оваа команда ги регистрира сите PostGIS типови на податоци ({{{geography}}}, {{{geometry}}}), функции ({{{ST_Distance}}}, {{{ST_MakePoint}}}, {{{ST_LineInterpolatePoint}}} итн.) и оператори (просторниот KNN оператор {{{<->}}}) во моменталната база. ---- == 5. Промени на постоечките табели == === 5.1 Додавање на колоните latitude и longitude === Првиот чекор беше да се обогати табелата {{{Airport}}} со географски координати. Овие податоци ги имавме од оригиналниот CSV фајл ({{{airports.csv}}} од !OpenFlights), но во Фаза 2Б беа отфрлени бидејќи тогаш немавме просторна функционалност. {{{ #!sql ALTER TABLE airport ADD COLUMN latitude numeric; ALTER TABLE airport ADD COLUMN longitude numeric; }}} Колоните потоа се пополнија со податоци од привремена табела {{{temp_airports}}}, спарувајќи по IATA код: {{{ #!sql UPDATE airport a SET latitude = ta.latitude::numeric, longitude = ta.longitude::numeric FROM ( SELECT DISTINCT ON (iata) iata, latitude, longitude FROM temp_airports WHERE latitude NOT IN ('\N','') AND longitude NOT IN ('\N','') ORDER BY iata ) ta WHERE a.code = ta.iata; }}} Овие колони служат како „сурова" форма на координатите — лесни за читање и експортирање, но не директно искористливи во просторни прашалници. === 5.2 Додавање на колоната location (geography Point) === Главната просторна колона е {{{location}}}, која ги обединува latitude и longitude во една PostGIS геометрија: {{{ #!sql ALTER TABLE airport ADD COLUMN location geography(Point, 4326); UPDATE airport SET location = ST_MakePoint(longitude, latitude)::geography; }}} Неколку важни детали: * '''Тип {{{geography(Point, 4326)}}}.''' PostGIS нуди два главни типа на просторни податоци: {{{geometry}}} (рамна, евклидска геометрија) и {{{geography}}} (вистинска сферна геометрија на Земјата). За авиокомпаниски проект, {{{geography}}} е правилниот избор бидејќи летовите се на голема скала и кривината на Земјата е значајна — растојанијата мора да се мерат како great-circle distances, а не како евклидски растојанија. * '''SRID 4326.''' Ова е стандардот WGS 84 (World Geodetic System 1984), истиот референтен систем кој го користат GPS уредите. Со ова, координатите од нашиот CSV (кои се во WGS 84) се компатибилни со колоната без потреба од репроекција. * '''Редослед на параметри: {{{ST_MakePoint(longitude, latitude)}}}.''' PostGIS следи XY-конвенција, што значи дека longitude е X (надолжна, „хоризонтална" оска) и latitude е Y. Замена на овие два параметра е честа грешка која води до точки во погрешен дел на светот. === 5.3 Просторен GIST индекс === За да може PostGIS брзо да изврши просторни прашалници врз милиони редици, на колоната {{{location}}} се додаде GIST индекс: {{{ #!sql CREATE INDEX idx_airport_location ON airport USING GIST (location); }}} За разлика од B-tree индексите (кои се користат за подредени скаларни вредности — броеви, текстови, датуми), GIST индексите се специјализирани за повеќедимензионални податоци. Тие овозможуваат: * '''Range queries во просторот''' — „пронајди ги сите аеродроми во радиус од 500 km околу точката X" * '''Nearest-neighbor queries''' — „пронајди ги најблиските 10 аеродроми од дадена точка", преку K-nearest-neighbor операторот {{{<->}}} * '''Intersection queries''' — „пронајди ги сите рути кои поминуваат низ дадена област" Без GIST индекс, секоја таква претрага би била sequential scan низ целата табела. ---- == 6. Поглед FlightRoutes == Создаден е нов поглед кој ги претставува летовите како геометриски линии на мапата: {{{ #!sql CREATE VIEW FlightRoutes (FlightID, FlightNumber, Departure, Arrival, Origin, Destination, Route, Distance) AS SELECT flight.id, flight.flightnumber, flight.departure, flight.arrival, ad.code, aa.code, ST_MakeLine(ad.location::geometry, aa.location::geometry), scheduledflight.distance FROM flight JOIN gate AS ga ON flight.actualgatearrivalid = ga.id JOIN gate AS gd ON flight.actualgatedepartureid = gd.id JOIN terminal AS ta ON ta.id = ga.terminalid JOIN terminal AS td ON td.id = gd.terminalid JOIN airport AS aa ON aa.id = ta.airportid JOIN airport AS ad ON ad.id = td.airportid JOIN scheduledflight ON flight.scheduleid = scheduledflight.id WHERE aa.location IS NOT NULL AND ad.location IS NOT NULL; }}} '''Како функционира:''' * Функцијата {{{ST_MakeLine(departure_point, arrival_point)}}} гради {{{LineString}}} геометрија — права линија помеѓу две точки во геометрискиот простор. * Низ ланецот од JOIN-ови, погледот ги доведува аеродромите за полетување и слетување на секој лет (преку gate → terminal → airport). * Резултатот е една редица по лет, со полно име, временски печат, кодови на полетниот и слетниот аеродром, реална географска линија помеѓу нив и пресметана дистанца. '''Случаи на употреба:''' * '''Визуелизација во QGIS''' — погледот може да се вчита како просторен слој и да прикаже сите рути на мапа. * '''Мрежна анализа''' — кои аеродроми се најпрометни, кои рути се најфреквентни, која е географската покриеност на авиокомпанијата. * '''Просторни прашалници''' — на пример, „кои летови поминуваат во радиус од 200 km околу одредена точка" може да се изврши со {{{ST_DWithin}}} врз колоната {{{Route}}}. === 6.1 Скриншоти === '''Сите аеродроми на светот:'''[[BR]] [[Image(Screenshots/World-Airports.png)]] '''Аеродроми на Балканот:'''[[BR]] [[Image(Screenshots/Balkan.png)]] '''Рути од Скопје:'''[[BR]] [[Image(Screenshots/RoutesFromSkopje.png)]] '''Рути до Newark (EWR):'''[[BR]] [[Image(Screenshots/RoutesToNewark.png)]] ---- == 7. Ажурирање на дистанците во ScheduledFlight == Една од најважните придобивки на PostGIS во овој проект е замена на лажните, синтетички дистанци со вистински географски вредности. {{{ #!sql UPDATE scheduledflight AS sf SET distance = GREATEST(1, ROUND(ST_Distance(dep.location, arr.location) / 1000)::int) FROM slot AS ds JOIN gate AS dg ON ds.gateid = dg.id JOIN terminal AS dt ON dg.terminalid = dt.id JOIN airport AS dep ON dep.id = dt.airportid, slot AS asl JOIN gate AS ag ON asl.gateid = ag.id JOIN terminal AS at ON at.id = ag.terminalid JOIN airport AS arr ON arr.id = at.airportid WHERE sf.departure = ds.id AND sf.arrival = asl.id AND dep.location IS NOT NULL AND arr.location IS NOT NULL; }}} '''Детали за пресметката:''' * {{{ST_Distance(dep.location, arr.location)}}} враќа great-circle дистанца помеѓу две {{{geography}}} точки во метри. * Делењето со 1000 ја претвора во километри. * {{{ROUND(...)::int}}} ја кружи до цел број за да одговара на типот на колоната. * {{{GREATEST(1, ...)}}} гарантира минимална вредност од 1 km (поради CHECK ограничувањето {{{distance > 0}}}). Ова е важно за ретки случаи каде два аеродроми се толку близу што дистанцата заокружена на километри би била 0. '''Последица низ остатокот на системот:''' Постоечкиот тригер {{{AwardMilePoints}}} (од Фаза 4) ги пресметува миљните поени на патниците врз основа на {{{ScheduledFlight.Distance}}}, помножена со множител по класа (Economy: 1×, Business: 2×, First: 3×). По ова ажурирање, поените повеќе не се „измислени" — патник со First класа на лет JFK → LAX добива ~4000 km × 3 = 12,000 поени, што одговара на реално долг лет. ---- == 8. Тригер SetDistance (CalculateDistance) == За да не мора рачно да се пресметува дистанцата секогаш кога ќе се додаде нов закажан лет, имплементиран е тригер кој автоматски ја пресметува: {{{ #!sql CREATE OR REPLACE FUNCTION CalculateDistance() RETURNS trigger AS $$ BEGIN SELECT GREATEST(1, ROUND(ST_Distance(dep.location, arr.location) / 1000)::int) INTO NEW.distance FROM slot AS ds JOIN gate AS dg ON ds.gateid = dg.id JOIN terminal AS dt ON dg.terminalid = dt.id JOIN airport AS dep ON dep.id = dt.airportid, slot AS asl JOIN gate AS ag ON asl.gateid = ag.id JOIN terminal AS at ON at.id = ag.terminalid JOIN airport AS arr ON arr.id = at.airportid WHERE NEW.departure = ds.id AND NEW.arrival = asl.id AND dep.location IS NOT NULL AND arr.location IS NOT NULL; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER SetDistance BEFORE INSERT OR UPDATE OF departure, arrival ON scheduledflight FOR EACH ROW EXECUTE FUNCTION CalculateDistance(); }}} '''Карактеристики:''' * '''{{{BEFORE INSERT OR UPDATE}}}''' — тригерот се извршува ''пред'' редицата да биде впишана/ажурирана. Тоа му овозможува да ја смени вредноста на {{{NEW.distance}}} пред таа да биде валидирана од CHECK ограничувањата. * '''{{{UPDATE OF departure, arrival}}}''' — тригерот се извршува само кога овие конкретни колони се менуваат. Доколку се ажурира некоја друга колона (на пример, {{{active}}}), тригерот не се активира — оптимизација која штеди непотребна работа. * '''{{{SELECT ... INTO NEW.distance}}}''' — наместо да враќа вредност на повикувачот, тригерот ја менува вредноста на {{{NEW}}} пред commit-ирање. '''Зошто тригер наместо generated column?''' PostgreSQL поддржува {{{GENERATED ALWAYS AS (...) STORED}}} колони, но тие не можат да користат subqueries или JOIN-ови (бидејќи мораат да бидат детерминистички и без странични ефекти). Бидејќи пресметката на дистанцата бара JOIN преку {{{slot → gate → terminal → airport}}}, мораше да се користи тригер. '''Случај на употреба:''' доколку во иднина се додадат нови закажани летови (нови дестинации, проширување на мрежата), нивната дистанца автоматски се пресметува правилно — администраторот не мора да внимава да ја внесе како посебен чекор. ---- == 9. Функција FindNearestAirport == {{{ #!sql CREATE OR REPLACE FUNCTION FindNearestAirport( longitude_P numeric, latitude_P numeric, distanceKm int ) RETURNS TABLE ( AirportCode char(3), AirportName text, AirportLocation geography(Point, 4326), Distance_KM numeric ) AS $$ BEGIN RETURN QUERY SELECT a.code, a.name, a.location, (ST_Distance(a.location, ST_MakePoint(longitude_P, latitude_P)::geography) / 1000)::numeric FROM airport AS a WHERE ST_DWithin( a.location, ST_MakePoint(longitude_P, latitude_P)::geography, distanceKm * 1000 ) ORDER BY a.location <-> ST_MakePoint(longitude_P, latitude_P)::geography; END; $$ LANGUAGE plpgsql; }}} '''Како функционира:''' * Прима три параметри: longitude и latitude на референтна точка, плус максимален радиус во километри. * Враќа табела на сите аеродроми во тој радиус, заедно со нивната локација и точното растојание. * Резултатите се сортирани по растојание — најблискиот аеродром е прв. '''Клучни PostGIS компоненти:''' * '''{{{ST_DWithin(geog_a, geog_b, distance_meters)}}}''' — враќа TRUE доколку две географски точки се на растојание помало или еднакво на дадениот број метри. Ова е првиот филтер кој го „сече" пребарувањето. * '''Множењето {{{distanceKm * 1000}}}''' ги претвора километрите во метри (PostGIS интерно работи во метри за geography). * '''KNN оператор {{{<->}}}''' — посебен оператор за nearest-neighbor сортирање. За разлика од обичен {{{ORDER BY ST_Distance(...)}}}, овој оператор го користи GIST индексот директно и е значајно побрз при големи табели. '''Случаи на употреба:''' * '''Препораки за алтернативни аеродроми''' — кога одреден лет се откажува, можеме да им предложиме на патниците блиски аеродроми (на пример, ако се откажува лет до LHR, можеме да предложиме LGW, STN, LTN). * '''Логистика на товар''' — наоѓање најблизок аеродром до магацин или дистрибутивен центар. * '''Кориснички интерфејс''' — „наоѓаш ли се близу аеродром?" функционалност за мобилна апликација. '''Демонстрација:''' наоѓање на најблиските аеродроми во радиус од 100 km околу Скопје (координати 21.62°E, 41.96°N): {{{ #!sql SELECT * FROM FindNearestAirport(21.62, 41.96, 100); }}} [[Image(Screenshots/NearestAirportsSkopje.png)]] ---- == 10. Функција FlightAtTime == Оваа функција пресметува каде се наоѓа определен лет (естимативно) во даден временски момент — без потреба од посебна табела со логирани локации. {{{ #!sql CREATE OR REPLACE FUNCTION FlightAtTime( FlightID_P int, Time_P timestamp ) RETURNS TABLE ( CurrentLocation geography, Route geography ) AS $$ DECLARE DepartureTime timestamp; ArrivalTime timestamp; DepartureLocation geography; ArrivalLocation geography; Fraction numeric; CurrentPosition_V geography; Route_V geography; BEGIN SELECT flight.departure, flight.arrival, ad.location, aa.location INTO DepartureTime, ArrivalTime, DepartureLocation, ArrivalLocation FROM flight JOIN gate AS ga ON flight.actualgatearrivalid = ga.id JOIN gate AS gd ON flight.actualgatedepartureid = gd.id JOIN terminal AS ta ON ta.id = ga.terminalid JOIN terminal AS td ON td.id = gd.terminalid JOIN airport AS aa ON aa.id = ta.airportid JOIN airport AS ad ON ad.id = td.airportid WHERE flight.id = FlightID_P; IF Time_P <= DepartureTime THEN CurrentPosition_V := DepartureLocation; ELSIF Time_P >= ArrivalTime THEN CurrentPosition_V := ArrivalLocation; ELSE Fraction := EXTRACT(epoch FROM (Time_P - DepartureTime)) / EXTRACT(epoch FROM (ArrivalTime - DepartureTime)); CurrentPosition_V := ST_LineInterpolatePoint( ST_MakeLine(DepartureLocation::geometry, ArrivalLocation::geometry), Fraction )::geography; END IF; Route_V := ST_MakeLine( DepartureLocation::geometry, ArrivalLocation::geometry )::geography; RETURN QUERY SELECT CurrentPosition_V, Route_V; END; $$ LANGUAGE plpgsql; }}} '''Како функционира:''' 1. '''Се извлекуваат деталите за летот''' — времето на полетување и слетување, и локациите на двата аеродрома преку познатиот ланец на JOIN-ови. 2. '''Се определува моменталната локација според временскиот параметар:''' * Доколку времето е ''пред'' полетувањето, авионот сè уште е на полетниот аеродром. * Доколку времето е ''по'' слетувањето, авионот веќе пристигнал на слетниот аеродром. * Во спротивно, се пресметува {{{Fraction}}} — фракција помеѓу 0 и 1 која означува „колку процент од летот е изминат". 3. '''Се користи {{{ST_LineInterpolatePoint}}}''' — функција која враќа точка на одредена фракција должина по една линија. Со fraction = 0.5, се добива средината на патот; со fraction = 0.25, една четвртина од него. 4. '''Се враќа и моменталната локација и целата рута''' — корисно за визуализација (точка на мапа за авионот, плус линија која ја прикажува неговата траектoрија). '''Битна забелешка за реалност:''' Оваа функција користи '''линеарна интерполација по great-circle линија''' — претпоставува дека авионот лета со константна брзина по најкраткиот пат на сфера. Во вистинскиот свет: * Авионите не летаат со константна брзина (имаат фази на качување, крстарење и спуштање). * Реалните рути често отстапуваат од идеалната great-circle линија поради воздушни кориди, временски услови, забранети воздушни простори. * Реалните системи за следење на летови (Flight Tracking) користат '''посебна табела за логирање на локации''' — на пример, ADS-B транспондерите на авионите емитуваат локација секои неколку секунди, а сите тие точки се чуваат во база. Бидејќи во овој проект немаме вистински авиони, импровизираме со пресметана локација. Во документацијата експлицитно споменуваме дека ова е демонстративен пристап, не продукциски модел. '''Случаи на употреба:''' * '''Air Traffic Control визуелизација''' — приказ каде се сите авиони во моментот. * '''Информации за патници''' — „вашиот лет моментално е над Атлантскиот океан, на 70% од патот". * '''Анализа на оперативни инциденти''' — со повикување на {{{FlightAtTime(flight_id, incident_time)}}} се добива каде се наоѓал авионот кога настанал инцидентот. '''Демонстрација:''' [[Image(Screenshots/FlightAtDemo.png)]] ---- == 11. Визуелизација со QGIS == Една од најголемите предности на PostGIS е природната интеграција со '''QGIS''' — индустриски стандарден отворен GIS алат. QGIS може директно да се поврзе со нашата PostGIS база и да ги визуелизира просторните слоеви во реално време. '''Процес на конекција:''' 1. Во QGIS се додава PostGIS конекција (Browser → !PostgreSQL → New Connection) со параметрите на базата. 2. Сите табели и погледи со просторни колони (Airport, !FlightRoutes итн.) автоматски се препознаваат како слоеви. 3. Со drag-and-drop се додаваат на мапата. 4. Како подлога се користи !OpenStreetMap (преку XYZ Tiles), кој дава реален географски контекст. '''Што е визуелизирано:''' * '''Слој на аеродроми''' — секој аеродром се прикажува како точка, со label-от поставен на IATA код. * '''Слој на рути''' — секоја линија претставува лет помеѓу два аеродроми. При зголемување на zoom, се гледа конкретниот географски пат. * '''Демонстрација на функциите''' — {{{FindNearestAirport}}} и {{{FlightAtTime}}} се повикуваат преку DB Manager → SQL Window, и нивните резултати се вчитуваат како нови слоеви. QGIS проектот е зачуван како {{{SBAirlines.qgz}}} и претставува дел од испораката на оваа фаза. ---- == 12. Краток преглед на новите компоненти == || '''Компонента''' || '''Тип''' || '''Намена''' || || {{{airport.latitude}}}, {{{airport.longitude}}} || Нови колони || Сурови географски координати || || {{{airport.location}}} || Нова колона || PostGIS геометриска точка (geography Point, 4326) || || {{{idx_airport_location}}} || Просторен индекс || GIST индекс за брзи просторни прашалници || || {{{FlightRoutes}}} || Нов поглед || Сите летови како !LineString геометрии || || {{{ScheduledFlight.distance}}} || Ажурирана колона || Реални great-circle растојанија наместо синтетички || || {{{CalculateDistance()}}} + {{{SetDistance}}} || Нов тригер || Автоматска пресметка на дистанца при INSERT/UPDATE || || {{{FindNearestAirport(lon, lat, km)}}} || Нова функција || Враќа аеродроми во даден радиус, сортирани по близина || || {{{FlightAtTime(id, time)}}} || Нова функција || Пресметува локација на лет во даден момент || ---- == 13. Заклучок == Интеграцијата на PostGIS во проектот SBAirlines не претставува површно додавање на функционалност, туку природна еволуција на моделот кон вистинскиот свет на авиокомпанијата. Со неа: * '''Аеродромите имаат реални координати''', а не само имиња и кодови. * '''Растојанијата на летовите се вистински''', што влијае врз постоечките компоненти (миљни поени, оптимизација на флота, оперативни извештаи). * '''Се отвораат нови случаи на употреба''' кои се невозможни во чист релациски модел (наоѓање најблиски аеродроми, визуализација на мрежа, следење на локација). * '''Се демонстрира просторно индексирање''' — нов вид индекс кој не беше покриен во основните фази на проектот. * '''Базата на податоци станува извор на вистина''' за визуелни алатки како QGIS, кои директно ги читаат просторните слоеви. PostGIS ја претвора нашата база од „евиденција за авиокомпанија" во „геопросторна оперативна платформа" — токму она што го користат вистинските авиокомпании во светот.