INTERLIS leicht gemacht #64 - Modellwechsel auf Knopfdruck
2026-07-09
Ich weiss nicht mehr recht, was zuerst da war: das Huhn oder das Ei. In diesem Kontext heisst das: der Wunsch ein Werkzeug zu haben mit dem ich relativ einfach Daten von einem Modell in ein anderes Modell umwandeln kann oder das konkrete Bedürfnis Daten vom DM01 in das neue DMAV (und umgekehrt) zu transformieren. Letzteres hatte ich schon ein paar mal versucht aber ich bin nie wirklich glücklich geworden. Einerseits weil es ohne PostgreSQL/PostGIS nicht smooth ging (h2gis unterstützt keine Kreisbogen, GeoPackage mangelt es an GIS-Funktionalität) und andererseits weil schon nur das Herstellen irgendeiner möglichen Konfiguration des Mappings einen Heidenaufwand bedeuten würde.
Es gibt den Ansatz Daten im minimalen Geodatenmodell in das ÖREB-Rahmenmodell (Transferstruktur) mittels XSLT zu transformieren. Der Vorteil hier ist, dass man - solange die Geometrien nicht angefasst werden - die physische Datenstruktur nicht verändert: Die Daten werden in eine interne Datenstruktur (XDM) gelesen, transformiert und serialisiert. Damit tauchen keine Fragen auf, ob ein Zwischensystem Kreisbogen oder ähnliches unterstützt, es bleibt im Prinzip immer XML. Die Idee ist gut, mir gefiel aber - auch als sehr grosser und treuer Freund von XSLT - die Umsetzung nicht wirklich. Irgendwie fand ich, dass das noch besser geht.
Wenn unsere liebgewonnen INTERLIS-Werkzeuge Transferdateien lesen oder schreiben, machen sie das mit einer IoxReader- oder IoxWriter-Klasse. Jedes INTERLIS-Objekt landet beim Lesen in einem IomObject. Dieses kann von einem IoxWriter wieder geschrieben werden (klein wenig vereinfacht ausgedrückt). Der Clou an der ganzen Sache ist folgender: Man kann für verschiedene Formate einen IoxReader und/oder IoxWriter implementieren. Macht man das, bekommt man z.B. einen Validator für dieses Format praktisch (auf Basis ilivalidator) geschenkt. Wir machen das so in GRETL für Shapefiles, GeoPackage und JSON. Für unseren ilitransformer heisst das, dass wir für die Herstellung von XTF-Dateien als Input auch z.B. Shapefiles anzapfen können.
Die Grundidee war wie immer sehr einfach: Bisschen YAML-Konfiguration mit Input- und Output-Datei, bisschen Mapping zwischen Klassen und sicher noch ein paar Funktionen (z.B. Substring und ähnliches). Ja, so war das angedacht aber natürlich war es schon ein wenig mehr, um wirklich ernsthaft damit arbeiten zu können. Anstelle von YAML habe ich mich unterwegs für eine DSL (Domain Specific Language) entschieden.
Starten wir mit einem trivialen Beispiel. Gegeben ist folgendes Datenmodell:
INTERLIS 2.4;
MODEL HelloDemo (en)
AT "https://transformer.interlis.guru" VERSION "2026-06-22" =
TOPIC Persons =
BASKET OID AS INTERLIS.UUIDOID;
OID AS INTERLIS.UUIDOID;
CLASS Source =
Name: MANDATORY TEXT*60;
Anzahl: 0 .. 9999;
Aktiv: BOOLEAN;
END Source;
CLASS Target =
Label: MANDATORY TEXT*60;
LabelGross: TEXT*60;
Anzahl: 0 .. 9999;
Aktiv: BOOLEAN;
END Target;
END Persons;
END HelloDemo.
Ich denke, man erkennt es bereits an den Klassennamen, was wir vorhaben. Wir haben eine Transferdatei mit Objekten der Klasse Source. Diese wollen wir umwandeln in Objekte der Klasse Target, wobei aus Name das Attribut Label wird und zusätzlich gibt es noch das Attribut LabelGross, das den Namen in Grossbuchstaben beinhalten soll.
Die Input-Datei:
<?xml version="1.0" encoding="UTF-8"?>
<ili:transfer
xmlns:ili="http://www.interlis.ch/xtf/2.4/INTERLIS"
xmlns:HelloDemo="http://www.interlis.ch/xtf/2.4/HelloDemo">
<ili:headersection>
<ili:models>
<ili:model>HelloDemo</ili:model>
</ili:models>
<ili:sender>demo</ili:sender>
</ili:headersection>
<ili:datasection>
<HelloDemo:Persons ili:bid="00000000-0000-0000-0000-0000000000b1">
<HelloDemo:Source ili:tid="00000000-0000-0000-0000-000000000001">
<HelloDemo:Name> Alice Mueller </HelloDemo:Name>
<HelloDemo:Anzahl>42</HelloDemo:Anzahl>
<HelloDemo:Aktiv>true</HelloDemo:Aktiv>
</HelloDemo:Source>
<HelloDemo:Source ili:tid="00000000-0000-0000-0000-000000000002">
<HelloDemo:Name>Bob Meier</HelloDemo:Name>
<HelloDemo:Anzahl>7</HelloDemo:Anzahl>
<HelloDemo:Aktiv>false</HelloDemo:Aktiv>
</HelloDemo:Source>
</HelloDemo:Persons>
</ili:datasection>
</ili:transfer>
Meine Transformations-Konfiguration sieht wie folgt aus:
mapping v2 "demo-01-hello-copy" {
job {
description "1:1-Kopie mit skalaren Attributen und einfachen String-Expressions";
modeldir "models/";
}
input src {
path "data/input.xtf";
model "HelloDemo";
}
output tgt {
path "output.xtf";
model "HelloDemo";
}
oid {
strategy uuid;
}
rule copy {
target tgt class "HelloDemo.Persons.Target";
source s from src class "HelloDemo.Persons.Source";
assign {
Label = trim(s.Name);
LabelGross = upper(trim(s.Name));
Anzahl = s.Anzahl;
Aktiv = s.Aktiv;
}
}
}
Bis und mit Zeile 15 bedarf es wohl keiner Erklärung. Spannend wird es aber auf Zeile 17. Der oid-Block bedeutet, dass für jedes Target-Objekt eine neue zufällige UUID erzeugt und verwendet wird. Es gibt neben uuid noch andere Strategien. Weiter geht es mit den Zeilen 21ff: Es wird eine Regel definiert. Die Regel schreibt ein Objekt der Klasse «HelloDemo.Persons.Target» in das Zielmodell, das über den Alias tgt angesprochen wird. Bei source funktioniert es ähnlich: Dort wird festgelegt, aus welcher Quellklasse die Objekte gelesen werden. Da ein Mapping mehrere Eingaben haben kann, braucht die source-Deklaration eine zusätzliche Ebene: Sie benennt nicht nur die Klasse, sondern auch den konkreten Input, hier src (Ich bin mir noch nicht sicher, ob mir die DSL hier wirklich gefällt oder ob ich sie noch ändern werde.) Im assign-Block werden den Target-Objekt-Attributen die Werte zugewiesen. Es stehen verschiedene Funktionen zur Verfügung, um die Input-Werte zu verändern.
Vor der Transformation der Daten können wir die .ilimap-Datei validieren:
./bin/ilitransformer validate-mapping \
--mapping=./demo/01-hello-copy/profile.ilimap
Achtung: Es ist bis jetzt eine reine Syntax-Prüfung. Es wird nicht überprüft, ob z.B. die verwendeten INTERLIS-Klassen existieren.
Die Transformation macht man mit diesem Aufruf:
./bin/ilitransformer transform \
--mapping=./demo/01-hello-copy/profile.ilimap \
--modeldir=./demo/01-hello-copy/models
Die --modeldir-Option wird benötigt, weil das INTERLIS-Datenmodell nicht in einem Standard-Repository ist, sondern nur lokal vorliegt. Funktioniert die Transformation, erscheint eine kleine Statistik in der Konsole:
Transform summary: 2 source records read, 0 filtered, 2 targets created, 2 written (0 errors, 0 warnings) [OID: UUID, Basket: PRESERVE]
Metrics: 2 read, 0 filtered, 2 targets created, 2 written, 0 joins, 0 bags, 2 rule matches in 11 ms
Output committed successfully.
Die Output-Datei sieht tiptop aus:
<?xml version="1.0" encoding="UTF-8"?>
<ili:transfer
xmlns:ili="http://www.interlis.ch/xtf/2.4/INTERLIS"
xmlns:geom="http://www.interlis.ch/geometry/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:HelloDemo="http://www.interlis.ch/xtf/2.4/HelloDemo">
<ili:headersection>
<ili:models>
<ili:model>HelloDemo</ili:model>
</ili:models>
<ili:sender>ilitransformer-0.1.0-SNAPSHOT-63bb61806353b6b7d5101c5f4b6c76e8889d3885</ili:sender>
</ili:headersection>
<ili:datasection>
<HelloDemo:Persons ili:bid="00000000-0000-0000-0000-0000000000b1">
<HelloDemo:Target ili:tid="227c5401-4e99-49b3-b410-c0167e4383d5">
<HelloDemo:Label>Alice Mueller</HelloDemo:Label>
<HelloDemo:LabelGross>ALICE MUELLER</HelloDemo:LabelGross>
<HelloDemo:Anzahl>42</HelloDemo:Anzahl>
<HelloDemo:Aktiv>true</HelloDemo:Aktiv>
</HelloDemo:Target>
<HelloDemo:Target ili:tid="dafc7063-e456-44da-8356-6fe32da41b3f">
<HelloDemo:Label>Bob Meier</HelloDemo:Label>
<HelloDemo:LabelGross>BOB MEIER</HelloDemo:LabelGross>
<HelloDemo:Anzahl>7</HelloDemo:Anzahl>
<HelloDemo:Aktiv>false</HelloDemo:Aktiv>
</HelloDemo:Target>
</HelloDemo:Persons>
</ili:datasection>
</ili:transfer>
Man beachte auch das Wirken der Funktionen trim() und upper().
Die ilimap-Konfiguration kann schnell umfangreicher werden, wenn wir nicht bloss eine 1:1-Transformation machen. Das und weitere spannende Features zu einem späteren Zeitpunkt. Eine Snapshot-Version von ilitransformer könnt ihr hier herunterladen. Die automatisierte Referenzdokumentation — die definitiv noch bereinigt werden muss — findet man hier. Oder schaut euch die Projektwebseite an.