AdvancedTopics: gis_dokumentacija.sql

File gis_dokumentacija.sql, 11.3 KB (added by 231102, 11 hours ago)
Line 
1-- ============================================================================
2-- ГИС (просторни податоци) — дефиниција на просторниот слој
3--
4-- Овој фајл го документира географско-информацискиот (ГИС) слој на системот,
5-- имплементиран со PostGIS врз табелата or_mapa_objekt. Опфаќа: енум за
6-- геометриски тип и просторните функции,
7-- тригери и процедура што ја носат бизнис логиката над просторот.
8--
9-- Дизајнерски начела:
10-- * Геометријата се чува како PostGIS geometry(Geometry, 4326) — WGS84
11-- (географска должина/ширина), а не како текст.
12-- * Геомриетскиот тип не се чува во посебен шифрарник — PostGIS веќе го знае
13-- преку GeometryType(); изложен е како изведен енум mapa_geom_tip.
14-- * Должините и површините се мерат во метри преку кастирање во geography.
15-- * Хиерархијата кампус → зграда → кат → просторија е и логичка (parent FK)
16-- и просторна (геометриска содржаност, гарантирана со тригери).
17-- ============================================================================
18
19
20-- Екстензија PostGIS (предуслов за просторните типови и функции).
21CREATE EXTENSION IF NOT EXISTS postgis;
22
23
24-- ============================================================================
25-- 1. Енум за геометриски тип
26--
27-- Лабелите се идентични со резултатот на GeometryType() (голема буква, без
28-- префикс ST_), што овозможува директен каст од геометрија во енум.
29-- ============================================================================
30CREATE TYPE mapa_geom_tip AS ENUM ('POINT', 'LINESTRING', 'POLYGON', 'MULTIPOLYGON');
31
32
33-- ============================================================================
34-- 2. Просторна табела or_mapa_objekt
35--
36-- Секој ред е просторен објект (кампус, зграда, кат, просторија, точка на
37-- интерес). Хиерархијата се гради преку самореференцата parent_mapa_objekt_id.
38-- ============================================================================
39CREATE TABLE or_mapa_objekt (
40 id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
41 geom geometry(Geometry, 4326) NOT NULL, -- отпечаток (полигон) или локација (точка) во WGS84
42 geom_tip mapa_geom_tip, -- изведен од geom (се поставува со тригер)
43 kapacitet integer, -- капацитет на простории, пресметан од површина
44 parent_mapa_objekt_id integer REFERENCES or_mapa_objekt(id) -- родител во просторната хиерархија
45);
46
47-- ============================================================================
48-- 3. Функции
49--
50-- Функциите враќаат вредност и се вградуваат директно во прашалник, поглед или
51-- друга рутина.
52-- ============================================================================
53
54-- 3.1 fn_povrshina_m2 — површина на објект во квадратни метри.
55-- Геометријата се кастира во geography бидејќи SRID 4326 е во степени;
56-- ST_Area врз чиста geometry би вратил степени², а не метри. За објекти без
57-- површина (POINT, LINESTRING) враќа 0.
58CREATE OR REPLACE FUNCTION fn_povrshina_m2(p_mapa_objekt_id integer)
59RETURNS numeric
60LANGUAGE sql
61STABLE
62AS $$
63 SELECT ROUND(ST_Area(geom::geography)::numeric, 2)
64 FROM or_mapa_objekt
65 WHERE id = p_mapa_objekt_id;
66$$;
67
68-- 3.2 fn_najblisku_objekti — k најблиски објекти до дадена точка (KNN),
69-- опционо филтрирани по геометриски тип. Подредувањето користи GiST преку
70-- операторот <-> врз geometry (индексирано), а прикажаното растојание е во
71-- точни метри преку geography.
72CREATE OR REPLACE FUNCTION fn_najblisku_objekti(
73 p_lon double precision,
74 p_lat double precision,
75 p_k integer DEFAULT 5,
76 p_geom_tip mapa_geom_tip DEFAULT NULL
77)
78RETURNS TABLE (mapa_objekt_id integer, tip mapa_geom_tip, rastojanie_m numeric)
79LANGUAGE sql
80STABLE
81AS $$
82 SELECT o.id,
83 o.geom_tip,
84 ROUND(ST_Distance(
85 o.geom::geography,
86 ST_SetSRID(ST_MakePoint(p_lon, p_lat), 4326)::geography
87 )::numeric, 2)
88 FROM or_mapa_objekt o
89 WHERE p_geom_tip IS NULL OR o.geom_tip = p_geom_tip
90 ORDER BY o.geom <-> ST_SetSRID(ST_MakePoint(p_lon, p_lat), 4326)
91 LIMIT p_k;
92$$;
93
94
95-- ============================================================================
96-- 4. Тригери
97--
98-- Тригерите гарантираат дека просторното правило важи без оглед кој пишува во
99-- табелата — апликација, процедура или рачен INSERT.
100-- ============================================================================
101
102-- 4.1 trg_postavi_geom_tip — го изведува енумот geom_tip од самата геометрија
103-- при секој внес/измена на geom, со што geom_tip секогаш е во синхрон со
104-- податокот. (Изведениот тип не може да биде GENERATED колона бидејќи изразот
105-- GeometryType()::enum не е IMMUTABLE.) Неподдржан геометриски тип паѓа на
106-- кастот и внесот е одбиен.
107CREATE OR REPLACE FUNCTION trgf_postavi_geom_tip()
108RETURNS trigger
109LANGUAGE plpgsql
110AS $$
111BEGIN
112 NEW.geom_tip := GeometryType(NEW.geom)::mapa_geom_tip;
113 RETURN NEW;
114END;
115$$;
116
117DROP TRIGGER IF EXISTS trg_postavi_geom_tip ON or_mapa_objekt;
118CREATE TRIGGER trg_postavi_geom_tip
119 BEFORE INSERT OR UPDATE OF geom ON or_mapa_objekt
120 FOR EACH ROW
121 EXECUTE FUNCTION trgf_postavi_geom_tip();
122
123
124-- 4.2 trg_geom_vo_roditel — геометријата на детето мора да лежи во родителот
125-- (ST_CoveredBy). Реализирано како тригер, а не CHECK, бидејќи правилото гледа
126-- во друг ред (родителот). Се користи ST_CoveredBy наместо ST_Within за да се
127-- дозволи допир по раб (нпр. просторија до надворешниот ѕид на катот).
128CREATE OR REPLACE FUNCTION trgf_geom_vo_roditel()
129RETURNS trigger
130LANGUAGE plpgsql
131AS $$
132DECLARE
133 v_parent geometry;
134BEGIN
135 IF NEW.parent_mapa_objekt_id IS NULL THEN
136 RETURN NEW; -- врвен објект (кампус) — нема родител
137 END IF;
138
139 SELECT geom INTO v_parent
140 FROM or_mapa_objekt
141 WHERE id = NEW.parent_mapa_objekt_id;
142
143 IF v_parent IS NULL THEN
144 RETURN NEW;
145 END IF;
146
147 IF NOT ST_CoveredBy(NEW.geom, v_parent) THEN
148 RAISE EXCEPTION 'Геометријата мора да лежи во родителскиот објект % (ST_CoveredBy = false)',
149 NEW.parent_mapa_objekt_id;
150 END IF;
151
152 RETURN NEW;
153END;
154$$;
155
156DROP TRIGGER IF EXISTS trg_geom_vo_roditel ON or_mapa_objekt;
157CREATE TRIGGER trg_geom_vo_roditel
158 BEFORE INSERT OR UPDATE OF geom, parent_mapa_objekt_id ON or_mapa_objekt
159 FOR EACH ROW
160 EXECUTE FUNCTION trgf_geom_vo_roditel();
161
162
163-- 4.3 trg_bez_preklopuvanje — два полигони под ист родител не смеат да се
164-- преклопуваат. Комбинацијата ST_Intersects AND NOT ST_Touches го издвојува
165-- вистинското преклопување на внатрешности од дозволениот случај кога две
166-- соседни простории делат само ѕид (раб). Точките и линиите не се проверуваат.
167CREATE OR REPLACE FUNCTION trgf_bez_preklopuvanje()
168RETURNS trigger
169LANGUAGE plpgsql
170AS $$
171BEGIN
172 IF NEW.parent_mapa_objekt_id IS NULL THEN
173 RETURN NEW;
174 END IF;
175
176 IF GeometryType(NEW.geom) NOT IN ('POLYGON', 'MULTIPOLYGON') THEN
177 RETURN NEW;
178 END IF;
179
180 IF EXISTS (
181 SELECT 1
182 FROM or_mapa_objekt o
183 WHERE o.parent_mapa_objekt_id = NEW.parent_mapa_objekt_id
184 AND o.id <> NEW.id
185 AND GeometryType(o.geom) IN ('POLYGON', 'MULTIPOLYGON')
186 AND ST_Intersects(o.geom, NEW.geom)
187 AND NOT ST_Touches(o.geom, NEW.geom)
188 ) THEN
189 RAISE EXCEPTION 'Полигонот се преклопува со постоечки соседен објект под родителот %',
190 NEW.parent_mapa_objekt_id;
191 END IF;
192
193 RETURN NEW;
194END;
195$$;
196
197DROP TRIGGER IF EXISTS trg_bez_preklopuvanje ON or_mapa_objekt;
198CREATE TRIGGER trg_bez_preklopuvanje
199 BEFORE INSERT OR UPDATE OF geom, parent_mapa_objekt_id ON or_mapa_objekt
200 FOR EACH ROW
201 EXECUTE FUNCTION trgf_bez_preklopuvanje();
202
203
204-- ============================================================================
205-- 5. Процедура
206--
207-- pr_dodeli_kapacitet_od_povrshina — за сите „лист" полигони (простории без
208-- подобјекти) поставува kapacitet = floor(површина_м² / m²_по_студент).
209-- Реализирана е како процедура бидејќи е batch DML операција врз повеќе редови
210-- без повратна вредност за прашалник. Капацитет се пресметува само за крајните
211-- простории, не за катови/згради што ги содржат.
212-- ============================================================================
213CREATE OR REPLACE PROCEDURE pr_dodeli_kapacitet_od_povrshina(p_m2_po_student numeric)
214LANGUAGE plpgsql
215AS $$
216DECLARE
217 r RECORD;
218 v_count integer := 0;
219BEGIN
220 IF p_m2_po_student <= 0 THEN
221 RAISE EXCEPTION 'м² по студент мора да биде позитивно (добиено: %)', p_m2_po_student;
222 END IF;
223
224 FOR r IN
225 SELECT o.id, ST_Area(o.geom::geography) AS povrsina
226 FROM or_mapa_objekt o
227 WHERE GeometryType(o.geom) IN ('POLYGON', 'MULTIPOLYGON')
228 AND NOT EXISTS (SELECT 1 FROM or_mapa_objekt c
229 WHERE c.parent_mapa_objekt_id = o.id)
230 LOOP
231 UPDATE or_mapa_objekt
232 SET kapacitet = floor(r.povrsina / p_m2_po_student)
233 WHERE id = r.id;
234 v_count := v_count + 1;
235 END LOOP;
236
237 RAISE NOTICE 'Поставен капацитет за % простории.', v_count;
238END;
239$$;