Heute soll es darum gehen, Geometrien, die nicht Simple Feature-konform sind, soweit möglich zu korrigieren.
Für eine solche Korrektur steht seit 11gR1 die Funktion RECTIFY_GEOMETRY im Package SDO_UTIL bereit, welche die korrigierte (rektifizierte) Geometrie zurückliefert.
Ein Blick in die Oracle Spatial Online-Dokumentation verrät dabei, welche Fehler korrigierbar sind:
- doppelte Stützpunkte (bezogen auf die Toleranz)
- sich selbst schneidende Polygone
- falsche Orientierung von innerem oder/und äusseren Ring(en) eines Polygons
Dort findet sich ebenso das Format beschrieben.
SDO_UTIL.RECTIFY_GEOMETRY( geom IN SDO_GEOMETRY, tolerance IN NUMBER ) RETURN SDO_GEOMETRY;
Wie kann man nun diese Funktion in Anwendung bringen? Schauen wir uns das einfach mal genauer an.
Zunächst wird eine Tabelle angelegt, in welcher alle fehlerhaften Geometrien registriert werden sollen.
drop table geometry_errors purge; -- Fehlertabelle anlegen create table geometry_errors ( table_name varchar2(30), column_name varchar2(30), obj_rowid rowid, geometry sdo_geometry, tolerance number, error_code char(5), error_message varchar2(256), error_context varchar2(256), is_fixed number(1) default 0 );
Die eigentliche Prozedur zum Überprüfen der Geometrien, kann dann wie nachfolgend aussehen. Die problematischen Geometrien darunter werden in die Fehlertabelle übernommen.
-- Prozedur zum Prüfen auf invalide Geometrien im gesamten Schema
-- Achtung: Kann einige Zeit in Anspruch nehmen!
declare
DEFAULT_TOLERANCE number := 0.0000005;
COMMIT_FREQUENCY number := 100;
geom_cursor sys_refcursor;
v_diminfo sdo_dim_array;
v_srid number;
v_tolerance number;
v_rowid rowid;
v_geometry sdo_geometry;
v_num_rows number;
v_num_errors number;
v_error_code char(5);
v_error_message varchar2(256);
v_error_context varchar2(256);
v_status varchar2(256);
begin
-- Alle Tabellen mit SDO_GEOMETRY Spalten prozessieren
for t in (
select table_name, column_name
from user_tab_columns
where data_type = 'SDO_GEOMETRY'
order by table_name, column_name )
loop
-- Lies Toleranz aus den Metadaten
begin
select diminfo, srid into v_diminfo, v_srid
from user_sdo_geom_metadata
where table_name = t.table_name
and column_name = t.column_name;
exception
when no_data_found then
v_diminfo := null;
v_srid := null;
end;
-- Bei fehlenden Metadaten, Default-Toleranz annehmen
if v_diminfo is null then
v_tolerance := DEFAULT_TOLERANCE;
else
v_tolerance := v_diminfo(1).sdo_tolerance;
end if;
-- Geometrien prozessieren
v_num_rows := 0;
v_num_errors := 0;
open geom_cursor for
'select rowid,' || t.column_name || ' from ' || t.table_name;
loop
v_status := NULL;
-- Hole Geometrie
fetch geom_cursor into v_rowid, v_geometry;
exit when geom_cursor%notfound;
v_num_rows := v_num_rows + 1;
-- Validiere Geometry
v_status := sdo_geom.validate_geometry_with_context (v_geometry, v_tolerance);
-- Wenn Fehler, dann protokollieren
if v_status <> 'TRUE' then
-- Fehler hochzählen
v_num_errors := v_num_errors + 1;
-- Format the error message
if length(v_status) >= 5 then
v_error_code := substr(v_status, 1, 5);
v_error_message := sqlerrm(-v_error_code);
v_error_context := substr(v_status,7);
else
v_error_code := v_status;
v_error_message := null;
v_error_context := null;
end if;
-- Fehler wegschreiben
insert into geometry_errors (
table_name,
column_name,
obj_rowid,
geometry,
tolerance,
error_code,
error_message,
error_context
)
values (
t.table_name,
t.column_name,
v_rowid,
v_geometry,
v_tolerance,
v_error_code,
v_error_message,
v_error_context
);
end if;
-- Commit entsprechend COMMIT_FREQUENCY
if mod(v_num_rows,COMMIT_FREQUENCY) = 0 then
commit;
end if;
end loop;
end loop;
-- Finales Commit
commit;
end;
/
Zu beachten ist dabei, dass das Ausführen der Prozedur eine Weile in Anspruch nehmen kann, je nach Anzahl der zu überprüfenden Geometrien.
Bevor es an die eigentliche Korrektur geht, schauen wir uns zunächst die Fehlertabelle genauer an.
-- Fehler pro Tabelle, Error Code select table_name, error_code, error_message, count(*) from geometry_errors group by table_name, error_code, error_message order by table_name, error_code; -- Fehler pro Error Code, Tabelle select error_code, error_message, table_name, count(*) from geometry_errors group by error_code, error_message, table_name order by error_code, table_name; -- Fehlerdetails select table_name, obj_rowid, is_fixed, error_message, error_context from geometry_errors where is_fixed = 0 order by table_name, obj_rowid;
Für die Korrektur wird nun die Funktion SDO_UTIL.RECTIFY_GEOMETRY eingesetzt.
-- Prozedur zum Korrigieren der Geometrien
declare
-- Fehlerbehandlung für nicht korrigierbare Geometrien
-- "ORA-13199: the given geometry cannot be rectified"
cannot_rectify exception;
pragma exception_init(cannot_rectify, -13199);
v_geometry_fixed sdo_geometry;
begin
-- Invalide Geometrien prozessieren
for e in (
select rowid, table_name, column_name, obj_rowid, tolerance, geometry
from geometry_errors
where is_fixed = 0
order by table_name, column_name
)
loop
-- Wenn möglich, Geometrie korrigieren mit SDO_UTIL.RECTIFY_GEOMETRY.
-- Andernfalls:
-- In 11.1.0.6: Funktion gibt NULL zurück
-- In 11.1.0.7 und 11.2: Funktion wirft ORA-13199 Fehler
begin
v_geometry_fixed := sdo_util.rectify_geometry (e.geometry, e.tolerance);
exception
when cannot_rectify then
v_geometry_fixed := null;
end;
if v_geometry_fixed is not null then
-- Korrigiere Geometrie
execute immediate 'update ' || e.table_name || ' set '|| e.column_name || ' = :g where rowid = :r'
using v_geometry_fixed, e.obj_rowid;
-- Kennzeichne korrigierte Geometrien in Fehlertabelle mit is_fixed = 1
update geometry_errors set is_fixed = 1 where rowid = e.rowid;
-- Alternativ ist auch DELETE möglich
-- delete from geometry_error where rowid = e.rowid;
end if;
end loop;
end;
/
Was wurde korrgiert? Was nicht? Das Ergebnis kann am Ende in der Fehlertabelle überprüft werden.
-- Fehler nach Tabelle, Korrekturstatus und Error Code select table_name, is_fixed, error_code, error_message, count(*) from geometry_errors group by table_name, is_fixed, error_code, error_message order by table_name, is_fixed, error_code; -- Fehler nach Korrekturstatus und Error Code select is_fixed, error_code, error_message, count(*) from geometry_errors group by is_fixed, error_code, error_message order by is_fixed, error_code; -- Details select table_name, obj_rowid, is_fixed, error_message, error_context from geometry_errors order by is_fixed, table_name, obj_rowid;
Möglicherweise konnten nicht alle fehlerhaften Geometrien auf diese Weise korrigiert werden, aber ein erster Schritt in Richtung Qualitätsverbesserung ist getan.
Wer die Fehlertabelle nach erfolgter Korrektur nicht mehr benötigt, sollte übrigens nicht vergessen, diese wieder zu entfernen.
Wer die Fehlertabelle nach erfolgter Korrektur nicht mehr benötigt, sollte übrigens nicht vergessen, diese wieder zu entfernen.
drop table geometry_errors purge;