<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>sogeo.services -- Blog</title>
    <link>http://blog.sogeo.services</link>
    <atom:link href="http://blog.sogeo.services/feed.xml" rel="self" type="application/rss+xml" />
    <description>INTERLIS and more</description>
    <language>en-gb</language>
    <pubDate>Wed, 1 Apr 2026 12:20:55 +0000</pubDate>
    <lastBuildDate>Wed, 1 Apr 2026 12:20:55 +0000</lastBuildDate>

    <item>
      <title>Let&apos;s Hop #6 - Pixel für Pixel</title>
      <link>http://blog.sogeo.services/blog/2026/03/30/lets-hop-06.html</link>
      <pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/30/lets-hop-06.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wäre ein Spatial-ETL ohne Rasterunterstützung? Wir haben die &lt;a href=&quot;https://github.com/edigonzales/gdal-java-bindings&quot;&gt;&lt;em&gt;gdal-java-bindings&lt;/em&gt;&lt;/a&gt; und das &lt;a href=&quot;https://github.com/edigonzales/hop-gdal-plugin/&quot;&gt;&lt;em&gt;hop-gdal-plugin&lt;/em&gt;&lt;/a&gt;, das bis jetzt &amp;laquo;nur&amp;raquo; einen OGR-Input- und -Output-Transform bereitstellt. Wenn wir Rasterprocessing in &lt;em&gt;Apache Hop&lt;/em&gt; wollen, müssen wir uns überlegen, wie wir das strukturieren. Ich habe mich entschieden, mich an der Struktur der &lt;a href=&quot;https://gdal.org/en/stable/programs/gdal_raster.html&quot;&gt;neuen GDAL-Tools&lt;/a&gt; zu orientieren. D.h. nicht mehr ein &lt;code&gt;gdal_translate&lt;/code&gt; und &lt;code&gt;gdalwarp&lt;/code&gt;, die fast alles können und vor Optionen nur so strotzen, sondern &amp;laquo;do one thing and do it well&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Et voilà, neu gibt es im &lt;em&gt;hop-gdal-plugin&lt;/em&gt; folgende Raster-Transforms:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Beispiel will ich für unser Bürogebäude die Höhe berechnen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes lese ich mit einem OGR-Input-Transform die Gebäude eines bestimmten Ausschnittes aus einer GeoPackage-Datei. Gleichzeitig clippe ich mit dem Raster Clip Transform von einer extern gehosteten Cloud Optimized GeoTIFF-Datei (mit den Gebäudehöhen aus LiDAR-Daten) den gleichen Ausschnitt und speichere die Datei temporär:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil der Raster Clip Transform nur eine einzige Row zurückliefert, können wir mit dem Stream aus dem OGR-Input einfach das kartesische Produkt bilden, damit erhält jede Row des OGR-Inputs auch den Pfad der temporären, geclippten Rasterdatei. Anschliessend können wir diesen Stream mit dem Raster Zonal Stats Transform verbinden. Das GUI dieses Transforms (und auch anderer) ist noch verbesserungswürdig und irgendwie verwirrend (was ist hier was?). Erstes weil einfach schlecht strukturiert und zweites hat es wirklich viele Optionen und Möglichkeiten, weil vieles auch aus seinem Field eines Streams übernommen werden kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster05.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es fehlt in &lt;a href=&quot;https://eclipse.dev/eclipse/swt/&quot;&gt;&lt;em&gt;SWT&lt;/em&gt;&lt;/a&gt; so etwas wie der &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details&quot;&gt;&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;&lt;/a&gt;-Tag in HTML. Aber hey, es geht noch schlimmer und ich denke da an das Leuchturmprojekt (Zeit- und Leistungserfassung) der &amp;laquo;digitalen Transformation&amp;raquo; im Kanton:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/zemas.png&quot; alt=&quot;Zemas macht nicht so Spass&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich meine, wer um Herrgottswillen kommt auf eine solche Idee?! Aber egal, weiter mit &lt;em&gt;Apache Hop&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In diesem Transform sind alle Statistiken gemäss dem &lt;a href=&quot;https://gdal.org/en/stable/programs/gdal_raster_zonal_stats.html&quot;&gt;GDAL-Rastertool möglich&lt;/a&gt;. Wir wählen Durchschnitt und Maximum (pro Gebäude):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster06.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Berechnungsvariante in diesem Transform ist Row-By-Row (Geometrie für Geometrie im Stream). Anschliessend entfernen wir noch ein paar Attribute aus dem Stream und speichern die Daten in einer GeoPackage-Datei mit dem OGR-Output-Transform. Das Resultat:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-06/raster07.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler. Das &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot;&gt;Komplettpaket&lt;/a&gt; wurde mit dem verbesserten GDAL-Plugin upgedatet. Und das kostet euch gar nichts. Nicht mal ein &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7439758001986719745?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7439758001986719745%2C7442848881903624192%29&amp;amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287442848881903624192%2Curn%3Ali%3Aactivity%3A7439758001986719745%29&quot;&gt;Kafi/Gipfeli&lt;/a&gt; pro Tag. Wow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Let&apos;s Hop #5 - Geometrien bearbeiten, Layer vergleichen, Coverages prüfenº</title>
      <link>http://blog.sogeo.services/blog/2026/03/17/lets-hop-05.html</link>
      <pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/17/lets-hop-05.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In diesem fünften Teil meiner kleinen Serie zu Apache-Hop-Plugins geht es um das &lt;a href=&quot;https://github.com/edigonzales/hop-geoprocessing-plugin&quot;&gt;hop-geoprocessing-plugin&lt;/a&gt;. Das Plugin bündelt Geoverarbeitung für &lt;em&gt;Apache Hop&lt;/em&gt; in fünf Transform-Familien: Geometry Operation, Spatial Predicate, Layer Overlay, Layer Aggregate und Coverage Operation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Geometry Operation:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Familie &lt;em&gt;Geometry Operation&lt;/em&gt; ist der grosse Werkzeugkasten für einzelne Geometrien. Sie arbeitet mit einem Input-Layer, umfasst 34 Operationen und verwendet das Ausführungsmodell Streaming. Das bedeutet: Es wird Zeile für Zeile gearbeitet, ohne den ganzen Layer zu puffern. Im RAM bleibt im Wesentlichen nur die aktuelle Zeile. Typische Beispiele sind Buffer, Centroid, Vereinfachung, Geometrie-Bereinigung oder ähnliche Bearbeitungsschritte auf Einzelfeatures.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Spatial Predicate:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Familie &lt;em&gt;Spatial Predicate&lt;/em&gt; arbeitet mit einem Primary Input und einem Secondary Layer. Es gibt 9 Operationen. Technisch wichtig ist: Der Secondary Layer wird zuerst vollständig eingelesen, im RAM gehalten und anschliessend mit einem STRtree auf den Envelopes indiziert. Erst danach beginnt die Verarbeitung der Primary Rows. Entsprechend ist der Secondary Layer möglichst früh zu reduzieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Layer Overlay:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Familie &lt;em&gt;Layer Overlay&lt;/em&gt; deckt klassische GIS-Fälle wie Intersection, Clip, Erase oder Identity ab, insgesamt umfasst sie 4 Operationen und arbeitet ebenfalls mit primary + secondary input, ist technisch aber blocking per layer. Im RAM liegen dabei der vollständige Secondary Layer, ein STRtree auf dem Secondary Layer und optional zusätzlich eine Union-Geometrie des Secondary Layers. Es gibt hier auch die Berechnungsvariante &lt;code&gt;FIXED_PRECISION&lt;/code&gt;, welche uns in &lt;em&gt;PostGIS&lt;/em&gt; einiges besser dünkt, als die bisherige Variante.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Layer Aggregate:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Familie &lt;em&gt;Layer Aggregate&lt;/em&gt; arbeitet mit einem Input-Layer und optionaler Gruppierung. Es gibt 4 Operationen (Dissolve- oder Collect-artige Operationen). Sie ist blocking per layer/group, also gerade nicht streaming. Stattdessen werden alle Zeilen eines Layers oder einer Gruppe gepuffert, bevor das Resultat emittiert wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Coverage Operation:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Familie &lt;em&gt;Coverage Operation&lt;/em&gt; ist für Polygon-Coverages (INTERLIS-Area lässt grüssen) gedacht, umfasst 5 Operationen und arbeitet mit einem Input-Layer und optionaler Gruppierung und ist ebenfalls blocking per layer/group. Im RAM liegt der gesamte Layer oder die aktive Gruppe, ergänzt um den Zustand, den Coverage-weite Validierung oder Simplifizierung benötigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kleines Beispiel gefällig? Machen wir gleich Nägel mit Köpfen und prüfen das Coverage &amp;laquo;Waldplan&amp;raquo; und bereinigen allfällige Fehler. Der Waldplan besteht aus 61&apos;971 Polygonen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Bearbeitung hat einiges gesehen: &lt;em&gt;GRASS GIS&lt;/em&gt;, &lt;em&gt;PostGIS&lt;/em&gt;, &lt;em&gt;FME&lt;/em&gt;, &lt;em&gt;ArcGIS&lt;/em&gt; und einen Bezugsrahmenwechsel durchgemacht. Entsprechend gibt es auch kleinere Unschönheiten wie Überlappungen und Löcher:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Meine &lt;em&gt;Apache Hop&lt;/em&gt; Pipeline sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einerseits prüfe ich den Waldplan, ob es Überlappungen gibt und gleichzeitig bereinige ich allfällige Überlappungen. Beide Resultate speichere ich in einer GeoPackage-Datei. Der Validator schreibt Linien-Geometrien (die betroffenen Linien-Segmente) und nicht einen exakten Ort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die beiden Transformer sind mehr oder weniger selbsterklärend:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing05.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat kann sich sehen lassen. Zuerst ein Beispiel eines gefundenen Fehlers (die blauen Linien) und anschliessend die bereinigten Geometrien:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-05/geoprocessing06.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ganze dauerte für beide Prozesse insgesamt 30 Sekunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler. Das &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot;&gt;Komplettpaket&lt;/a&gt; wurde mit dem geoprocessing-Plugin upgedatet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, ich habe fertig. خلاص.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oder vielleicht doch nicht ganz? Einige Ideen habe ich noch (Geometrie-Feldrechner, Python 3 Skripte einbinden, &lt;code&gt;gdal_translate&lt;/code&gt; etc. und Ideen für Hop Server), aber nun brauchts eine Pause und ich will die gröbsten Bugs ausmisten, wenn ich bei Gelegenheit &lt;em&gt;Apache Hop&lt;/em&gt; verwende und dann will ich mir auch noch was bezüglich guter Dokumentation überlegen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Let&apos;s Hop #4 - Ich möchte Teil einer Validierung sein</title>
      <link>http://blog.sogeo.services/blog/2026/03/16/lets-hop-04.html</link>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/16/lets-hop-04.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach &lt;em&gt;ili2db&lt;/em&gt; ist vor &lt;em&gt;ilivalidator&lt;/em&gt;: &lt;a href=&quot;https://github.com/edigonzales/hop-ilivalidator-plugin&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/hop-ilivalidator-plugin&lt;/a&gt;. Wahrlich nichts Grossartiges aber ohne geht es natürlich nicht. Auch hier das Beispiel, das alle XTF aus einem Verzeichnis liest und diese einzeln prüft:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem &amp;laquo;Get file names&amp;laquo; Transformer lesen wir alle INTERLIS-Transferdateien aus einem Verzeichnis:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und dann kommt bereits das ilivalidator-Plugin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der erste Tab scheint mir noch harmlos zu sein. Natürlich kann man den Dateinamen entweder als statischen Text eingeben oder aus einem Field. Im zweiten Tab wird es interessanter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit den beiden Optionen &lt;code&gt;--config&lt;/code&gt; und &lt;code&gt;--metaConfig&lt;/code&gt; kann man entweder statisch oder dynamisch oder via &lt;code&gt;ilidata:xxx&lt;/code&gt; ein &amp;laquo;Prüfprofil&amp;laquo; angeben und muss sich nicht um die vielen Optionen und Validierungsmodellen etc. kümmern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der dritte Tab beinhaltet alle möglichen Optionen, um notfalls manuell eingreifen zu können:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator05.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im letzten Tab kann man noch das Logfile und die optionalen Output-Felder bestimmen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator06.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein erfolgreicher Run sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-04/ilivalidator07.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was noch nicht implementiert ist, ist die Plugin-Unterstützung. Mich beschleicht ein Gefühl, dass es hier wieder zu Classloader-Issues kommt und nicht einfach mit dem Exponieren der Option gemacht ist. Irgendwie wird es schon gehen und muss es auch, das &lt;a href=&quot;https://www.youtube.com/watch?v=izQB2-Kmiic&amp;amp;list=RDizQB2-Kmiic&amp;amp;start_radio=1&quot;&gt;DMAV wartet&amp;#8230;&amp;#8203;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler. Das &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot;&gt;Komplettpaket&lt;/a&gt; wurde mit dem ilivalidator-Plugin upgedatet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Let&apos;s Hop #3 - A native ili2db Integration for Apache Hop</title>
      <link>http://blog.sogeo.services/blog/2026/03/13/lets-hop-03.html</link>
      <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/13/lets-hop-03.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es musste ja soweit kommen. Here it is, ein &lt;a href=&quot;https://github.com/edigonzales/hop-ili2db-plugin&quot;&gt;ili2db-Plugin&lt;/a&gt; für &lt;em&gt;Apache Hop&lt;/em&gt;. Es gibt das Plugin als Action wie auch als Transform. Korrekterweise müsste es wahrscheinlich &amp;laquo;ili2db Input&amp;raquo; heissen, da es sich momentan nur um den Import kümmert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee ist, dass es nicht für jeden Flavor ein Plugin gibt, sondern ein ili2db-Plugin, welches die unterschiedlichen Flavor beinhaltet. Wie immer scheint mir eine der Herausforderung die schiere Fülle an möglichen Usecases und die sehr hohe Anzahl an Optionen zu sein. Wie man das sinnvoll dem Benutzer bereitstellt? Schwierig. Notfalls argumentiert man (aka Ausrede) einfach mit: &amp;laquo;Profi-Software&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was das Plugin (in Teilen kann) erkläre ich am Besten mit einem Transform-Beispiel: Ich möchte alle XTF-Dateien in einem Verzeichnis mit &lt;em&gt;ili2gpkg&lt;/em&gt; in GeoPackages umwandeln. Die Pipeline sieht am Ende so aus (&amp;laquo;Text file output&amp;raquo; ist nur noch fürs Debuggen):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes lesen wir alle Dateinamen mit der Endung .xtf aus einem Verzeichnis und bilden den Stream:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In unserem Fall aus dem Verzeichnis &lt;em&gt;/Users/stefan/tmp/xtf/&lt;/em&gt;. Man darf ja nicht vergessen, nach der Auswahl des Verzeichnisses den &amp;laquo;Add&amp;raquo;-Button zu drücken. Sonst passiert nämlich nichts. Als nächstes fügen wir diesem Stream eine Spalte hinzu: Nämlich den Speicherort inkl. Dateinamen der GeoPackage-Datei, die aus jedem XTF erstellt werden soll:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Feld &lt;code&gt;Formula&lt;/code&gt; schreibt man die Expression, die es braucht, um aus dem XTF-Dateinamen den Speicherort inkl. GPKG-Dateinamen pro Row zu erzeugen. Es ist halt so ein klassischer Feldrechner (interessanterweise basierend auf &lt;em&gt;Apache POI&lt;/em&gt;. Leider fehlt sowas wie ein Extension Point, um eigene Funktionen registrieren zu können):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun kommt das Spannende. Der Import der Daten in eine GeoPackage-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db05.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ersten beiden Optionen dürften klar sein. Es stehen alle Funktionen zur Verfügung ausser - wie bereits erwähnt - Export. &lt;code&gt;Connection&lt;/code&gt; kann nur ausgewählt werden, falls man den ili2pg-Flavor wählt. Bei GeoPackage oder anderen (zukünftigen) dateibasierten Datenbanken muss man den Namen der zu erstellenden Datei angeben. Das kann entweder statisch sein oder eben aus einem Feld aus dem Stream stammen. Wir wählen das im vorangegangenen Schritt erzeugte Feld &lt;code&gt;gpkgPath&lt;/code&gt;. Ähnlich verhält es sich beim zu importierenden XTF. Auch hier kann man entweder statisch was hinschreiben oder wieder ein Feldname aus dem Stream wählen. Die XTF-Dateien müssen nicht lokal vorliegen, sondern sie können auch in einem INTERLIS-Datenrepository publiziert sein. In diesem Fall muss es einfach die ID des Datensatzes sein (&lt;code&gt;ilidata:xxxx&lt;/code&gt;). Die anderen Optionen dürften wieder klar sein. Ich habe im Haupt-Tab noch die für mich wichtigsten (sehr subjektiv) Optionen exponiert. Man hat aber im zweiten Tab Zugriff auf alle Optionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db06.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann auch mit Datasets arbeiten. Hier müsste ich noch schauen, ob das so implementiert ist, dass man es auch sinnvoll verwenden kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db07.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt hat der Transformer auch einige Output-Felder:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db08.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat eines gelungenen Pipeline-Runs sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-03/ili2db09.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler. Das &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot;&gt;Komplettpaket&lt;/a&gt; wurde mit dem ili2db-Plugin upgedatet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Let&apos;s Hop #2 - Die Idee ist gut, die Vorschau ist besser</title>
      <link>http://blog.sogeo.services/blog/2026/03/09/lets-hop-02.html</link>
      <pubDate>Mon, 9 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/09/lets-hop-02.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nachdem wir Import und Export von &lt;a href=&quot;https://blog.sogeo.services/blog/2026/03/06/lets-hop-01.html&quot;&gt;Geodatenformaten erledigt haben&lt;/a&gt;, könnte man auf die Idee kommen, dass man die Geometrien innerhalb einer Pipeline auch anschauen möchte. In &lt;em&gt;Apache Hop&lt;/em&gt; nennt sich das &lt;em&gt;Preview&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-02/preview-text.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dank des &lt;a href=&quot;https://github.com/edigonzales/hop-geometry-type-plugin/&quot;&gt;nativen Geometriedatentypes&lt;/a&gt; erscheinen die Geometrien in tabellarischer Form in EWKT. Das ist zwar schön und gut aber Geometrien will ich in der Regel nicht als blossen Text anschauen müssen, sondern tatsächlich auch sehen: Hallo &lt;a href=&quot;https://github.com/edigonzales/hop-geometry-inspector-plugin/&quot;&gt;Geometry-Inspector-Plugin&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Geometry-Inspector-Plugin bettet sich neben den bereits bestehenden Preview-Funktionen ein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-02/preview01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wählt man die Inspect Geometries Aktion, erscheint ein zweites Fenster mit verschiedenen Optionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-02/preview02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Geometry source: &lt;code&gt;Auto&lt;/code&gt; | &lt;code&gt;Output&lt;/code&gt; | &lt;code&gt;Input&lt;/code&gt;. Das ist notwendig, weil nicht jeder Transformer Output-Geometrien hat. Entsprechend müssen auch Input-Geometrien verwendet werden können.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Geometry field: Falls es mehrere Geometrieattribute hat, kann man das gewünschte wählen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sample size: Die Anzahl der Features, die gerendert werden sollen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sampling mode: &lt;code&gt;FIRST&lt;/code&gt; | &lt;code&gt;LAST&lt;/code&gt; | &lt;code&gt;RANDOM&lt;/code&gt;. Welche Features sollen verwendet werden.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich kann man &lt;em&gt;einen&lt;/em&gt; Hintergrundkarten-WMS verwenden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-02/preview03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Klickt man den Start-Button, sollte die Preview-Karte inkl. Featureinfo-Abfrage erscheinen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-02/preview04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Karte wurde mit &lt;a href=&quot;https://geotools.org/&quot;&gt;&lt;em&gt;GeoTools&lt;/em&gt;&lt;/a&gt; umgesetzt. &lt;em&gt;GeoTools&lt;/em&gt; kennt zwar einige High Level Komponenten, wie z.B. &lt;a href=&quot;https://docs.geotools.org/stable/userguide/unsupported/swing/jmapframe.html&quot;&gt;JMapFrame&lt;/a&gt;, diese sind aber unsupported und passen nicht wirklich zu einem &lt;a href=&quot;https://eclipse.dev/eclipse/swt/&quot;&gt;SWT&lt;/a&gt;-basiertem GUI wie &lt;em&gt;Apache Hop&lt;/em&gt;. Aus diesem Grund habe ich nur das eigentliche Rendering und die Kommunikation mit dem WMS von &lt;em&gt;GeoTools&lt;/em&gt; übernommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Kartenkomponente ist noch nicht allzu umfrangreich. So kann man das Aussehen der Geometrien oder das Koordinatensystem nicht ändern. Vom WMS wird jeweils das Koordinatensystem der Geometrien requestet. Ein Transformation findet aber nie statt. Ein komplettes Desktop-GIS nachbauen, will man zwar nicht, aber das eine oder andere Feature hat sicher noch Platz.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Am Schwierigsten war interessanterweise der Umgang mit dem nativen Geometrietyp. Dieser existiert zwar, aber er existiert nur als Plugin. Und soweit ich es verstanden haben, führt das zu Problemen, weil mehrere Plugins diesen Geometrietyp als Bibliothek mitliefern: Das ogr-Plugin streamt Records mit diesem Geometrietypen. Das Geometry-Inspector-Plugin erwartet einen solchen Geometrietypen zum Rendern. Weil aber die beiden Plugins in einem unterschiedlichen Classloader geladen werden, sind es eben nicht die identischen Java-Klassen. D.h. &lt;code&gt;instanceof&lt;/code&gt; funktioniert nicht so wie man sich das vorstellen würde. Würde sich aber in Wohlwollen auflösen, wenn der Geometrietyp in den Hop-Kern wandern würde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler. Das &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot;&gt;Komplettpaket&lt;/a&gt; wurde mit dem Geometry-Inspector upgedatet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Let&apos;s Hop #1 - Laying the Foundation</title>
      <link>http://blog.sogeo.services/blog/2026/03/06/lets-hop-01.html</link>
      <pubDate>Fri, 6 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/03/06/lets-hop-01.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Apache Hop&lt;/em&gt; hat vielleicht ein &lt;a href=&quot;https://www.geowebforum.ch/t/webinar-moeglichkeiten-der-enterprise-und-geo-datenintegration-mit-apache-hop-10-02-2026-11-12-uhr/1320/3&quot;&gt;halbes Momentum&lt;/a&gt;. Obwohl wir lieber &lt;a href=&quot;https://gretl.app&quot;&gt;ohne GUI&lt;/a&gt; unterwegs sind, fand ich bereits &lt;a href=&quot;https://blog.sogeo.services/blog/2014/02/09/fun-with-geokettle-episode-1.html&quot;&gt;(Geo-)Kettle&lt;/a&gt; &lt;a href=&quot;https://blog.sogeo.services/blog/2014/11/29/fun-with-geokettle-episode-2.html&quot;&gt;eine gute Sache&lt;/a&gt;. Es war schon immer eine gute Basis, um nicht mehr von FME abhängig zu sein. Hätte man vor 10 Jahren angefangen, wäre man nun 10 Jahre weiter. Lieber vergibt man aber als öffentliche Hand einen 44 Millionen Auftrag. Freihändig&amp;#8230;&amp;#8203; weil alternativlos&amp;#8230;&amp;#8203; Ich hoffe, dass das aber ein Generationenproblem ist und/oder ein geopolitisches, das sich dann so oder so von fast alleine löst.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ausser bei &lt;em&gt;GeoKettle&lt;/em&gt; war es immer das Problem wie man &amp;laquo;Geo&amp;raquo; nach &lt;em&gt;Kettle&lt;/em&gt; resp. &lt;em&gt;Apache Hop&lt;/em&gt; bringt. Bei &lt;em&gt;GeoKettle&lt;/em&gt; war das Problem, dass es ein Fork war und nicht mittels einem Plugin-Ansatz funktionierte. Klar gibt es Workarounds wie alles in &lt;em&gt;PostgreSQL/PostGIS&lt;/em&gt; oder &lt;em&gt;DuckDB&lt;/em&gt; machen. Aber dann kann man sich fragen, warum man überhaupt noch &lt;em&gt;Apache Hop&lt;/em&gt; verwenden will.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt vielleicht drei grössere Themenfelder damit man &amp;laquo;Geo&amp;raquo; in &lt;em&gt;Apache Hop&lt;/em&gt; machen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Es gibt keinen nativen Geometrie-Datentyp.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es gibt keine Import- und Exportfunktionen von GIS-Datenformaten.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es gibt keine Geoprocessing-Funktionen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.atolcd.com/&quot;&gt;Atol CD&lt;/a&gt; hat ein GIS-Plugin für &lt;em&gt;Apache Hop&lt;/em&gt; geschrieben, dass im Prinzip die drei Dinge löst: &lt;a href=&quot;https://github.com/atolcd/hop-gis-plugins&quot; class=&quot;bare&quot;&gt;https://github.com/atolcd/hop-gis-plugins&lt;/a&gt;. Ich dachte mir dann, warum muss man eigentlich die ganzen Import- und Exportfunktionen nochmals programmieren, wenn es &lt;em&gt;GDAL/OGR&lt;/em&gt; gibt? Und es gibt ein ogr2ogr-Plugin von &lt;a href=&quot;https://www.ost.ch/de/&quot;&gt;OST&lt;/a&gt;: &lt;a href=&quot;https://gitlab.ost.ch/apache-hop-plugin-sa/apache-hop-plugin-ogr2ogr&quot; class=&quot;bare&quot;&gt;https://gitlab.ost.ch/apache-hop-plugin-sa/apache-hop-plugin-ogr2ogr&lt;/a&gt;. Wenn ich es aber richtig verstehe, ist das bloss ein Wrapper um das &lt;em&gt;ogr2ogr&lt;/em&gt; CLI. Und da muss ich passen. Was bringt mir eine Java-Anwendung und damit verbunden die Einfachheit der Installation, wenn ich anschliessend noch eher non-trivial &lt;em&gt;GDAL/OGR&lt;/em&gt; installieren muss? Und wie soll das gemeinsam verpackt und bereitgestellt werden? Für mich ein No-Go.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ja, &lt;em&gt;Apache Hop&lt;/em&gt; ist Java und &lt;em&gt;GDAL/OGR&lt;/em&gt; ist in C++/C geschrieben. Ist das ein Problem? Jein. Es gibt schon seit geraumer Zeit Java-Bindings für &lt;em&gt;GDAL/OGR&lt;/em&gt;. Das ist aber ein Mega-Geknorze und so richtig unterhalten sind sie meines Erachtens auch nicht mehr. Ich habe das paar Mal probiert und es war immer kein gutes Erlebnis. Das so ein Verheiraten aber auch gut funktionieren kann, zeigt z.B. der SQlite-JDBC-Treiber. Der verwendet ebenfalls native libraries.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was nun? Java kennt seit Version 22 die &lt;a href=&quot;https://openjdk.org/jeps/454&quot;&gt;Foreign Function &amp;amp; Memory API&lt;/a&gt;. Die Java Foreign Function &amp;amp; Memory API ist eine moderne Schnittstelle, mit der Java-Programme direkt auf nativen Code ausserhalb der Java Virtual Machine zugreifen können. Sie ermöglicht es, Funktionen aus in C oder C++ geschriebenen Bibliotheken aufzurufen, ohne auf ältere, komplexere Technologien wie JNI zurückgreifen zu müssen. Ein minimales Beispiel (für macOS):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;mylib.c&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;int add(int a, int b) {
    return a + b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;C-Bibliothek kompilieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;clang -shared -o libmylib.dylib mylib.c&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Main.java&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import static java.lang.foreign.ValueLayout.*;

public class Main {
    public static void main(String[] args) throws Throwable {
        Linker linker = Linker.nativeLinker();

        // Bibliothek laden
        SymbolLookup lib = SymbolLookup.libraryLookup(&quot;libmylib.dylib&quot;, Arena.global());

        // Funktionssignatur: int add(int, int)
        MethodHandle add = linker.downcallHandle(
                lib.find(&quot;add&quot;).orElseThrow(),
                FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)
        );

        int result = (int) add.invokeExact(3, 4);
        System.out.println(&quot;Result: &quot; + result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java kompilieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;javac Main.java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java ausführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;java -Djava.library.path=. Main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;Result: 7&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;em&gt;GDAL/OGR&lt;/em&gt; ist das natürlich weitaus umfangreicher. Aber heute geht das - zumindest als PoC - eigentlich ganz mühelos: &lt;a href=&quot;https://github.com/edigonzales/gdal-java-bindings/&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/gdal-java-bindings/&lt;/a&gt;. Als erstes habe ich Bindings für ein paar CLI Tools gemacht (&lt;code&gt;gdal_translate&lt;/code&gt;, &lt;code&gt;ogro2ogr&lt;/code&gt;). Später musste ich natürlich die API aufbohren, da ich genau nicht nur z.B. einen Formatumbau machen will, sondern direkt auf einzelne Records zugreifen will, damit &lt;em&gt;Apache Hop&lt;/em&gt; auch streamen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben dem eigentlichen Herstellen der Bindings gibt es weitere Herausforderungen, die man irgendwie lösen muss. Spannend ist die Frage des Packaging. Die &lt;em&gt;GDAL/OGR&lt;/em&gt; Binaries sind relativ gross (circa 1 GB) und natürlich OS-abhängig. D.h. wenn wir die Java-Bindings (inkl. &lt;em&gt;GDAL/OGR&lt;/em&gt;) für die wahrscheinlich drei verbreitesten Betriebssysteme anbieten wollen, kommt das was zusammen. Momentan habe ich das so gelöst, dass es ein &lt;a href=&quot;https://jars.sogeo.services/snapshots/ch/so/agi/gdal-ffm-core&quot;&gt;Core-Paket&lt;/a&gt; gibt und &lt;a href=&quot;https://jars.sogeo.services/snapshots/ch/so/agi/gdal-ffm-natives/0.1.0-gdal3.11.1-SNAPSHOT&quot;&gt;je ein Paket pro OS&lt;/a&gt; mit den Binaries. Die schiere Grösse kommt weniger vom Programm selber, sondern von den Ressourcen, die &lt;em&gt;GDAL/OGR&lt;/em&gt; mitliefert (insb. Transformationsdateien). Ich habe noch eine &lt;a href=&quot;https://jars.sogeo.services/#/snapshots/ch/so/agi/gdal-ffm-natives-swiss/0.1.0-gdal3.11.1-SNAPSHOT&quot;&gt;&amp;laquo;swiss&amp;raquo;-Variante&lt;/a&gt; gemacht, die viel kleiner ist und für uns im Regelfall reichen sollte. Wenn ich die Bindings so paketiere, sind nachfolgende Arbeiten aber auch immer OS-abhängig. Für&amp;#8217;s Erste lass ich es aber so. Ganz so schlimm finde ich es nicht, da ich immer noch in der Lage bin einfach self-contained Distributionen anbieten zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes muss man das eigentliche &lt;em&gt;Apache Hop&lt;/em&gt; Plugin programmieren. Auch das geht heute halt schneller als früher: &lt;a href=&quot;https://github.com/edigonzales/hop-gdal-plugin&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/hop-gdal-plugin&lt;/a&gt;. Den nativen Geometrietyp habe ich aus dem AtolCD-Plugin extrahiert und als eigenes Plugin publiziert: &lt;a href=&quot;https://github.com/edigonzales/hop-geometry-type-plugin/&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/hop-geometry-type-plugin/&lt;/a&gt;. Das kann aber nur eine temporäre Lösung sein. Ich erachte die Integration eines solchen Geometrietyps als einen ersten wichtigen Schritt. Wenn das nicht passiert, muss wieder jeder was basteln und es führt zu Inkompatibilitäten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich &lt;em&gt;Apache Hop&lt;/em&gt; mit dem Plugin starte, habe ich neu einen &lt;code&gt;ogr-reader&lt;/code&gt; und &lt;code&gt;ogr-exporter&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-01/hop01.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &lt;code&gt;ogr-reader&lt;/code&gt; Transformer sollte mehr oder weniger intuitiv sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-01/hop02.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man könnte selbstverständlich noch andere ogr2ogr-Optionen bereits beim Import exponieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Pipelinevorschau zeigt die Records innerhalb der Tabelle der ausgewählten Geopackage-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-01/hop03.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem &lt;code&gt;ogr-exporter&lt;/code&gt;-Transformer können wir die Geodaten in anderen (allen von ogr unterstützten) Formaten speichern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/lets-hop-01/hop04.png&quot; alt=&quot;Apache Hop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So ganz alleine ergibt das hop-gdal-Plugin noch wenig Sinn. Spannend wird es später in Verbindung mit Geoprocessing-Operationen. Und was natürlich cool ist, man muss nicht mehr das Rad neu erfinden, um viele Geodatenformate in &lt;em&gt;Apache Hop&lt;/em&gt; zu unterstützen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil das Plugin aufgrund der Paketierung der Java-GDAL-Bindungs noch OS-abhängig ist, habe ich hier für macOS, Linux und Windows aktuelle &lt;em&gt;Apache Hop&lt;/em&gt; Distributionen gemacht inkl. GDAL-Plugin: &lt;a href=&quot;https://github.com/edigonzales/hop-distributions/releases&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/hop-distributions/releases&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil wir die Java FFM-API verwenden, brauchen wird mindestens Java 22. In den Build-Pipelines verwende ich Java 23. Das wäre also die Mindestversion, um &lt;em&gt;Apache Hop&lt;/em&gt; zu starten. Bei mir mache ich das so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-linenums&quot; data-lang=&quot;linenums&quot;&gt;HOP_JAVA_HOME=/Users/stefan/.sdkman/candidates/java/25.0.1-tem \
HOP_OPTIONS=&quot;--enable-native-access=ALL-UNNAMED -Xmx2048m&quot; \
./hop-gui.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probiert es aus und meldet Fehler.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #61 - INTERLIS goes AI</title>
      <link>http://blog.sogeo.services/blog/2026/01/12/interlis-leicht-gemacht-number-61.html</link>
      <pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/01/12/interlis-leicht-gemacht-number-61.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Während man noch darüber diskutiert, ob der Jurist in einem UML-Klassendiagramm selber Hand an ein INTERLIS-Datenmodell anlegen würde und so die Monate ins Land ziehen lässt, bin ich einen Schritt weiter: Warum überhaupt in einem GUI rumklicken oder INTERLIS-&amp;laquo;Code&amp;raquo; schreiben? Warum das nicht gleich der KI überlassen? Ich habe bereits im Oktober mit &lt;a href=&quot;https://blog.sogeo.services/blog/2025/10/18/interlis-leicht-gemacht-number-58.html&quot;&gt;meinem INTERLIS-MCP-Server angefangen&lt;/a&gt; und wollte ihn nun weiterentwicklen Richtung &lt;em&gt;brauchbar&lt;/em&gt;. &lt;em&gt;Brauchbar&lt;/em&gt; heisst für mich, dass er ein korrektes Datenmodell zurückliefern kann resp. das LLM fähig ist aus Snippets ein korrektes Datenmodell zu erstellen und dass er selbstverständlich die meist genutzten Anwendungsfälle abdecken kann. Letzteres bleibt noch die Achillesferse, weil die Sprache halt schon sehr umfangreich ist. Genauso wichtig für &lt;em&gt;brauchbar&lt;/em&gt; ist das Tooling drum herum. Da habe ich im Oktober eigentlich nicht viel gemacht, ausser hingekriegt, dass die Tools des MCP-Servers aufgerufen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den INTERLIS-MCP-Server gibt es als Docker Image &lt;a href=&quot;https://hub.docker.com/repository/docker/sogis/interlis-mcp/general&quot;&gt;hier&lt;/a&gt;. Der Quellcode zum Selberbauen &lt;a href=&quot;https://codeberg.org/sogis/mcp-interlis&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Meine Idee wie man damit arbeiten kann, ist immer noch folgende: Ich habe in &lt;em&gt;VS Code&lt;/em&gt; oder &lt;em&gt;Eclipse Theia&lt;/em&gt; ein INTERLS-Modell geöffnet und ein Chatfenster. Im Chatfenster kann ich mit Prosaeingaben das Modell ergänzen oder verändern wie es mir beliebt. Aber beginnen wir beim Tooling noch einen Schritt vorher. Ich habe mich immer ein wenig gefragt, was bei einem MCP-Server eigentlich rauskommt? Die &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25&quot;&gt;Spezifikation&lt;/a&gt; erklärt das zwar aber man will es sehen. Dazu gibt es den &lt;a href=&quot;https://modelcontextprotocol.io/docs/tools/inspector&quot;&gt;MCP Inspector&lt;/a&gt;. In meinem Fall rufe ich den Inspector mit diesem Befehl auf:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;npx @modelcontextprotocol/inspector java -jar interlis-mcp.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Inspector bietet z.B. die Möglichkeit sämtliche Tools des Servers aufzulisten und die dazugehörigen Input-Parameter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/mcp-inspector.png&quot; alt=&quot;mcp-inspector&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So bleibt das nicht irgendeine schwarze Magie, sondern man sieht wie Client und Server miteinander kommunizieren können. Der nächste Schritt ist das Erfassen des Servers in &lt;em&gt;VS Code&lt;/em&gt;. Das geht mit dem Kommando &lt;code&gt;MCP: Add Server &amp;#8230;&amp;#8203;&lt;/code&gt;. Da klickt man sich durch, gibt das Startkommando des MCP-Servers an (siehe Inspector) und gibt dem Server einen eindeutigen Namen. Es öffnet sich die Datei &lt;em&gt;mcp.json&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode01.png&quot; alt=&quot;vscode01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man sieht im grauen Kleingedruckten, dass der Server bereits gestartet ist und 27 Tools anbietet. Als nächstes will ich via OpenAI-API Zugriff auf verschiedene LLM haben. Das geht mit dem Befehl &lt;code&gt;Chat: Manage Language Models&lt;/code&gt;. Und hier dünkt mit das ganze VS Code Copilot Zeugs eher einschränkend. Man kann nur bestimmte Anbieter konfigurieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode02.png&quot; alt=&quot;vscode02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Konfiguration ist denkbar einfach, man muss den Provider aussuchen und den API-Key eintöggelen. Aber einen Drittanbieter (z.B. Infomaniak) mit einer OpenAI-kompatiblen API geht nicht. Modelle kann man einzeln freischalten (im Screenshot nur GPT-5-mini).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der letzte Schritt ist das Erzeugen eines Custom Agents (Befehl: &lt;code&gt;Chat: New Custom Agent &amp;#8230;&amp;#8203;&lt;/code&gt;). Es öffnet sich eine Markdown-Datei mit &lt;em&gt;&amp;lt;Namen&amp;gt;.agent.md&amp;gt;&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode03.png&quot; alt=&quot;vscode03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Header beschreibt man den Agenten und definiert auf welche Tools er Zugriff haben soll:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode04.png&quot; alt=&quot;vscode04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der restlichen Datei macht man nun dem Agenten Vorgaben was er tun soll und was nicht. Bei mir sind das &lt;a href=&quot;https://codeberg.org/sogis/mcp-interlis/src/commit/81ff7370e23a3aef8fc1cd47a675766dd6dd01d8/docs/INTERLIS.agent.md&quot;&gt;circa 330 Zeilen&lt;/a&gt; geworden, wobei ich sicher noch ein wenig ausmisten kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode05.png&quot; alt=&quot;vscode05&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Endlich ist &lt;em&gt;VS Code&lt;/em&gt; ready mir richtig beim Modellieren zu helfen. Dazu muss ich den INTERLIS-Agenten auswählen und das passende Modell:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode06.png&quot; alt=&quot;vscode06&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode07.png&quot; alt=&quot;vscode07&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Testen wir den Agenten und wollen alle INTERLIS-Text-Funktionen sehen. Er fragt zuerst nach, ob er überhaupt den MCP-Server verwendet darf. Das kann man relativ feingranular einstellen. Ich erlaube es ihm ohne ständig nachzufragen für diese Session:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode08.png&quot; alt=&quot;vscode08&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes lasse ich mir ein leeres Datenmodell erstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode10.png&quot; alt=&quot;vscode10&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Praktische an &lt;em&gt;VS Code&lt;/em&gt; ist, dass ich einen Code-Block in eine neue, zu erstellende Datei kopieren lassen kann. Noch praktischer wird es wenn ich in einem Folgebefehl eine Klasse erstellen lasse und die neue ili-Datei als Kontext mitliefere (siehe kleiner Badge &amp;laquo;MyModelA.ili&amp;raquo; unter meinem Prompt):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode11.png&quot; alt=&quot;vscode11&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Agent liefert mir ein Diff zurück, dass ich auf die bestehende ili-Datei anwenden kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode12.png&quot; alt=&quot;vscode12&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p61/vscode13.png&quot; alt=&quot;vscode13&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und wenn man im Chatfenster genau hinschaut, sieht man oberhalb den MCP Tool Call &lt;code&gt;Ran validateIliModel - interlis-mcp (MCP-Server)&lt;/code&gt;. Hier prüft der Agent das vollständige Modell auf syntaktische Korrektheit mit &lt;em&gt;ili2c&lt;/em&gt; (das ebenfalls als Tool exponiert ist, dito Pretty Print).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viel, wenn nicht fast alles, vom Verhalten des Agenten stecken in meinen circa 330 Zeilen. Unsicher bin ich, ob es sinnvoll ist, z.B. mehrere Modi anzubieten: immer ganzes Modell, nur Snippets oder Diff-Code-Blöcke. Da fehlt mir die Erfahrung wie gut man das dem Agenten beibringen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie erwähnt, ist es schade, dass man nicht die LLM von Infomaniak in Copilot nutzen kann. Es gibt aber einen Haufen an VS Code Extensions, die man ausprobieren kann. Eines davon ist z.B. &lt;em&gt;Continue&lt;/em&gt;. Der Aufbau und Konfiguration ist leicht anders und womöglich muss das Agenten-File jeweils auf das Tooling (und ggf. an die LLM-Familie) angepasst werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was gibt es noch zu tun? Einiges:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ich müsste mit einem Realweltbeispiel die wichtigsten Funktionen ergänzen (und vor allem prüfen).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wie funktionieren Agenten in &lt;em&gt;Eclipse Theia&lt;/em&gt;?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Den MCP-Server kann man auch gut in den &lt;a href=&quot;https://edigonzales.github.io/interlis-ide/&quot;&gt;besten INTERLIS-Editor&lt;/a&gt; dazupacken.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reizen würde mich noch sowas wie ein &amp;laquo;fractional compiler&amp;raquo; der einzelne INTERLIS-Snippets auf syntaktische Korrektheit prüft (ili2c kann nur ganze Datenmodelle).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #60 - INTERLIS IDE noch besser gemacht</title>
      <link>http://blog.sogeo.services/blog/2026/01/06/interlis-leicht-gemacht-number-60.html</link>
      <pubDate>Tue, 6 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2026/01/06/interlis-leicht-gemacht-number-60.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch im neuen Jahr heisst das Motto wieder &amp;laquo;INTERLIS macht Spass&amp;raquo; und es gibt auch zukünftig viel Neues und Tolles über das man berichten kann und selber ausprobieren darf.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe die Tage an &lt;a href=&quot;https://edigonzales.github.io/interlis-ide/&quot;&gt;meiner INTERLIS IDE&lt;/a&gt; gearbeitet. Dabei rausgekommen ist die Version &lt;a href=&quot;https://github.com/edigonzales/interlis-ide/releases/tag/v0.0.6&quot;&gt;0.0.6&lt;/a&gt; und als &lt;em&gt;VS Code&lt;/em&gt; Plugin die Version &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=edigonzales.interlis-editor&quot;&gt;0.0.25&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes musste ich einen &lt;a href=&quot;https://github.com/edigonzales/interlis-lsp/issues/58&quot;&gt;Bug&lt;/a&gt; fixen. Das Problem war nicht nur beim graphml-Export vorhanden, sondern bereits auch beim Mermaid- und PlantUML-Diagramm, verursacht durch Referenzen auf Objekte in anderen Datenmodellen. Das sollte nun behoben sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spannender ist aber ein neu eingebautes Feature: Ist es nicht so, dass man häufig das Modell auch abgebildet in einer Datenbank sehen möchte? Das beantwortet Fragen wie z.B. &amp;laquo;Wie wird die Vererbung abgebildet?&amp;raquo; oder &amp;laquo;Hat die JSON-Abbildung das gemacht, was ich erwarte?&amp;raquo; etc. pp. Meine Idee war nun, dass ich in der Extension einfach &lt;em&gt;ili2gpkg&lt;/em&gt; mitliefern kann (Java ist ja bereits an Bord) und so in der Extension integriere, dass der Benutzer mit möglichst wenig Klicks eine GPKG-Datei erstellen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Mitliefern ist das kleinste Problem. Mühseliger wird es bei der guten Umsetzung für die Ausführung des ili2gpkg-Kommandos. Wenn ich es richtig verstanden habe, kann man in &lt;em&gt;VS Code&lt;/em&gt; nicht einfach hübsche GUIs in die Extension packen. Sondern man ist beim Ausführen von Kommandos sehr eingeschränkt, d.h. man wählt das Kommando aus und wizard-mässig kann man immer weitere Angaben zum Kommando machen und mit Enter bestätigen. Das ist bei der Anzahl an möglichen ili2gpkg-Optionen suboptimal:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg_options.png&quot; alt=&quot;ili2gpkg_options&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fair enough: Nicht alle sind relevant für die Erstellung des Schemas und der Tabellen. Aber es bleibt ein grosse Anzahl und kein Benutzer will 30 Mal Enter drücken. Abhilfe schafft die Meta-Config-ini-Datei. In dieser ini-Datei lassen sich die ili2gpkg-Optionen speichern und nur noch die Allerwichtigsten müssen vom Benutzer gewählt resp. bestätigt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[ch.ehi.ili2db]
defaultSrsCode=2056
createEnumTabs=true
nameByTopic=true
createFk=true
createGeomIdx=true
createUnique=true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Ansatz führt zur berechtigten Frage was passiert, wenn ich andere Werte für diese Optionen verwenden will? Meine Lösung dafür: der Benutzer wählt einfach ein anderes ini-File aus. Die &lt;em&gt;VS Code&lt;/em&gt; Extension bringt bereits eine ini-Datei mit, die der Benutzer nur zu bestätigen braucht oder aber er kann eine eigene ini-Datei auswählen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bilder sagen mehr als 1000 Worte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ausgangslage (ein hoffentliches kompilierendes INTERLIS-Datenmodell):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg01.png&quot; alt=&quot;ili2gpkg01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auswahl des ili2gpkg-Kommandos:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg02.png&quot; alt=&quot;ili2gpkg02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auswahl der ini-Datei. Entweder die eingebaute Default-Datei oder eine eigene:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg03.png&quot; alt=&quot;ili2gpkg03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Name und Speicherort der resultierenden GPKG-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg04.png&quot; alt=&quot;ili2gpkg04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wahl des Datenmodelles. Es wird der Namen des aktuellen Datenmodelles vorgeschlagen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg05.png&quot; alt=&quot;ili2gpkg05&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auswahl der Modelrepositories. Es werden die Standardrepositories und der Speicherort des aktuellen Modelles vorgeschlagen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg06.png&quot; alt=&quot;ili2gpkg06&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fenster mit dem auszuführenden ili2gpkg-Kommando zwecks Review/Debugging:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg07.png&quot; alt=&quot;ili2gpkg07&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Log-Messages in einem neuen Channel (siehe oben rechts im Output-Fenster &amp;laquo;ILI2DB&amp;raquo;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/ili2gpkg08.png&quot; alt=&quot;ili2gpkg08&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und jetzt? Entweder kann man sich die GeoPackage-Datei in &lt;em&gt;dbeaver&lt;/em&gt; anschauen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p60/dbeaver.png&quot; alt=&quot;dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oder es gibt auch SQLite-Extensions für &lt;em&gt;VS Code&lt;/em&gt; resp. &lt;em&gt;Theia&lt;/em&gt;. Leider habe ich da keine brauchbare gefunden, die ER-Diagramme darstellen kann. Und ausserdem könnte man das ER-Diagramm ebenfalls wieder mit &lt;em&gt;Mermaid&lt;/em&gt; oder &lt;em&gt;PlantUML&lt;/em&gt; direkt im Language-Server machen (analog dem UML-Klassendiagramm).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #59 - INTERLIS IDE</title>
      <link>http://blog.sogeo.services/blog/2025/10/19/interlis-leicht-gemacht-number-59.html</link>
      <pubDate>Sun, 19 Oct 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/10/19/interlis-leicht-gemacht-number-59.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich denke, dass man jetzt aufhören sollte mit dem Projekt &amp;laquo;neuer UML/INTERLIS Editor&amp;raquo;. Warum noch viel Geld und Stunden verlochen, wenn es - tadaaa - meine &lt;a href=&quot;https://edigonzales.github.io/interlis-ide/&quot;&gt;&lt;em&gt;INTERLIS IDE&lt;/em&gt;&lt;/a&gt; gibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe bereits für &lt;em&gt;jEdit&lt;/em&gt; ein &lt;a href=&quot;https://blog.sogeo.services/blog/2025/09/17/interlis-leicht-gemacht-number-56.html&quot;&gt;INTERLIS-Plugin&lt;/a&gt; geschrieben und dann feststellen müssen, dass (a) die Akzeptanz eher ein Kampf wäre und (b) Visual Studio Code schon auch Vorteile hat. Es gibt jedoch ein sehr grosses Aber! Die ganze Entwicklung von Visual Studio Code ist stark von Microsoft abhängig. Auch wenn das die meisten Digitalisierungs-&amp;laquo;Profis&amp;raquo; etc., die in die kantonale Verwaltungen strömen, nicht stört, mich tut es! Und darum war ich positiv überrascht, dass es mit &lt;a href=&quot;https://theia-ide.org/&quot;&gt;&lt;em&gt;Eclipse Theia&lt;/em&gt;&lt;/a&gt; einen kompatiblen Editor gibt. Er verwendet im Frontend sogar den gleichen Kern wie Visual Studio Code aber &lt;em&gt;Theia&lt;/em&gt; selber ist mehr Framework als fertiges Produkt. Die &lt;em&gt;Theia IDE&lt;/em&gt; ist also ein Produkt, das auf dem &lt;em&gt;Theia&lt;/em&gt; Framework aufsetzt. Die Extensions von Visual Studio Code sind kompatibel. Nichts sprach also gegen die Portierung des jEdit-Plugins in eine VSCode-Extension.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um das als VSCode-Extension anständig zu machen, muss man einen Language Server implementieren und drumherum eine kleine VSCode-Extension bauen. Den Language Server kann man eigentlich in irgendeiner Sprache programmieren. Er muss am Ende des Tages einfach auf der Zielplattform, wo auch VSCode läuft, ausführbar sein. Weil ich unbedingt wollte, dass sich die INTERLIS-Datenmodelle mit dem offiziellen INTERLIS-Compiler &lt;em&gt;ili2c&lt;/em&gt; prüfen lassen können, musste es Java sein (umgesetzt mit &lt;a href=&quot;https://github.com/eclipse-lsp4j/lsp4j&quot;&gt;&lt;em&gt;lsp4j&lt;/em&gt;&lt;/a&gt;). Damit der Anwender sich nicht um eine Java Runtime auf seinem PC kümmern muss, liefere ich neben dem Language Server auch gleich eine möglichst kleine JRE mit. Und zwar für hoffentlich alle gängigen Betriebssysteme. Die Extension kümmert sich um das Starten und Beenden des Language Servers. Davon merkt der Anwender nichts. Die Kommunikation zwischen Editor und Language Server läuft via STDIO und JSON. Theoretisch funktioniert der Language Server auch mit anderen Editoren / IDE wie z.B. &lt;em&gt;Eclipse IDE&lt;/em&gt; und &lt;em&gt;Netbeans&lt;/em&gt;. Es gibt gewisse Standardfunktionen, die funktionieren werden. Implementiert der Language Server zusätzliche Befehle, müsste ein Client diese auch implementieren, sonst bringen sie nichts. Soweit die Theorie.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was kann &lt;a href=&quot;https://github.com/edigonzales/interlis-lsp&quot;&gt;mein Language Server&lt;/a&gt; in Kombination mit der &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=edigonzales.interlis-editor&quot;&gt;VSCode-Extension&lt;/a&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Datenmodell kompilieren:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-compile.png&quot; alt=&quot;lsp-compile&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Werden Fehler gefunden, sind diese strukturiert und können mittels Klick angesprungen werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-error.png&quot; alt=&quot;lsp-error&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Modell und Objekt Suggestions:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-suggest.png&quot; alt=&quot;lsp-suggest&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Pretty Print:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-format.png&quot; alt=&quot;lsp-format&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Rename-Funktion:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-rename.png&quot; alt=&quot;lsp-rename&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Könnte noch bisschen wackeling sein&amp;#8230;&amp;#8203;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;UML-Klassendiagramm:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-mermaid.png&quot; alt=&quot;lsp-mermaid&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es kann auch ein PlantUML-Klassendiagramm erstellt werden. Zudem ist der jeweilige Code kopierbar, um das Diagramm nach eigenen Wünschen anzupassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Objektkatalog (HTML und .docx):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-html.png&quot; alt=&quot;lsp-html&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Verlinkte Modelle:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-link.png&quot; alt=&quot;lsp-link&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Importierte Modelle und deren Objekte können geöffnet und angesprungen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Hierarchie-Baum:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/lsp-tree.png&quot; alt=&quot;lsp-tree&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und ja, das ist jetzt alles noch nicht battle proven. Aber ein guter Start. Und wie man richtig erkennen kann, sind alle Screenshots mit VSCode gemacht. Was ist nun mit &lt;em&gt;Eclipse Theia IDE&lt;/em&gt;? Funktioniert genau gleich. Man lädt die &lt;a href=&quot;https://theia-ide.org/#theiaidedownload&quot;&gt;Software herunter&lt;/a&gt; und installiert die &lt;a href=&quot;https://open-vsx.org/extension/edigonzales/interlis-editor&quot;&gt;Extension&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/theia-ide.png&quot; alt=&quot;theia-ide&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und was ist die &amp;laquo;INTERLIS IDE&amp;raquo;? Momentan noch nichts anderes als eine geforkte und leicht angepasste &lt;em&gt;Theia-IDE&lt;/em&gt; und eingebauter VSCode-Extension:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p59/interlis-ide.png&quot; alt=&quot;interlis-ide&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ob das langfristig wirklich notwendig ist, weiss ich noch nicht. Immerhin könnte so eine getailorete Variante den Einstieg vereinfachen? Nur sollten die &lt;a href=&quot;https://github.com/edigonzales/interlis-ide/releases&quot;&gt;bereitgestellten Installationspakete&lt;/a&gt; aber auch problemlos funktionieren&amp;#8230;&amp;#8203; Unter macOS laufe ich natürlich prompt in ein Problem und man muss den Klassiker machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;xattr -dr com.apple.quarantine InterlisIDE.app&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sonst kommt die Fehlermeldung, dass die Anwendung kaputt ist. Endziel hier wären natürlich signierte Pakete oder aber mindestens eine bessere Fehlermeldung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein zweiter Grund für den speziellen Theia-Build wäre, falls eine VSCode-Extension nicht mehr reicht, um alle Anforderungen zu erfüllen. Und Anforderungen führt mich gleich noch zum Thema, dass sowohl UML-Diagramm wie auch Objektkatalog read-only sind. Auch wenn ich es technisch interessant finde, wenn man in jeder der drei Ansichten editieren könnte, so frage ich mich doch wie wichtig das wirklich ist (UML möchte ich mir zwar noch anschauen, aber eher aus Neugier und eventuell der Möglichkeit, dass man notfalls bezüglich Platzierung selber eingreifen kann). In einem Workshop habe ich das Argument gehört, dass ein Jurist seinen Input in der Objektkatalog-Ansicht machen kann. Da frage ich mich dann schon (noch sehr neutral), in welchem Universum wir leben. Definitiv nicht im gleichen und nicht weil wir schlechte Juristinnen und Juristen haben, sondern &amp;laquo;Hallo Usecase?!?&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben den mächtigeren Erweiterungsmöglichkeiten (als Visual Studio Code) bietet das Theia-Framework auch noch Microsoft-freie Kollaboration. Man kann den Server - falls man möchte - sogar selber hosten. Und man kann die IDE auch als Browser-Anwendung laufen lassen, entweder lokal und in Kubernetes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Pakete konnte ich unter den verschiedenen Betriebssystemen nicht ausprobieren. Mir steht nur macOS zur Verfügung. Die VSCode-Extension wurde unter Windows ausprobiert und sollte funktionieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also, machen wir doch weniger Papiere und Workshops und arbeiten an etwas Konkretem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://theia-ide.org/&quot; class=&quot;bare&quot;&gt;https://theia-ide.org/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/interlis-lsp&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/interlis-lsp&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=edigonzales.interlis-editor&quot; class=&quot;bare&quot;&gt;https://marketplace.visualstudio.com/items?itemName=edigonzales.interlis-editor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://open-vsx.org/extension/edigonzales/interlis-editor&quot; class=&quot;bare&quot;&gt;https://open-vsx.org/extension/edigonzales/interlis-editor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://edigonzales.github.io/interlis-ide/&quot; class=&quot;bare&quot;&gt;https://edigonzales.github.io/interlis-ide/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #58 - Jetzt aber wirklich einfach gemacht</title>
      <link>http://blog.sogeo.services/blog/2025/10/18/interlis-leicht-gemacht-number-58.html</link>
      <pubDate>Sat, 18 Oct 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/10/18/interlis-leicht-gemacht-number-58.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich glaube ja immer noch, dass die einfachen Dinge in INTERLIS nicht schwierig zu modellieren sind. Die Syntax scheint mir sehr überschaubar. Schwieriger wird es bei komplizierteren Constraints. Das liegt mir nicht. Die grösste Herausforderung ist sowieso ein gutes Modell zu schreiben, das der Fragestellung gerecht wird und wirklich weniger die einfache Syntax.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Aber&amp;raquo; wäre es im momentanen KI-Hype nicht doch schön, man könnte ein LLM beauftragen, es solle eine Klasse soundso mit den und den Attributen eines bestimmten Typs erstellen? Ich habe die Erfahrung gemacht, dass z.B. ChatGPT zwischenzeitlich recht gut &amp;laquo;INTERLIS kann&amp;raquo;. Interessanterweise kennt es auch den INTERLIS-Compiler recht gut, um damit zu programmieren. Aber wie kann man sicher sein, dass da nicht kompletter Mist rauskommt, wenn man Modellierungsfragen stellt? Man kann dazu z.B. einen MCP-Server verwenden. Was ist das?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;MCP (Model Context Protocol) ist ein offenes Protokoll, mit dem KI-Clients (z. B. Chat- oder Agent-Apps) sicher und standardisiert auf externe MCP-Server zugreifen. Ein MCP-Server stellt dem Modell Tools, Ressourcen/Daten und Prompts bereit. Der Client entdeckt die Fähigkeiten des Servers, ruft Tools auf, liest Ressourcen und erhält Ergebnisse – transportagnostisch (z. B. stdio oder WebSocket).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p58/mcp-sequence.png&quot; alt=&quot;ili2mermaid&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den MCP-Server habe ich mit &lt;em&gt;Spring Boot&lt;/em&gt; und &lt;em&gt;Spring AI&lt;/em&gt; umgesetzt. Beim Austausch via STDIO ist wirklich darauf zu achten, dass nichts nach STDIO geloggt wird, vor allem auch beim Hochfahren der Anwendung nicht. Austausch via STDIO bedeutet, dass das Teil lokal laufen muss. Es gibt noch die Möglichkeit den Austausch via WebSocket stattfinden zu lassen. Im letzteren Fall poppen oft Fragen hinsichtlich Authentifizierung und Autorisierung auf.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich verzichte hier auf das Auflisten sämtlicher Tools, die der MCP-Server unterstützt. Ein vollständige Liste findet sich im &lt;a href=&quot;https://github.com/edigonzales/interlis-mcp/blob/main/docs/USER_GUIDE.md#tool-reference&quot;&gt;User Guide&lt;/a&gt; des Github-Repos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie kann man den Server nun benutzen? Als wichtigster Reminder: Es braucht schon noch eine KI, also ein LLM. Als erstes hat meines Wissens &lt;em&gt;Claude Desktop&lt;/em&gt; MCP unterstützt. Hier muss man die Datei &lt;code&gt;claude_desktop_config.json&lt;/code&gt; konfigurieren. Diese liegt unter macOS im Ordner &lt;code&gt;~/Library/Application Support/Claude&lt;/code&gt; und sieht z.B. so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &quot;mcpServers&quot; : {
        &quot;sogis-mcp&quot; : {
            &quot;command&quot; : &quot;/Users/stefan/.sdkman/candidates/java/21.0.4-graal/bin/java&quot;,
            &quot;args&quot; : [
                &quot;-jar&quot;,
                &quot;/Users/stefan/sources/sogis-mcp-poc/build/libs/sogis-mcp-poc-0.0.1-SNAPSHOT.jar&quot;
            ]
        },
        &quot;interlis-mcp&quot;: {
            &quot;command&quot; : &quot;/Users/stefan/.sdkman/candidates/java/21.0.4-graal/bin/java&quot;,
            &quot;args&quot;: [&quot;-jar&quot;, &quot;/Users/stefan/sources/interlis-mcp/build/libs/interlis-mcp.jar&quot;],
            &quot;env&quot;: {
                &quot;JAVA_TOOL_OPTIONS&quot;: &quot;-Xms512m -Xmx512m&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier ein Beispiel mit &lt;em&gt;Claude Desktop&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p58/mcp_claude.png&quot; alt=&quot;mcp claude&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man erkennt gut den strukturierten Request, den der Client an den MCP-Server macht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  `name`: `Gebaeude`,
  `oidDecl`: `OID AS UUIDOID`,
  `attrLines`: [],
  `extendsFqn`: ``,
  `isAbstract`: false
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was genau übermittelt werden kann und in welcher Struktur, weiss der Client, weil der MCP-Server seine Tools beschreibt und diese Beschreibung auch ausliefert (ähnlich GetCapabilities von WMS o.ä.).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch Visual Studio Code unterstützt MCP-Server. Hinzufügen kann man sie über den &lt;code&gt;MCP: Add Server&lt;/code&gt; Befehl. Das Hinzufügen geschieht anschliessend interaktiv. Am Ende landet die Konfig doch wieder in einer JSON-Datei, die man auch selber bearbeiten kann. Out-of-the-box ist in VSCode Copilot der Client. Hier habe ich noch nicht ganz den Durchblick. Ich glaube, man hat ein paar ChatGPT-Requests geschenkt, anschliessend muss der Rubel rollen. Erwähnenswert ist vielleicht folgendes (mit viel Halbwissen): Man kann sich einen eigenen Chatmodus erstellen (siehe Screenhot ganz unten rechts &amp;laquo;SuperAgent&amp;raquo;). Ich habe dies machen müssen, um Copilot dazu zu bringen, dass er bei INTERLIS-Fragen immer meinen MCP-Server fragt und nicht selber etwas wurstelt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;---
description: &apos;Description of the custom chat mode.&apos;
tools: [&apos;interlis-mcp&apos;]
---
Bitte verwende für alle INTERLIS-Modellbausteine immer den MCP-Server.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p58/mcp-vscode.png&quot; alt=&quot;mcp vscode&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben der grundsätzlichen Sinnhaftigkeit, gibt es auch ein paar Nachteile: (1) Ich denke, dass es relativ aufwändig wird, wenn man praktisch die ganze INTERLIS-Sprache so umsetzen will. Aber vielleicht ist es auch bloss Fleissarbeit und gute Prompts. Und (2): Weil der MCP-Server nicht immer komplette Modelle zurückliefert resp. erstellt, kann man diese Konstrukte nicht mit &lt;em&gt;ili2c&lt;/em&gt; prüfen. Entweder baut man sich einen &amp;laquo;Snippet-Compiler&amp;raquo; oder man macht z.B. um eine vom Server erstellt Klasse noch ein generisches &amp;laquo;Mantel-Modell&amp;raquo; drum und prüft dann mit &lt;em&gt;ili2c&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/interlis-mcp&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/interlis-mcp&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #57 - Python, Python und nochmals Python</title>
      <link>http://blog.sogeo.services/blog/2025/10/17/interlis-leicht-gemacht-number-57.html</link>
      <pubDate>Fri, 17 Oct 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/10/17/interlis-leicht-gemacht-number-57.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe bereits mehrere Male über INTERLIS und Python geschrieben und gezeigt, dass man durchaus was damit machen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.sogeo.services/blog/2025/07/22/interlis-leicht-gemacht-number-53.html&quot; class=&quot;bare&quot;&gt;https://blog.sogeo.services/blog/2025/07/22/interlis-leicht-gemacht-number-53.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.sogeo.services/blog/2022/12/11/interlis-leicht-gemacht-number-33.html&quot; class=&quot;bare&quot;&gt;https://blog.sogeo.services/blog/2022/12/11/interlis-leicht-gemacht-number-33.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html&quot; class=&quot;bare&quot;&gt;https://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das funktioniert soweit tadellos. Der funktionale Nachteil dieses Approaches mit &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; ist, dass man quasi nur immer einen Befehl ausführen kann und immer nur genau eine simple Antwort zurück bekommt. Z.B. kann man sehr gut eine INTERLIS-Transferdatei validieren, aber die Antwort kann faktisch nur sein: valide oder nicht valide. Klar gibt es die Logdatei, die man auslesen kann. Ähnlich bei &lt;em&gt;ili2db&lt;/em&gt;: Hier muss und will man als Anwender der Software viele Optionen mitgeben und entsprechend muss man hier Lösungen finden, weil im Prinzip nur einfache Datentypen übergeben werden können (z.B. JSON als String und wieder als JSON interpretieren). Das alles mag für einige Fragestellungen gar nicht so problematisch sein, für eine ist sie denkbar ungünstig: Was mache ich, wenn ich mehr über das INTERLIS-Datenmodell wissen will? Ganz einfache und dumme Fragestellung: Wie viele Klassen gibt es in meinem Modell und wie heissen sie? Den INTERLIS-Compiler &lt;em&gt;ili2c&lt;/em&gt; habe ich ebenfalls als Versuch in eine native shared library kompiliert und als &lt;a href=&quot;https://pypi.org/project/ili2c/&quot;&gt;Python Package&lt;/a&gt; angeboten. Diese library liefert aber eben nur zurück, ob das Modell syntaktisch korrekt ist oder nicht. Jetzt könnte man natürlich in Java das Modell mit der entsprechenden Java-API auslesen und ebenfalls wieder strukturiert als String (z.B. JSON) an den Python Code zurückliefern. Dieser Python Code müsste den String wiederum interpretieren können. Machbar aber auch aufwändig und man hat immer noch die shared native library an der Backe. Diese Variante wird m.E. sehr ähnlich aber ohne den ersten Schritt momentan auf Wunsch verschiedener Kantone umgesetzt. Man wählt den Weg über die ilismeta-Datei. Das ist das Datenmodell abgebildet in einem INTERLIS-Metamodell als Transferdatei. Dabei muss die Python-Logik &amp;laquo;nur&amp;raquo; XML interpretieren können. Mein Problem mit dieser Lösung: Woher kriege ich diese ilismeta-Datei? Der Deal müsste doch sein: &amp;laquo;Python only&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zugegebenermassen leicht angefixt, dachte ich mir: Mmmmh, mit &lt;a href=&quot;https://chatgpt.com/codex&quot;&gt;ChatGPT Codex&lt;/a&gt; könnte man doch Teile des ili2c-Codes nach Python umschreiben lassen. Interessant an diesem Experiment ist meines Erachtens fast weniger das &lt;a href=&quot;https://pypi.org/project/ili2c-python/&quot;&gt;Resultat&lt;/a&gt;, sondern der Weg dahin und was es für zukünftige Programmierarbeiten bedeutet. Und auf beides habe ich natürlich nicht die perfekte und definitive Antwort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ich am Ende haben wollte, war sowas wie die &lt;a href=&quot;https://github.com/claeis/ili2c/blob/master/ili2c-core/src/main/java/ch/interlis/ili2c/metamodel/TransferDescription.java&quot;&gt;TransferDescription&lt;/a&gt; damit ich sämtliche Informationen aus einem INTERLIS-Datenmodell direkt mit Python auslesen kann. &lt;em&gt;Ili2c&lt;/em&gt; basiert auf &lt;em&gt;antlr2&lt;/em&gt;, mit dem man nicht Python-Klassen herstellen kann. Ich wusste, dass es für INTERLIS 2.4 bereits eine antlr4-Grammatik &lt;a href=&quot;https://github.com/maxcollombin/interlis-antlr-parser&quot;&gt;gibt&lt;/a&gt;. Ebenfalls war (und ist) mir diese ganze Parser/Lexer-Welt noch eine ziemliche Unbekannte. Ich habe also ChatGPT Codex beauftragt das &lt;a href=&quot;https://github.com/claeis/ili2c/tree/master/ili2c-core/src/main/java/ch/interlis/ili2c/metamodel&quot;&gt;Java metamodel-Package&lt;/a&gt; (wo auch die TransferDescription drin ist) nach Python umzuschreiben und ihm die antlr4-Grammatik mitgeliefert. Das hat mit verschiedenen weiteren Schritten halbwegs funktioniert, bis ich merkte, dass ich nur die vereinfachte antlr4-Grammatik aus dem Repository verwendet habe. Unterwegs habe ich festgestellt, dass natürlich (weil auch beauftragt) Tests geschrieben werden, nur halt teilseise mit syntaktisch falschen Datenmodellen (weil von ChatGPT selber hergestellt). Das ist sicher eine Lektion, die man lernen kann: Die Testmodelle müssen stimmen. Ich habe mich dazu entschlossen nochmals von Null zu starten und eben die Testmodelle mitzuliefern. Bei diesem Startprompt habe ich Wahrscheinlich den Fehler gemacht, dass ich nicht genug erwähnt habe, dass ich explizit dieses Metamodell als Abstraktionslayer haben will. Inkl. sämtlicher Typen (z.B. EnumerationType, TextType etc.). Sondern als Ziel habe ich ihm gesetzt, dass ich UML-Klassendiagramme mit Mermaid herstellen will. Was auch schon relativ gut gelang ohne all dieses Type-Klassen. Aber trotzdem kam ich beim zweiten Mal schneller an mein Resultat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben dem eigentlichen Parser wollte ich noch die ilirepository-Logik in Python haben. D.h. Python sucht nun - gleich wie ili2c - ein Modell in den INTERLIS-Modellablagen und speichert es in einem lokalen Cache. Dieses Feature habe ich zuerst losgelöst vom Parser entwickeln lassen und erst als es mehr oder weniger korrekt funktionierte mit einem weiteren Auftrag an ChatGPT im Parser implementiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich glaube nicht, dass es funktionieren würde, wenn man gar nichts von INTERLIS versteht und/oder gar nichts vom Resultat versteht, was man erreichen will (das Java-Metamodell in Python). Hellsehen kann ChatGPT immer noch nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die andere Frage ist: würde man mit sowas früher oder später in Produktion gehen? Jein. Wahrscheinlich falls es eher Wegwerf-Code ist (aber was heisst dann schon Produktion), kann man es riskieren. Oder falls man es nur in einem engeren Kreis braucht und immer noch das Wissen hat, richtig zu reagieren und notfalls Bugs zu fixen (oder fixen zu lassen). Auch wenn Tests geschrieben werden, sind manche Tests schlicht nutzlos, da sie nicht das Testen, was eigentlich sinnvollerweise getestet werden sollte. Im konkreten Fall könnte ich mir gut vorstellen, dass die Basis, die gelegt wurde, gar nicht mal so schlecht ist und dass man mit ein wenig reingesteckter Liebe noch weit kommt. Man müsste sich sicher die Grundarchitektur zu Gemüte führen und z.B. die Tests genauer anschauen etc. pp.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige einfache Beispiele:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Modell in Modellablage suchen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;import logging

from ili2c.ilirepository import IliRepositoryManager

logging.basicConfig(level=logging.INFO)

manager = IliRepositoryManager([&quot;https://models.interlis.ch/&quot;])
for metadata in manager.list_models():
    logging.info(&quot;%s %s -&amp;gt; %s&quot;, metadata.name, metadata.version, metadata.full_url)

metadata = manager.find_model(&quot;DMAV_Grundstuecke_V1_0&quot;)
if metadata:
    local_path = manager.get_model_file(metadata.name)
    logging.info(&quot;Cached copy stored at %s&quot;, local_path)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Modell parsen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;import logging
from pathlib import Path

from ili2c.pyili2c import parser
from ili2c.pyili2c.metamodel import Table

logging.basicConfig(level=logging.INFO)

settings = parser.ParserSettings(
    ilidirs=[&quot;examples/models&quot;],
    repositories=[&quot;http://models.interlis.ch/&quot;],
)

transfer_description = parser.parse(
    Path(&quot;examples/models/DMAV_Grundstuecke_V1_0.ili&quot;),
    settings=settings,
)

for model in transfer_description.getModels():
    logging.info(&quot;Loaded model %%s (schema %%s)&quot;, model.getName(), model.getSchemaLanguage())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;UML-Klassendiagramm herstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;import logging

from ili2c.pyili2c import parser
from ili2c.pyili2c.mermaid import render

logging.basicConfig(level=logging.INFO)

transfer_description = parser.parse(&quot;path/to/model.ili&quot;)
mermaid_source = render(transfer_description)
logging.info(&quot;Generated diagram:\n%s&quot;, mermaid_source)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das funktioniert jedenfalls mit meinem neuen &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/ili2c/refs/heads/master/python/tests/pyili2c/data/TestSuite_mod-0.ili&quot;&gt;Lieblings-Test-Modell&lt;/a&gt; und mit seinem &lt;a href=&quot;https://github.com/edigonzales/ili2c/blob/master/python/tests/pyili2c/data/SO_ARP_SEin_Konfiguration_20250115.ili&quot;&gt;Vorgänger&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was kann man nun mit dem Python Package anstellen? Man erstellt einen &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/ili2c/refs/heads/master/python/tests/pyili2c/data/ili2mermaid.html&quot;&gt;Mermaid-Renderer&lt;/a&gt;, der lokal im Browser läuft. Der Python-Code läuft in einer &lt;a href=&quot;https://pyodide.org/en/stable/&quot;&gt;Python-WebAssembly-Variante&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p57/ili2mermaid.png&quot; alt=&quot;ili2mermaid&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit die Modellablagen verwendet werden können, müssen sie CORS unterstützen, was bei den meisten nicht der Fall ist. Auch aus diesem Grund habe ich die wichtigsten Repos bei uns &lt;a href=&quot;https://geo.so.ch/models/mirror/&quot;&gt;gecached&lt;/a&gt; und kann sie für solche Fälle verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Tage unter die Augen gekommen: &lt;a href=&quot;https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html&quot;&gt;Spec Driven Development&lt;/a&gt;. Unter Umständen würde das hier gut funktionieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pypi.org/project/ili2c-python/&quot; class=&quot;bare&quot;&gt;https://pypi.org/project/ili2c-python/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili2c&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili2c&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #56 - Einfacher modellieren mit jEdit</title>
      <link>http://blog.sogeo.services/blog/2025/09/17/interlis-leicht-gemacht-number-56.html</link>
      <pubDate>Tue, 16 Sep 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/09/17/interlis-leicht-gemacht-number-56.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir modellieren INTERLIS-Modelle mit dem &lt;a href=&quot;http://www.umleditor.org/&quot;&gt;&lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt;&lt;/a&gt;. Warum wir das so machen, weiss ich nicht mehr recht. Wahrscheinlich weil wir damals glaubten, dass man das halt so macht, obwohl das Teil bereits 2016 eher unmodern wirkte. Aber gegen unser neues Zeit- und Leistungserfassungstool scheint der &lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt; als hätte ihn Jony Ive persönlich designed. Aber das ist eine ganz andere Geschichte. Zurück zu den wichtigen Dingen des Lebens:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach all den Jahren können wir sagen, was uns wichtig ist: Die Datenmodelle sollen immer gleich formatiert sein und es muss eine visuelle Repräsentation in Form eines UML-Klassendiagramms vorhanden sein resp. einfach ableitbar sein. Nicht zwingend notwendig ist die Erstellung des Datenmodelles mit einem &amp;laquo;Klick&amp;raquo;-Editor. Die wirklichen Ur-Beweggründe für die Programmierung des &lt;em&gt;UML/INTERLIS-Editors&lt;/em&gt; kenne ich nicht, aber mich dünkt der Mehrwert / die Unterstützung bei der Modellierung marginal bis nicht vorhanden bis kontraproduktiv. Die Syntax von INTERLIS für die einfachen Fälle (also Modell, Topic, Klasse und Attribute) ist ja wirklich trivial. Mit einem Text-Editor, der mich noch ein wenig unterstützt, bin ich garantiert schneller als jemand, der gefühlte 1000 Mausklicks machen muss. Wird es komplizierter (beginnt bei mir bereits bei Assoziationen: Mmmh, wohin gehört welche Rolle?), muss ich sowieso in die Modelldatei nachschauen gehen. Und wenn es wirklich grausam wird (komplexe Constraints), müsste ein guter &lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt; &lt;strong&gt;wirklich&lt;/strong&gt; gut sein, damit ich einen Mehrwert habe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusammengefasst: Mir reicht ein Text-Editor mit Syntax-Highlighting, der die Syntax meines INTERLIS-Modelles überprüft (kompiliert) und mir eine read-only UML-Darstellung erstellt. &lt;a href=&quot;https://blog.sogeo.services/blog/2025/07/22/interlis-leicht-gemacht-number-53.html&quot;&gt;In einem älteren Beitrag&lt;/a&gt; habe ich gezeigt, wie ich eine VSCode-Extension für den INTERLIS-Compiler &lt;em&gt;ili2c&lt;/em&gt; programmiert habe. Technisch ist es nicht gerade straight forward mit der native shared library, aber machbar. Stärker wiegt aber der Umstand, dass &lt;em&gt;VSCode&lt;/em&gt; mehr oder weniger komplett von Microsoft kontrolliert wird. Und das fand ich dann weniger gut. Schon nur, weil wir bereits mit Github zu Microsoft-Jünger geworden sind. Mit &lt;a href=&quot;https://www.jedit.org/&quot;&gt;&lt;em&gt;jEdit&lt;/em&gt;&lt;/a&gt; steht ein Veteran bereit, der bereits seit Jahren Syntax-Highlighting für INTERLIS bietet und zudem in Java geschrieben ist. Damit dürfte die Entwicklung eines Plugins für INTERLIS (hauptsächlich für &lt;em&gt;ili2c&lt;/em&gt;) sehr gut machbar sein. Der Nachteil von &lt;em&gt;jEdit&lt;/em&gt; ist sicher seine kleine Community und dass er nicht das &lt;a href=&quot;https://en.wikipedia.org/wiki/Language_Server_Protocol&quot;&gt;Language Server Protocol&lt;/a&gt; unterstützt. Weil ich das doch ziemlich gut finde, habe ich mich schlau gemacht und mit &lt;a href=&quot;https://theia-ide.org/&quot;&gt;Eclipse Theia&lt;/a&gt; eine Alternative für &lt;em&gt;VSCode&lt;/em&gt; gefunden. Aber wieder zurück zu &lt;em&gt;jEdit&lt;/em&gt;. Ich habe ein recht umfangreiches INTERLIS-Plugin für &lt;em&gt;jEdit&lt;/em&gt; geschrieben. Ob ich später etwas für &lt;em&gt;Eclipse Theia&lt;/em&gt; mache, weiss ich noch nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;JEdit&lt;/em&gt; ist &lt;a href=&quot;https://www.jedit.org/users-guide/writing-plugins-part.html&quot;&gt;einfach erweiterbar&lt;/a&gt; und es gibt &lt;a href=&quot;https://plugins.jedit.org/list.php&quot;&gt;viele Plugins&lt;/a&gt;, die als Beispiele dienen. Was ich gut finde bei &lt;em&gt;jEdit&lt;/em&gt;, ist die Einfachheit:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;plugin.ch.so.agi.jedit.InterlisPlugin.menu=compileCurrentFile \
    toggleCompileOnSave \
    prettyPrintCurrentFile \
    interlis-collect-keywords \
    interlis-uml-show \
    interlis-uml-show-static \
    interlis.create-object-catalog

compileCurrentFile.label=Compile current file
toggleCompileOnSave.label=Toggle compile on save
prettyPrintCurrentFile.label=Pretty print current file
interlis-uml-show.label=Show UML diagram (interactive)
interlis-uml-show-static.label=Show UML diagram (static)
interlis-collect-keywords.label=Collect model tags
interlis.create-object-catalog.label=Export object catalog (.docx)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Eintrag in einer Properties-Datei erzeugt mir folgenden Menueintrag:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-menu.png&quot; alt=&quot;jedit-menu&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So einfach geht es eigentlich weiter. Es gibt auch eine Art Dependency Injection, wenn man sich auf andere Plugins abstützt und eine Implementierung einer Konsolenshell speziell für INTERLIS machen will. &lt;em&gt;JEdit&lt;/em&gt; hat auch eine praktische Funktion wie eigene Fenster an das Hauptfenster gedockt werden kann. Die meisten dieser Dinge lassen sich einfach in einer zentralen Properties-Datei oder in verschiedenen XML-Dateien steuern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was habe ich alles für INTERLIS implementiert? Als allererstes habe ich das Syntax-Highlightung für INTERLIS 2.4 upgedatet. Dabei habe ich eine kleine funktionale Änderung bei den Kommentare vorgenommen. Neu werden Metaattribute separat behandelt. Die Farben wiederum sind nicht Bestandteil der Konfiguration, sondern können individuell in &lt;em&gt;jEdit&lt;/em&gt; vorgenommen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als zweites wollte ich die Syntax des Modells kontrollieren indem ich das Modell mit &lt;em&gt;ili2c&lt;/em&gt; kompiliere. Das geht natürlich sehr einfach aber ich wollte das Resultat (also der eigentlich Kommandozeilenoutput von &lt;em&gt;ili2c&lt;/em&gt;) auch irgendwie in &lt;em&gt;jEdit&lt;/em&gt; sichbar machen. Für solche Fälle ist das &lt;a href=&quot;https://plugins.jedit.org/plugindoc/Console/&quot;&gt;Console-Plugin&lt;/a&gt; zweckdienlich. Man kann damit beliebige Shells implementieren. In meinem Fall wollte ich die Logmeldungen von &lt;em&gt;ili2c&lt;/em&gt; reinschreiben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-console.png&quot; alt=&quot;jedit-console&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt die Möglichkeit, das Kompilieren an eine Tastenkombination zu binden, oder das Plugin bietet die Einstellung, dass die Datei nach jedem Speichern automatisch kompiliert wird (&amp;laquo;Toggle compile on save&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was passiert wenn Fehler gefunden werden? Die Fehler werden natürlich in der Konsole geloggt. Es wird zusätzlich ein Fenster mit den Fehlern geöffnet. Mittels Klick auf eine dieser Fehlerzeilen, landet man am korrekten Ort in der Datei. Diese Fehlerliste hat leider noch einen Fehler: Wenn &lt;em&gt;ili2c&lt;/em&gt; mehrere Fehler pro Zeile meldet, stimmt irgendwas nicht (auch die Sortierung der Zeilennummern funktioniert nicht).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-console-error.png&quot; alt=&quot;jedit-console-error&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Grund für die Verwendung vom &lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt; war, dass er die Modelle beim Export immer gleich formattiert. Für diesen Anwendungsfall gibt es im Plugin die &amp;laquo;Pretty Print&amp;raquo;-Funktion. Die Umsetzung war sehr einfach: &lt;em&gt;ili2c&lt;/em&gt; liest die Modelldatei und schreibt sie wieder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiteres wichtiges Feature für mich war die Unterstützung beim Schreiben von Klassen, Topics etc. Ich möchte, wenn ich beginne eine Klasse zu schreiben (&lt;code&gt;CLASS Fubar =&lt;/code&gt;) nicht auch das Ende schreiben müssen (&lt;code&gt;END Fubar;&lt;/code&gt;). Hier unterstützt mich das Plugin stark: Der Footer wird automatisch in den Editor geschrieben nachdem ich &lt;code&gt;=&lt;/code&gt; getippt habe und der Cursor wird zwischen Header und Footer  platziert, so dass ich Attribute hinzufügen kann. Dies funktioniert für die Elemente &lt;code&gt;MODEL&lt;/code&gt;, &lt;code&gt;TOPIC&lt;/code&gt;, &lt;code&gt;CLASS&lt;/code&gt;, &lt;code&gt;STRUCTURE&lt;/code&gt; und &lt;code&gt;VIEW&lt;/code&gt; mit den jeweiligen Modifiern (falls die so heissen). Also auch z.B. &lt;code&gt;CLASS Foo (ABSTRACT) EXTENDS ModelA.Bar =&lt;/code&gt;. Beim Element &lt;code&gt;MODEL&lt;/code&gt; schreibt es mir einen kompletten Kommentar-Header rein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-autoclose-model.png&quot; alt=&quot;jedit-autoclose-model&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weiter geht es mit Vorschlägen bei &lt;code&gt;IMPORTS&lt;/code&gt;. Da schlägt mir das Plugin die Modelle der Standardrepositories (und ihren verknüpften Repositories) vor:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-suggestion-model.png&quot; alt=&quot;jedit-suggestion-model&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Vorschläge funktionieren auch bei Attributen, die z.B. eine Struktur oder ähnliches aus einem anderen Modell referenzieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ebenfalls umgesetzt ist das Anzeigen von importieren Modellen. Das funktioniert natürlich nur, wenn es mindestens eine erfolgreiche Kompilierung gab. Sonst kennt das Plugin das Modell ja gar noch nicht (gilt natürlich auch für andere Feature). Ein Klick auf das importierte Modell öffnet dieses in einem neuen Tab:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-link-model.png&quot; alt=&quot;jedit-link-model&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich gibt es so etwas wie eine Baumstruktur. Bei &lt;em&gt;jEdit&lt;/em&gt; heisst es Sidekick oder Outline:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-sidekick.png&quot; alt=&quot;jedit-sidekick&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Last but not least und für mich zwingend, ist die visuelle Repräsentation als UML-Klassendiagramm. Ich habe zwei Varianten implementiert: eine interaktive und eine statische. Die interaktive Variante habe ich mit &lt;em&gt;jhotdraw&lt;/em&gt; umgesetzt. Das ist die gleiche Bibliothek wie sich auch &lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt; einsetzt. Das Problem bei dieser Bibliothek ist, dass sie nicht mehr wirklich unterhalten wird oder sehr spärlich. Die Bibliothek bietet sehr viel, um sehr schnell das gewünschte Resultat zu erhalten. Ich habe mich entschieden, dass ich pro Topic einen Tab in einem separaten (dockable) Fenster generiere und einen zusätzlichen Tab für die Topic-Übersicht (und andere Elemente auf der Modellebene):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-uml-static-overview.png&quot; alt=&quot;jedit-uml-static-overview&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-uml-static-topic.png&quot; alt=&quot;jedit-uml-static-topic&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Objekte können umhergeschoben und eingefärbt werden. Das Ganze funktioniert nicht mal so schlecht für kleine und mittelgrosse Modelle. Bei grösseren Modellen bräuchte es einen sehr guten Platzierungsalgorithmus. Die UML-Diagramme können als PNG exportiert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der zweiten Variante wird ein &lt;a href=&quot;https://blog.sogeo.services/blog/2025/09/08/interlis-leicht-gemacht-number-54.html&quot;&gt;Mermaid-UML-Diagramm hergestellt&lt;/a&gt;. Man kann zwar rein- und rauszoomen und rumpannen aber ansonsten ist es eine statische Angelegenheit. Das Diagramm wird im OS-Standardbrowser dargestellt. Java (Swing) bringt zwar auch eine Browserkomponente mit, die ist jedoch veraltet und da ist nix mit Javascript, SVG und CSS. Es gäbe in JavaFX eine aktuelle Browserkomponente aber ich wollte nicht noch weitere Abhängigkeiten im Plugin haben. Wenn das Datenmodell erfolgreich kompiliert wird, wird das Klassendiagramm im Browser automatisch nachgeführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eigentlich wollte ich hier ja aufhören, aber habe trotzdem noch zwei Funktionen hinzugefügt. Die erste ermittelt mittels KI aus den Topic- und Klassennamen sowie dem Metaattribut &lt;code&gt;shortDescription&lt;/code&gt; 10 bis 15 Synonyme, die man als Tags für die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei verwenden kann. Ich lasse die Tags auch gleich in verschiedene Sprachen übersetzen. Damit wäre es für eine &lt;a href=&quot;https://geo.so.ch/modelfinder&quot;&gt;Suchmaschine&lt;/a&gt; endlich möglich auch das MGDM Hazard Mapping (= Naturgefahren) zu finden. Leider ist hier die User Experience noch ungenügend. Da es relativ lange geht (warum ist mir nicht klar), fehlt ein Feedback in der Anwendung, dass was passiert. Gewisse Zeit später erscheint folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-collect-tags-prompt.png&quot; alt=&quot;jedit-collect-tags-prompt&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-collect-tags-response.png&quot; alt=&quot;jedit-collect-tags-response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Tags erfassen wir bei uns als Metaattribut zum Modell. Unser automatisierter INTERLIS-Modellrepository-Herstellungsprozess liest diese aus und füllt sie in der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei ab.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die letzte Funktion ist ein Export eines Objektkataloges in eine Wordatei. Das ist mir persönlich nicht wichtig, da wir eigentlich so einen Objektkatalog nicht verwenden. Es wird sich zeigen, ob er überlebt oder aus dem Plugin rausfliegt (ist halt nur LibreOffice, aber trotzdem .docx):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p56/jedit-objekt-katalog.png&quot; alt=&quot;jedit-objekt-katalog&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sämtliche Funktionen des Plugins sind sicher noch buggy und teilweise ist es auch inkonsistent in der Bedienung. Nach ersten Tests wird sich zeigen, was uns wirklich wichtig ist und weiterverfolgt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein (hoffentlich) Sorglospaket kann man &lt;a href=&quot;https://drive.proton.me/urls/41HWZWEJG4#iNAreL3aBVGb&quot;&gt;hier&lt;/a&gt; herunterladen. Die Zipdatei auspacken und ins Verzeichnis wechseln. Anschliessend kann &lt;em&gt;jEdit&lt;/em&gt; mit dem INTERLIS-Plugin wie folgt gestartet werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar jedit.jar -settings=./portable-settings -log=2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es läuft ab Java 11. Unter macOS ist Java 17 oder höher empfohlen, ansonsten macht das Arbeiten mit dem interaktiven UML-Klassendiagramm nicht Spass. Es ist viel &lt;a href=&quot;https://openjdk.org/jeps/382&quot;&gt;zu käsig&lt;/a&gt;. Reingepackt habe ich zudem eine modernere Theme und ein Font, den ich persönlich sehr gut zum Entwicklen finde (JetBrains Mono).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und als nächstes möchte ich einen INTERLIS-MCP-Server integrieren. Dafür muss aber zuerst überhaupt der MCP-Server her.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/jedit_interlis_plugin&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/jedit_interlis_plugin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://drive.proton.me/urls/41HWZWEJG4#iNAreL3aBVGb&quot; class=&quot;bare&quot;&gt;https://drive.proton.me/urls/41HWZWEJG4#iNAreL3aBVGb&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #55 - Des ili2pgs neue Kleider</title>
      <link>http://blog.sogeo.services/blog/2025/09/15/interlis-leicht-gemacht-number-55.html</link>
      <pubDate>Mon, 15 Sep 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/09/15/interlis-leicht-gemacht-number-55.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Ili2pg&lt;/em&gt; hat bekanntlich ein Wahnsinns-GUI:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-gui.png&quot; alt=&quot;ili2pg-gui&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und wenn das neue GUI nicht zu &lt;em&gt;ili2pg&lt;/em&gt; kommt, muss &lt;em&gt;ili2pg&lt;/em&gt; zum neuen GUI kommen. Und das &amp;laquo;neue&amp;raquo; GUI ist &lt;a href=&quot;https://dbeaver.io/&quot;&gt;&lt;em&gt;dbeaver&lt;/em&gt;&lt;/a&gt;. Ich denke, bei mir gab es wie zwei Auslöser ein ili2pg-dbeaver-Plugin zu schreiben: Ich hatte bereits vor längerer Zeit die Idee, dass es eigentlich ganz praktisch wäre, wenn man auf Knopfdruck Daten in einer PostgreSQL-Datenbank mit &lt;em&gt;ilivalidator&lt;/em&gt; resp. &lt;em&gt;ili2pg&lt;/em&gt; prüfen könnte. Und als ein Mitarbeiter vor kurzem meinte, dass er Daten mit &lt;em&gt;ili2pg&lt;/em&gt; via Kommandozeile nicht aus der Datenbank exportieren kann, war der Zeitpunkt für mich gekommen, das anzupacken. Natürlich mit der arroganten Meinung, dass ich dafür sicher nicht mehr Zeit brauche, als der Mitarbeiter für den Export (was sich natürlich als eine totale Fehleinschätzung erwies).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Im Prinzip&amp;raquo; und &amp;laquo;eigentlich&amp;raquo; müsste das doch ganz schnell gehen. Beide Anwendungen sind in Java geschrieben. Wie schwer kann das noch sein? Die Antwort ist: ziemlich schwer. Und zwar nicht wegen der eigentlichen Business-Logik. Das habe ich schon ein paar Mal gemacht (also Schema anlegen, Daten importieren und exportieren und solches Zeugs). Sondern das ganze Drumherum, also das eigentliche Plugin. Es ist ein schönes Beispiel dafür, dass man nicht nur die Programmiersprache kennen muss, sondern das ganze Ökosystem, um erfolgreich und effizient entwickeln zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Stolperstein hier ist wie ein Plugin für &lt;em&gt;Eclipse&lt;/em&gt; (&lt;em&gt;dbeaver&lt;/em&gt; ist nichts Anderes als eine Eclipse-Anwendung) gepackt, bereitgestellt und installiert werden muss. Eclipse-Plugins sind &lt;a href=&quot;https://en.wikipedia.org/wiki/OSGi&quot;&gt;OSGi&lt;/a&gt;-Bundles (plus noch ein paar spezifische Eclipse-Sachen). OSGi-Bundles sind Module für die Java-Welt: jedes Bundle ist ein JAR mit Metadaten (Manifest), das genau beschreibt, welche Pakete es bereitstellt und welche es benötigt. Dadurch können Module dynamisch geladen, gestartet, gestoppt und aktualisiert werden, ohne die gesamte Anwendung neu zu starten. Das Ganze stammt aber aus einer andere Epoche, ist zwar mächtig, aber nicht sonderlich etwicklerfreundlich (dünkt mich). Es war für mich eine ziemliche Herausforderung eine Entwicklungsumgebung so hinzukriegen, damit ich relativ effizient programmieren konnte. Hot- resp. Livereload ist da nicht, resp. habe ich nicht hingekriegt. Das Zückerchen gab es dann am Schluss: wie stellt man nun so ein Plugin bereit? Es sind sogenannte Update-Sites (oder Repositories). Leider ist das wiederum auch nicht so trivial wie ein Maven Repository. Fazit hier: Mein Projekt ist momentan ein Eclipse-Projekt (kein Maven- oder Gradleprojekt) und ich muss es noch manuell deployen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanter als mein Gejammer sind sicherlich die Features des Plugins. Wobei wir bei der Installation anfangen müssen. Wie soeben erwähnt, müssen die Plugins über eine Update-Site installiert werden. Ich habe für mein Plugin eine solche Update-Seite unter der URL &lt;a href=&quot;https://dbeaver.sogeo.services/updates&quot; class=&quot;bare&quot;&gt;https://dbeaver.sogeo.services/updates&lt;/a&gt; erstellt. In &lt;em&gt;dbeaver&lt;/em&gt; muss man unter &lt;code&gt;Help&lt;/code&gt; - &lt;code&gt;Install New Software&lt;/code&gt; die Update-Site angeben und kann anschliessend das ili2pg-Plugin installieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-install.png&quot; alt=&quot;ili2pg-install&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man muss verschiedenen Dingen &amp;laquo;trusten&amp;raquo;, so auch meiner Update-Seite. Nach einem Restart von &lt;em&gt;dbeaver&lt;/em&gt; steht das Plugin zur Verfügung. Das Plugin macht sich zum ersten Mal bemerkbar, wenn ich mit der rechten Maustaste auf ein Datenbank-Icon klicke:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-create-schema.png&quot; alt=&quot;ili2pg-create-schema&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus Bequemlichkeit habe ich im GUI nicht jede Option nachgebildet, sondern nur die absolut Notwendigsten. Nämlich eine Option für den Schemanamen und eine Option für eine &lt;a href=&quot;https://blog.sogeo.services/blog/2023/05/10/interlis-leicht-gemacht-number-35.html&quot;&gt;INI-Datei&lt;/a&gt;. Die INI-Datei beinhaltet alle Infos, die &lt;em&gt;ili2pg&lt;/em&gt; benötigt, um eine Schema anzulegen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[ch.ehi.ili2db]
models=SO_Hoheitsgrenzen_Publikation_20170626
nameByTopic=true
defaultSrsCode=2056
createFk=true
createFkIdx=true
createMetaInfo=true
createUnique=true
createNumChecks=true
createTextChecks=true
createDateTimeChecks=true
createEnumTabs=true
strokeArcs=true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-create-schema-gui.png&quot; alt=&quot;ili2pg-create-schema-gui&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Logmeldungen erscheinen in einem speziellen &amp;laquo;ili2pg&amp;raquo;-Tab in der Konsole:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-create-schema-finished.png&quot; alt=&quot;ili2pg-create-schema-finished&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man Daten importieren, muss ich das soeben erstellte Schema anwählen und wiederum die rechte Maustaste klicken. Jetzt erscheinen mehrere Befehle:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-befehle.png&quot; alt=&quot;ili2pg-befehle&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den Datenimport werden mehr Optionen benötigt, um sinnvolle Imports machen zu können. Wahrscheinlich fehlt aber noch die eine oder andere (z.B. &lt;code&gt;--replace&lt;/code&gt;) Option, um wirklich production-ready zu sein. Erwähnenswert ist die Option &lt;code&gt;Model&lt;/code&gt;. Man muss ili2pg immer mitteilen, welches Modell man importieren resp. exportieren will. Aus den Metatabellen wird auch nicht ersichtlich, um welches Modell es sich handelte, als man Daten importierte. Es werden gleichberechtigt sämtliche benötigten Modelle in der Tabelle &lt;code&gt;t_ili2db_model&lt;/code&gt; vorgehalten. Aus diesem Grund muss der Benutzer auch immer das Modell auswählen. Ich habe es nun so gelöst, dass ich vier Modellarten ignoriere (&lt;code&gt;TYPE&lt;/code&gt;, &lt;code&gt;CONTRACTED&lt;/code&gt;, &lt;code&gt;REFSYSTEM&lt;/code&gt; und &lt;code&gt;SYMBOLOGY&lt;/code&gt;). Diese wird man nicht importieren oder exportieren wollen. Sind nach Abzug dieser Modelle noch mehrere Modelle übrig, muss der Benutzer entscheiden. Ist nur noch eines übrig, wird automatisch dieses einzige Modell für den ili2pg-Befehl verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-import-data.png&quot; alt=&quot;ili2pg-import-data&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p55/ili2pg-import-data-finished.png&quot; alt=&quot;ili2pg-import-data-finished&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das geht nun so weiter für die restlichen Befehle des Plugins: &lt;code&gt;Export schema&amp;#8230;&amp;#8203;&lt;/code&gt;, &lt;code&gt;Export schema with options&amp;#8230;&amp;#8203;&lt;/code&gt; und &lt;code&gt;Validate schema&amp;#8230;&amp;#8203;&lt;/code&gt;. Die Einstellungen zu den Modellrepositories kann man unter &lt;code&gt;Settings&lt;/code&gt; - &lt;code&gt;ili2pg&lt;/code&gt; vornehmen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Dokumentation des Plugin-Repos ist noch ungenügend. Aber vielleicht kann jemand das Plugin bereits gewinnbringend einsetzen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dbeaver.sogeo.services/updates/&quot; class=&quot;bare&quot;&gt;https://dbeaver.sogeo.services/updates/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/dbeaver-ili2pg-plugin&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/dbeaver-ili2pg-plugin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/dbeaver-ilitools-feature&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/dbeaver-ilitools-feature&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #54 - INTERLIS meets Vibe Coding</title>
      <link>http://blog.sogeo.services/blog/2025/09/08/interlis-leicht-gemacht-number-54.html</link>
      <pubDate>Mon, 8 Sep 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/09/08/interlis-leicht-gemacht-number-54.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://de.wikipedia.org/wiki/Vibe_Coding&quot;&gt;&amp;laquo;Vibe Coding&amp;raquo;&lt;/a&gt; ist wahrscheinlich eher ein Gaga-Begriff und vor allem eine nicht sonderlich schlaue Arbeitsweise aber ich brauchte einen catchy Blogtitel. Ich habe im Juli unseren &lt;a href=&quot;https://geo.so.ch/modelfinder&quot;&gt;Modelfinder&lt;/a&gt; ein wenig aufgefrischt. Ich konnte das Frontend technisch massiv vereinfachen: simples Templating mit &lt;a href=&quot;https://htmx.org/&quot;&gt;&lt;em&gt;HTMX&lt;/em&gt;&lt;/a&gt; im Gegensatz zu &lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;&lt;em&gt;GWT&lt;/em&gt;&lt;/a&gt; und und ich habe die Anwendung auch funktional erweitert. Neu gibt es eine Detailansicht für das INTERLIS-Datenmodell. Diese zeigt neben verschiedenen Metainformationen auch das Modell mit Syntax Highlighting und ein UML-Klassendiagramm:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p54/modelfinder02.png&quot; alt=&quot;modelfinder 01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Klassendiagramme werden mit &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;&lt;em&gt;Mermaid&lt;/em&gt;&lt;/a&gt; erstellt. Ganz glücklich war ich jedoch mit meiner Umsetzung noch nicht: Gewisse Modelle konnten nicht gerendert werden (looking at you &lt;a href=&quot;https://www.geo.admin.ch/dam/de/sd-web/528vKWlohxZJ/Empfehlungen%20Kataloge.pdf&quot;&gt;&amp;laquo;Kataloge&amp;raquo;&lt;/a&gt;) und umfangreichere Modelle wurden zu klein gerendert und entsprechend hat man nichts mehr erkannt. Der Code zur Herstellung des Mermaid-UML-Klassendiagramms habe ich selber geschrieben. Weil mir wegen eines anderen Projektes auffiel, dass ChatGPT 5 ein relativ gutes Wissen über das &lt;a href=&quot;https://github.com/claeis/ili2c/tree/master/ili2c-core/src/main/java/ch/interlis/ili2c/metamodel&quot;&gt;INTERLIS-Java-Metamodell&lt;/a&gt; verfügt, versuchte ich mit einem möglichst akkuraten Prompt mir Code für die UML-Herstellung generieren zu lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;I want to create a mermaidjs class diagram with Java. The input is a INTERLIS TransferDescription which you know kinda well. It is defined in the official INTERLIS compiler: https://github.com/claeis/ili2c and https://github.com/claeis/ili2c/blob/master/ili2c-core/src/main/java/ch/interlis/ili2c/metamodel/TransferDescription.java

What should be supported:

- For rendering: consider only models from last file (method TransferDescription.getModelsFromLastFile())
- Classes with attributes and cardinality. Please render attributes like &quot;Geometrie[1] : MultiSurface&quot;. Classes can be &quot;Abstract&quot;. Use a stereotype for this.
- Structures are like Classes. But with a stereotype &quot;Structure&quot;.
- Topics: Use namespaces for this and put the viewables in it.
- Classes can also be outside of a topic. Then place it on the root level (like the topic).
- Enumeration: Use &quot;Enumeration&quot; stereotype for this. Do not list the enumeration values.
- Show inheritance. If the base class is not in a model from the last file, render the base class on the root of the diagram and add a &quot;External&quot; stereotype to it.
- Show associations with cardinality on both sides.

Think before you start coding. Especially about a clean architecture and use a pattern if there is one the fits this task.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vielleicht hat es die eine oder andere Peinlichkeit drin aber ich war positiv vom Resultat überrascht. Nach etwas mehr als 1.5 Minuten kam eine recht brauchbare Variante heraus. Interessanterweise erfindet ChatGPT nicht zum ersten Mal eine Methode &lt;code&gt;getElements()&lt;/code&gt; für die Klasse &lt;code&gt;ch.interlis.ili2c.metamodel.Container&lt;/code&gt;. Und im vorliegenden Fall verwechselte ChatGPT die Klasse &lt;code&gt;ModelElement&lt;/code&gt; mit &lt;code&gt;Element&lt;/code&gt;. Wahrscheinlich könnte man beides noch in den Prompt einbauen und gut ist. Ebenso hat ChatGPT sich bei der einfachen Mermaid-Syntax leicht vertan. Aber all das war schnell gefixed. Ich benötigte noch zwei bis drei Iterationen weil ich im Ausgangsprompt einige Features vergessen hatte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der erste Härtetest kam beim Testen mit dem &lt;a href=&quot;https://models.geo.admin.ch/BAK/ISOS_V2.ili&quot;&gt;ISOS-MDM&lt;/a&gt;, das zu einer Exception führte. Und hier zeigt sich eine schöne Herausforderung, wenn die KI dir den Code schreibt: Habe ich genügend Wissen, um der KI überhaupt wieder genug Informationen zu füttern, damit sie den Bug fixen kann? Glücklich schätzen kann man sich, wenn das vor dem Go-live passiert. Im vorliegenden Fall wusste ich, wo es passiert und was am Modell speziell sein könnte (Hallo &amp;laquo;Kataloge&amp;raquo;!?). Mit diesen Inputs konnte ChatGPT den Fehler finden und beheben und das ISOS-MDM wird nun korrekt gerendert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Problem der unleserlichen Darstellung habe ich so gelöst, dass es neu eine zusätzliche Seite nur mit dem Klassendiagramm gibt. In der Seite kann man zoomen und pannen. Somit wird auch &lt;a href=&quot;https://geo.so.ch/modelfinder/uml?serverUrl=https://models.interlis.ch&amp;amp;file=core/IlisMeta16.ili&quot;&gt;unser aller liebstes INTERLIS-Datenmodell&lt;/a&gt; erlebbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p54/umldiagramm.png&quot; alt=&quot;UML Diagramm&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://geo.so.ch/modelfinder&quot; class=&quot;bare&quot;&gt;https://geo.so.ch/modelfinder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/modelfinder&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/modelfinder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #53 - INTERLIS-Compiler-Bindings für Python und Node.js</title>
      <link>http://blog.sogeo.services/blog/2025/07/22/interlis-leicht-gemacht-number-53.html</link>
      <pubDate>Tue, 22 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/07/22/interlis-leicht-gemacht-number-53.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;https://blog.sogeo.services/blog/2025/06/13/interlis-leicht-gemacht-number-50.html&quot;&gt;50. INTERLIS-Beitrag&lt;/a&gt; habe ich etwas über Verbesserungen der Developer Experience von INTERLIS geschrieben. Dafür habe ich eine Visual Studio Code Extension geschrieben, die drei Dinge macht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Prüft mit dem INTERLIS-Compiler die syntaktische Korrektheit eines Modelles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formatiert die Modelldatei (wie es auch der INTERLIS-Compiler macht).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Erstellt ein UML-Diagram (&lt;em&gt;PlantUML&lt;/em&gt; oder &lt;em&gt;Mermaid&lt;/em&gt;) aus einer Modelldatei.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Businesslogik dazu steckt nicht in der VS Code Extension, sondern die Modelldatei wird an einen Webserver geschickt, der diese drei Aufgaben übernimmt und das entsprechende Resultat zurückschickt. Kann man so machen, aber schöner wäre natürlich, wenn das alles lokal (direkt mit der Extension) möglich wäre. Ggf. will man seine Daten nicht irgendwem auf einen Server laden oder man will nicht selber einen solchen Server betreiben. Das erklärte Ziel ist also den &lt;a href=&quot;https://github.com/claeis/ili2c&quot;&gt;INTERLIS-Compiler&lt;/a&gt; &lt;em&gt;irgendwie&lt;/em&gt; in die VS Code Extension zu packen, aber selbstverständlich ohne von Java abhängig zu sein. In &lt;a href=&quot;https://blog.sogeo.services/blog/2025/07/20/interlis-leicht-gemacht-number-51.html&quot;&gt;meiner WebAssembly-Euphorie&lt;/a&gt; dachte ich, dass es damit geht. Aber das stellte sich als nicht machbar heraus. Die Backup-Variante ist nun folgende: Wir kompilieren &lt;em&gt;ili2c&lt;/em&gt; resp. einige Methoden davon mit &lt;a href=&quot;https://www.graalvm.org&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; in eine Native Shared Library. Anschliessend erstellen wir eine Node-Modul &amp;laquo;ili2c&amp;raquo;, welches auf diese Library (je nach OS ist das eine .dll-, .so- oder .dylib-Datei) zugreift und gegen aussen ein API definiert. D.h. als Benutzer möchte ich sowas machen können:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;npm install ili2c&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;const ili2c = require(&apos;ili2c&apos;);
const result = ili2c.compileModel(&quot;test/Test1.ili&quot;, &quot;ili2c.log&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit wäre der Grundstein gelegt, um das ili2c-Modul auch in der VS Code Extension zu verwenden. Der Weg dahin war aber nicht ganz so einfach und trivial und straight forward wie erwünscht. Es gab einige Hürden überwinden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der erste Schritt ist der einfachste. Das Herstellen der Native Shared Library. Ich habe das bereits ausführlich mit &lt;a href=&quot;https://blog.sogeo.services/blog/2022/12/11/interlis-leicht-gemacht-number-33.html&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt; und Python durchgespielt&lt;/a&gt;. Es gibt für die Java-Methoden, die in der Shared Library landen sollen, verschiedene Rahmenbedingungen. Eine davon ist, dass sie statisch sein muss und man kann nur beschränkt Objekte als Parameter übergeben und zurückliefern. Im vorliegenden Fall ist das alles nicht tragisch kompliziert, ich möchte z.B. die Modelldatei und eine Logdatei (resp. der jeweilige Pfad als String) übergeben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@CEntryPoint(name = &quot;compileModel&quot;)
public static int compileModel(IsolateThread thread, CCharPointer iliFile, CCharPointer logFile) {
    String iliFileStr = CTypeConversion.toJavaString(iliFile);
    String logFileStr = CTypeConversion.toJavaString(logFile);
    return compileModelImpl(iliFileStr, logFileStr);
}

public static int compileModelImpl(String iliFile, String logFile) {
    try {
        Files.deleteIfExists(new File(logFile).toPath());
    } catch (IOException e) {
        e.printStackTrace();
    }
    FileLogger fileLogger = new FileLogger(new File(logFile), false);
    EhiLogger.getInstance().addListener(fileLogger);

    EhiLogger.logState(&quot;ili2c-&quot;+TransferDescription.getVersion());
    EhiLogger.logState(&quot;ilifile &amp;lt;&quot; + iliFile + &quot;&amp;gt;&quot;);

    IliManager manager = new IliManager();
    manager.setRepositories(Ili2cSettings.DEFAULT_ILIDIRS.split(&quot;;&quot;));
    ArrayList&amp;lt;String&amp;gt; iliFiles = new ArrayList&amp;lt;String&amp;gt;();
    iliFiles.add(iliFile);
    Configuration config;
    try {
        config = manager.getConfigWithFiles(iliFiles);
    } catch (Ili2cException e) {
        EhiLogger.getInstance().removeListener(fileLogger);
        fileLogger.close();
        return 1;
    }

    DateFormat dateFormatter = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);
    Date today = new Date();
    String dateOut = dateFormatter.format(today);

    TransferDescription td = null;
    try {
        td = ch.interlis.ili2c.Ili2c.runCompiler(config);
    } catch (Ili2cFailure e) {
        EhiLogger.logError(&quot;...compiler run failed &quot; + dateOut);
        EhiLogger.getInstance().removeListener(fileLogger);
        fileLogger.close();
        return 1;
    }

    EhiLogger.logState(&quot;...compiler run done &quot; + dateOut);
    EhiLogger.getInstance().removeListener(fileLogger);
    fileLogger.close();
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Methode mit der Annotation &lt;code&gt;@CEntryPoint&lt;/code&gt; ist die Methode, die exponiert wird. Die Aufsplittung in zwei Methoden habe ich gemacht, damit ich in Java den Grossteil der Businesslogik testen kann. Das Resultat sind Header-Dateien (.h-Files) und die betriebssystemabhängigen Libraries. Mit den Github Actions kann man für Linux (amd64), Windows (amd64) und macOS (amd64 &lt;em&gt;und&lt;/em&gt; arm64) Binaries herstellen. Das bedeutet, dass das zukünftige ili2c-Node-Modul auf anderen Betriebssystemen und anderen Architekturen nicht funktionieren wird (was aber eher Exoten wären). Die Libraries sind keine statisch gelinkten Libraries, sondern benötigen z.B. unter Linux und macOS &lt;code&gt;libc&lt;/code&gt; und &lt;code&gt;zlib&lt;/code&gt;. Das ist aber in der Regel vorhanden und die Libraries funktionieren somit out-of-the-box. Auch unter Windows sollten sie soweit ohne weiteres Zutun funktionieren. Unter Linux kann man wirkliche statisch gelinkte Libraries herstellen, indem man sie gegen die musl-libc-Implementierung linkt. Dann könnte man die Bibliotheken z.B. auch in &lt;code&gt;FROM scratch&lt;/code&gt; Dockerimages verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ansprechen der Shared Native Libraries im Node-Module war sehr mühsam. Es gibt verschiedene Varianten wie das gemacht werden kann und als bequemer Mensch möchte man natürlich die vermeintlich einfachste Variante verwenden. Daraus wurde aber nichts. Die m.E. einfachere Variante ist das Laden der Library mit purem JavaScript. Dazu wird z.B. das &lt;code&gt;node-ffi-napi&lt;/code&gt;-Modul benötigt. Und hier beginnt der Spiessrutenlauf. Das funktioniert nicht auf einem Apple Silicon Gerät. Dann gibt es Forks und Forks von Forks und neue Ansätze, die aber allesamt bei mir überhaupt nicht funktionierten oder zu einem Segfault führten. Somit musste ich die Kröte schlucken und die zweite Variante angehen. Bei dieser muss man ein natives Nodes.js Addon in C/C++ schreiben und gegen meine Shared Library linken. Da und bei der richtigen Konfiguration der Befehle etc. half mir mein neuer 23-Franken-Mitarbeiter (ChatGPT) sehr. Ich weiss nicht, ob ich das ohne ihn hingekriegt hätte. Gut, man hätte wahrscheinlich in einem Forum oder in einer Mailingliste nachgefragt. Ein Auszug aus diesem Addon:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-cpp&quot; data-lang=&quot;cpp&quot;&gt;Napi::Value CompileModel(const Napi::CallbackInfo&amp;amp; info) {
    Napi::Env env = info.Env();

    if (thread == nullptr) {
        Napi::Error::New(env, &quot;Isolate not initialized. Call initIsolate first.&quot;).ThrowAsJavaScriptException();
        return env.Null();
    }

    if (info.Length() &amp;lt; 2 || !info[0].IsString() || !info[1].IsString()) {
        Napi::TypeError::New(env, &quot;Expected (string iliFile, string logFile)&quot;).ThrowAsJavaScriptException();
        return env.Null();
    }

    std::string iliFile = info[0].As&amp;lt;Napi::String&amp;gt;();
    std::string logFile = info[1].As&amp;lt;Napi::String&amp;gt;();

    int res = compileModel(thread, const_cast&amp;lt;char*&amp;gt;(iliFile.c_str()), const_cast&amp;lt;char*&amp;gt;(logFile.c_str()));

    if (res &amp;lt; 0) {
        Napi::Error::New(env, &quot;compileModel critical failure with code: &quot; + std::to_string(res)).ThrowAsJavaScriptException();
        return env.Null();
    }

    // 0 means success, 1 means model compile failure
    return Napi::Boolean::New(env, res == 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der JavaScript-Code, der mittels Node-API meine Libs anspricht und gegen aussen ein einfaches API definiert, ist eher trivial:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;const path = require(&apos;path&apos;);

const platform = process.platform;
const arch = process.arch;

let runtime = &quot;node&quot;;
if (process.versions.electron) {
  runtime = &quot;electron&quot;;
}

if (process.platform === &quot;win32&quot;) {
  const dllFolder = path.join(__dirname, &apos;prebuilds&apos;, `${platform}-${arch}`);
  process.env.PATH = `${dllFolder};${process.env.PATH}`;
}

const nativePath = path.join(__dirname, &apos;prebuilds&apos;, `${platform}-${arch}`, runtime, &apos;ili2c.node&apos;);
const native = require(nativePath);

let initialized = false;

// wrapper function that automatically handles isolate
function compileModel(iliFile, logFile) {
  if (!initialized) {
    native.initIsolate();
    initialized = true;
  }
  return native.compileModel(iliFile, logFile);
}

function prettyPrint(iliFile) {
  if (!initialized) {
    native.initIsolate();
    initialized = true;
  }
  return native.prettyPrint(iliFile);
}

// auto-teardown on exit
process.on(&apos;exit&apos;, () =&amp;gt; {
  if (initialized) {
    native.tearDownIsolate();
  }
});

module.exports = {
  compileModel, prettyPrint
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das ist aber nicht das Ende des Leidenswegs. Das wird tiptop mit einer normalen Node.js-Anwendung funktionieren aber nicht mit Visual Studio Code. VS Code basiert auf Electron. Electron verwendet wie auch Node.js &lt;em&gt;V8&lt;/em&gt;. Aber in einer anderen ABI-Version, d.h. die Binaries sind untereinander nicht kompatibel. Das betrifft aber nicht meine Native Shared Library, sondern bloss das Addon. Dieses muss für Node &amp;laquo;pur&amp;raquo; und für Electron separat kompiliert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man das alles zusammen, ist es gar nicht mehr so wild. Vor allem dünkt mich, dass man es schon verstehen kann, was abgeht. Unterstützung benötigt man vor allem bei der korrekten Konfiguration der Befehle beim Herstellen der Binaries. Das Ganze resultierte jedoch zu einer umfangreichen Pipeline / Github Action. In meinem Fall habe ich alles in den gleichen Workflow in unterschiedliche Jobs gepackt. Ich bin momentan aber eher der Meinung, dass man sogar das Github-Repository - also den Code - eher wieder auseinanderpfrimeln sollte. Oder aber mindestens die Workflows trennen: ein Workflow inkl. Deployment der Native Shared Libraries, ein Workflow für die Node.js-Bindings und ein Workflow für die Python-Bindings. Heute führt eine kleine Änderung z.B. im Node.js-Binding-Code zu neuen Native Shared Libraries und auch neuen Python-Bindings. Das finde ich nicht gut.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In meiner nicht sehr elaborierten und first-ever Visual Studio Code &lt;a href=&quot;https://github.com/edigonzales/ili-vscode/&quot;&gt;Extension&lt;/a&gt; (muss mindestens bissle aufgeräumt werden, das README.md ist doch eher peinlich) kann ich das ili2c-Node-Modul als Abhängigkeit definieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;  &quot;dependencies&quot;: {
    &quot;ili2c&quot;: &quot;^0.0.27&quot;,
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man muss aufpassen, dass die Native Shared Libraries vor dem Paketieren der VS Code Extension an den richtigen Ort kopiert werden, sonst landen sie eben nicht in der Extension:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;  &quot;scripts&quot;: {
    &quot;vscode:prepublish&quot;: &quot;npm run package &amp;amp;&amp;amp; mkdir -p dist/prebuilds &amp;amp;&amp;amp; cp -r node_modules/ili2c/prebuilds/* dist/prebuilds/&quot;,
    ...
    &quot;watch&quot;: &quot;mkdir -p dist/prebuilds &amp;amp;&amp;amp; cp -r node_modules/ili2c/prebuilds/* dist/prebuilds/ &amp;amp;&amp;amp; npm-run-all -p watch:*&quot;,
    ...
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit Version &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=edigonzales.ili2c&quot;&gt;0.0.10 der Extension&lt;/a&gt; wird nun auf den Webservice komplett verzichtet und für das Pretty Printing und das Kompilieren des Modelles wird mein ili2c-Node-Modul mit den Native Shared Libraries verwendet. Das Herstellen des UML-Diagramms habe ich aus der Extension entfernt. Soweit funktioniert es bei mir unter macOS tadellos, ob es unter Linux und Windows gleich gut funktioniert, kann ich nicht prüfen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p53/vscode01.png&quot; alt=&quot;vscode 01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ist mit Python? Das ist eher ein Abfallprodukt und man kriegt das praktisch geschenkt. Dort gibt es keine ähnlichen Schwierigkeiten wie bei Node.js. Keine ABI-Unverträglichkeit und man kann ein Python-Paket zum Ansprechen der Native Shared Libraries verwenden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from ctypes import *
from importlib_resources import files
import platform

if platform.uname()[0] == &quot;Windows&quot;:
    lib_name = &quot;libili2c.dll&quot;
elif platform.uname()[0] == &quot;Linux&quot;:
    lib_name = &quot;libili2c.so&quot;
else:
    lib_name = &quot;libili2c.dylib&quot;

class Ili2c:
    @staticmethod
    def create_ilismeta16(iliFile: str, xtfFile: str) -&amp;gt; bool:
        lib_path = files(&apos;ili2c.lib_ext&apos;).joinpath(lib_name)
        # str() seems to be necessary on windows: https://github.com/TimDettmers/bitsandbytes/issues/30
        dll = CDLL(str(lib_path))
        isolate = c_void_p()
        isolatethread = c_void_p()
        dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))

        try:
            result = dll.createIlisMeta16(isolatethread, c_char_p(bytes(iliFile, &quot;utf8&quot;)), c_char_p(bytes(xtfFile, &quot;utf8&quot;)))
            return result == 0
        finally:
            dll.graal_tear_down_isolate(isolatethread)

    @staticmethod
    def compile_model(iliFile: str, logFile: str) -&amp;gt; bool:
        lib_path = files(&apos;ili2c.lib_ext&apos;).joinpath(lib_name)
        dll = CDLL(str(lib_path))
        isolate = c_void_p()
        isolatethread = c_void_p()
        dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))

        try:
            result = dll.compileModel(isolatethread, c_char_p(bytes(iliFile, &quot;utf8&quot;)), c_char_p(bytes(logFile, &quot;utf8&quot;)))
            return result == 0
        finally:
            dll.graal_tear_down_isolate(isolatethread)

    @staticmethod
    def pretty_print(iliFile: str) -&amp;gt; bool:
        lib_path = files(&apos;ili2c.lib_ext&apos;).joinpath(lib_name)
        dll = CDLL(str(lib_path))
        isolate = c_void_p()
        isolatethread = c_void_p()
        dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))

        try:
            result = dll.prettyPrint(isolatethread, c_char_p(bytes(iliFile, &quot;utf8&quot;)))
            return result == 0
        finally:
            dll.graal_tear_down_isolate(isolatethread)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Installieren kann man das Python-Paket ganz normal:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;pip install ili2c&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Modell kompilieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from ili2c import Ili2c
result = Ili2c.compile_model(&quot;test/Test1.ili&quot;, &quot;ili2c.log&quot;)
print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In Python habe ich eine weitere Methode exponiert (&lt;code&gt;create_ilismeta16&lt;/code&gt;). Diese erzeugt aus einem INTERLIS-Datenmodell die dazugehörige IlisMeta16-INTERLIS-Transferdatei. Nun kann man z.B. mit den XML-Fähigkeiten einer beliebigen Programmiersprache das INTERLIS-Datenmodell besser verstehen und daraus andere Dinge ableiten. Oder man spinnt - weil so spassig - mit XQuery 3.1 ein wenig rum und versucht aus dem XML Python Data Classes herzustellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xquery&quot; data-lang=&quot;xquery&quot;&gt;xquery version &quot;3.1&quot;;

declare namespace ili = &quot;http://www.interlis.ch/xtf/2.4/INTERLIS&quot;;
declare namespace IlisMeta16 = &quot;http://www.interlis.ch/xtf/2.4/IlisMeta16&quot;;

let $modelTid := &quot;SO_ARP_SEin_Konfiguration_20250115&quot;
let $classes :=
  //IlisMeta16:Class[
    starts-with(@ili:tid, concat($modelTid, &quot;.&quot;)) and
    IlisMeta16:Kind = &apos;Class&apos;
  ]

return
  string-join(
    for $cls in $classes
    let $clsName := $cls/IlisMeta16:Name/text()
    let $clsTid := string($cls/@ili:tid)
    let $attrs := //IlisMeta16:AttrOrParam[IlisMeta16:AttrParent/@ili:ref = $clsTid]
    return
      string-join((
        &quot;from dataclasses import dataclass&quot;,
        &quot;from typing import Optional&quot;,
        &quot;&quot;,
        &quot;@dataclass&quot;,
        &quot;class &quot; || $clsName || &quot;:&quot;,
        if (empty($attrs)) then
          &quot;    pass&quot;
        else
          string-join(
            for $attr in $attrs
            let $attrName := $attr/IlisMeta16:Name/text()
            let $doc := normalize-space($attr//IlisMeta16:Text/text())
            let $typeRef := $attr/IlisMeta16:Type/@ili:ref
            let $typeEl := //*[namespace-uri() = &apos;http://www.interlis.ch/xtf/2.4/IlisMeta16&apos; and @ili:tid = $typeRef]
            let $typeKind := name($typeEl)
            let $pyType :=
              if ($typeKind = &apos;IlisMeta16:TextType&apos;) then &quot;str&quot;
              else if ($typeKind = &apos;IlisMeta16:NumType&apos;) then &quot;float&quot;
              else if ($typeKind = &apos;IlisMeta16:DateType&apos;) then &quot;str&quot; (: or use date :)
              else &quot;Optional[str]&quot; (: default fallback :)
            return
              &quot;    &quot; || $attrName || &quot;: &quot; || $pyType || (
                if ($doc) then &quot;  # &quot; || $doc else &quot;&quot;
              )
          , codepoints-to-string(10))  (: newline :)
      ), codepoints-to-string(10))
  , codepoints-to-string(10) || codepoints-to-string(10))  (: double newline between classes :)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xquery&quot; data-lang=&quot;xquery&quot;&gt;from dataclasses import dataclass
from typing import Optional

@dataclass
class Gemeinde:
    Name: str  # Name der Gemeinde (gemäss amtlichem Verzeichnis)
    BFSNr: float  # Offizielle Gemeindenummer
    Geometrie: Optional[str]  # Geometrie
    Bezirk: str  # Name des Bezirks
    Handlungsraum: Optional[str]

from dataclasses import dataclass
from typing import Optional

@dataclass
class Gemeinde:
    BoundingBox: str  # Ausdehnung / Bounding Box
    Gruppen: Optional[str]  # Liste sämtlicher Themen-Gruppen.

from dataclasses import dataclass
from typing import Optional

@dataclass
class Gemeinde:
    pass

from dataclasses import dataclass
from typing import Optional

@dataclass
class Gruppe:
    pass

from dataclasses import dataclass
from typing import Optional

@dataclass
class Objektinfo:
    pass

from dataclasses import dataclass
from typing import Optional

@dataclass
class Thema:
    pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist jetzt nicht das Ende der Fahnenstange aber mit wenig schon einiges erreicht. Vieles fehlt natürlich: Aufzähltypen, abstrakte Klassen, Strukturen, qualifizierte Klassennamen, etc. pp. Meine Prompts waren sicher nicht allzu gut und zu detailliert. Dazu müsste ich sicher auch das Metamodell besser verstehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nochmals: Unter macOS funktioniert das alles tadellos. Wie es mit anderen Betriebssystemen aussieht, weiss ich nicht. Es gibt ein paar Tests in der Github Action, die auf jedem Betriebssystem laufen. Ob jedoch das Endprodukt wirklich überall funktionstüchtig ist, ist noch eine Unbekannte.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #52 - Neues vom Modelfinder</title>
      <link>http://blog.sogeo.services/blog/2025/07/21/interlis-leicht-gemacht-number-52.html</link>
      <pubDate>Mon, 21 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/07/21/interlis-leicht-gemacht-number-52.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vor Jahren habe ich eine &lt;a href=&quot;https://geo.so.ch/modelfinder&quot;&gt;Webanwendung&lt;/a&gt; geschrieben, die regelmässig (2x pro Tag) die INTERLIS-Modellablagen (genauer die ilimodels.xml-Dateien) durchforstet, diese mit &lt;a href=&quot;https://lucene.apache.org/&quot;&gt;&lt;em&gt;Lucene&lt;/em&gt;&lt;/a&gt; indexiert und via GUI durchsuchbar macht. Die Anwendung ist in sich selber genügend, d.h. es gibt keine Abhängigkeiten zu einer Datenbank o.ä. Das soll auch so bleiben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Klammer-Rant&amp;raquo;: Leider stellte sich heraus, dass eine solche Anwendung (genau wie auch &lt;a href=&quot;https://ilimodels.ch&quot; class=&quot;bare&quot;&gt;https://ilimodels.ch&lt;/a&gt;) nur halb sinnvoll ist: Es fehlt das gemeinsame Verständnis wie die ilimodels.xml-Datei abgefüllt werden soll. Welche Informationen gehören da rein? Eventuell fehlt auch noch ein Attribut oder auch zwei im darunterliegenden Datenmodell. Die Krux ist eben die, dass man nun nach &amp;laquo;Naturgefahren&amp;raquo; suchen kann, aber das MGDM nicht findet. Der Grund dafür ist, dass das MGDM in Englisch modelliert ist und der Name des Modelles &amp;laquo;Hazard_Mapping_LV95_V1_3&amp;raquo; ist. Nun gibt es im &lt;em&gt;ilimodels.xml&lt;/em&gt; das &lt;code&gt;Tags&lt;/code&gt;-Attribut, das beliebige Schlagwörter aufnehmen kann. Leider füllt hier Swisstopo nur das GeoIV-Nümmerli (z.B. 166.1) ab. Somit bringt die Indexierung dieses Attributes für das konkrete Problem auch nichts. Lange Rede, kurzer Sinn: Man findet heute eigentlich die gesuchten Modelle eher schlecht. &amp;laquo;Jemand&amp;raquo; müsste das endlich angehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von Zeit zu Zeit bin ich jedoch dankbar um unseren Modelfinder. Im Regelfall suche ich eigentlich eh nur unsere Modelle oder MGDM oder Kernmodelle. Via Modelfinder bin ich schneller als wenn ich einfach durchklicken würde. An unserer Lösung störte mich insbesondere die technische Umsetzung des Frontends. Dieses wurde mit &lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;&lt;em&gt;GWT&lt;/em&gt;&lt;/a&gt; umgesetzt. Mit &lt;em&gt;GWT&lt;/em&gt; schreibt man die JavaScript-Anwendung in Java und ein Transpiler wandelt den Java-Code in JavaScript um. Das funktioniert sehr gut und der Transpiler erzeugt effizientes JavaScript. Das ganze Drumherum ist aber für meine Usecases meistens ein Overkill. Oftmals habe ich bloss ein Suchfeld in dem der Benutzer z.B. nach Kartenlayer, nach Datensätzen oder ähnlichem suchen kann. Gefundene Objekte werden tabellarisch dargestellt. In diesem Fall sind es INTERLIS-Datenmodelle. Da ist nicht viel JavaScript drin. Man könnte natürlich JavaScript direkt schreiben, aber das will ich trotzdem lieber nicht, wenn ich es umgehen kann. Dabei kommt mir &lt;a href=&quot;https://htmx.org/&quot;&gt;&lt;em&gt;HTMX&lt;/em&gt;&lt;/a&gt; zu Hilfe. &lt;em&gt;HTMX&lt;/em&gt; scheint mir ein wenig HTML auf Steroiden zu sein. Mit zusätzlichen Attributen kann z.B. jedes beliebige HTML-Element HTTP-Requests machen. Und es müssen nicht mehr zwingend &lt;code&gt;click&lt;/code&gt; und &lt;code&gt;submit&lt;/code&gt; Events sein, welche diese Requests triggern. Das offizielle Quickstart-Beispiel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- have a button POST a click via AJAX --&amp;gt;
&amp;lt;button hx-post=&quot;/clicked&quot; hx-swap=&quot;outerHTML&quot;&amp;gt;
  Click Me
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was passiert da? Wenn man auf den Button klickt, wird ein AJAX-Request ausgeführt und der Inhalt des Buttons wird mit der Antwort ersetzt. Die Definition meines &lt;code&gt;input&lt;/code&gt;-Elementes (das eigentliche Suchfeld nach Datenmodellen) sieht mit &lt;em&gt;HTMX&lt;/em&gt; wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;input id=&quot;search-input&quot; class=&quot;search-input&quot;
    type=&quot;text&quot;
    name=&quot;query&quot;
    hx-get=&quot;models&quot;
    hx-target=&quot;#results&quot;
    hx-trigger=&quot;input keyup changed delay:300ms&quot;
    hx-headers=&apos;{&quot;Accept&quot;: &quot;text/html&quot;}&apos;
    autocomplete=&quot;off&quot;
    placeholder=&quot;Search for INTERLIS models...&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Templating-Engine verwende ich neuerdings &lt;a href=&quot;https://jte.gg/&quot;&gt;&lt;em&gt;jte&lt;/em&gt;&lt;/a&gt;. Mit &lt;a href=&quot;https://www.thymeleaf.org/&quot;&gt;&lt;em&gt;Thymeleaf&lt;/em&gt;&lt;/a&gt; wurde ich nie richtig warm. Entweder stimmt was mit mir nicht oder mit &lt;em&gt;Thymeleaf&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Setup kann ich den ganzen GWT-&amp;laquo;Ballast&amp;raquo; über Bord werfen. Damit verschwindet auch ein weiteres Projekt, welches nicht &lt;em&gt;Gradle&lt;/em&gt;, sondern &lt;em&gt;Maven&lt;/em&gt; als Build-Tool verwendet. Nichts gegen &lt;em&gt;Maven&lt;/em&gt; per se aber ich möchte mich bei uns auf ein Build-Tool beschränken und es fällt mir einfacher mit &lt;em&gt;Gradle&lt;/em&gt; zu arbeiten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Startseite mit den Suchresultaten sieht nicht viel anders aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p52/modelfinder01.png&quot; alt=&quot;modelfinder 01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben den technischen (weniger sichtbaren) Neuerungen gibt es ein paar funktionale Neuerungen: Neu gibt es eine Detailansicht eines Datenmodelles. Die Detailansicht beinhaltet die Attribute aus der ilimodels.xml-Datei. Das hilft v.a. auch für das Aufdecken von Flüchtigkeitsfehlern. Interessanter wird es bei der Darstellung des Modelles. Es gibt eine textuelle Vorschau des Modelles mit Syntax Highlighting. Das Modell wird nicht zur Laufzeit vom Repository gelesen, sondern liegt ebenfalls im Suchindex vor. Das Syntax Hightlighing mache ich mit &lt;a href=&quot;https://prismjs.com/&quot;&gt;&lt;em&gt;Prism&lt;/em&gt;&lt;/a&gt;. Die dazugehörige Konfiguration habe ich der Konfiguration der &lt;a href=&quot;https://github.com/GeoWerkstatt/vsc_interlis2_extension&quot;&gt;Visual Studio Code INTERLIS Extension&lt;/a&gt; entnommen und mittels &lt;em&gt;ChatGPT&lt;/em&gt; umgewandelt. Das hat auf Anhieb funktioniert. Neben der textuellen Vorschau gibt es eine visuelle Vorschau mittels UML-Diagramm. Das UML-Diagramm wird mit bereits &lt;a href=&quot;https://blog.sogeo.services/blog/2025/06/13/interlis-leicht-gemacht-number-50.html&quot;&gt;vorhandenem Code&lt;/a&gt; zur Laufzeit hergestellt. Zur Laufzeit weil ich beim Indexieren nicht die Modelle kompilieren will. Einige Modelle können nicht dargesestellt werden. Das liegt meines Erachtens jedoch an Fehlern in meinem Code. Diesen möchte ich eh nochmals refactoren und schauen, ob er gänzlich ohne UML/INTERLIS-Editor-Code auskommt. Ich bin nicht ganz sicher wie resp. wo ich das Diagramm darstellen will. Unterhalb des Modelles ist nicht ganz glücklich. Eigentlich will man doch das Modell lesen können und das UML-Diagramm gleichzeitig sehen können? Mal schauen, ob mir da noch eine gute Idee kommt. Für &lt;a href=&quot;https://geo.so.ch/modelfinder/modelmetadata?serverUrl=http://models.geo.zh.ch&amp;amp;file=ARE/Abstandslinien_ZH_Master_V5.ili&quot;&gt;grössere Modelle&lt;/a&gt; muss ich noch das Zoom-und-Pan-JavaScript-Plugin einbauen. Aber so ganz grundsätzlich bin ich eigentlich zufrieden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p52/modelfinder02.png&quot; alt=&quot;modelfinder 01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie bereits die alte Version des Modelfinders wollte ich auch die neue Version mit &lt;em&gt;GraalVM&lt;/em&gt; in ein Native Image runterkompilieren und damit Ressourcen sparen. Das stellte sich aber als Unmöglichkeit heraus, weil die UML-Diagramm-Abhängigkeit UML/INTERLIS-Editor-Code verwendet, der wiederum AWT-Klassen referenziert. Das ist mit &lt;em&gt;GraalVM&lt;/em&gt; noch ein Gefummel. Mit der Liberica-Version von &lt;em&gt;GraalVM&lt;/em&gt; sollte es funktionieren. Bei mir nur theoretisch. Ich habe dann einen anderen Weg beschritten, um die Startup-Zeit und den Ressourcenbedarf der Java-Anwendung zu reduzieren. Java versucht mit dem &lt;a href=&quot;https://openjdk.org/jeps/8335368&quot;&gt;Projekt Leyden&lt;/a&gt; diese Fragestellungen anzugehen und auch ohne das Runterkompilieren der gesamten Anwendung in ein Native Image zu lösen. In einem &lt;a href=&quot;https://bell-sw.com/blog/how-to-use-cds-with-spring-boot-applications/&quot;&gt;Bell Software Blogbeitrag&lt;/a&gt; wird gezeigt, was heute mit Java 24 bereits möglich ist. Die Herstellung des Dockerimages wird zwar leicht komplizierter, die Startup-Zeit in unserem OpenShift-Cluster reduzierte sich aber von 2.5 Sekunden auf circa 0.8 Sekunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://geo.so.ch/modelfinder&quot; class=&quot;bare&quot;&gt;https://geo.so.ch/modelfinder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/modelfinder&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/modelfinder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #51 - INTERLIS im Browser</title>
      <link>http://blog.sogeo.services/blog/2025/07/20/interlis-leicht-gemacht-number-51.html</link>
      <pubDate>Sun, 20 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/07/20/interlis-leicht-gemacht-number-51.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Stück weit hat sich gedanklich eingebürgert, dass INTERLIS-Prüfungen nur via Webservice funktionieren. &amp;laquo;Schuld&amp;raquo; daran ist wohl die amtliche Vermessung mit dem dazugehörigen Checkservice. Damit wären wir auch gleich bei den positiven Aspekten eines Checkservices: Oftmals werden neben der eigentlichen Modellkonformität weitere Aspekte geprüft. Im Falle von &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; sind diese in einem sogenannten Validierungsmodell (z.B. &lt;a href=&quot;https://geo.so.ch/models/ARP/SO_Nutzungsplanung_20171118_Validierung_20231101.ili&quot;&gt;Nutzungsplanung&lt;/a&gt;) mit zusätzlichen INTERLIS-Constraints definiert. Manchmal benötigen diese Zusatzcontraints eigene INTERLIS-Funktionen. Also Funktionen, die nicht Bestandteil der INTERLIS-Spezifikation sind. Diese Funktionen (verpackt in einer Jar-Datei) müssen &lt;em&gt;ilivalidator&lt;/em&gt; bekannt gemacht werden. Das alles ist machbar aber nicht sehr anwenderfreundlich. Man kann das als Auftraggeber (im Sinne von INTERLIS-Datenerfassung an Dritte beauftragen) vereinfachen, indem man im &lt;a href=&quot;https://geo.so.ch/models/ilidata.xml&quot;&gt;Datenrepository&lt;/a&gt; ein sogenanntes Prüfprofil anlegt (entspricht einer metaConfig-Datei, z.B. &lt;a href=&quot;https://geo.so.ch/models/ARP/SO_Nutzungsplanung_20171118_20231101-meta.ini&quot;&gt;Nutzungsplanung&lt;/a&gt;). Damit ist für die Auftragnehmer der ilivalidator-Aufruf viel einfacher:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar ilivalidator.jar --metaConfig ilidata:SO_Nutzungsplanung_20171118_20231101-meta npl.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und schon werden sämtliche vom Auftragnehmer zusätzlich definierten Constraints und andere Spezialkonfigurationen bei der Validierung angewendet. Ist die Prüfung jedoch abhängig von zusätzlichem Code (also den zusätzlichen Funktionen), funktioniert es trotzdem nicht und der Auftragnehmer müsste die Jar-Datei(en) irgendwo herunterladen. Also auch zu kompliziert, insbesondere wenn im Jahre 2025 bereits ein Konsolenaufruf eine Herausforderung ist. Das ilivalidator-GUI hilft hier halt auch nicht weiter (&amp;laquo;Was? Ilivalidator hat ein GUI?!&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus diesem Grund ist ein zentral angebotener INTERLIS-Checkservice (inkl. sinnvoller maschineller Schnittstelle) nicht die dümmste Idee. Ein Grund warum &lt;em&gt;ilivalidator&lt;/em&gt; überhaupt entstanden ist, war das Ziel, ihn frei verfügbar zu machen. Damit ist es auch möglich den (Prüf-)Algorithmus zu den Daten zu bringen und nicht die Daten zum Algorithmus. Der zentrale Checkservice hat entsprechend auch Nachteile: Die Daten verlassen die Organisation (oftmals zu einem Drittanbieter) und der Checkservice-Anbieter muss theoretisch beliebig viel Last verarbeiten können resp. er hat keine Ahnung, was ihn erwartet. Wir haben INTERLIS-Validierungen, die circa 8GB RAM benötigen. Dies aber nur ein paar Mal pro Jahr. Da wäre es eben einfacher, wenn der Auftragnehmer/Datenerfasser auf seinem Rechner (der wahrscheinlich mehr als das Doppelte an Arbeitsspeicher aufweist) die Validierung durchführen könnte und nicht wir mit unserem ilivalidator-web-service und diesem für drei Aufrufe pro Jahr 8GB RAM reservieren müssen. Ein weiteres Übel für den Diensteanbieter ist der Platzbedarf. Einerseits wenn die Transferdateien sehr gross werden (hier zudem noch die Firewall dazu kommt) und andererseits die temporären Dateien, die während der Validierung angelegt werden. Plattenplatz ist zwar relativ billig aber skaliert dann trotzdem nicht gegen unendlich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun könnte man doch quasi &amp;laquo;best of both worlds&amp;raquo; anbieten: Einen INTERLIS-Checkservice, der die Transferdateien aber nicht auf einen fremden Server hochlädt, sondern lokal im Browser prüft. Wie das? Als erstes kommt einem &lt;a href=&quot;https://webassembly.org/&quot;&gt;WebAssembly&lt;/a&gt; in den Sinn. Die Frage ist also bloss, wie mache ich aus dem &lt;em&gt;ilivalidator&lt;/em&gt; eine WASM-Datei? Es stellt sich natürlich heraus, dass das nicht wirklich simpel ist. &lt;a href=&quot;https://www.graalvm.org&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; hat eine &lt;a href=&quot;https://www.graalvm.org/webassembly/docs/&quot;&gt;WebAssembly-Laufzeitumgebung&lt;/a&gt; aber ich will das Gegenteil. Und das &lt;a href=&quot;https://graalvm.github.io/graalvm-demos/native-image/wasm-javac/&quot;&gt;Gegenteil&lt;/a&gt; steckt noch sehr in den Kinderschuhen. Ein zweites sehr interessantes Projekt ist &lt;a href=&quot;https://teavm.org/&quot;&gt;&lt;em&gt;TeaVM&lt;/em&gt;&lt;/a&gt;: Ein Ahead-of-Time Compiler, der aus Java-Bytecode JavaScript und WebAssembly erzeugt. Voller Vorfreude machte ich mich an einen Test aber schon bald stellte sich Ernüchterung ein. Man kann nicht einfach beliebige Java-Bibliotheken mir nichts, dir nichts nach WebAssembly kompilieren. Das hat damit zu tun, dass in WebAssembly nicht alles möglich ist, was mit Java geht und dass nicht alles was Java hergibt von &lt;em&gt;TeaVM&lt;/em&gt; unterstützt wird. Zuerst musste ich SQLException-Klasse stubben nur um dann bei den antlr-Aufrufen von &lt;em&gt;ili2c&lt;/em&gt; und den XML-Klassen zu scheitern. Also WebAssembly in dieser Form ad acta gelegt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ganz aufgeben kann/will man dann doch nicht. Es gibt noch &lt;a href=&quot;https://cheerpj.com&quot;&gt;&lt;em&gt;CheerpJ&lt;/em&gt;&lt;/a&gt;: Das ist eine (vollständige) JVM für den Browser in WebAssembly und JavaScript. Ein Fokus liegt z.B. auf der Weiterverwendung von Legacy-Apps (z.B. Applets oder Swing-Anwendungen). Man kann aber auch headless beliebige Java-Bibliotheken ansprechen. Gesagt, getan. Der Einfachheit halber habe ich &lt;em&gt;ilivalidator&lt;/em&gt; zu einer Fat Jar kompiliert. Somit muss ich mich nur um &lt;em&gt;eine&lt;/em&gt; Jar-Datei kümmern. Die &lt;a href=&quot;https://labs.leaningtech.com/blog/cheerpj-4.1#licensing&quot;&gt;Lizenz&lt;/a&gt; sieht vor, dass man für Open Source Projekte &lt;em&gt;CheerpJ&lt;/em&gt; gratis verwenden darf. Man kann aber die JavaScript-Datei (und zu guter Letzt die Browser-JVM) nicht lokal hosten. Aber zum Ausprobieren reicht es. Man muss einzig eine &lt;em&gt;loader.js&lt;/em&gt;-Datei laden. Die ganze &amp;laquo;Anwendung&amp;raquo; sieht wie folgt aus (CSS-Teil weggelassen):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;
  &amp;lt;title&amp;gt;ilivalidator service&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://cjrtnc.leaningtech.com/4.2/loader.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;style&amp;gt;
    ...
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
  &amp;lt;script&amp;gt;
    (async function () {
      await cheerpjInit({ version: 17, status: &quot;none&quot; });
    })();
  &amp;lt;/script&amp;gt;

  &amp;lt;div id=&quot;overlay&quot;&amp;gt;
    &amp;lt;div class=&quot;spinner&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;main class=&quot;container&quot;&amp;gt;
    &amp;lt;h2&amp;gt;ilivalidator service&amp;lt;/h2&amp;gt;

    &amp;lt;form onsubmit=&quot;handleFile(event)&quot;&amp;gt;
      &amp;lt;input
        type=&quot;file&quot;
        id=&quot;fileInput&quot;
        accept=&quot;.xml,.xtf,.itf&quot;
        required
      /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;Validate&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

    &amp;lt;p id=&quot;status&quot;&amp;gt;&amp;lt;/p&amp;gt;

    &amp;lt;section id=&quot;logContainer&quot;&amp;gt;
      &amp;lt;h3&amp;gt;Validation Log&amp;lt;/h3&amp;gt;
      &amp;lt;pre id=&quot;logOutput&quot;&amp;gt;Loading...&amp;lt;/pre&amp;gt;
    &amp;lt;/section&amp;gt;
  &amp;lt;/main&amp;gt;

  &amp;lt;script&amp;gt;
    function handleFile(event) {
      event.preventDefault(); // prevent form from submitting

      const input = document.getElementById(&apos;fileInput&apos;);
      const file = input.files[0];
      const status = document.getElementById(&apos;status&apos;);
      const overlay = document.getElementById(&apos;overlay&apos;);
      const logContainer = document.getElementById(&apos;logContainer&apos;);
      const logOutput = document.getElementById(&apos;logOutput&apos;);

      if (!file) {
        status.textContent = &quot;Please select a file first.&quot;;
        return;
      }

      const reader = new FileReader();
      reader.onload = async function(e) {
        const content = e.target.result;
        const filename = file.name;
        const path = &quot;/str/&quot; + filename;
        const logPath = &quot;/files/&quot; + filename + &quot;.log&quot;;

        // Ensure cheerpOSAddStringFile exists
        if (typeof cheerpOSAddStringFile === &apos;function&apos;) {
          cheerpOSAddStringFile(path, content);
          status.textContent = `File &quot;${filename}&quot; loaded. Starting validation...`;
          overlay.style.display = &quot;flex&quot;;

          try {
            const exitCode = await cheerpjRunJar(&quot;/app/ilivalidator-1.14.8-SNAPSHOT-all.jar&quot;, &quot;--modeldir&quot;, &quot;https://geo.so.ch/models;https://geo.so.ch/models/mirror/interlis.ch/;https://geo.so.ch/models/mirror/geoadmin/&quot;, &quot;--log&quot;, logPath, path);
            status.textContent = `Validation finished with exit code ${exitCode}.`;

            const logBlob = await cjFileBlob(logPath);
            const logText = await logBlob.text();
            logOutput.textContent = logText;
            logContainer.style.display = &quot;block&quot;;
          } catch (err) {
            status.textContent = `Error during validation: ${err.message || err}`;
          } finally {
            overlay.style.display = &quot;none&quot;;
          }
        } else {
          status.textContent = &quot;Error: cheerpOSAddStringFile is not defined.&quot;;
        }
      };
      reader.readAsText(file);
    }
  &amp;lt;/script&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt zwei technische Herausforderungen damit man überhaupt die Anwendung zum Laufen kriegt: Da ist neu der Umstand, dass die INTERLIS-Modellablagen korrekt CORS konfiguriert haben müssen, weil &lt;em&gt;ilivalidator&lt;/em&gt; nun ja in einem Browser läuft und die Modelle suchen muss. Verschiedene Modellablagen sind nicht korrekt konfiguriert. Aus diesem Grund setze ich bewusst beim ilivalidator-Aufruf in Zeile 76 die &lt;code&gt;--modeldir&lt;/code&gt;-Option und verwende unsere Mirror-Repositories. Eine weitere Herausforderung ist der Umgang mit Dateien. Da ist einerseits die Konvention, dass die Jar-Dateien im Root-Verzeichnis liegen müssen und dann mit dem Pfad &lt;code&gt;/app/&amp;#8230;&amp;#8203;&lt;/code&gt; angesprochen werden müssen. Weitere Konventionen gelten für den &lt;a href=&quot;https://cheerpj.com/docs/guides/filesystem&quot;&gt;Austausch&lt;/a&gt; von Dateien zwischen JavaScript und Java, z.B. muss &lt;em&gt;ilivalidator&lt;/em&gt; die zu prüfende XTF-Datei bekannt gemacht werden. Und JavaScript muss am Ende auf den Inhalt der Logdatei, die &lt;em&gt;ilivalidator&lt;/em&gt; erstellt hat, zugreifen können. Funktionieren tut es grundsätzlich tadellos:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p51/ilivalidator-cheerpj-01.png&quot; alt=&quot;ilivalidator cheerpj&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Perfekte Welt? Mitnichten. Im Logfile sieht man den Memory, der der JVM zugewiesen wurde. Das sind 2GB. Ich weiss nicht, ob man das steuern kann. Lokal wird der JVM bei mir (mit 16GB) standardmässig 4GB zugewiesen. So wie ich es verstehe, stehen einer 32bit-WebAssembly-Anwendung anscheinend maximal 4GB zur Verfügung (gemäss Spezifikation) oder nur 2GB durch Browser-Limits. Will man &lt;em&gt;ilivalidator&lt;/em&gt; im Browser wirklich verwenden, müsste sicher mehr drinliegen als bloss die 2GB. Das andere und vielleicht gewichtigere Problem ist die Performance. Diese scheint mir nicht auf Java-Niveau zu sein. Dauert die Prüfung im Browser 8 Sekunden, ist es lokal bloss eine&amp;#8230;&amp;#8203; Upsi. Es erinnert mich an die Tests, die ich mit &lt;em&gt;ilivalidator&lt;/em&gt; gemacht habe und versuchte nur den Bytecode auszuführen (ohne das - während der Laufzeit - Runterkompilieren nach Maschinencode).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;INTERLIS im Browser scheint also nicht ganz so einfach zu sein und hat wohl auch einige Rahmenbedingungen, mit denen man leben muss. Dazu gehört sicher die Performance. Aber für den einen oder anderen (vielleicht exotischen) Usecase kann man mindestens mit &lt;em&gt;CheerpJ&lt;/em&gt; ganz einfach die ilitools-Welt in den Browser bringen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ilivalidator-local-web-service&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ilivalidator-local-web-service&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #50 - INTERLIS Developer Experience Improvements</title>
      <link>http://blog.sogeo.services/blog/2025/06/13/interlis-leicht-gemacht-number-50.html</link>
      <pubDate>Fri, 13 Jun 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/06/13/interlis-leicht-gemacht-number-50.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum &lt;a href=&quot;https://geobeer.ch&quot;&gt;Geobeer&lt;/a&gt;-Jubiläum (50. Ausgabe coming soon) gehe ich in Vorleistung und schreibe den 50. INTERLIS-leicht-gemacht-Beitrag. Es gibt ein paar Dinge, die mich beim täglichen Arbeiten mit INTERLIS und den Werkzeugen stören. Und ein paar Dinge, die ich zwar nicht wirklich benötige aber trotzdem hilfreich fände.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beginnen wir mit den störenden Dingen: Ich habe verschiedenste Versionen der verschiedenen INTERLIS-Tools installiert. Das kann zum Debuggen von Bugs manchmal ganz hilfreich sein (&amp;laquo;Das ging doch noch mit Version XY&amp;raquo;). Das Herunterladen und in den korrekten Ordner kopieren, ist jetzt nicht so ein Ding. Was mich grundsätzlich eher stört, ist dass ich immer &lt;code&gt;java -jar /Users/stefan/apps/ili2c-5.5.3/ili2c.jar fubar.ili&lt;/code&gt; tippen muss. Ja, jede Sekunde des Kantonsangestellten zählt. Ich bin grosser Fan von &lt;a href=&quot;https://sdkman.io/&quot;&gt;&lt;em&gt;SDKMAN!&lt;/em&gt;&lt;/a&gt;. Damit lassen sich einfach verschiedenste JDK und andere Java-Software installieren. Etwas Ähnliches für die INTERLIS-Werkzeuge habe ich mir immer gewünscht. Gescheitert ist es an meinem Unvermögen brauchbare Bash-Skripte zu schreiben. Times have changed: Nein, ich kann immer noch keine Bash-Skripte schreiben, aber ChatGPT kann es. Welcome &lt;a href=&quot;https://github.com/edigonzales/ilitools-manager&quot;&gt;&lt;em&gt;ilitools-manager&lt;/em&gt;&lt;/a&gt; (sehr origineller Name&amp;#8230;&amp;#8203;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie installiert man &lt;em&gt;ilitools-manager&lt;/em&gt;? Als erstes muss gesagt werden, dass es nur in einer Shell unter Linux oder macOS funktioniert resp. unter Windows mit WSL2. Jedoch nicht in der Powershell. Zudem muss &lt;em&gt;curl&lt;/em&gt; und &lt;em&gt;unzip&lt;/em&gt; installiert sein. Unter macOS ist das bereits dabei, in meiner nackten Ubuntu-Installation musste ich glaube &lt;em&gt;unzip&lt;/em&gt; installieren. Wenn diese Rahmenbedingungen erfüllt sind, reicht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -s https://raw.githubusercontent.com/edigonzales/ilitools-manager/refs/heads/main/install-ilitools.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend entweder ein Terminal neu öffnen oder &lt;code&gt;source ~/.bashrc&lt;/code&gt;. Dann sollte &lt;em&gt;ilitools&lt;/em&gt; im Pfad verfügbar sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/ilitools01.png&quot; alt=&quot;ilitools 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;ilitools&lt;/em&gt; kennt drei Befehle: Man kann sich für ein bestimmtes Werkzeug die vorhandenen Versionen anzeigen lassen, z.B. alle verfügbaren INTERLIS-Compiler, die man sich installieren lassen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/ilitools02.png&quot; alt=&quot;ilitools 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zurzeit werden die Werkzeuge &lt;em&gt;ili2c&lt;/em&gt;, &lt;em&gt;ilivalidator&lt;/em&gt; und &amp;laquo;oldie but goldie&amp;raquo; &lt;em&gt;UML/INTERLIS-Editor&lt;/em&gt; unterstützt. Zum Installieren einer Version reicht folgender Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/ilitools03.png&quot; alt=&quot;ilitools 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das erstellt mir zusätzlich ein Wrapper-Skript &lt;em&gt;ili2c&lt;/em&gt; mit dem ich den INTERLIS-Compiler starten kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/ilitools04.png&quot; alt=&quot;ilitools 4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich nochmals &lt;code&gt;ilitools l ili2c&lt;/code&gt; ausführe, zeigt es mir in der Liste an, welche Versionen ich installiert habe (&lt;code&gt;(installed)&lt;/code&gt;) und welches die vom Wrapper-Skript aktuell verwendete ist (&lt;code&gt;&amp;gt;&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/ilitools05.png&quot; alt=&quot;ilitools 5&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;ilitools u ili2c 5.6.2&lt;/code&gt; kann ich die zu verwendende Version ändern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Woher die Liste mit den Versionen stammt? Ich verwende die &lt;a href=&quot;https://jars.interlis.ch/ch/interlis/ili2c-tool/maven-metadata.xml&quot;&gt;maven-metadata.xml-Datei&lt;/a&gt; aus dem Maven-Repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nächstes Thema: &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;&lt;em&gt;Visual Studio Code&lt;/em&gt;&lt;/a&gt; wird dank der &lt;a href=&quot;https://github.com/GeoWerkstatt/vsc_interlis2_extension&quot;&gt;INTERLIS-Extension&lt;/a&gt; von &lt;a href=&quot;https://www.geowerkstatt.ch/&quot;&gt;GeoWerkstatt&lt;/a&gt; gerne zum Schreiben von INTERLIS-Modellen verwendet. Wie cool wäre es, wenn ich das Modell auch gleich mit dem INTERLIS-Compiler prüfen könnte und zwar ohne den Compiler installieren zu müssen? Wie soll das gehen? Viele Möglichkeiten dazu gibt es ja nicht. Ich habe mich dazu entschieden einen Webservice zu schreiben, der eine Datei entgegen nimmt, mit &lt;em&gt;ili2c&lt;/em&gt; die Datei kompiliert und das Logfile zurückschickt. Eingebettet in eine Visual Studio Code Extension. Es gibt einen global verfügbaren Webservice, der standardmässig in der Extension konfiguriert ist. Man kann den Service jedoch auch einfach in der eigenen Organisation betreiben, damit die Modelle nicht irgendwo in einem schwarzes Loch (auf einem Hetzner-Server in Deutschland) verschwinden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine besondere Anforderung habe ich an den Webservice gestellt: Er soll zukünftig fähig sein mit verschiedenen Compiler-Versionen umgehen zu können. Wie kann ich das mit &lt;em&gt;Spring Boot&lt;/em&gt; umsetzen? Frägt man unbedarft das Internet, kommt als erstes natürlich das Stichwort &amp;laquo;Microservice&amp;raquo; zurück. D.h. für jede Version des Compilers einen eigenen Webservice. Das ist mir aber irgendwie zu mühselig. Am anderen Ende der Fahnestange wäre die Variante mit einer Spring Boot Anwendung und dem dynamischen Laden der korrekten Version der ili2c-Abhängigkeiten. Das Stichwort hier ist &amp;laquo;Class Loader Isolation&amp;raquo;. Das wiederum scheint mir zu heikel zu sein, da mein Verständnis dafür auch eher beschränkt ist. Den Mittelweg, den ich eingeschlagen habe, ist das WAR-Deployment. Dabei wird anstelle einer Fat-JAR-Datei (inkl. embedded Tomcat) eine WAR-Datei klassisch in einer Tomcat-Instanz deployed. D.h. ich kann für zukünftige Compiler-Versionen eine neue WAR-Datei erstellen und in der gleichen Tomcat-Instanz deployen (innerhalb eines &lt;a href=&quot;https://github.com/edigonzales/ili-web-service-docker/blob/main/Dockerfile#L11&quot;&gt;Dockerfiles&lt;/a&gt;). Der Nachteil bei dieser Variante mit &lt;em&gt;Spring Boot&lt;/em&gt; ist, dass jeweils das gesamte &lt;em&gt;Spring Boot&lt;/em&gt; Framework ebenfalls in die WAR-Datei gepackt wird. Wenn man z.B. mit Jakarta EE unterwegs wäre, würde nur genau die Business-Logik (inkl. deren Abhängigkeiten) in die WAR-Datei gepackt. Es gäbe zwar verschiedene Workarounds für solche Thin-WAR-Dateien aber es sind halt Workarounds.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich zum Compiler möchte ich auch einen &lt;a href=&quot;https://blog.sogeo.services/blog/2025/04/13/interlis-leicht-gemacht-number-49.html&quot;&gt;INTERLIS-Modell-Pretty-Print-Service&lt;/a&gt; anbieten und weil wir gerade dabei sind, möchte ich aus dem &lt;em&gt;iliprettyprint&lt;/em&gt; mehr machen als bloss Modelle schön formatieren. Weil ich dazu die Bibliotheken vom UML/INTERLIS-Editor verwende, habe ich mir vorgestellt, dass man sicher relativ einfach auch UML-Diagramme erstellen kann. Das Modell ist nach dem Einlesen dahingehend interpretiert und strukturiert, dass das Herstellen von z.B. &lt;a href=&quot;https://plantuml.com/&quot;&gt;&lt;em&gt;PlantUML&lt;/em&gt;&lt;/a&gt;- und &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;&lt;em&gt;Mermaid&lt;/em&gt;&lt;/a&gt;-Code relativ einfach sein sollte. D.h. ich erstelle auch aus dem erweiterten iliprettyprint-Code eine WAR-Datei und deploye sie in einer Tomcat-Instanz.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Tomcat-Instanz laufen also zwei Anwendungen mit ingesamt drei API-Endpunkten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/ili2c/api/compile&quot; class=&quot;bare&quot;&gt;http://localhost:8080/ili2c/api/compile&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/iliprettyprint/api/prettyprint&quot; class=&quot;bare&quot;&gt;http://localhost:8080/iliprettyprint/api/prettyprint&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/iliprettyprint/api/uml&quot; class=&quot;bare&quot;&gt;http://localhost:8080/iliprettyprint/api/uml&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt müssen wir uns um die Visual Studio Code Extension kümmern. Dazu muss ich - wie beim &lt;em&gt;ilitools manager&lt;/em&gt; - ChatGPT verwenden. Erstes kann ich überhaupt kein TypeScript programmieren und zweitens wüsste ich nicht wo beginnen mit der Extension und wie das gepackt und publiziert wird. Auch mit der Hilfe von ChatGPT war es bisschen hakelig. Aber am Ende hat es dann doch relativ wenig Aufwand benötigt. Die Extension hat den fantasievollen Namen &amp;laquo;INTERLIS Tools&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode01.png&quot; alt=&quot;vscode 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man eine INTERLIS-Modell-Datei im Editor lädt, fällt auf - jedenfalls ist es bei mir so -, dass die GeoWerkstatt-Extension automatisch ein UML-Diagramm (Mermaid) erstellt. Als zweites fällt auf, dass das Diagramm Fehler aufweist. Ich glaube, das hat damit zu tun, dass im Mermaid-Code für ein Objekt nicht ein eindeutiger Identifikator verwendet wird, sondern es wird nur der Name verwendet. So auch bei der Definition von Vererbungen. Aus diesem Grund zeigen alle Vererbungen irgendwie auf sich selber.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode02.png&quot; alt=&quot;vscode 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für meine Extension kann man verschiedene Einstellungen vornehmen. Für jeden Service kann man den Endpunkt definieren und man kann zwischen zwei UML-Varianten wählen: &lt;em&gt;PlantUML&lt;/em&gt; und &lt;em&gt;Mermaid&lt;/em&gt;. Bei &lt;em&gt;PlantUML&lt;/em&gt; schickt der Service die gerenderte PNG-Datei zurück, bei Mermaid den &lt;em&gt;Sourcecode&lt;/em&gt;. Dieser wird in Visual Studio Code in einem eingebetteten Browser zu SVG gerendert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode03.png&quot; alt=&quot;vscode 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Kompilieren des INTERLIS-Modelles muss erstmalig mit &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; ausgewählt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode04.png&quot; alt=&quot;vscode 4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Allfällige Fehler werden in einem separaten Fenster geloggt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode05.png&quot; alt=&quot;vscode 5&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Modell-Datei wird anschliessend bei jedem Speichern geprüft:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode06.png&quot; alt=&quot;vscode 6&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ähnlich verhalten sich die Dienste für das Pretty Printing und das Herstellen des UML-Diagramms:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode07.png&quot; alt=&quot;vscode 7&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p50/vscode08.png&quot; alt=&quot;vscode 8&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So und jetzt das Ganze auch noch für &lt;a href=&quot;https://www.jedit.org/&quot;&gt;&lt;em&gt;jEdit&lt;/em&gt;&lt;/a&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hub.docker.com/repository/docker/sogis/ili-web-service/general&quot; class=&quot;bare&quot;&gt;https://hub.docker.com/repository/docker/sogis/ili-web-service/general&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili-web-service-docker&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili-web-service-docker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili2c-web-service&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili2c-web-service&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/iliPrettyPrint&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/iliPrettyPrint&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili-vscode&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili-vscode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>AI - Model Context Protocol (MCP)</title>
      <link>http://blog.sogeo.services/blog/2025/04/16/ai_mcp.html</link>
      <pubDate>Wed, 16 Apr 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/04/16/ai_mcp.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, ich steig&apos; jetzt ebenfalls auf den fahrenden MCP-Zug auf. Der neueste KI-Hype. Was ist das Model Context Protocol? Ich glaube, vereinfacht darf man sagen, dass es sich um eine standardisierte Variante von Function-Calling handelt. Der LLM erlaubt/ermöglicht man das Aufrufen bestimmer (zur Verfügung gestellter) Funktionen. Was die Funktionen machen und was sie zurückliefern, ist völlig offen. Erfunden hat das Protokoll &lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;Anthropic&lt;/a&gt;, die Firma hinter &lt;em&gt;claude.ai&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich denke schon, dass eine öffentliche Verwaltung gewisse Mehrwerte mit LLM schaffen kann. Vor allem, wenn man auf eigene Daten zugreifen kann. Nun ist das so eine Sache. Bei uns ist halt in der Regel halb richtig, ganz falsch. Wem gehört ein Grundstück? Da gibt es eine richtige Antwort. Bei der Verwendung von LLM für die Beantwortung dieser und ähnlicher Fragen kann MCP sehr gut helfen. Das LLM entscheidet selbständig, dass es auf die Frage nicht mit seinem Basiswissen antworten kann und macht eine Anfrage via MCP. Die MCP-Funktion frägt dann z.B. beim Grundbuch (via GBDBS-Schnittstelle) nach und liefert den Namen des Eigentümers / der Eigentümerin. Das LLM macht anschliessend einen überzeugenden, toll klingenden Satz daraus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um bei Demos nicht gleich zu riskieren, dass die Leute wegen &amp;laquo;LLM und Grundbuch&amp;raquo; in Ohnmacht fallen, wähle ich andere Testfragen. Zum Aufwärmen soll mir das LLM alle Gemeindenamen des Kantons zurückliefern. Ich möchte auch fragen können, ob es in einer Gemeinde schützenswerte Ortsbilder (ISOS) gibt. Und zu guter Letzt soll es mir sagen, von welchen ÖREB-Themen ein Grundstück in einer Gemeinde betroffen ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dazu braucht es einen MCP-Server und einen MCP-Client. Ich weiss nicht, ob es korrekt formuliert ist, aber ich würde sagen, dass der &lt;a href=&quot;https://modelcontextprotocol.io/introduction#general-architecture&quot;&gt;MCP-Client auch als LLM-Inferencer&lt;/a&gt; wirkt. Client und Server können gemäss Standard unterschiedlich &lt;a href=&quot;https://modelcontextprotocol.io/docs/concepts/transports&quot;&gt;miteinander kommunizieren&lt;/a&gt;: STDIO und Server-Sent Events. Für meine Tests reicht STDIO locker aus. Als Client wähle ich die &lt;a href=&quot;https://claude.ai/download&quot;&gt;Claude-Desktop-Anwendung&lt;/a&gt;. Für die Implementierung des Servers wähle ich natürlich &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://spring.io/projects/spring-ai&quot;&gt;&lt;em&gt;Spring AI&lt;/em&gt;&lt;/a&gt; und ein &lt;a href=&quot;https://www.danvega.dev/blog/creating-your-first-mcp-server-java&quot;&gt;Video&lt;/a&gt; von His Masters Voice Dan Vega. Im Grunde genommen, muss man in Spring nur eine Service-Klasse mit einer Methode erstellen, welche die Businesslogik (im folgenden Beispiel eine Datenbankabfrage) enthält und diese korrekt annotieren, z.B.:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@Service
public class GemeindeService {
    private static final Logger log = LoggerFactory.getLogger(GemeindeService.class);

    private final JdbcClient jdbcClient;

    public GemeindeService(@Qualifier(&quot;pubJdbcClient&quot;) JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    @Tool(name = &quot;sogis_get_area_of_municipalities&quot;, description = &quot;Alle offiziellen Namen der Gemeinden des Kantons Solothurn mit ihrer Fläche / ihrem Flächenmass in Quadratmeter.&quot;)
    public List&amp;lt;Gemeinde&amp;gt; getGemeinden() {
        return jdbcClient.sql(&quot;SELECT gemeindename AS name,ST_Area(geometrie) AS flaeche FROM agi_hoheitsgrenzen_pub_v1.hoheitsgrenzen_gemeindegrenze&quot;)
                .query(Gemeinde.class)
                .list();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Matchentscheidend ist die Beschreibung der Methode. Anhand dieser wird das LLM entscheiden für welche Frage es die Methode aufruft. Die Methoden müssen zusätzlich registriert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@Bean
public List&amp;lt;ToolCallback&amp;gt; sogisTools(
        GemeindeService gemeindeService,
        IsosService isosService,
        GrundstueckService grundstueckService,
        OerebService oerebService) {
    return List.of(ToolCallbacks.from(gemeindeService, isosService, grundstueckService, oerebService));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil die Kommunikation über STDIO geschieht, darf die Anwendung nichts loggen und keinen Banner in die Konsole rendern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;spring.main.banner-mode=off
logging.pattern.console=&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das ist aber auch schon alles. Den vollständigen Quellcode gibt es im &lt;a href=&quot;https://github.com/edigonzales/sogis-mcp-poc&quot;&gt;Github-Repo&lt;/a&gt;. Die Konfiguration des MCP-Clients / von Claude-Desktop geschieht über die JSON-Datei &lt;em&gt;claude_desktop_config.json&lt;/em&gt;. Diese kommt im Claude-Applikations-Verzeichnis zu liegen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;{
    &quot;mcpServers&quot; : {
        &quot;sogis-mcp&quot; : {
            &quot;command&quot; : &quot;/Users/stefan/.sdkman/candidates/java/21.0.4-graal/bin/java&quot;,
            &quot;args&quot; : [
                &quot;-jar&quot;,
                &quot;/Users/stefan/sources/sogis-mcp-poc/build/libs/sogis-mcp-poc-0.0.1-SNAPSHOT.jar&quot;
            ]
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn alles so ist, wie es sein soll, sollte das GUI wie folgt aussen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude01.png&quot; alt=&quot;claude01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wichtig ist das Hammer-Symbol mit der Zahl Vier. Das bedeutet, dass es vier verfügbare MCP Tools (Funktionen) gibt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude02.png&quot; alt=&quot;claude02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie man auf dem Screenshot erkennt, habe ich vier MCP-Funktionen implementiert. Bis auf die Letzte sind alles Datenbankabfragen. Die ÖREB-Funktion ruft natürlich den ÖREB-Webservice auf und wertet die zurückgelieferte XML-Datei aus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was liefert &lt;em&gt;Claude&lt;/em&gt; bei der einfachsten Frage? Freude herrscht: &lt;em&gt;Claude&lt;/em&gt; frägt, ob er den MCP-Server benutzen darf. Das kann so eingestellt werden, dass nicht bei jeder gestellten Frage wieder nachgefragt wird:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude03.png&quot; alt=&quot;claude03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Antwort des MCP-Servers kann man sich anzeigen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude04.png&quot; alt=&quot;claude04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat überzeugt. Er listet alle 106 Gemeinden auf. Lustigerweise hat &lt;em&gt;Claude&lt;/em&gt; auch schon 107 Gemeinden gelistet gehabt (Drei Höfe 2x). Der MCP-Server liefert natürlich immer das korrekte Resultat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetzt möchte ich wissen, ob es in Grenchen ein schützenswertes Ortsbild gibt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude05.png&quot; alt=&quot;claude05&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Antwort ist tiptop. &lt;em&gt;Claude&lt;/em&gt; listet mir sogar die von der MCP-Funktion gelieferten Links zum PDF-Objektblatt und zum Web GIS Client auf. Ich will noch wissen, ob es in Messen schützenswerte Ortsbilder gibt (ja, zwei) und in Kyburg-Buchegg, wobei ich bewusst den Gemeindenamen falsch schreibe:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude06.png&quot; alt=&quot;claude06&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude07.png&quot; alt=&quot;claude07&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Claude&lt;/em&gt; erfüllt die Aufgabe auch hier. Nun zur Champions League: Ich möchte wissen, von welchen ÖREB-Themen das Grundstück 4497 in Grenchen betroffen ist. Dazu muss &lt;em&gt;Claude&lt;/em&gt; zweimal eine MCP-Funktion aufrufen. Zuerst muss er aus Grundbuchnummer und Gemeindename den E-GRID ausfindig machen und anschliessend mit dem E-GRID eine andere MCP-Funktion aufrufen. Faszinierend ist für mich bereits, dass er Grundbuchnummer und Gemeindename korrekt der Funktion übergeben kann (und sie z.B. nicht vertauscht). Auch diese Aufgabe meistert &lt;em&gt;Claude&lt;/em&gt; problemlos:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_mcp/claude08.png&quot; alt=&quot;claude08&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wahrscheinlich lässt sich noch was rausholen, wenn man mit System-Prompts arbeiten würde/könnte und so ggf. die Themen selber besser beschreiben kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Löst MCP alle Probleme? Nein, insbesondere muss man aufpassen, dass man den Spagat hinkriegt zwischen sehr allgemeinen GIS-Fragestellungen und sehr spezifischen, die man mittels Funktionen beantworten will. Sind sie zu spezifisch, implementiert man sich wohl den Wolf. Sind sie zu allgemein, kann die LLM doch wieder keine schlüssige Antwort daraus formulieren.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #49 - Der UML/INTERLIS-Editor ist tot, lang lebe der UML/INTERLIS-Editor</title>
      <link>http://blog.sogeo.services/blog/2025/04/13/interlis-leicht-gemacht-number-49.html</link>
      <pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/04/13/interlis-leicht-gemacht-number-49.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der UML/INTERLIS-Editor wird völlig zu unrecht schlechtgeredet. Was gibt es daran auszusetzen? Spass beiseite. Aber so übel wie immer darüber gesprochen wird (von den Leuten, die eh nicht modellieren), ist er nicht. Es ist vielleicht nicht das sexieste Werkzeug ever aber interessanterweise kann ich auch seine positiven Seiten wertschätzen. Grundsätzlich denke ich, dass er logisch aufgebaut ist und entsprechend (halb-)intuitiv zu bedienen ist. Wenn man INTERLIS halt gar nicht versteht, kann man auch mit dem Editor keine Modelle erstellen. Mich dünkt, dass er mich das eine oder andere Mal definitiv was gelehrt hat aufgrund seines logischen Aufbaues. Was mich schaurig stört ist, dass Änderungen manchmal nicht übernommen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo Licht ist, ist auch Schatten: Wie oft habe ich schon den Dateinamen der ILI-Datei geändert und auf &amp;laquo;Apply&amp;raquo; oder &amp;laquo;OK&amp;raquo; geklickt und nichts ist passiert resp. der alte Namen stand wieder drin. Herrje. Oder wenn die Änderungen tatsächlich übernommen werden, dafür aber der komplette Element-Tree links zusammengeklappt wird. Übel. Und die Diagramme funktionieren zwar, das automatische Anordnen bei grösseren Modellen ist aber schlecht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich wurde innerhalb kurzer Zeit zweimal gefragt, warum wir überhaupt mit dem UML/INTERLIS-Editor die Modelle erstellen. In unserer INTERLIS-Anfangszeit wäre die Antwort wohl gewesen, weil es man so macht. Jahre später war die Antwort: vor allem damit die Modelldateien immer gleich und schön formatiert sind. Immerhin erstellen bei uns circa acht Personen Datenmodelle (und nicht nur der/die Lernende oder der Praktikant), was wahrscheinlich etwa der Hälfte aller UML/INTERLIS-Editor-Benutzern weltweit entspricht. Wenn es nur noch das ist, könnte man dann nicht einfach den Code aus dem UML/INTERLIS-Editor dazu nutzen, einen Pretty-Printer (als CLI-Tool) zu erstellen, der die ILI-Datei liest und schön formatiert wieder speichert?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst muss man den entsprechenden Code im Github-Repository finden. Dank der guten Suche wird man relativ schnell fündig. Es sind vor allem zwei resp. vier Klassen, die sich um den Import und Export von ILI-Dateien kümmern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/umleditor/blob/master/src/ch/ehi/umleditor/interlis/iliimport/ImportInterlis.java&quot;&gt;ImportInterlis.java&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/umleditor/blob/master/src/ch/ehi/umleditor/interlis/iliimport/TransferFromIli2cMetamodel.java&quot;&gt;TransferFromIli2cMetamodel.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/umleditor/blob/master/src/ch/ehi/umleditor/interlis/iliexport/ExportInterlis.java&quot;&gt;ExportInterlis.java&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/umleditor/blob/master/src/ch/ehi/umleditor/interlis/iliexport/TransferFromUmlMetamodel.java&quot;&gt;TransferFromUmlMetamodel.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Schwerstarbeit übernehmen die &amp;laquo;Transfer&amp;raquo;-Klassen. Nomen est Omen: Beim Import wandeln sie das Ili2c-Metamodell in ein internes UML-Editor-Metamodell um und umgekehrt. Somit muss man für meinen Pretty-Printer nur diese Klassen verwenden. Ein paar kleine Hürden gab es natürlich trotzdem:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Jar-Datei des UML/INTERLIS-Editors sind im Maven-Repository &lt;a href=&quot;https://jars.interlis.ch&quot; class=&quot;bare&quot;&gt;https://jars.interlis.ch&lt;/a&gt; publiziert und man kann sie in seinem Projekt ganz normal als Abhängigkeit definieren. Leider erschien eine Fehlermeldung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;FAILURE: Build failed with an exception.

* What went wrong:
Configuration cache state could not be cached: field `classpath` of task `:compileJava` of type `org.gradle.api.tasks.compile.JavaCompile`: error writing value of type &apos;org.gradle.api.internal.artifacts.configurations.DefaultUnlockedConfiguration&apos;
&amp;gt; Could not resolve all files for configuration &apos;:compileClasspath&apos;.
   &amp;gt; Could not resolve ch.interlis:umleditor:3.10.3.
     Required by:
         root project :
      &amp;gt; Could not resolve ch.interlis:umleditor:3.10.3.
         &amp;gt; Could not parse POM https://jars.interlis.ch/ch/interlis/umleditor/3.10.3/umleditor-3.10.3.pom
            &amp;gt; Missing required attribute: dependency groupId&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich gehe davon aus, dass es damit zusammenhängt, dass beim UML/INTERLIS-Editor nicht alle Abhängigkeiten aus einem Maven-Repository bezogen werden, sondern auch lokale verwendet werden (Mmmh, &lt;a href=&quot;https://github.com/claeis/umleditor/blob/master/lib/jhotdraw53.jar&quot;&gt;jhotdraw53&lt;/a&gt; ist 23 Jahre alt gemäss &lt;a href=&quot;https://sourceforge.net/projects/jhotdraw/files/JHotDraw/&quot;&gt;SourceForge&lt;/a&gt;&amp;#8230;&amp;#8203; und wo ist wohl der Quellcode dieser lokalen Libraries?). D.h. ich muss neben einigen der lokalen Libraries zusätzlich die UML/INTERLIS-Editor-Abhängigkeit in meinem Pretty-Printer &lt;a href=&quot;https://github.com/edigonzales/iliPrettyPrint/tree/main/lib&quot;&gt;lokal vorhalten&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine weitere Unschönheit ist, dass ich für den Export die Klasse &lt;code&gt;TransferFromUmlMetamodel&lt;/code&gt; copy/pasten musste und ein paar wenige Veränderungen vornehmen musste. Die Klasse ruft das &lt;a href=&quot;https://github.com/edigonzales/iliPrettyPrint/blob/main/src/main/java/ch/so/agi/pprint/TransferFromUmlMetamodel.java#L333&quot;&gt;GUI auf&lt;/a&gt;, das es bei mir nicht gibt. Und ich möchte von aussen steuern können, wo die schön formatierte ILI-Datei gespeichert wird. Letzten Endes aber Kleinigkeiten. Der Aufruf ist wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;java -jar iliprettyprint-0.0.11-all.jar --ili ../sources/iliPrettyPrint/src/test/data/SO_ARP_SEin_Konfiguration_20250115.ili --out ../tmp&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt zudem eine &lt;code&gt;--modeldir&lt;/code&gt;-Option, um Modell-Repositories wählen zu können. Herunterladen kann man den Pretty-Printer auf der &lt;a href=&quot;https://github.com/edigonzales/iliPrettyPrint/releases&quot;&gt;Github-Seite&lt;/a&gt;. Funktional ist er mit dem UML/INTERLIS-Editor-Prozess &amp;laquo;Modell-Import mit anschliessendem Modell-Export&amp;raquo; identisch. Wenn es in diesem Workflow &lt;a href=&quot;https://github.com/claeis/umleditor/issues/82&quot;&gt;Probleme gibt&lt;/a&gt;, gibt es sie auch beim &lt;em&gt;IliPrettyPrinter&lt;/em&gt;. Den Praxistest mit ein paar Modellen hat er bestanden. Erwähnenswert ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ein &lt;a href=&quot;https://geo.so.ch/models/AGI/GeoW_FunctionsExt_23.ili&quot;&gt;Funktionsmodell&lt;/a&gt; hat zu jeder Funktion einige Metaattribute und einen normalen Kommentar &lt;code&gt;!!sample = &quot;&amp;#8230;&amp;#8203;&quot;&lt;/code&gt;. Dieser normale Kommentar verschwindet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beim &lt;a href=&quot;https://vsa.ch/models/2020/VSADSSMINI_2020_2_d_LV95-20230807.ili&quot;&gt;Endgegner-Modell&lt;/a&gt; verschwinden die Metaattribute &lt;code&gt;!!@ comment = &quot;&amp;#8230;&amp;#8203;&quot;&lt;/code&gt; bei der Einheiten-Definition &lt;code&gt;UNIT&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Constraints werden auf einer Zeile geschrieben. Bei langen Expressions ergibt das &lt;a href=&quot;https://geo.so.ch/models/ARP/SO_Nutzungsplanung_20171118_Validierung_20231101.ili&quot;&gt;sehr lange Zeilen&lt;/a&gt;. Um diesen Umstand zu verbessern, bräuchte es zuerst Stilvorgaben.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;IliPrettyPrinter&lt;/em&gt; ist als Tool eigentlich überflüssig. Diese Funktion sollte in den Compiler eingebaut werden (aber besser ohne Editor-Abhängigkeiten) im Sinne einer Entwicklung zu einem Linter o.ä. Was ich mir ebenfalls noch anschauen möchte ist, ob der Export in eine &lt;a href=&quot;https://plantuml.com/&quot;&gt;PlantUML&lt;/a&gt;- oder &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;Mermaid&lt;/a&gt;-Datei einfach möglich ist. Dann entschärft sich eventuell auch die Diagramm-Situation. Ein andere Variante UML-Diagramme schöner (?) darzustellen, ist der Umweg über den &lt;a href=&quot;https://de.wikipedia.org/wiki/XML_Metadata_Interchange&quot;&gt;XMI-Output&lt;/a&gt; des Compilers und einem Werkzeug, das diese XMI-Datei darstellen kann. So richtig erfolgreich bin ich damit noch nicht geworden. Mit &lt;a href=&quot;https://eclipse.dev/papyrus/&quot;&gt;&lt;em&gt;Eclipse Papyrus&lt;/em&gt;&lt;/a&gt; ging was, scheint mir aber zu kompliziert zu sein. Jedenfalls schaffe ich es momentan nicht mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ich mir definitiv auch noch anschauen möchte, ist eine neue Theme für den UML/INTERLIS-Editor. Mit &lt;a href=&quot;https://www.formdev.com/flatlaf/&quot;&gt;&lt;em&gt;FlatLaf&lt;/em&gt;&lt;/a&gt; gibt es eine ansprechende Java-Swing-Theme. Ein quick &apos;n&apos; dirty Test zeigt, dass es funktioniert, jedoch die Icons ersetzt werden müssten. Diese sehen nun noch steinzeitlicher aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p49/umleditor.png&quot; alt=&quot;UML/INTERLIS-Editor mit neuer Theme&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und zu guter Letzt noch der pixelige Splashscreen ersetzen und die Hilfe mit einer funktionierenden und nachführten Online-Hilfe ersetzen und ich bin schon fast wieder richtig Fan vom UML/INTERLIS-Editor.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #48 - Der INTERLIS-Super-GAU</title>
      <link>http://blog.sogeo.services/blog/2025/04/11/interlis-leicht-gemacht-number-48.html</link>
      <pubDate>Fri, 11 Apr 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/04/11/interlis-leicht-gemacht-number-48.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ist passiert? Ich wollte ein nachgeführtes Modell in unsere &lt;a href=&quot;https://geo.so.ch/models&quot;&gt;INTERLIS-Modellablage&lt;/a&gt; (aka INTERLIS-Repository) publizieren. Wie es sich gehört, ist dieser Prozess automatisiert. Wir müssen bloss die Modelldatei in ein &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository&quot;&gt;Github-Repository&lt;/a&gt; einchecken und dann beginnt die Pipeline / die Github-Action zu laufen. Ein &lt;a href=&quot;https://plugins.gradle.org/plugin/ch.so.agi.interlis-repository-creator&quot;&gt;Gradle-Plugin&lt;/a&gt; sucht alle Modelldateien im Repository und liest die notwendigen Informationen aus diesen aus und erstellt die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei und ein Dockerimage, das automatisch in unseren OpenShift-Cluster deployed wird. Das Dockerimage wird vor dem Pushen in die Registry in der Pipeline mit &lt;code&gt;--check-repo-ilis&lt;/code&gt; &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/build.gradle#L133&quot;&gt;geprüft&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Unheil fing am 27. April 2025 an: Die Pipeline lief nicht mehr durch. Das kann schon mal vorkommen, kleiner Schluckauf irgendwo. Im konkreten Fall war es auch nicht super tragisch, da ich das nachgeführte Modell einfach temporär dort speichern kann, wo es auch verwendet wird. Damit konnte ich immerhin weiterarbeiten. Am nächsten Morgen geschaut, ob die Pipeline nun durchläuft. Mmmh, immer noch nicht. Die Logdatei der Action meldet folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p48/gh_action_log.png&quot; alt=&quot;Github Action Logfile&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein Gradle-Plugin findet die Mutter aller INTERLIS-Modellablagen nicht. Das Plugin muss das &lt;a href=&quot;https://models.interlis.ch/core/IliRepository20.ili&quot;&gt;IliRepository20&lt;/a&gt;-Modell kompilieren, um die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei herstellen zu können. Ich hatte den Compiler in &lt;a href=&quot;https://github.com/sogis/interlis-repository-creator/blob/ee9196c4eb8cce1e8b86fa70ecf25da64c6e52e7/src/main/java/ch/so/agi/tasks/ModelRepositoryCreator.java#L312&quot;&gt;meinem Code&lt;/a&gt; so konfiguriert, dass er nicht lokale Modelle verwendet, sondern das Modell in den externen Repositories sucht. Und genau das Repository mit dem gewünschten Modell war aus der Github Action nicht mehr erreichbar. Mit dem Repository schien jedoch alles in Ordnung zu sein. Mit dem Browser konnte ich darauf zugreifen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was machen? Eine E-Mail in die Runde ohne grosse Erkenntnisse und anschliessend im INTERLIS-Forum nachgeschaut: Immerhin hatten &lt;a href=&quot;https://interlis.discourse.group/t/models-interlis-ch-down/364&quot;&gt;andere auch Probleme&lt;/a&gt;. Ok, uncool aber handlebar und sowieso: Ich kann das Modell zu meinem Code kopieren und bin nicht mehr auf ein externes Repository angewiesen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oops! Diese Idee hat leider nur ein Problem: Um diese Änderungen am Gradle-Plugin vornehmen zu können, benötige ich INTERLIS-Bibliotheken vom INTERLIS-Maven-Repository &lt;a href=&quot;https://jars.interlis.ch&quot; class=&quot;bare&quot;&gt;https://jars.interlis.ch&lt;/a&gt;. Dieses wird - wie auch das Modell-Repository - auf einem Infomaniak-Server gehostet. Und ja, auch mit diesem Server gab es in den Github Actions Probleme. Die Bibliotheken konnten nicht gefunden werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p48/gh_action_log2.png&quot; alt=&quot;Github Action Logfile #2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es schien irgendwas mit der Kommunikation zwischen den Github-Actions-Servern und Infomaniak im Argen zu liegen. Es war Freitag und ich hätte natürlich warten können, dass sich das Problem bis Montag irgendwie selber löst. Aber wenn es Montag immer noch besteht, wird es mühsam, weil alle Mitarbeiter anders als gewohnt arbeiten müssten. D.h. als erstes musste ich irgendwie wieder an die INTERLIS-Bibliotheken kommen. Gott sei Dank hatte ich mich im letzten Sommer mit einem amtseigenen Maven-Repository-Caching-Proxy auseinandergesetzt. Die Idee dabei ist, dass wir eine Software betreiben, welche die verschiedenen Maven-Repositories zwischenspeichert. Ich müsste beim Builden der Java-Software nicht mehr die verschiedenen externen Maven-Repositories konfigurieren, sondern nur das eigene (z.B. &lt;a href=&quot;https://jars.so.ch&quot; class=&quot;bare&quot;&gt;https://jars.so.ch&lt;/a&gt;). Die Software lädt die benötigten Bibliotheken z.B. von &lt;a href=&quot;https://jars.interlis.ch&quot; class=&quot;bare&quot;&gt;https://jars.interlis.ch&lt;/a&gt; herunter und speichert sie bei sich. Damit ist sichergestellt, dass ich meine Java-Software auch builden kann, falls die externen Maven-Repositories down oder nicht erreichbar sind. Dieses Prinzip eines Caching-Proxies für die eigene Organisation ist keine Erfindung des Jahres 2025, sondern ist schon lange gute Praxis (auch aus anderen Gründen: &lt;a href=&quot;https://www.sonatype.com/blog/maven-central-and-the-tragedy-of-the-commons&quot;&gt;lesenswert!&lt;/a&gt;). Also 5-Euro-Hetzner-Server hochgefahren und Anleitung zu &lt;a href=&quot;https://reposilite.com/&quot;&gt;Reposilite&lt;/a&gt; rausgesucht und fertig war unser Maven-Caching-Proxy. Die Github-Action mit dem veränderten Gradle-Plugin lief wieder durch.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das war jedoch erst ein Teil der Lösung. Ich konnte zwar eine neue Plugin-Version in Betrieb nehmen aber das Herstellen der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei und das Prüfen des INTERLIS-Repositories gingen noch nicht, weil unsere Modelle wiederum Modelle vom nicht erreichbaren INTERLIS-Repository importieren und diese beim Kompilieren des eigenen Modelles benötigt werden. Da war schon einiges vorbereitet, das mir half: Nämlich konnte ich in einen Ordner einfach &amp;laquo;fremde&amp;raquo; Modelle reinkopieren, so als wären es eigene. Entsprechend findet der Compiler sie beim Kompilieren unserer Modelle. Ich musste also nur ein paar weitere Modelle in diesen Ordner kopieren und konnte beruhigt ins Wochenende.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich wusste aber, dass das auch nur ein Provisorium ist. Die Frage der Verfügbarkeit fremder Modelle (= ausserhalb des Kantonsnetzes) beschäftigt mich schon eine Weile. Bequemlichkeit hat mich davon abgehalten, dieses Problem halbwegs anständig zu lösen. Nun ist aber die Zeit gekommen. Ich habe mich entschieden, dass wir mittels &lt;code&gt;--clone-repos&lt;/code&gt; die Repositories klone, die wir unbedingt benötigen und diese als &amp;laquo;Mirror-Repo&amp;raquo; publizieren. Die Modelle kommen zwar technisch auf dem gleichen Server (resp. im gleichen Docker-Container) zu liegen wie unser &amp;laquo;Original-Repo&amp;raquo;, es sind aber zwei getrennte INTERLIS-Modellablagen. Ein Detail, das mich stört und ich ändern möchte: die URL ist momentan &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/build.gradle#L230&quot;&gt;hardcodiert&lt;/a&gt; und sollte konfigurierbar gemacht werden. Eine eher witzige Frage war, wie jetzt die Repositories miteinander verknüpft werden sollen. Die Suche nach einem Modell läuft nach dem &lt;a href=&quot;https://geostandards-ch.github.io/doc_ilirepo/#_bedeutung_2&quot;&gt;Breadth-First-Verfahren&lt;/a&gt;. Wir mussten ein wenig ausprobieren, damit wir - hoffentlich - die sinnvollste Variante fanden. Das &lt;a href=&quot;https://geo.so.ch/models/ilisite.xml&quot;&gt;&amp;laquo;Original-Repo&amp;raquo;&lt;/a&gt; kennt das &lt;a href=&quot;https://geo.so.ch/models/mirror/ilisite.xml&quot;&gt;&amp;laquo;Mirror-Repo&amp;raquo;&lt;/a&gt; nicht. Das &amp;laquo;Mirror-Repo&amp;raquo; wiederum kennt als &amp;laquo;Kind-Repo&amp;raquo;  das &amp;laquo;Original-Repo&amp;raquo;. Warum? Somit erreichen wir, dass &lt;code&gt;--modeldir &lt;a href=&quot;https://geo.so.ch/models/mirror&quot; class=&quot;bare&quot;&gt;https://geo.so.ch/models/mirror&lt;/a&gt;&lt;/code&gt; genügt, um sämtliche unserer Prozesse am Laufen zu halten. D.h. zuerst werden die Modelle in den geklonten Repositories gesucht und anschliessend im &amp;laquo;Original-Repository&amp;raquo;. Erst wenn das benötigte Modell in diesen Repos nicht gefunden wird, wird die Hierarchiestufe geändert, was nicht vorkommen darf.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fazit: Durch den Vorfall viel gelernt und die Prozesse robuster gemacht. Und das Problem hat sich Montag/Dienstag von selbst (?) &lt;a href=&quot;https://interlis.discourse.group/t/models-interlis-ch-down/364/2&quot;&gt;erledigt&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #47 - INTERLIS, das bessere JSON-Schema?</title>
      <link>http://blog.sogeo.services/blog/2025/03/24/interlis-leicht-gemacht-number-47.html</link>
      <pubDate>Mon, 24 Mar 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/03/24/interlis-leicht-gemacht-number-47.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich hätte ja gerne ein Ausrufezeichen im Titel verwendet. Nur so allgemeingültig ist die Aussage wohl nicht. Jedoch für uns könnte sie passen. Im &lt;a href=&quot;https://blog.sogeo.services/blog/2025/02/09/interlis-leicht-gemacht-number-46.html&quot;&gt;letzten Blogbeitrag&lt;/a&gt; habe ich geschrieben, dass bei uns viel Konfiguration anfällt. Ein Teil wird (zukünftig) originär in JSON von Mitarbeitern erfasst. Die Konfiguration der vielen Microservices im Umfeld des Web GIS Clients ist ebenfalls JSON. Diese ist teilweise sehr umfangreich und wird in der Regel nicht mehr von Hand erstellt. Zu jeder dieser Microservice-Konfiguration gibt es ein JSON-Schema, das die Konfiguration definiert. Im Fall des &lt;a href=&quot;https://github.com/qwc-services/qwc-elevation-service&quot;&gt;&amp;laquo;elevation&amp;raquo;-Services&lt;/a&gt; ist die Konfiguration maximal simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;$schema&quot;: &quot;https://github.com/qwc-services/qwc-elevation-service/raw/master/schemas/qwc-elevation-service.json&quot;,
  &quot;service&quot;: &quot;elevation&quot;,
  &quot;config&quot;: {
    &quot;elevation_dataset&quot;: &quot;/data/geodata/ch.so.agi.lidar_2014.dtm/ch.so.agi.lidar_2014.dtm.tif&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Service hat zwei Funktionen: eine liefert für eine Koordinate einen Höhenwert zurück, die zweite Funktion liefert Höhenwerte entlang eines Linestrings als Antwort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das dazugehörige Schema ist schon um einiges umfangreicher:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;$schema&quot;: &quot;http://json-schema.org/draft-07/schema#&quot;,
  &quot;$id&quot;: &quot;https://raw.githubusercontent.com/qwc-services/qwc-elevation-service/master/schemas/qwc-elevation-service.json&quot;,
  &quot;title&quot;: &quot;QWC Elevation Service&quot;,
  &quot;type&quot;: &quot;object&quot;,
  &quot;properties&quot;: {
    &quot;$schema&quot;: {
      &quot;title&quot;: &quot;JSON Schema&quot;,
      &quot;description&quot;: &quot;Reference to JSON schema of this config&quot;,
      &quot;type&quot;: &quot;string&quot;,
      &quot;format&quot;: &quot;uri&quot;,
      &quot;default&quot;: &quot;https://raw.githubusercontent.com/qwc-services/qwc-elevation-service/master/schemas/qwc-elevation-service.json&quot;
    },
    &quot;service&quot;: {
      &quot;title&quot;: &quot;Service name&quot;,
      &quot;type&quot;: &quot;string&quot;,
      &quot;const&quot;: &quot;elevation&quot;
    },
    &quot;config&quot;: {
      &quot;title&quot;: &quot;Config options&quot;,
      &quot;type&quot;: &quot;object&quot;,
      &quot;properties&quot;: {
        &quot;elevation_dataset&quot;: {
          &quot;description&quot;: &quot;Elevation dataset (file or URL). Example: https://data.sourcepole.com/srtm_1km_3857.tif&quot;,
          &quot;type&quot;: &quot;string&quot;
        }
      },
      &quot;required&quot;: [
        &quot;elevation_dataset&quot;
      ]
    }
  },
  &quot;required&quot;: [
    &quot;service&quot;,
    &quot;config&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für JSON-Liebhaber mag das Kunst sein, für mich sieht es eher leicht chaotisch aus. Inhaltlich dürfte so ein Schema wohl viele Anforderungen abdecken. Man kann z.B. Konstanten oder Default-Werte definieren. Irgendwie noch schick. Oder auch URL verlangen. Wobei man anscheinend zwei Aussagen dazu braucht: Das URL-Attribut muss vom Typ &amp;laquo;string&amp;raquo; sein und gemäss einer &amp;laquo;uri&amp;raquo; formatiert sein. Scheint mir irgendwie umständlich. Was wäre aber, wenn wir das JSON-Schema als einfach lesbares INTERLIS-Modell definieren könnten?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;INTERLIS 2.4;

MODEL Elevation (de) AT &quot;mailto:edigonzales@localhost&quot; VERSION &quot;20250324&quot; =

    TOPIC Elevation =

        STRUCTURE Config_ =
            elevation_dataset : TEXT*100;
        END Config_;

        CLASS Configuration =
            service : TEXT*100;
            config : Config_;
        END Configuration;

    END Elevation;

END Elevation.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und ja, im Modell fehlen die Kommentare (&amp;laquo;description&amp;raquo;), die im JSON-Schema vorhanden sind. Ebenfalls können keine Default-Werte und Konstanten gesetzt werden. Für beide Fragestellungen könnten z.B. Constraints verwenden werden. Und die Sache mit Modell &amp;#8594; Topic &amp;#8594; Class macht es auf den ersten Blick auch ziemlich &lt;em&gt;verbose&lt;/em&gt;. Und trotzdem: Auch wenn wir Kommentare und Constraints noch einpflegen würden, dünkt es mich klarer, aussagekräftiger und sauberer strukturiert. Schlichtweg eleganter. Aber dazu gibt es wahrscheinlich auch gegenteilige Meinungen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die zum INTERLIS-Modell passende JSON-Datei sieht wie folgt aus / muss wie folgt aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;[
    {
      &quot;@type&quot;: &quot;Elevation.Elevation.Configuration&quot;,
      &quot;@id&quot;: &quot;o1&quot;,
      &quot;@bid&quot;: &quot;bid1&quot;,
      &quot;@topic&quot;: &quot;Elevation.Elevation&quot;,
      &quot;service&quot;: &quot;elevation&quot;,
      &quot;config&quot;: {
        &quot;@type&quot;: &quot;Elevation.Elevation.Config_&quot;,
        &quot;elevation_dataset&quot; : &quot;/data/geodata/ch.so.agi.lidar_2014.dtm/ch.so.agi.lidar_2014.dtm.tif&quot;
      }
    }
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Boah, wird ja immer komplizierter und hässlicher!&amp;raquo; hört man sie sagen. Aber das hat alles schon seine Richtigkeit und Notwendigkeit. Aber der Reihe nach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bereits mit der ersten Version von &lt;a href=&quot;https://gretl.app&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; wollten wir neben INTERLIS-Dateien auch CSV- und Shapedateien validieren können. GeoPackage ist im Laufe der Zeit noch hinzugekommen. Zur Validierung von Shapedateien habe ich vor Jahren &lt;a href=&quot;https://blog.sogeo.services/blog/2018/02/19/interlis-leicht-gemacht-number-18.html&quot;&gt;was geschrieben&lt;/a&gt;. Im Prinzip geht das fast für jedes beliebige Format: man muss &amp;laquo;nur&amp;raquo; einen &lt;a href=&quot;https://github.com/claeis/iox-api/blob/master/src/ch/interlis/iox/IoxReader.java&quot;&gt;IoxReader&lt;/a&gt; &lt;a href=&quot;https://github.com/claeis/iox-wkf/blob/master/src/main/java/ch/interlis/ioxwkf/shp/ShapeReader.java&quot;&gt;implementieren&lt;/a&gt;. Anschliessend muss man einen &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/src/org/interlis2/validator/Validator.java&quot;&gt;Validator&lt;/a&gt; &lt;a href=&quot;https://github.com/sogis/gretl/blob/main/gretl/src/main/java/ch/so/agi/gretl/tasks/impl/ShpValidatorImpl.java&quot;&gt;erweitern&lt;/a&gt; und schon hat man seinen Custom-Format-Validator mit der Mächtigkeit von INTERLIS und &lt;em&gt;ilivalidator&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vor einiger Zeit haben wir ebensolche &lt;a href=&quot;https://github.com/claeis/iox-wkf/tree/master/src/main/java/ch/interlis/ioxwkf/json&quot;&gt;JsonReader und GeoJsonReader&lt;/a&gt; programmieren lassen. Jetzt ist die Zeit gekommen diese in Wert zu setzen und sie als Basis für einen JsonValidator zu verwenden. Der JsonValidator wird als &lt;a href=&quot;https://github.com/sogis/gretl/blob/V3_1_ili2duckdb/gretl/src/main/java/ch/so/agi/gretl/tasks/JsonValidator.java&quot;&gt;GRETL-Task&lt;/a&gt; umgesetzt. Ein Standalone-Werkzeug wäre aber mit minimalen Aufwand auch möglich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zurück zum hässlichen &amp;laquo;INTERLIS-JSON&amp;raquo;: Um möglichst viel von der Sprache INTERLIS verwenden zu können und Daten in JSON transportieren zu können, braucht es diese Metaattribute in der JSON-Datei. Man muss wissen welcher Basket geliefert wird, um welchen Klassentyp es sich beim konkreten Objekt handelt etc. pp. Für unseren momentanen Anwendungsfall/Spezialfall ist aber nur das Attribut &lt;code&gt;@type&lt;/code&gt; notwendig. D.h. ich möchte die JSON-Datei nicht unnötig aufblasen. Das löse ich, indem ich fehlende Metaattribute vor der Validierung &lt;a href=&quot;https://github.com/sogis/gretl/blob/V3_1_ili2duckdb/gretl/src/main/java/ch/so/agi/gretl/tasks/impl/JsonValidatorImpl.java#L70&quot;&gt;hinzufüge&lt;/a&gt;. Ebenso ein allfällig fehlendes Toplevel-Array. Den JsonReader lasse ich so sein, wie er ist. Unsere Konfigurationsdatei sieht abgespeckt so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &quot;@type&quot;: &quot;Elevation.Elevation.Configuration&quot;,
    &quot;service&quot;: &quot;elevation&quot;,
    &quot;config&quot;: {
        &quot;@type&quot;: &quot;Elevation.Elevation.Config_&quot;,
        &quot;elevation_dataset&quot; : &quot;/data/geodata/ch.so.agi.lidar_2014.dtm/ch.so.agi.lidar_2014.dtm.tif&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Unschönheit besteht/bestand noch: Ein JSON-Array mit z.B. Strings konnte nicht modelliert werden resp. nur über den Umweg mit INTERLIS-Strukturen. Das ist natürlich sehr umständlich. Weil mit INTERLIS 2.4 auch &lt;code&gt;LIST&lt;/code&gt; und &lt;code&gt;BAG&lt;/code&gt; mit einfachen Datentypen möglich ist, musste der JsonReader angepasst werden. &lt;a href=&quot;https://github.com/claeis/iox-wkf/pull/53&quot;&gt;Pullrequest&lt;/a&gt; ist gemacht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der JsonReader unterstützt auch Geometrien im Format von WKT. Ich kann z.B. folgendes Modell schreiben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;INTERLIS 2.4;

MODEL Test2 (de) AT &quot;mailto:edigonzales@localhost&quot; VERSION &quot;20250324&quot; =

    DOMAIN
        Coord2 = COORD
        2460000.000 .. 2870000.000,
        1045000.000 .. 1310000.000,
        ROTATION 2 -&amp;gt; 1;

    TOPIC Topic2 =

        CLASS ClassA =
            attrText : TEXT*60;
            attrArea : AREA WITH (STRAIGHTS, ARCS) VERTEX Coord2 WITHOUT OVERLAPS &amp;gt; 0.001;
        END ClassA;

    END Topic2;

END Test2.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der JsonValidator überprüft problemlos die Area-Bedingung für folgende JSON-Datei (und findet die Überlappung):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;[
    {
      &quot;@type&quot;: &quot;Test2.Topic2.ClassA&quot;,
      &quot;attrText&quot; : &quot;line0&quot;,
      &quot;attrArea&quot; : &quot;POLYGON ((2460000 1045000, 2460001 1045000, 2460001 1045001, 2460000 1045001, 2460000 1045000))&quot;
    },
    {
      &quot;@type&quot;: &quot;Test2.Topic2.ClassA&quot;,
      &quot;attrText&quot; : &quot;line1&quot;,
      &quot;attrArea&quot; : &quot;POLYGON ((2460000.5 1045000, 2460002 1045000, 2460002 1045001, 2460001 1045001, 2460000.5 1045000))&quot;
    }
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist INTERLIS das bessere JSON-Schema? Für uns glaub schon. Wir müssen uns nicht in eine neue Spezifikation kämpfen und können auch weiterhin die gleichen Werkzeuge und die gleiche Sprache verwenden. Zudem die Werkzeuge und Sprache sehr mächtig sind. Ein weiterer interessanter Aspekt ist, dass die Formatfrage so mehr und mehr in den Hintergrund rückt, weil das Format abstrahiert wird.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #46 - INTERLIS, GRETL und DuckDB: A match made in heaven</title>
      <link>http://blog.sogeo.services/blog/2025/02/09/interlis-leicht-gemacht-number-46.html</link>
      <pubDate>Sun, 9 Feb 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/02/09/interlis-leicht-gemacht-number-46.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In einer Geodateninfrastruktur fällt eine Unmenge an Konfiguration an. Unter Konfiguration verstehe ich in diesem Kontext nicht die direkte, technische Steuerung einer Software (z.B. wie viel RAM wird der Software zugewiesen), sondern z.B. die Darstellung eines WMS-Layers. Im Falle von QGIS wäre das eine QML-Datei. Oder auch das INTERLIS-Datenmodell kann man als Konfigurationsartefakt ansehen. Ebenso unsere &lt;a href=&quot;https://github.com/sogis/gretljobs&quot;&gt;GRETL-Jobs&lt;/a&gt;, die aus einer &lt;a href=&quot;https://gradle.org&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt; Build-Datei und in der Regel aus SQL-Dateien bestehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unsere Konfigurationen sind an unterschiedlichen Orten gespeichert und werden mit unterschiedlichen Ansätzen bearbeitet. Die Dienste (WMS, WFS, Dataservice) und die Datenabgabe werden über eine Webanwendung bearbeitet. Z.B. wählt man eine Datenbanktabelle aus, lädt die QML-Datei hoch und definiert unter Umständen noch ein paar Aliase für die Attributnamen. Diese Informationen werden anschliessend in einer Datenbank gespeichert und daraus die QGS-Datei für QGIS-Server hergestellt. Ein weiterer Teil unserer Konfigruation wird in Git-Repositories gespeichert. Dazu gehören die GRETL-Jobs und die Schema-Jobs (dienen zum Anlegen von Datenbankschemen mit &lt;a href=&quot;https://gretl.app&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt;). Das &amp;laquo;GUI&amp;raquo; ist der Texteditor nach Wahl. Zu guter Letzt liegt der Master sämtlicher Datenmodelle im &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository&quot;&gt;Datenmodell-Repository&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obwohl sich einiges über eine längere Zeit entwickelt hat, ist das Grundkonzept circa sechs bis sieben Jahre alt und in dieser Zeit konnten wir viele Erfahrungen sammeln. Obwohl wir nicht direkt unzufrieden mit der Webanwendung sind, bin ich der Meinung, dass wir erfolgreicher mit dem Git-Ansatz arbeiten. Das hängt vor allem damit zusammen, dass die Webanwendung sehr umfangreich geworden ist und immer mehr verknüpfte Informationen verwalten muss. Dadurch ist sie leider auch nicht intuitiver geworden. Funktional bietet sie viel: so kann ich z.B. auf Knopfdruck herausfinden, welche Datenbanktabelle in welchem WMS-Layer steckt. Dieses Wissen ist insbesondere bei Modelländerungen (und daraus folgend Datenbankschemaänderungen) wichtig.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Grundidee ist, dass wir sämtliche Konfiguration in Zukunft in einem Themen-Git-Repository speichern und&amp;#8201;&amp;#8212;&amp;#8201;falls immer sinnvoll möglich&amp;#8201;&amp;#8212;&amp;#8201;nur noch mit dem Texteditor arbeiten. Am liebsten hätten wir pro Datenthema ein eigenes Git-Repository aber wahrscheinlich machen wir ein Mono-Repo mit den Themen als Unterordner. Bei der Menge an Themen scheint uns das übersichtlicher zu sein. Nehmen wir als Beispiel das Thema &lt;a href=&quot;https://geo.so.ch/map/?k=c46753707&quot;&gt;&lt;em&gt;Bauzonengrenzen&lt;/em&gt;&lt;/a&gt;. Heute gibt es im GRETL-Jobs-Repository den Unterordner &lt;a href=&quot;https://github.com/sogis/gretljobs/tree/main/arp_bauzonengrenzen_pub&quot;&gt;&lt;em&gt;arp_bauzonengrenzen_pub&lt;/em&gt;&lt;/a&gt;. In diesem Unterordner steckt der GRETL-Job, der die Daten aus der Edit-DB herstellt und in der Pub-DB speichert. Ebenfalls gehört der Export nach XTF, GeoPackage und Shapefile dazu. Dieser GRETL-Job würde im Themen-Repo im Ordner &lt;em&gt;arp_bauzonen&lt;/em&gt; zukünftig im Unterordner &lt;em&gt;gretl&lt;/em&gt; zu liegen kommen. Der Schema-Job, mit dem wir das Schema in der Datenbank anlegen, im Unterordner &lt;em&gt;schema&lt;/em&gt;. Das Datenmodell im Unterordner &lt;em&gt;ili&lt;/em&gt; etc. pp. Notwendige Konfigurationen für die verschiedenen &lt;a href=&quot;https://www.duden.de/rechtschreibung/Software&quot;&gt;Softwares&lt;/a&gt; (Dienste, Datenabgabe, &amp;#8230;&amp;#8203;) würden mit GRETL in einer Pipeline (Github-Action, Jenkins, &amp;#8230;&amp;#8203;) hergestellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zwei schnell identifizierte Herausforderungen sind die referentielle Integrität, die es mit dem neuen Ansatz nicht geschenkt gibt und die Frage nach dem passenden Format für die Datenerfassung im Texteditor. Anhand eines konkreten Beispieles (die Datenabgabe) können Lösungen für beide Herausforderungen gezeigt werden. Ich verwende hier Statistikdaten und nicht unsere Geodaten. Grund dafür ist, dass wir womöglich zukünftig die eine oder andere Synergie nutzen wollen. Alles in allem ist der Prozess sehr ähnlich aber einfacher. Mein Themen-Git-Repo sieht für vier Themen so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p46/themenrepo-01.png&quot; alt=&quot;Themenrepo&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man erkennt die vier Themen: zwei vom AGI, je eines vom AWA und der MFK. Diese sind immer gleich aufgebaut. Es gibt einen &lt;em&gt;ili&lt;/em&gt;- und einen &lt;em&gt;meta&lt;/em&gt;-Unterordner. Fehlen tut noch mindestens der &lt;em&gt;gretl&lt;/em&gt;-Unterordner mit dem GRETL-Job, der die Daten entegennimmt, in eine Parquet-Datei umwandelt und auf eine Ablage kopiert. Das Datenmodell im &lt;em&gt;ili&lt;/em&gt;-Ordner wird benötigt, um das &amp;laquo;best practice Datenformat CSV&amp;raquo; (Anonym, 2025) zu prüfen. Wenn die Daten bereitgestellt werden, braucht es noch Metadaten dazu. Das sind eine Beschreibung, Stichworte, Publikationsdatum etc. Die ersten beiden sind statisch und können einmalig definiert werden. Das Publikationsdatum ist dynamisch und ändert sich mit jeder Publikation der Daten. Die statischen Daten speichern wir in einer JSON-Datei &lt;em&gt;thema.json&lt;/em&gt; im Unterordner &lt;em&gt;meta&lt;/em&gt;. Der Inhalt der Datei ist circa folgender:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &quot;ID&quot; : &quot;ch.so.mfk.fahrzeugbestand&quot;,
    &quot;Titel&quot; : &quot;Fahrzeugbestand&quot;,
    &quot;Beschreibung&quot; : &quot;Fahrzeugbestand seit 1991 im Kanton Solothurn gruppiert in verschiedene Kategorien. Wird jährlich aktualisiert.&quot;,
    &quot;Datenherr&quot; : &quot;ch.so.mfk&quot;,
    &quot;Modell&quot; : &quot;SO_MFK_Fahrzeugbestand_20250201&quot;,
    &quot;Tags&quot; : [
        &quot;Fahrzeug&quot;,
        &quot;Bestand&quot;,
        &quot;PKW&quot;,
        &quot;Auto&quot;,
        &quot;Lastwagen&quot;,
        &quot;Anzahl&quot;
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zwei Attribute sind von besonderem Interesse: Die &lt;code&gt;ID&lt;/code&gt; darf natürlich in der gesamten Dateninfrastruktur nur einmal vorkommen. Heute nimmt mir die Datenbank diese Aufgabe ab. Ohne Datenbank muss ich das anders lösen. Das zweite interessante Attribut ist der &lt;code&gt;Datenherr&lt;/code&gt;. Natürlich wird später für den Benutzer bei den Metadaten nicht einfach &lt;code&gt;ch.so.mfk&lt;/code&gt; stehen, sondern die normale Adresse mit Kontaktdaten. Hier in der JSON-Datei steht nur der Fremdschlüssel. Und ja, er ist sprechend und folgt einer Logik. Aber dünkt mich handlebar. Die Adresse und Kontaktdaten der Ämter stecken in einer XTF-Datei &lt;em&gt;amt.xtf&lt;/em&gt; im Order &lt;em&gt;stammdaten&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Publikationsdatum wird während der Datenpublikation mit einem GRETL-Job ebenfalls in eine JSON-Datei geschrieben. Diese wird bei den publizierten Daten gespeichert (hier z.B. S3) und nicht im Themen-Repo. In der Datei steckt nur die ID und das Datum:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &quot;ID&quot; : &quot;ch.so.mfk.fahrzeugbestand&quot;,
    &quot;publiziertAm&quot; : &quot;2025-02-01&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie kann man verhindern, dass die &lt;code&gt;ID&lt;/code&gt; wirklich nur einmal vorkommt ist? Und wie kann ich eine Konfigurationsdatei mit allen Themen herstellen, die ich für meine Datenabgabe-Anwendung benötige und auch das Publikationsdatum beinhaltet und die korrekte Adresse des Datenherrn? Die Antwort ist natürlich &lt;em&gt;INTERLIS&lt;/em&gt;, &lt;em&gt;GRETL&lt;/em&gt; und &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes braucht es ein Metamodell. Ein unvollständiges kann z.B. so aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.4;

MODEL SO_AGI_Metadaten_20250202 (de)
AT &quot;https://agi.so.ch&quot;
VERSION &quot;2025-02-02&quot; =
  DOMAIN

    SOOID = OID TEXT*255;

  STRUCTURE Amt_ =
    Name : MANDATORY TEXT;
    Abkuerzung : TEXT*10;
    Abteilung : TEXT;
    AmtImWeb : MANDATORY URI;
    Zeile1 : TEXT*50;
    Zeile2 : TEXT*50;
    Strasse : MANDATORY TEXT*50;
    Hausnr : MANDATORY TEXT*10;
    PLZ : MANDATORY TEXT*4;
    Ort : MANDATORY TEXT*100;
  END Amt_;

  TOPIC Amt =
    OID AS SO_AGI_Metadaten_20250202.SOOID;

    CLASS Amt EXTENDS SO_AGI_Metadaten_20250202.Amt_ =
      UNIQUE Abkuerzung, Abteilung;
    END Amt;
  END Amt;

  TOPIC ThemaPublikation =
    OID AS SO_AGI_Metadaten_20250202.SOOID;

    CLASS ThemaPublikation =
        ID : MANDATORY TEXT*100;
        Titel : MANDATORY TEXT*200;
        Beschreibung : TEXT*2000;
        Datenherr : MANDATORY SO_AGI_Metadaten_20250202.Amt_;
        Modell : TEXT*100;
        publiziertAm : INTERLIS.XMLDate;
        Tags : TEXT;
        UNIQUE ID;
    END ThemaPublikation;

  END ThemaPublikation;

END SO_AGI_Metadaten_20250202.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Klasse &lt;code&gt;Amt&lt;/code&gt; im Topic &lt;code&gt;Amt&lt;/code&gt; benötige ich zum Vorhalten der Ämterinformationen. Die Klasse &lt;code&gt;ThemaPublikation&lt;/code&gt; im Topic &lt;code&gt;ThemaPublikation&lt;/code&gt; brauche ich zum Herstellen der Konfigurationsdatei für meine Datenabgabe-Software. In dieser Klasse sieht man, dass &lt;code&gt;ID&lt;/code&gt; eindeutig sein muss. Wie geht es weiter?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es braucht einen GRETL-Job, der sowohl lokal beim Entwickeln resp. bei der Bearbeitung der Informationen ausgeführt werden kann wie auch in einer Pipeline. Es wird mehrere Jobs geben, da sie verschiedene Zwecke erfüllen müssen. Geht es nur darum zu prüfen, ob die ID eindeutig ist, muss ich die erwähnte Konfigurationsdatei für die Datenabgabe-Software nicht herstellen. Und es wird weitere Konfigurationsdateien geben, die ebenfals mittels GRETL-Jobs hergestellt werden (und weiteren Klassen im Metamodell). Der GRETL-Job für das Herstellen der Konfigurationsdatei sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import java.nio.file.Paths
import ch.so.agi.gretl.tasks.*
import ch.so.agi.gretl.api.*
import de.undercouch.gradle.tasks.download.Download

apply plugin: &apos;ch.so.agi.gretl&apos;
apply plugin: &apos;de.undercouch.download&apos;

defaultTasks &apos;uploadToExoscale&apos;

def awsAccessKey = System.getenv(&quot;AWS_ACCESS_KEY_ID&quot;)
def awsSecretAccessKey = System.getenv(&quot;AWS_SECRET_ACCESS_KEY&quot;)
def awsRegion = System.getenv(&quot;AWS_DEFAULT_REGION&quot;)
def awsEndpoint = System.getenv(&quot;AWS_ENDPOINT_URL&quot;)

tasks.register(&quot;createStammdatenSchema&quot;, Ili2duckdbImportSchema) {
    dbfile = file(&quot;shubidu.duckdb&quot;)
    models = &quot;SO_AGI_Metadaten_20250202&quot;
    modeldir = rootProject.projectDir.toString() + &quot;/../../_ili/&quot; + &quot;;http://models.interlis.ch&quot;
    dbschema = &quot;stammdaten&quot;
    nameByTopic = true
    smart2Inheritance = true
    createEnumTabs = true
}

tasks.register(&quot;importStammdaten&quot;, Ili2duckdbImport) {
    dependsOn &apos;createStammdatenSchema&apos;
    dbfile = file(&quot;shubidu.duckdb&quot;)
    models = &quot;SO_AGI_Metadaten_20250202&quot;
    modeldir = rootProject.projectDir.toString() + &quot;/../../_ili/&quot; + &quot;;http://models.interlis.ch&quot;
    dbschema = &quot;stammdaten&quot;
    dataFile = file(&quot;../../_stammdaten/amt.xtf&quot;)
}

tasks.register(&quot;createKonfigSchema&quot;, Ili2duckdbImportSchema) {
    dependsOn &apos;importStammdaten&apos;
    dbfile = file(&quot;shubidu.duckdb&quot;)
    models = &quot;SO_AGI_Metadaten_20250202&quot;
    modeldir = rootProject.projectDir.toString() + &quot;/../../_ili/&quot; + &quot;;http://models.interlis.ch&quot;
    dbschema = &quot;konfig&quot;
    nameByTopic = true
    smart2Inheritance = true
    createEnumTabs = true
}

def pwd = rootProject.projectDir.toString()
def dbUri = &quot;jdbc:duckdb:$pwd/shubidu.duckdb&quot;.toString()
def dbUser = &quot;&quot;
def dbPass = &quot;&quot;

tasks.register(&quot;createKonfigData&quot;, SqlExecutor) {
    dependsOn &apos;createKonfigSchema&apos;
    database = [dbUri, dbUser, dbPass]
    sqlFiles = [&apos;create_konfig_data.sql&apos;]
}

tasks.register(&quot;exportKonfigData&quot;, Ili2duckdbExport) {
    dependsOn &apos;createKonfigData&apos;
    dbfile = file(&quot;shubidu.duckdb&quot;)
    models = &quot;SO_AGI_Metadaten_20250202&quot;
    modeldir = rootProject.projectDir.toString() + &quot;/../../_ili/&quot; + &quot;;http://models.interlis.ch&quot;
    dbschema = &quot;konfig&quot;
    dataFile = file(&quot;themen_publikation.xtf&quot;)
}

tasks.register(&apos;uploadToExoscale&apos;, S3Upload) {
    dependsOn &apos;exportKonfigData&apos;
    accessKey = awsAccessKey
    secretKey = awsSecretAccessKey
    sourceFile = file(&apos;themen_publikation.xtf&apos;)
    endPoint = awsEndpoint
    region = awsRegion
    bucketName = &apos;XXXXX.YYYYY.ZZZZZ&apos;
    acl = &quot;public-read&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes importiere ich die Stammdaten (mit den Ämterinfos) in eine DuckDB. Anschliessend erstelle ich ein weiteres Schema &lt;code&gt;konfig&lt;/code&gt; in der gleichen DuckDB. In diesem Schema muss ich aus den vorliegenden JSON-Dateien (lokal und S3) und den Ämterinfos die Klasse &lt;code&gt;ThemaPublikation&lt;/code&gt; abfüllen. Ist das gemacht, exportiere ich die Klasse in eine XTF-Datei / in die Konfigurationsdatei. Das Wichtigste passiert im Task &lt;code&gt;createKonfigData&lt;/code&gt;. Hier wird einzig mit SQL und DuckDB-Magie die Klasse &lt;code&gt;ThemaPublikation&lt;/code&gt; befüllt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;CREATE OR REPLACE SECRET asecret (
    TYPE S3,
    PROVIDER CREDENTIAL_CHAIN,
    CHAIN &apos;env&apos;,
    ENDPOINT &apos;sos-xx-yy-z.exo.io&apos;
);

DROP TABLE IF EXISTS
    themapublikation_tmp
;
CREATE TEMP TABLE themapublikation_tmp AS
SELECT
    nextval(&apos;konfig.t_ili2db_seq&apos;) AS T_Id,
    &apos;_&apos; || nextval(&apos;konfig.t_ili2db_seq&apos;) AS T_Ili_Tid,
    m.ID AS id,
    m.Titel AS titel,
    m.Beschreibung AS beschreibung,
    m.Modell AS modell,
    m.tags,
    m.datenherr,
    p.publiziertAm AS publiziertAm
FROM
    read_json(&apos;*/meta/thema.json&apos;) AS m
    LEFT JOIN read_json(&apos;s3://XXXXX.YYYYY.ZZZZZ/*/publishedat.json&apos;) AS p
    ON m.ID = p.ID
;

DELETE FROM
    konfig.themapublikation_themapublikation
;
INSERT INTO
    konfig.themapublikation_themapublikation
    (
        T_Id,
        T_Ili_Tid,
        id,
        titel,
        beschreibung,
        modell,
        tags,
        publiziertam
    )
SELECT
    T_Id,
    T_Ili_Tid,
    id,
    titel,
    beschreibung,
    modell,
    list_aggregate(tags, &apos;string_agg&apos;, &apos;,&apos;) AS tags,
    publiziertam
FROM
    themapublikation_tmp
;

DELETE FROM
    konfig.amt_
;
INSERT INTO
    konfig.amt_
    (
        T_Id,
        aname,
        abkuerzung,
        abteilung,
        amtimweb,
        zeile1,
        zeile2,
        strasse,
        hausnr,
        plz,
        ort,
        thempblktn_thmpblktion_datenherr
    )
SELECT
    nextval(&apos;konfig.t_ili2db_seq&apos;) AS T_Id,
    aname,
    abkuerzung,
    abteilung,
    amtimweb,
    zeile1,
    zeile2,
    strasse,
    hausnr,
    plz,
    ort,
    themapublikation_tmp.T_Id AS thempblktn_thmpblktion_datenherr
FROM
    themapublikation_tmp
    LEFT JOIN stammdaten.amt_amt AS amt
    ON themapublikation_tmp.datenherr = amt.T_Ili_Tid
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;DuckDB&lt;/em&gt; stellt die &lt;code&gt;read_json()&lt;/code&gt;-Funktion bereit. Mit dieser kann ich JSON-Dateien lesen und sie wie Tabellen ansprechen (ohne sie importieren zu müssen) und die Funktion &lt;a href=&quot;https://duckdb.org/docs/data/multiple_files/overview.html&quot;&gt;unterstützt&lt;/a&gt; die &lt;a href=&quot;https://en.wikipedia.org/wiki/Glob_(programming)&quot;&gt;glob-Syntax&lt;/a&gt;. Vereinfacht gesagt, kann ich sämtliche &lt;em&gt;thema.json&lt;/em&gt;-Dateien an der gleichen Stelle in den Unterordnern ansprechen (siehe Zeile 23). Gleiches gilt auch für die JSON-Dateien, die im S3-Bucket bei den publizierten Daten liegen und das Publikationsdatum beinhalten. Die Ämterinfos hole ich einfachst aus dem anderen Schema.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls nun eine ID doppelt vorkommt oder man auf ein Amt verweist, das es nicht gibt, wird spätestens beim Export &lt;em&gt;ilivalidator&lt;/em&gt; einen Fehler melden. Gewisse Constraints werden bereits auf der Datenbank angelegt. So auch der UNIQUE-Constraint. In diesem Fall wird schon der Datenumbau einen Fehler melden. Der gesamte Prozess dauert lokal circa 20 Sekunden. Dünkt mich vertretbar. Wahrscheinlich wird es zusätzlich eine read-only Metadatenbank als Webanwendung geben. Sowohl für uns zum Nachschlagen wie auch für die Kunden/Benutzer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann das Ganze noch ein wenig weitertreiben, indem man z.B. nicht mehr direkt in den main-Branch pushen darf, sondern via separatem Branch, der die Github Action durchlaufen muss. Die Github Action ist mit &lt;em&gt;GRETL&lt;/em&gt; auch sehr einfach. Man kann der Action sagen, dass sie auf einem bestimmten Dockerimage laufen soll:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;name: thema_publikation

on:
  push

jobs:

  build:

    env:
      AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
      AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
      AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}}
      AWS_ENDPOINT_URL: ${{secrets.AWS_ENDPOINT_URL}}

    runs-on: ubuntu-latest

    container:
      image: sogis/gretl:3.1

    steps:
      - uses: actions/checkout@v4

      - name: Run GRETL job
        run: |
          gradle -b _gretl/thema_publikation/build.gradle --init-script /home/gradle/init.gradle uploadToExoscale --no-daemon&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiterer Anwendungsfall von INTERLIS-, GRETL- und DuckDB-Magie.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>The house at the lake #3 - The dashboard diaries</title>
      <link>http://blog.sogeo.services/blog/2025/01/26/house-at-the-lake-03.html</link>
      <pubDate>Sun, 26 Jan 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/01/26/house-at-the-lake-03.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sie gehören halt einfach dazu: Dashboards. Ich habe mich mit der Visualisierung von (OGD-)Daten vor knapp 1.5 Jahren schon einmal &lt;a href=&quot;https://blog.sogeo.services/blog/2023/08/13/ogd-made-easy-03.html&quot;&gt;auseinandergesetzt&lt;/a&gt;. Damals habe ich mir &lt;a href=&quot;https://grafana.com/&quot;&gt;&lt;em&gt;Grafana&lt;/em&gt;&lt;/a&gt; angeschaut und wie man damit nette Grafiken und Auswertungen in Dashboards herstellen kann. So richtig überzeugt hat es mich nachhaltig nicht. Am meisten störte mich, dass man keine Parquet-Dateien direkt verwenden konnte, sondern nur CSV (via Plugin) und somit das Wissen über die Datentypen verloren geht. Fairerweise muss man erwähnen, dass momentan meine selber gesetzte Rahmenbedingung ist, dass sämtliche Daten via z.B. &lt;a href=&quot;https://trino.io/&quot;&gt;&lt;em&gt;Trino&lt;/em&gt;&lt;/a&gt; greifbar sind (siehe &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/12/house-at-the-lake-02.html&quot;&gt;Teil 2&lt;/a&gt;). Parquet-Dateien direkt (ohne serverseitige Query-Engine o.ä.) anzapfen ist und bleibt natürlich ein Plus einer möglichen Lösung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun wollte ich mir endlich &lt;a href=&quot;https://superset.apache.org/&quot;&gt;&lt;em&gt;Apache Superset&lt;/em&gt;&lt;/a&gt; genauer anschauen. Und habe es doch wieder sein lassen. Mir macht immer noch die schiere &amp;laquo;Grösse&amp;raquo; Angst. Ich weiss selber nicht so ganz genau, was ich damit meine. Aber wenn man sich die Anleitung zur &lt;a href=&quot;https://superset.apache.org/docs/installation/architecture&quot;&gt;Installation&lt;/a&gt; anschaut, dünkt es mich trotz Docker irgendwie overwhelming. Vor etwas mehr als einem Jahr stiess ich auf einen &lt;a href=&quot;https://motherduck.com/blog/the-future-of-bi-bi-as-code-duckdb-impact/&quot;&gt;Blogbeitrag&lt;/a&gt; von &lt;a href=&quot;https://motherduck.com/&quot;&gt;&lt;em&gt;MotherDuck&lt;/em&gt;&lt;/a&gt;. Sie haben drei BI-as-code Lösungen vorgestellt. Mir gefiel vor allem &lt;a href=&quot;https://evidence.dev/&quot;&gt;&lt;em&gt;Evidence&lt;/em&gt;&lt;/a&gt; und die Möglichkeit das Endprodukt als statische Webseite zu deployen. Also, schaue ich mir anstelle von &lt;em&gt;Apache Superset&lt;/em&gt; &lt;em&gt;Evidence&lt;/em&gt; an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Evidence&lt;/em&gt; funktioniert kurz zusammengefasst ganz einfach: Man schreibt SQL-Queries in einer Markdown-Datei und buildet die statische Webseite. That&amp;#8217;s it. Klar gibt es auch hier eine gewisse Lernkurve. Aber dass der Master die Markdown-Dateien sind und ich diese in ein Git-Repository einchecken kann, ist schon ziemlich cool und kommt unserer Arbeitsweise sehr entgegen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt zwar ein VSCode-Plugin, ich habe jedoch alles mit der Kommandozeile durchexerziert. Ein neues Projekt erstellt man mit folgenden Befehlen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;npx degit evidence-dev/template my-project
cd my-project
npm install
npm run sources
npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant sind die beiden letzten Befehle. &lt;code&gt;npm run dev&lt;/code&gt; startet die Entwicklungsumgebung. Der Entwicklungsserver buildet die Webseite während des Entwickelns und stellt diese bereit. Zudem stellt er eine Weboberfläche zur Verfügung, die unter anderem zur Konfiguration neuer Datenquellen dient. Und um Datenquellen geht es auch beim zweitletzten Befehl: &lt;code&gt;npm run sources&lt;/code&gt;. Hat man nämlich Datenquellen definiert, müssen die Daten aus der Quelle exportiert werden, damit &lt;em&gt;Evidence&lt;/em&gt; auf sie zugreifen kann. Das passiert mit diesem Befehl. Dieser Schritt ist quasi der Kern des Ganzen und soll mit einem Beispiel erklärt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich möchte die Steuerfüsse der Gemeinden (der natürlichen Personen) visualisieren. Die Daten stecken in einem Data Lakehouse basierend auf &lt;a href=&quot;https://iceberg.apache.org/&quot;&gt;&lt;em&gt;Apache Iceberg&lt;/em&gt;&lt;/a&gt; (siehe &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/05/house-at-the-lake-01.html&quot;&gt;Teil 1&lt;/a&gt;). Die Iceberg-Tabellen sind auf AWS S3 gespeichert und mittels &lt;em&gt;Trino&lt;/em&gt; und SQL für Mensch und Maschine greifbar (siehe &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/12/house-at-the-lake-02.html&quot;&gt;Teil 2&lt;/a&gt;). Daraus folgt, dass &lt;em&gt;Evicence&lt;/em&gt; entweder sowas wie einen Connector für Iceberg-Tabellen benötigt (hat es nicht) oder aber für &lt;em&gt;Trino&lt;/em&gt; (hat es):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/trino-datasource.png&quot; alt=&quot;trino datasource&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bestätigt man die Angaben, wird im Projektverzeichnis ein Ordner &lt;em&gt;sources/mytrino&lt;/em&gt; erstellt. In diesem Ordner gibt es mindestens eine Datei &lt;em&gt;connection.yaml&lt;/em&gt; mit den Connection-Parametern. Eine zweite Datei wird erstellt, falls die Connection ein Passwort benötigt. Diese Datei sollte man nicht in ein Repo einchecken. Der nächste Schritt ist der Export aus der Datenquelle, um mit den Daten in &lt;em&gt;Evidence&lt;/em&gt; arbeiten zu können. Dabei handelt es sich um eine SQL-Query, die in einer Datei (z.B. &lt;em&gt;steuerfuesse.sql&lt;/em&gt;) im soeben erstellen Ordner gespeichert wird. Die Query kann völlig beliebig sein und kann von ganz einfach bis ganz kompliziert reichen. Die SQL-Syntax muss der Syntax der Datenquelle entsprechen. In meinem Fall also &lt;em&gt;Trino&lt;/em&gt;. Meine Query ist denkbar einfach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;SELECT
  *
FROM
  iceberg.agem_steuerfuesse.natuerliche_personen&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Befehl &lt;code&gt;npm run sources&lt;/code&gt; exportiert das Resultat dieser Query in eine Parquet-Datei. Falls beim Export was schief geht, sieht man das in der Konsole. Bei mir ist es bei einem anderen Beispiel vorgekommen, dass gewisse Datentypen von &lt;em&gt;PostgreSQL&lt;/em&gt; (via &lt;em&gt;Trino&lt;/em&gt;) Probleme bereiteten. Aber nicht exotische Datentypen, sondern &lt;code&gt;varchar(256)&lt;/code&gt;. Abhilfe hat dann das Casten nach &lt;code&gt;varchar&lt;/code&gt; gebracht. Scheint mir ein Bug zu sein. Wo die Parquet-Datei liegt, muss mich nicht interessieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes will ich in meinem Dashboard einfach eine Tabelle mit den Steuerfüssen. Wie geht das? Ich erstelle im &lt;em&gt;pages&lt;/em&gt;-Ordner eine neue Markdown-Datei &lt;em&gt;fubar.md&lt;/em&gt; und schreibe folgendes rein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;```sql my_query
select * from mytrino.steuerfuesse
```
&amp;lt;DataTable data={my_query} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der SELECT-Query lese ich Daten aus der vorhin erstellten Parquet-Datei. Identifiziert wird die Parquet-Datei dabei über den Namen der Datenquelle (resp. des Ordners) und dem Namen der SQL-Datei (welche die Daten aus der Original-Datenquelle exportiert). Hier muss ich die DuckDB-SQL-Syntax verwenden. Warum? Weil mit DuckDB auf die Parquet-Datei zugegriffen wird. Die resultierenden Daten will ich in einem DataTable-Widget präsentiert bekommen. Im Widget greife ich über den Namen der Query auf die Daten zu. Das Resultat nach einem Page-Refresh:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/example-steuern-01.png&quot; alt=&quot;example-steuern-01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Noch ziemlich unspektakulär. Geschenkt bekommt man einen CSV-Download und einen Fullscreen-Modus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einiges gefällt mir noch nicht: Ich möchte z.B. mindestens 150 Gemeinden anzeigen und bei den Jahreszahlen will ich keine Tausendertrennzeichen. Das ist alles einstellbar über Optionen des Widgets:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;```sql my_query
select * from mytrino.steuerfuesse
```
&amp;lt;DataTable data={my_query} rows=150&amp;gt;
   &amp;lt;Column id=jahr title=&quot;Jahr&quot; fmt=&apos;###0&apos; /&amp;gt;
   &amp;lt;Column id=gemeinde /&amp;gt;
   &amp;lt;Column id=steuerfuss_in_prozent /&amp;gt;
&amp;lt;/DataTable&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird die Excelsyntax für die Formatierung verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt sowas wie einen Debugmodus. Dieser nennt sich &amp;laquo;Show Queries&amp;raquo; und zeigt auf der Webseite die verwendete Query, sowie das Resultat der Query:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/example-steuern-02.png&quot; alt=&quot;example-steuern-02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine typische Anforderung ist das Filtern von Daten. In meinem Fall will ich die Tabelle für ein bestimmtes Jahr filtern. Auch das dünkt mich logisch und straight forward. Ich brauche zusätzlich eine Query, um herauszufinden, welche Jahre überhaupt vorhanden sind und ein Dropdown-Widget, wo der Benutzer das zu filternde Jahr auswählen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;```sql unique_years
select
  jahr
from
  mytrino.steuerfuesse
group by
  1
order by
  jahr
```

&amp;lt;Dropdown
    name=selected_year
    data={unique_years}
    value=jahr
    title=&quot;Select a year&quot;
    noDefault=true
/&amp;gt;

```sql my_query_filtered_by_year
select
  *
from
  mytrino.steuerfuesse
where
  jahr = &apos;${inputs.selected_year.value}&apos;
order by
  jahr,gemeinde
```

&amp;lt;DataTable data={my_query_filtered_by_year} rows=all&amp;gt;
   &amp;lt;Column id=jahr title=&quot;Jahr&quot; fmt=&apos;###0&apos; /&amp;gt;
   &amp;lt;Column id=gemeinde /&amp;gt;
   &amp;lt;Column id=steuerfuss_in_prozent /&amp;gt;
&amp;lt;/DataTable&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/example-steuern-03.png&quot; alt=&quot;example-steuern-03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ähnlich einfach umzusetzen sind Linecharts, z.B. die Entwicklung des Steuerfusses in einer Gemeinde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;```sql unique_gemeinden
select
  gemeinde
from
  mytrino.steuerfuesse
group by
  1
order by
  gemeinde
```

&amp;lt;Dropdown
    name=selected_gemeinde
    data={unique_gemeinden}
    value=gemeinde
    title=&quot;Wählen Sie eine Gemeinde&quot;
    noDefault=true
/&amp;gt;

```sql query_steuerfuesse_filtered_by_gemeinden
select
  *
from
  mytrino.steuerfuesse
where
  gemeinde = &apos;${inputs.selected_gemeinde.value}&apos;
order by
  jahr,gemeinde
```

&amp;lt;LineChart
    data={query_steuerfuesse_filtered_by_gemeinden}
    x=jahr
    y=steuerfuss_in_prozent
    yAxisTitle=&quot;Steuerfuss pro Jahr&quot;
    markers=true
    xFmt=&apos;###0&apos;
    yMin=60
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/example-steuern-04.png&quot; alt=&quot;example-steuern-04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als letztes Beispiel möchte ich die Steuerfüsse in einer Karte darstellen. Dazu wird eine GeoJSON-Datei mit den Polygonen, die man anzeigen will, benötigt (EPSG:4326). In unserem Fall die Gemeindegrenzen des Kantons Solothurn. Die Datei speichert man im &lt;em&gt;static&lt;/em&gt;-Ordner im Projektverzeichnis. Den &lt;em&gt;static&lt;/em&gt;-Ordner muss man erstellen, falls er nicht existiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;```sql my_query
SELECT
    *
FROM
    mytrino.steuerfuesse
WHERE
    jahr = 2022
```

&amp;lt;AreaMap
    data={my_query}
    areaCol=gemeinde
    geoJsonUrl=&apos;/kanton_solothurn.geojson&apos;
    geoId=gemeindename
    value=steuerfuss_in_prozent
    title=&apos;Steuerfüsse (natürliche Personen)&apos;
    attribution=&apos;Kanton Solothurn&apos;
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Mapping der Gemeinde zur Geometrie wird über &lt;code&gt;areaCol=gemeinde&lt;/code&gt; gesteuert. D.h. die GeoJSON-Datei hat ein Property &lt;code&gt;gemeinde&lt;/code&gt; und das Query-Resultat hat ebenfalls ein Attribut &lt;code&gt;gemeinde&lt;/code&gt;. Die Zuweisung des Wertes erfolgt über &lt;code&gt;value=steuerfuss_in_prozent&lt;/code&gt;. Was ich mir noch nicht genau angeschaut habe, ist die Übersteuerung der Farbgebung. Sowohl hier bei der Karte wie auch bei den anderen Beispielen. Einiges scheint mir jedoch möglich zu sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-03/example-steuern-05.png&quot; alt=&quot;example-steuern-05&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man erkennt, dass verschiedene Gemeinden fehlen. Das liegt entweder daran, dass ich Zeitstände versuche zu matchen, die nicht passen (wegen Gemeindefusionen) oder das Mapping der Gemeindenamen funktioniert nicht, weil eventuell die Namen unterschiedlich geschrieben sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als letztes muss ich mein Projekt builden: &lt;code&gt;npm run build&lt;/code&gt;. Das erstellt einen &lt;em&gt;build&lt;/em&gt;-Ordner, dessen Inhalt ich als statische Homepage irgendwo hin deployen kann. &lt;em&gt;Evidence&lt;/em&gt; hat auch ein Hostingangebot, das für öffentliche Dashboards gratis ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sämtliche verfügbaren Widgets/Komponenten: &lt;a href=&quot;https://docs.evidence.dev/components/all-components/&quot; class=&quot;bare&quot;&gt;https://docs.evidence.dev/components/all-components/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beispiele mit &lt;em&gt;Evidence&lt;/em&gt;: &lt;a href=&quot;https://evidence.dev/examples&quot; class=&quot;bare&quot;&gt;https://evidence.dev/examples&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>The house at the lake #2 - Start your engines</title>
      <link>http://blog.sogeo.services/blog/2025/01/12/house-at-the-lake-02.html</link>
      <pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/01/12/house-at-the-lake-02.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nachdem wir uns im &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/05/house-at-the-lake-01.html&quot;&gt;ersten Teil&lt;/a&gt; um das Speichern der Daten in unserem Data Lakehouse gekümmert haben, möchten wir jetzt mit den Daten arbeiten, d.h. wir brauchen eine Compute oder Query Engine. Es gibt verschiedene Engines, die auf &lt;a href=&quot;https://iceberg.apache.org/&quot;&gt;&lt;em&gt;Iceberg&lt;/em&gt;&lt;/a&gt; Tables zugreifen können. Anschliessend drei Beispiele dazu. Es sei nochmals darauf hingewiesen, dass man es nicht mit drei verschiedenen Datenbankklienten vergleichen kann. In DB-Fall verursachen die Klienten die (gleiche) Last auf dem Datenbankserver. Aufgrund der Trennung von Speichern und Compute in einem Data Lakehouse, liegt die Rechenlast bei der Query Engine selber.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die erste Möglichkeit haben wir bereits im &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/05/house-at-the-lake-01.html&quot;&gt;vorangegangenen Blogbeitrag&lt;/a&gt; kennengelernt: &lt;a href=&quot;https://spark.apache.org/&quot;&gt;&lt;em&gt;Spark&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;Spark&lt;/em&gt; diente mir hauptsächlich zum Anlegen der Iceberg-Tabellen. Man kann damit aber auch beliebige Analysen machen. Es gibt drei Benutzerinterfaces: SQL, Scala und Python. Die beiden letzten würde man wohl verwenden, wenn man für seine Analyse weitere Bibliotheken verwende möchte oder muss. Ich setze mir als Ziel die Tabelle der Steuerfüsse der natürlichen Personen, die ich in mein Data Lakehouse importiert habe, in eine Excel-Datei zu exportieren. Ich verwende für dieses Beispiel &lt;em&gt;pyspark&lt;/em&gt; und muss zuerst noch weitere Bibliotheken herunterladen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;python -m venv .venv
source .venv/bin/activate
pip install setuptools pandas openpyxl&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Python-Skript sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;from pyspark.sql import SparkSession
import pandas as pd

spark = SparkSession.builder \
    .appName(&quot;IcebergLocal&quot;) \
    .config(&quot;spark.sql.extensions&quot;, &quot;org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions&quot;) \
    .config(&quot;spark.sql.catalog.local&quot;, &quot;org.apache.iceberg.spark.SparkCatalog&quot;) \
    .config(&quot;spark.sql.catalog.local.type&quot;, &quot;hadoop&quot;) \
    .config(&quot;spark.sql.catalog.local.warehouse&quot;, &quot;/Users/stefan/tmp/warehouse/&quot;) \
    .getOrCreate()

spark.sql(&quot;USE local.agem_steuerfuesse&quot;)

result_df = spark.sql(&quot;&quot;&quot;
    SELECT
        *
    FROM
        natuerliche_personen
&quot;&quot;&quot;
)

result_df.show()

pandas_df = result_df.select(&quot;*&quot;).toPandas()
pandas_df.to_excel(&quot;/Users/stefan/tmp/myresult.xlsx&quot;, index=False)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Aufruf muss nicht in der Shell passieren, sondern kann als pyspark-Befehl auf der Konsole ausgeführt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;pyspark --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.7.1 &amp;lt; to_excel.py&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man benötigt für den Export in eine Excel-Datei &lt;a href=&quot;https://pandas.pydata.org/&quot;&gt;&lt;em&gt;Pandas&lt;/em&gt;&lt;/a&gt;. Dazu muss der &lt;em&gt;Spark&lt;/em&gt; Dataframe in einen &lt;em&gt;Pandas&lt;/em&gt; Dataframe umgewandelt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So weit, so unspektakulär. Spannender wird es mit der zweiten Möglichkeit: &lt;em&gt;DuckDB&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;DuckDB&lt;/em&gt; erweitere ich meinen Anwendungsfall ein wenig. Ich möchte die Steuerfüsse in eine Excel-Datei exportieren. Zusätzlich soll die Fläche jeder Gemeinde als Spalte mitexportiert werden (bisschen Geo muss ja sein&amp;#8230;&amp;#8203;). &lt;em&gt;DuckDB&lt;/em&gt; hat eine &lt;a href=&quot;https://duckdb.org/docs/extensions/iceberg.html&quot;&gt;Iceberg Extension&lt;/a&gt;, die man installieren muss. Die Anwendung ist relativ simpel. Man kann auf die Live-Daten, Metadaten und Snapshots zugreifen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT count(*)
FROM iceberg_scan(&apos;/Users/stefan/tmp/warehouse/agem_steuerfuesse/natuerliche_personen&apos;, allow_moved_paths = true) AS f;

SELECT *
FROM iceberg_metadata(&apos;/Users/stefan/tmp/warehouse/agem_steuerfuesse/natuerliche_personen&apos;, allow_moved_paths = true);

SELECT *
FROM iceberg_snapshots(&apos;/Users/stefan/tmp/warehouse/agem_steuerfuesse/natuerliche_personen&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach &lt;em&gt;Iceberg&lt;/em&gt; schreiben geht noch nicht. Was - soweit ich es verstanden habe - auch noch &lt;a href=&quot;https://github.com/duckdb/duckdb-iceberg/issues/16&quot;&gt;nicht&lt;/a&gt; (gut) unterstützt wird, sind die Kataloge. &lt;em&gt;DuckDB&lt;/em&gt; benötigt eine &lt;em&gt;version-hint.text&lt;/em&gt;-Datei. Diese verweist auf die aktuelle Version der Daten. Fehlt diese, kann man versuchen direkt die &lt;a href=&quot;https://github.com/duckdb/duckdb-iceberg/issues/29&quot;&gt;Metadaten-JSON-Datei&lt;/a&gt; anzugeben. Aber von nativer Katalogunterstützung ist man noch ein Stück weit entfernt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Meinen Anwendungsfall kann ich mit purem SQL umsetzen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;CREATE TEMP TABLE myresult AS
WITH steuern AS (
  SELECT
    *
  FROM
    iceberg_scan(&apos;/Users/stefan/tmp/warehouse/agem_steuerfuesse/natuerliche_personen&apos;, allow_moved_paths = true)
)
SELECT
  gemndname,
  ST_Area(geom),
  steuerfuss_in_prozent
FROM
  ST_Read(&apos;/vsizip//vsicurl/https://files.geo.so.ch/ch.so.agi.av.hoheitsgrenzen/aktuell/ch.so.agi.av.hoheitsgrenzen.shp.zip&apos;, layer=&apos;gemeindegrenze&apos;) AS g
  LEFT JOIN steuern
  ON g.gemndname = steuern.gemeinde
;
COPY (SELECT * FROM myresult) TO &apos;/Users/stefan/tmp/myresult.xlsx&apos; WITH (FORMAT GDAL, DRIVER &apos;xlsx&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Immer wieder schön zu sehen, dass man dank &lt;a href=&quot;https://gdal.org/en/stable/user/virtual_file_systems.html&quot;&gt;&lt;em&gt;gdal/ogr&lt;/em&gt;&lt;/a&gt; die Daten nicht einmal vorgängig herunterladen muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die dritte Variante ist die aufwändigste und wohl auch die &amp;laquo;most enterprisey&amp;raquo;: &lt;a href=&quot;https://trino.io/&quot;&gt;&lt;em&gt;Trino&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;Trino&lt;/em&gt; ist eine clusterfähige SQL Query Engine. Die Clusterfähigkeit interessiert uns beim Rumspielen nicht. &lt;em&gt;Spark&lt;/em&gt; ist ebenfalls clusterfähig, im Gegensatz zu &lt;em&gt;DuckDB&lt;/em&gt;, wo zudem die Last nicht auf einem Server anfällt, sondern auf meinem Rechner. &lt;em&gt;Trino&lt;/em&gt; hat mit &lt;a href=&quot;https://prestodb.io/&quot;&gt;&lt;em&gt;Presto&lt;/em&gt;&lt;/a&gt; noch etwas ähnliches wie einen Klon (oder wie man das nennen mag). &lt;em&gt;Presto&lt;/em&gt; scheint mir eher das Community-Projekt zu sein. Weil ich aber zuerst ein Trino-Dockerimage gefunden habe, mache ich es jetzt damit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil &lt;em&gt;Trino&lt;/em&gt; in einem Dockercontainer läuft, muss ich mir Gedanken bezüglich des Zugriffs auf die Iceberg-Tabellen und den Katalog machen. Entweder mounte ich meine lokalen Tabellen einfach in den Container rein oder ich mache es gleich &amp;laquo;richtig&amp;raquo; (kompliziert) und speichere die Tabellen auf S3 und den Katalog in einer PostgreSQL-Datenbank. Letzteres ist keine Herausforderung, da wir es bereits mit SQLite im &lt;a href=&quot;https://blog.sogeo.services/blog/2025/01/05/house-at-the-lake-01.html&quot;&gt;ersten Teil&lt;/a&gt; gemacht haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil ich für meinen Anwendungsfall Geodaten benötige, starte ich eine weitere Datenbank, welche die Rolle des (Geo-)Datawarehouses übernimmt. Meine &lt;em&gt;docker-compose&lt;/em&gt;-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;services:
  pub-db:
    image: sogis/postgis:16-3.4-bookworm
    environment:
      POSTGRES_DB: pub
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
    ports:
      - &quot;54322:5432&quot;
    volumes:
      - type: volume
        source: pgdata_pub
        target: /var/lib/postgresql/data
  iceberg-db:
    image: sogis/postgis:16-3.4-bookworm
    environment:
      POSTGRES_DB: iceberg
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
    ports:
      - &quot;54324:5432&quot;
    volumes:
      - type: volume
        source: pgdata_iceberg
        target: /var/lib/postgresql/data
  trino:
    image: trinodb/trino
    ports:
      - &quot;8080:8080&quot;
volumes:
  pgdata_pub:
  pgdata_iceberg:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Anlegen des Icebgerg-Kataloges und für den Datenimport verwende ich wieder &lt;em&gt;Spark&lt;/em&gt;. Leider gibt es keine Möglichkeit einen Schemanamen zu definieren. So landen sämtliche Tabellen des Katalogs im public-Schema. Das Anlegen der Steuerfuss-Tabelle und Importieren der Daten war soweit auch kein Problem. Man muss einzig die korrekten Spark-Konfig-Parameter herausfinden. Das kann ein wenig hakelig sein. Was ich nicht geschafft habe, sind die AWS-S3-Credentials als Parameter zu definieren. Es funktioniert aber problemlos, wenn ich sie als Env-Variable definiere.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein pyspark-Skript sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName(&quot;IcebergS3&quot;) \
    .config(&quot;spark.sql.catalog.iceberg&quot;, &quot;org.apache.iceberg.spark.SparkCatalog&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.type&quot;, &quot;jdbc&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.uri&quot;, &quot;jdbc:postgresql://localhost:54324/iceberg&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.jdbc.user&quot;, &quot;postgres&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.jdbc.password&quot;, &quot;secret&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.warehouse&quot;, &quot;s3://XXXXXXXXXXXXXXX&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.io-impl&quot;, &quot;org.apache.iceberg.aws.s3.S3FileIO&quot;) \
    .config(&quot;spark.hadoop.fs.s3a.impl&quot;, &quot;org.apache.hadoop.fs.s3a.S3AFileSystem&quot;) \
    .config(&quot;spark.hadoop.fs.s3a.connection.ssl.enabled&quot;, &quot;true&quot;) \
    .config(&quot;spark.hadoop.fs.s3a.path.style.access&quot;, &quot;true&quot;) \
    .config(&quot;spark.sql.catalog.iceberg.s3.endpoint&quot;,&quot;https://s3-eu-central-1.amazonaws.com/&quot;) \
    .getOrCreate()

parquet_df = spark.read.parquet(&quot;/Users/stefan/Downloads/ch.so.agem.steuerfuesse.natuerliche_personen.parquet&quot;)
print(parquet_df)

parquet_df.writeTo(&quot;iceberg.agem_steuerfuesse.natuerliche_personen&quot;).using(&quot;iceberg&quot;).createOrReplace()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erscheinen keine Fehlermeldungen, sollte der Import funktioniert haben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-02/icebergs3.png&quot; alt=&quot;Iceberg S3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man mit &lt;code&gt;docker compose up&lt;/code&gt; die Container startet, passiert schon einiges aber noch nicht genau das, was man eigentlich will. &lt;em&gt;Trino&lt;/em&gt; hat noch keinen Zugriff auf die Geodatenbank und auf die Iceberg-Tabellen. Dazu müssen mittels &lt;a href=&quot;https://trino.io/docs/current/connector.html&quot;&gt;Connectoren&lt;/a&gt; &lt;a href=&quot;https://trino.io/docs/current/overview/concepts.html#trino-concept-catalog&quot;&gt;Kataloge&lt;/a&gt; definiert werden. Es sind Properties-Dateien, die in einen bestimmten Ordner (&lt;em&gt;/etc/trino/catalog/&lt;/em&gt;) kopiert werden müssen. Ich habe es so gelöst, dass ich diese in den Container reinmounte. D.h. die &lt;em&gt;docker-compose&lt;/em&gt;-Datei muss beim Trino-Service um ein Volume erweitert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;    volumes:
      - type: bind
        source: /Users/stefan/tmp/trino
        target: /etc/trino&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Konfig-Datei &lt;em&gt;pgpub.properties&lt;/em&gt; für PostgreSQL:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-properties&quot; data-lang=&quot;properties&quot;&gt;connector.name=postgresql
connection-url=jdbc:postgresql://pub-db:5432/pub
connection-user=postgres
connection-password=secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Konfig-Datei &lt;em&gt;iceberg.properties&lt;/em&gt; für die Iceberg-Tabellen auf S3:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-properties&quot; data-lang=&quot;properties&quot;&gt;connector.name=iceberg
fs.native-s3.enabled=true
iceberg.catalog.type=jdbc
iceberg.jdbc-catalog.catalog-name=iceberg
iceberg.jdbc-catalog.driver-class=org.postgresql.Driver
iceberg.jdbc-catalog.connection-url=jdbc:postgresql://iceberg-db:5432/iceberg
iceberg.jdbc-catalog.connection-user=postgres
iceberg.jdbc-catalog.connection-password=secret
iceberg.jdbc-catalog.default-warehouse-dir=s3://XXXXXXXXXXXXXXX
s3.endpoint=https://s3-eu-central-1.amazonaws.com/
s3.region=eu-central-1
s3.aws-access-key=YYYYYYYYYYYY
s3.aws-secret-key=ZZZZZZZZZZZZ&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls der Container immer noch startet, sollte alles korrekt konfiguriert sein. Für &lt;em&gt;Trino&lt;/em&gt; existiert ein JDBC-Treiber. Somit kann ich z.B. mit &lt;a href=&quot;https://dbeaver.io/&quot;&gt;&lt;em&gt;DBeaver&lt;/em&gt;&lt;/a&gt; elegant auf alle konfigurierten Trino-Kataloge zugreifen. &lt;em&gt;Trino&lt;/em&gt; unterstützt &lt;a href=&quot;https://trino.io/docs/current/functions/geospatial.html&quot;&gt;eine Vielzahl&lt;/a&gt; von Geo-Funktionen und ich kann, wie bei &lt;em&gt;DuckDB&lt;/em&gt;, eine SQL-Query absetzen, die Daten aus verschiedenen Quellen holt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
  np.gemeinde,
  ST_Area(hg.geometrie),
  np.steuerfuss_in_prozent
FROM
  pgpub.agi_hoheitsgrenzen_pub_v1.hoheitsgrenzen_gemeindegrenze AS hg
  LEFT JOIN iceberg.agem_steuerfuesse.natuerliche_personen AS np
  ON np.gemeinde = hg.gemeindename
WHERE
  jahr = 2000
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Gegensatz zu &lt;em&gt;DuckDB&lt;/em&gt; kann ich nicht direkt nach Excel exportieren. Als Workaround kann ich das &lt;a href=&quot;https://trino.io/docs/current/client/cli.html&quot;&gt;CLI-Tool&lt;/a&gt; verwenden und ein good old CSV erstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;java -jar trino-cli-468-executable.jar http://localhost:8080 --file=query.sql --output-format=CSV &amp;gt; myresult.csv&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf den ersten Blick erscheint &lt;em&gt;DuckDB&lt;/em&gt; eleganter und leichtgewichtiger. Ist es auch, nur vergleicht man Äpfel mit Birnen. Bei &lt;em&gt;Trino&lt;/em&gt; läuft die Query auf einem Server resp. kann die Last in einem Cluster auf verschiedene Server verteilt werden. &lt;em&gt;DuckDB&lt;/em&gt; läuft im Regelfall bei mir lokal auf dem PC. Ein anderer gewichtiger Unterschied ist die ganze Authentifizierung und Autorisierung. &lt;em&gt;Trino&lt;/em&gt; ist hier &lt;a href=&quot;https://trino.io/docs/current/security.html&quot;&gt;enorm mächtig&lt;/a&gt;. Man kann out-of-the-box bis auf Stufe Tabelle berechtigen. Mit &lt;a href=&quot;https://trino.io/docs/current/security/opa-access-control.html&quot;&gt;weiteren Komponenten&lt;/a&gt; sogar bis auf Stufe Attribut. Mit &lt;em&gt;DuckDB&lt;/em&gt; kann ich zwar auch Iceberg-Tabellen auf S3 benutzen. Wenn ich die S3-Credentials habe, sehe ich jedoch sämtliche Tabellen dieses Kataloges. Somit müsste also in einer Organisation der Zugang auf Iceberg-Tabellen einzig mittels einer Query Engine geschehen, die sich auch um die Autorisierung kümmert. Mehr Freiheiten hätte man, wenn sich der Iceberg-Katalog darum kümmert. Dann wäre es egal mit welcher Iceberg-Query-Engine ich auf die Tabellen zugreife. Mit &lt;a href=&quot;https://polaris.apache.org/&quot;&gt;&lt;em&gt;Apache Polaris&lt;/em&gt;&lt;/a&gt; will man das so umsetzen. &lt;em&gt;Polaris&lt;/em&gt; implementiert die &lt;a href=&quot;https://iceberg.apache.org/concepts/catalog/#overview&quot;&gt;REST-Variante&lt;/a&gt; eines Iceberg-Kataloges. Hier bannt sich - nach dem table format war - der nächste Krieg an: &amp;laquo;The War of the Catalogs&amp;raquo; (&lt;em&gt;Apache Polaris&lt;/em&gt; vs &lt;a href=&quot;https://www.unitycatalog.io/&quot;&gt;&lt;em&gt;Databricks Unity&lt;/em&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>The house at the lake #1 - Iceberg ahead</title>
      <link>http://blog.sogeo.services/blog/2025/01/05/house-at-the-lake-01.html</link>
      <pubDate>Sun, 5 Jan 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2025/01/05/house-at-the-lake-01.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Data Lake, Data Swamp, Data Lakehouse&amp;#8230;&amp;#8203; Man kommt fast nicht hinterher. Kaum meint man minimal etwas verstanden zu haben, kommt schon was neues. Weil auch im Kanton nun ab und an das Stichwort &amp;laquo;Dateninfrastruktur&amp;raquo; fällt, wollte ich mich ein wenig in die Data Lake Thematik einlesen. Nur eben um zu merken, dass man jetzt keinen Data Lake mehr macht, sondern eben ein Data Lakehouse. Ich habe nie wirklich verstanden, was so genial am Data Lake sein soll. Da werden lustig unstrukturiert irgendwo Daten abgelegt. Wie man nun darauf zugreift, resp. überhaupt was finden soll - haha im Data Swamp - entzieht sich meiner Kenntnis. Und da kommt nun das Data Lakehouse ins Spiel: Vielleicht lässt sich sagen, dass ein Data Lakehouse aus einem Data Lake besteht, der mit Metadaten und Data Governance angereichert wird (und so wieder mehr einem Data Warehouse ähnelt).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Statt eines Haufens von Dateien in beliebigen Formaten gibt es im Lakehouse z.B. ein einziges Tabellenformat und die Daten werden dort gespeichert. Der Datenzugriff erfolgt dann mit diversen Clients via Metadaten auf dieses Tabellenformat und nicht mehr direkt auf die Dateien. Es wird auch häufig von Katalogen gesprochen. Hier hat es bei mir noch nicht klick gemacht. Wahrscheinlich - déformation professionnelle - verstehe ich was anderes darunter. Vielleicht wird es auch verschiedene Kataloge geben müssen: eher was technisches für die Maschinen und etwas für den Menschen, der nach einem bestimmten Thema sucht und nicht weiss, wie eine bestimmte Tabelle genau heisst.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Tabellenformat ist das Tabellenformat. D.h. es darf nicht mit einem Dateiformat verwechselt werden, welches wiederum vom Tabellenformat verwendet werden kann, um die Daten tatsächlich zu speichern und es ist eben auch &amp;laquo;nur&amp;raquo; das Tabellenformat: Es stellt nicht eine Berechnungsengine zur Verfügung, so wie man das vom klassischen Data Warehouse kann, wo die relationale Datenbank sich um das Speichern der Daten kümmert und auch die Bearbeitungsmöglichkeit derselben mittels SQL zur Verfügung stellt. Wenn man mit den Daten in einem Data Lakehouse auch arbeiten will, braucht es noch mindestens eine weitere Kompenente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein für ein Data Lakehouse sinnvolles Tabellenformat muss  &lt;a href=&quot;https://en.wikipedia.org/wiki/ACID&quot;&gt;ACID&lt;/a&gt;-Transaktionen unterstützen, so wie man es auch von relationen Datenbanken kennt. Ein solches Tabellenformat ist z.B. &lt;a href=&quot;https://iceberg.apache.org/&quot;&gt;&lt;em&gt;Apache Iceberg&lt;/em&gt;&lt;/a&gt;. Es gibt noch weitere aber von &lt;em&gt;Iceberg&lt;/em&gt; liest man momentan relativ viel und anscheined hat &lt;em&gt;Iceberg&lt;/em&gt; den &amp;laquo;table format war&amp;raquo; gewonnen. Und mit &lt;em&gt;Iceberg&lt;/em&gt; wollte ich nun mal arbeiten, um ein Gespür zu bekommen, was das genau heisst und was für Komponenten im Spiel sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Apache Iceberg&lt;/em&gt; ist nicht etwas, was man einfach mittels &amp;laquo;setup.exe&amp;raquo; installiert. Es ist vor allem eine Spezifikation, bietet aber auch noch ein paar Libraries an, damit bestehende Werkzeuge damit arbeiten können. Starten müssen wir aber mit &lt;a href=&quot;https://spark.apache.org/&quot;&gt;&lt;em&gt;Apache Spark&lt;/em&gt;&lt;/a&gt;. Das wird unser Client, um Daten in &lt;em&gt;Iceberg&lt;/em&gt; speichern zu können. Ich &lt;a href=&quot;https://spark.apache.org/downloads.html&quot;&gt;wähle&lt;/a&gt; die Version 3.5.4 und den Package-Typ &amp;laquo;Pre-built for Apache Hadoop 3.3 and later&amp;raquo;. Grundvoraussetzung ist, dass Java installiert ist. Mit Version 11 und 17 liegt man nicht falsch. Mit Version 21 geht es noch nicht. &lt;em&gt;Spark&lt;/em&gt; bietet drei Benutzerinterfaces an: SQL, Scala-Shell und Python-Shell. Falls man Scala (Version 2.12) und Python verwenden will, muss man diese ebenfalls installieren. Scala habe ich mit &lt;a href=&quot;https://sdkman.io/&quot;&gt;SDKMAN&lt;/a&gt; installiert und Python hatte ich bereits installiert gehabt. &lt;em&gt;Apache Spark&lt;/em&gt; entzippt man und ergänzt die PATH-Variable, z.B. in der &lt;em&gt;.zshrc&lt;/em&gt;-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;export SPARK_HOME=/Users/stefan/apps/spark-3.5.4-bin-hadoop3
export PATH=$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Aufruf &lt;code&gt;spark-shell --version&lt;/code&gt; sollte circa folgenden Output liefern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;25/01/04 18:24:26 WARN Utils: Your hostname, Monterrico.local resolves to a loopback address: 127.0.0.1; using 10.0.1.12 instead (on interface en0)
25/01/04 18:24:26 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  &apos;_/
   /___/ .__/\_,_/_/ /_/\_\   version 3.5.4
      /_/

Using Scala version 2.12.18, OpenJDK 64-Bit Server VM, 17.0.10
Branch HEAD
Compiled by user yangjie01 on 2024-12-17T04:51:46Z
Revision a6f220d951742f4074b37772485ee0ec7a774e7d
Url https://github.com/apache/spark
Type --help for more information.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und nun wirds kompliziert: die Kataloge ins Spiel. Wir müssen eine Spark-Session (egal bei welchem der drei Interfaces) so konfigurieren, dass man mit &lt;em&gt;Iceberg&lt;/em&gt; zusammenarbeiten kann. Mit &lt;code&gt;spark-sql&lt;/code&gt; sieht ein Start-Aufruf so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;spark-sql --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.7.1 \
--conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \
--conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \
--conf spark.sql.catalog.local.type=hadoop \
--conf spark.sql.catalog.local.warehouse=/Users/stefan/tmp/warehouse&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird eine zusätzliche Library benötigt. Dabei ist auf die passende Version zu achten: &lt;code&gt;3.5&lt;/code&gt; für Spark, &lt;code&gt;2.12&lt;/code&gt; für Scala und &lt;code&gt;1.7.1&lt;/code&gt; für &lt;em&gt;Iceberg&lt;/em&gt;. Beim Start wird die Jar-Datei herunterladen. Man kann sie aber auch einmalig herunterladen und in den &lt;em&gt;jars&lt;/em&gt;-Ordern der Spark-Installation kopieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der zweiten Zeile teilen wir Spark mit, dass wir Iceberg-Extensions verwenden wollen. Und dann kommen drei Zeilen mit den Katalogen und ich kann immer noch nicht genau abgrenzen, ob es ein Spark-Katalog, ein Iceberg-Katalog oder was auch immer ist. Jedenfalls verstehe ich es so, dass es darum geht den Typ des Iceberg-Kataloges auszuwählen. Mit &lt;code&gt;hadoop&lt;/code&gt; wählen wir die einfachste file-basierte Variante eines Katalogs aus (eignet sich nicht für Produktion). Die Daten selber werden unter &lt;code&gt;warehouse&lt;/code&gt; gespeichert. Hier wird in Produktion häufig ein S3-Bucket stehen. Der Namen des Katalogs ist &lt;code&gt;local&lt;/code&gt; und darf auch anders heissen und trotzdem ein lokaler Katalog sein. Den Befehl setzt man in der Konsole ab und falls alles gut geht, erscheint: &lt;code&gt;spark-sql (default)&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zeit, um Tabellen und Daten zu erzeugen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;CREATE TABLE local.foo.steuerfuss (jahr int, gemeinde string, steuerfuss int) USING iceberg;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Tabellen werden qualifiziert angesprochen, wobei &lt;code&gt;local&lt;/code&gt; der Katalog ist, &lt;code&gt;foo&lt;/code&gt; der Namespace (entspricht plusminus einem DB-Schema) und &lt;code&gt;steuerfuss&lt;/code&gt; der Tabellenname ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf dem Dateisystem werden erste Verzeichnisse und Dateien erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-01/create-table-01.png&quot; alt=&quot;CREATE TABLE&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;INSERT INTO local.foo.steuerfuss (jahr, gemeinde, steuerfuss) VALUES (2000, &apos;Aedermannsdorf&apos;, 150);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;INSERT INTO local.foo.steuerfuss (jahr, gemeinde, steuerfuss) VALUES (2000, &apos;Aeschi (SO)&apos;, 135);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;INSERT INTO local.foo.steuerfuss (jahr, gemeinde, steuerfuss) VALUES (2000, &apos;Balm bei Günsberg&apos;, 110);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;SELECT&lt;/code&gt; kann ich mir die drei Records anzeigen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;spark-sql (default)&amp;gt; SELECT * FROM local.foo.steuerfuss;
2000	Aeschi (SO)	135
2000	Aedermannsdorf	150
2000	Balm bei Günsberg	110
Time taken: 0.067 seconds, Fetched 3 row(s)
spark-sql (default)&amp;gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viel interessanter ist jedoch, was auf dem Dateisystem abging:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-01/insert-01.png&quot; alt=&quot;INSERT INTO&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt mehr Metadaten und einen &lt;em&gt;data&lt;/em&gt;-Ordner. In diesem &lt;em&gt;data&lt;/em&gt;-Ordner sind drei Parquet-Dateien. Die Daten speichert &lt;em&gt;Iceberg&lt;/em&gt; in Parquet-Dateien (andere Formate stehen zur Auswahl). Was besonders auffällt, ist natürlich, dass es drei Stück gibt und wir haben drei INSERT-Befehle gemacht. Häufig führt wahrscheinlich schon jeder INSERT-Befehl zu einer zusätzlichen Datei, wobei ein INSERT-Befehl natürlich mehrere Records beinhalten kann. In unserem Fall ist in jeder Parquet-Datei auch jeweils nur genau ein Record. Man kann die maximale Grösse von Parquet-Dateien konfigurieren und man will  bei ganz vielen vorliegenden Dateien aus Performancegründen diese zu einer grösseren zusammenführen können (auch das ist möglich mit sogenannten &amp;laquo;compaction jobs&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Apache Iceberg&lt;/em&gt; unterstützt Snapshots und Time Travel. Jede Transaktion (in unserem Fall die drei INSERT-Befehle) führt zu einem Snapshot:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;spark-sql (default)&amp;gt; SELECT * FROM local.foo.steuerfuss.snapshots;
2025-01-04 19:01:55.286	4283993947365810884	NULL	append	/Users/stefan/tmp/warehouse/foo/steuerfuss/metadata/snap-4283993947365810884-1-c6c6b8bd-a8de-4a6e-88a5-fa59e263e328.avro	{&quot;added-data-files&quot;:&quot;1&quot;,&quot;added-files-size&quot;:&quot;940&quot;,&quot;added-records&quot;:&quot;1&quot;,&quot;app-id&quot;:&quot;local-1736012466059&quot;,&quot;changed-partition-count&quot;:&quot;1&quot;,&quot;engine-name&quot;:&quot;spark&quot;,&quot;engine-version&quot;:&quot;3.5.4&quot;,&quot;iceberg-version&quot;:&quot;Apache Iceberg 1.7.1 (commit 4a432839233f2343a9eae8255532f911f06358ef)&quot;,&quot;spark.app.id&quot;:&quot;local-1736012466059&quot;,&quot;total-data-files&quot;:&quot;1&quot;,&quot;total-delete-files&quot;:&quot;0&quot;,&quot;total-equality-deletes&quot;:&quot;0&quot;,&quot;total-files-size&quot;:&quot;940&quot;,&quot;total-position-deletes&quot;:&quot;0&quot;,&quot;total-records&quot;:&quot;1&quot;}
2025-01-04 19:02:01.306	4180574121117132588	4283993947365810884	append	/Users/stefan/tmp/warehouse/foo/steuerfuss/metadata/snap-4180574121117132588-1-364edb50-a639-4665-8e01-5e4a0797c75b.avro	{&quot;added-data-files&quot;:&quot;1&quot;,&quot;added-files-size&quot;:&quot;919&quot;,&quot;added-records&quot;:&quot;1&quot;,&quot;app-id&quot;:&quot;local-1736012466059&quot;,&quot;changed-partition-count&quot;:&quot;1&quot;,&quot;engine-name&quot;:&quot;spark&quot;,&quot;engine-version&quot;:&quot;3.5.4&quot;,&quot;iceberg-version&quot;:&quot;Apache Iceberg 1.7.1 (commit 4a432839233f2343a9eae8255532f911f06358ef)&quot;,&quot;spark.app.id&quot;:&quot;local-1736012466059&quot;,&quot;total-data-files&quot;:&quot;2&quot;,&quot;total-delete-files&quot;:&quot;0&quot;,&quot;total-equality-deletes&quot;:&quot;0&quot;,&quot;total-files-size&quot;:&quot;1859&quot;,&quot;total-position-deletes&quot;:&quot;0&quot;,&quot;total-records&quot;:&quot;2&quot;}
2025-01-04 19:02:05.316	2935673125777738477	4180574121117132588	append	/Users/stefan/tmp/warehouse/foo/steuerfuss/metadata/snap-2935673125777738477-1-790f349b-3cf5-4eba-b95b-738d16c2292d.avro	{&quot;added-data-files&quot;:&quot;1&quot;,&quot;added-files-size&quot;:&quot;968&quot;,&quot;added-records&quot;:&quot;1&quot;,&quot;app-id&quot;:&quot;local-1736012466059&quot;,&quot;changed-partition-count&quot;:&quot;1&quot;,&quot;engine-name&quot;:&quot;spark&quot;,&quot;engine-version&quot;:&quot;3.5.4&quot;,&quot;iceberg-version&quot;:&quot;Apache Iceberg 1.7.1 (commit 4a432839233f2343a9eae8255532f911f06358ef)&quot;,&quot;spark.app.id&quot;:&quot;local-1736012466059&quot;,&quot;total-data-files&quot;:&quot;3&quot;,&quot;total-delete-files&quot;:&quot;0&quot;,&quot;total-equality-deletes&quot;:&quot;0&quot;,&quot;total-files-size&quot;:&quot;2827&quot;,&quot;total-position-deletes&quot;:&quot;0&quot;,&quot;total-records&quot;:&quot;3&quot;}
Time taken: 0.055 seconds, Fetched 3 row(s)
spark-sql (default)&amp;gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die erste Spalte ist der Zeitstempel und die zweite Spalte die Snapshot-ID. Mit dieser ID kann ich einfach einen bestimmten Datenstand aufrufen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;spark-sql (default)&amp;gt; SELECT * FROM local.foo.steuerfuss VERSION AS OF 4180574121117132588;
2000	Aedermannsdorf	150
2000	Aeschi (SO)	135
Time taken: 0.065 seconds, Fetched 2 row(s)
spark-sql (default)&amp;gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und schon sind es nur noch zwei Records. Ähnlich funktioniert es mit dem Timestamp:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;spark-sql (default)&amp;gt; SELECT * FROM local.foo.steuerfuss TIMESTAMP AS OF &apos;2025-01-04 19:01:55.286&apos;;
2000	Aedermannsdorf	150
Time taken: 0.066 seconds, Fetched 1 row(s)
spark-sql (default)&amp;gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Snapshots kann und soll man wohl von Zeit zu Zeit löschen, da sonst auch wieder die Performance massiv leidet. Es beschleicht mich das Gefühl, dass die &amp;laquo;korrekte&amp;raquo; Konfiguration von &lt;em&gt;Iceberg&lt;/em&gt; auch nicht ganz ohne ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiteres interessantes Feature von &lt;em&gt;Apache Iceberg&lt;/em&gt; ist das Partitioning. Nehmen wir an, wir haben einen sehr grossen Datensatz. Es gibt pro Jahr (das als Attribut im Tabellenschema vorhanden ist) sehr viele Records. Abfragen werden häufig für ein bestimmtes Jahr gemacht. Dann können die Daten in der Tabelle auch gruppiert nach Jahr gespeichert werden. So werden die Queries viel schneller. Als Beispiel importieren wir mit der Python-Shell eine Parquet-Datei mit den Steuerfüssen sämtlicher Gemeinden für die Jahre 2000 - 2022. Das Starten ist gleich wie bei der SQL-Shell, nur mit &lt;code&gt;pyspark&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;pyspark --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.7.1 \
--conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \
--conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \
--conf spark.sql.catalog.local.type=hadoop \
--conf spark.sql.catalog.local.warehouse=/Users/stefan/tmp/warehouse&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit Python ist es möglich auf Basis einer Parquet-Datei eine Tabelle zu erstellen und sämtliche Daten innerhalb einer Transaktion zu importieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from pyspark.sql import SparkSession

parquet_df = spark.read.parquet(&quot;/Users/stefan/Downloads/ch.so.agem.steuerfuesse.natuerliche_personen.parquet&quot;)
print(parquet_df)

parquet_df.writeTo(&quot;local.bar.steuerfuss_partitioned&quot;).partitionedBy(&quot;jahr&quot;).using(&quot;iceberg&quot;).createOrReplace()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Befehle dünken mich selbsterklärend. Die Struktur auf dem Dateisystem sieht entsprechend gegliedert aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/house-at-the-lake-01/partitioning-01.png&quot; alt=&quot;partitioning&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt nochmal was zu meiner Nemesis, den Katalogen: Wie erwähnt gibt es verschiedene Typen von Iceberg-Katalogen. Ein weiterer Typ ist &amp;laquo;jdbc&amp;raquo;. In diesem Fall werden bestimmte Metainformationen in einer relationalen Datenbank gespeichert. Die Konfiguration (mit Scala) ist folgendermassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder()
  .appName(&quot;IcebergExample&quot;)
  .config(&quot;spark.sql.catalog.local&quot;, &quot;org.apache.iceberg.spark.SparkCatalog&quot;)
  .config(&quot;spark.sql.catalog.local.type&quot;, &quot;jdbc&quot;)
  .config(&quot;spark.sql.catalog.local.jdbc.url&quot;, &quot;jdbc:sqlite:/Users/stefan/tmp/iceberg_metadata.db&quot;)
  .config(&quot;spark.sql.catalog.local.jdbc.driver&quot;, &quot;org.sqlite.JDBC&quot;)
  .config(&quot;spark.sql.catalog.local.warehouse&quot;, &quot;file:/Users/stefan/tmp/warehouse&quot;)
  .getOrCreate()

import spark.implicits._

spark.sql(&quot;CREATE TABLE local.default.sample_table (id INT, name STRING) USING iceberg&quot;)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dabei darf man sich nicht irritieren lassen, dass die SQLite-Datenbank erst nach dem Erstellen einer ersten Iceberg-Tabelle erzeugt wird (Hallo Zukunfts-Stefan).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein Lakehouse-Zwischenfazit: Abklären, was man eigentlich will und was die Anforderungen sind, sollte man schon noch vor der Realisierung eines Data Lakehouses. Vielleicht reicht auch wie fast immer eine PostgreSQL-Datenbank und bisschen Parquet-Dateien und DuckDB. Wir werden wohl nicht so schnell hunderte von Petabytes an Daten vorliegen haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.upsolver.com/blog/apache-iceberg-vs-parquet-file-formats-vs-table-formats&quot; class=&quot;bare&quot;&gt;https://www.upsolver.com/blog/apache-iceberg-vs-parquet-file-formats-vs-table-formats&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@ajanthabhat/iceberg-catalogs-choosing-the-right-one-for-your-needs-77ff6dcfaec0&quot; class=&quot;bare&quot;&gt;https://medium.com/@ajanthabhat/iceberg-catalogs-choosing-the-right-one-for-your-needs-77ff6dcfaec0&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/snowflake/polaris-catalog-if-you-have-been-napping-9005909dc1fa&quot; class=&quot;bare&quot;&gt;https://medium.com/snowflake/polaris-catalog-if-you-have-been-napping-9005909dc1fa&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Geospatial Support: &lt;a href=&quot;https://github.com/apache/iceberg/issues/10260&quot; class=&quot;bare&quot;&gt;https://github.com/apache/iceberg/issues/10260&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #45 - INTERLIS goes OLAP</title>
      <link>http://blog.sogeo.services/blog/2024/12/27/interlis-leicht-gemacht-number-45.html</link>
      <pubDate>Fri, 27 Dec 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/12/27/interlis-leicht-gemacht-number-45.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, ich habe nochmals ein paar Stunden in den &lt;a href=&quot;https://blog.sogeo.services/blog/2024/01/15/interlis-leicht-gemacht-number-40.html&quot;&gt;ili2duckdb-Flavor&lt;/a&gt; &lt;a href=&quot;https://github.com/edigonzales/ili2db/commits/duckdb/&quot;&gt;investiert&lt;/a&gt; und schlussendlich einen &lt;a href=&quot;https://github.com/claeis/ili2db/pull/556&quot;&gt;Pull-Request&lt;/a&gt; gemacht. Wenn/falls er gemerget wird, gibt es &lt;a href=&quot;https://github.com/claeis/ili2db/&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt; nun auch für eine OLAP-Datenbank. Interessanterweise ist mein erster Einsatzzweck nicht mal per se eine Analyse, sondern eher klassisches Geoprocessing (wobei: wo genau hört das eine auf und beginnt das andere?). Zuerst aber ein paar Worte zur Umsetzung von &lt;em&gt;ili2duckdb&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie im ersten &lt;a href=&quot;https://blog.sogeo.services/blog/2024/01/15/interlis-leicht-gemacht-number-40.html&quot;&gt;Beitrag&lt;/a&gt; zu &lt;em&gt;ili2duckdb&lt;/em&gt; erwähnt, ist die Umsetzung relativ straight forward nach dem Motto: Wo ein JDBC-Treiber, da auch ein ili2xy. Notfalls muss/kann man sich sogar einen eigenen JDBC-Treiber (oder Wrapper) schreiben (siehe &lt;a href=&quot;https://github.com/claeis/ili2db/tree/master/ili2fgdb/src/ch/ehi/ili2fgdb/jdbc&quot;&gt;&lt;em&gt;ili2fgdb&lt;/em&gt;&lt;/a&gt;). Im Regelfall reicht es, wenn man fünf abstrakte Klassen erweitert resp. Interfaces implementiert und schon hat man seinen ili2db-Flavor. &lt;em&gt;Ili2db&lt;/em&gt; ist eben nicht bloss ein Werkzeug, sondern auch ein Framework, um weitere Flavors umzusetzen. Die Arbeiten mit diesem Framework gestalten sich meiner Meinung nach als angenehm und man durchschaut relativ schnell, wo man was anpassen muss. So gibt es z.B. eine zu implementierende Methode, die sich darum kümmert wie aus einem IOM-Koordinaten-Objekt ein Java-Objekt gemacht wird, welches anschliessend mit einer Geofunktion der Datenbank in die Datenbank geschrieben wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@Override
public Object fromIomCoord(IomObject value, int srid, boolean is3D)
  throws SQLException, ConverterException {
  if (value!=null) {
      Iox2wkb conv=new Iox2wkb(2, ByteOrder.BIG_ENDIAN, false);
      try {
        return conv.coord2wkb(value);
      } catch (Iox2wkbException ex) {
        throw new ConverterException(ex);
      }
    }
  return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die dazugehörige Geofunktion muss ebenfalls definiert werden, indem man folgende Methode implementiert (resp. überschreibt):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@Override
public String getInsertValueWrapperCoord(String wkfValue, int srid) {
  return &quot;ST_GeomFromWKB(&quot;+wkfValue+&quot;::blob)&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das explizite Casten nach &lt;code&gt;blob&lt;/code&gt; ist meines Erachtens eine/ein &lt;a href=&quot;https://github.com/duckdb/duckdb-spatial/issues/471&quot;&gt;Unschönheit/Bug&lt;/a&gt; von &lt;em&gt;DuckDB Spatial&lt;/em&gt;. Btw: Das Variable-Naming als &lt;code&gt;wkfValue&lt;/code&gt; finde ich unlogisch und ungünstig (oder ich steh&apos; auf dem Schlauch&amp;#8230;&amp;#8203; es wird nicht der WKF-Wert geliefert und es ist ein Prepared-Statement, d.h. der String ist nichts anderes als ein Fragezeichen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;code&gt;fromIomCoord&lt;/code&gt;-Methode kann für jeden Flavor völlig anders funktionieren. Für &lt;em&gt;ili2gpkg&lt;/em&gt; muss eine andere Methode für die Umwandlung nach WKB verwendet werden, weil GeoPackage ein leicht anderes WKB-Format verwendet. Das sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;@Override
public java.lang.Object fromIomCoord(IomObject value, int srsid, boolean is3D)
  throws SQLException, ConverterException {
  if (value!=null) {
      Iox2gpkg conv=new Iox2gpkg(is3D?3:2);
    try {
      return conv.coord2wkb(value,srsid);
    } catch (Iox2wkbException ex) {
      throw new ConverterException(ex);
    }
  }
  return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So geht das nun weiter für mehr oder weniger sämtliche möglichen Datentypen (nicht nur Geometrien), wie z.B. Date, UUID etc. pp. Auch muss ein ID-Generator implementiert werden, weil nicht zwingend jede Datenbank Sequenzen gleich unterstützt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das Coole ist, dass ich mich um alles andere nicht zu kümmern brauche. Jede der gefühlt 1000 ili2db-Optionen funktioniert mit jedem neuen Flavor. Ein super Fundament wurde hier geschaffen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wäre natürlich zu einfach und gelogen, wenn es nicht doch noch einen kleinen Stolperstein (oder auch zwei) gegeben hätte. Als ich als erstes die DuckDB-Version von 0.9.2 auf die aktuelle Version 1.1.3 upgedatet habe und einen Testrun machte, hing sich &lt;em&gt;ili2duckdb&lt;/em&gt; auf. Während des Testruns ging plötzlich nichts mehr. Es stellte sich heraus, dass es einen &lt;a href=&quot;https://github.com/duckdb/duckdb-java/issues/101&quot;&gt;Bug&lt;/a&gt; (?) gibt. In meinem Fall war nicht die zweite Connection das Problem, sondern ein zweites Statement, ohne das erste zu schliessen. Es dürfte sich aber wohl um den gleichen Bug handeln. Das gleiche Problem tauchte beim Erstellen des Schemas auf. Hier wird die &lt;a href=&quot;https://github.com/claeis/ehisqlgen/&quot;&gt;ehisqlgen-Bibliothek&lt;/a&gt; verwendet und muss deshalb &lt;a href=&quot;https://github.com/claeis/ehisqlgen/pull/5&quot;&gt;gefixed&lt;/a&gt; werden. Der zweite kleine Stolperstein, der es nötig machte den Kern-Testcode an der einen oder anderen Stelle anzupassen, ist die Besonderheit, dass DuckDB nur eine schreibende Connection erlaubt oder mehrere nur-lesende. Das war es aber auch schon.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erwähnenswert sind ebenfalls Dinge, die wegen DuckDB nicht gehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3D-Geometrien werden nicht unterstützt und werden nach 2D-Geometrien umgewandelt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kreisbögen werden nicht unterstützt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SRID wird bei Geometrien nicht unterstützt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die &lt;code&gt;ADD CONSTRAINT&lt;/code&gt;-Syntax wird noch nicht unterstützt. Das führt zu fast keinen Constraints auf der Datenbank.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Der Geometrie-Index ist zwar vorhanden, jedoch - soweit ich es verstehe - nicht wirklich brauchbar, da z.B. bei &lt;code&gt;ST_Intersects()&lt;/code&gt; ein Wert zur Planning Time bekannt sein muss.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ist nun mein Einsatzzweck? Exemplarisch ein Projekt des Amtes für Raumplanung (ARP). Sie möchten für jede Gemeinde auf Knopfdruck wissen, von welchen raumplanerisch relevanten Themen die Gemeinde betroffen ist. Ihre Idee war es diese Information pro Gemeinde händisch nachzuführen. Obwohl ich mich mit der Grundidee (also sicher nicht mit der händischen Nachführung) anfreunden kann, hadere ich mit dem Glauben, dass das in Realität wirklich gut kommt. Sie haben jetzt schon eine Liste mit über 70 Themen und da es nicht für einen ganz konkreten Anwendungfall gelten soll, sondern für Raumplanung als solches, ist die Antwort dann vielleicht zu allgemein. Naja&amp;#8230;&amp;#8203; Viele der benötigten Daten liegen natürlich in unserer zentralen Datenbank vor. Unter den 70 Themen sind aber auch viele Bundesinventare. Diese Daten importieren wir nicht, sondern zeigen nur den WMS des Bundes im Web GIS Client an. Wie finde ich nun heraus, ob die Gemeinde Solothurn vom Bundesinventar Amphibienlaichgebiete betroffen ist?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine erste Idee war den REST-Service anzuzapfen und die JSON-Antwort als Datei zu speichern. Diese in die PostgreSQL-Datenbank zu importieren und dann mit den JSON-Funktionen von PostgreSQL bisschen Geo-Magic zu machen. Das alles ginge heute bereits mit &lt;a href=&quot;https://gretl.app/&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; problemlos. Wenn ich den REST-Service aber richtig verstehe, werden nur 200 Objekte zurückgeliefert (dokumentiert sind 50, was falsch zu sein scheint). Weil ich ja nicht sicher sein kann, dass ich nie mehr als 200 habe, müsste ich mehrere Requests (Offsets werden unterstützt) machen. Das fand ich dann bisschen zu workaroundig/handgestrickt. Eine andere Möglichkeit, die wir im Angebot haben, ist das Hochfahren einer temporären PostgreSQL-Datenbank. In diese wird dann mit &lt;em&gt;GRETL&lt;/em&gt; die angebotene Shapedatei der Amphibienlaichgebiete importiert. Das Setup ist aber so, dass wir zur Shapedatei ein &amp;laquo;Dummy-INTERLIS-Modell&amp;raquo; schreiben müssen, um die Tabellen in der DB zu erstellen. Finde ich immer bisschen aufwändig für etwas, was eigentlich nicht von dauerndem Interesse ist. Und genau hier kommt eben &lt;em&gt;ili2duckdb&lt;/em&gt; ins Spiel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;DuckDB Spatial&lt;/em&gt; hat eine &lt;a href=&quot;https://duckdb.org/docs/extensions/spatial/functions#st_read&quot;&gt;&lt;code&gt;ST_Read()&lt;/code&gt;&lt;/a&gt;-Funktion im Angebot, die ein Wrapper um OGR ist. D.h. ich kann mit dieser Funktion jedes von OGR unterstützte Format via SQL in DuckDB ansprechen und verwenden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
  *
FROM
  ST_Read(&apos;amphibLaichgebiet.shp&apos;, spatial_filter_box={min_x: 2590925, min_y: 1212325, max_x: 2645288, max_y: 1263441}::BOX_2D)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Tüpfelchen auf dem I wäre, wenn die von Swisstopo angebotenen Zip-Dateien so gepackt wären, dass man GDALs Virtual File System verwenden könnte. Dann müsste man die Dateien nicht mal mehr herunterladen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ogrinfo -ro -al -so /vsizip/vsicurl/https://data.geo.admin.ch/ch.bafu.bundesinventare-amphibien/data.zip&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Beweis, dass das gehen würde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ogrinfo -ro -al -so /vsizip/vsicurl/https://files.geo.so.ch/ch.so.afu.abbaustellen/aktuell/ch.so.afu.abbaustellen.shp.zip&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ultra-Tüpfelchen wären natürlich Cloud Native Formate&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusammengefasst heisst das: Ich habe mit &lt;em&gt;DuckDB&lt;/em&gt; eine dateibasierte Datenbank, die Geoprocessing und INTERLIS unterstützt. Es gibt mit dieser Variante viel weniger Overhead und Komplexität: kein Datenmodell für Wegwerf-Daten, kein Dockercontainer, der gestartet werden muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein kleines INTERLIS-Datenmodell wird benötigt, um das Wissen zu speichern, welche Gemeinde von welchem Thema betroffen ist. Ad hoc sieht das etwa so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

/** !!------------------------------------------------------------------------------
 *  !! Version    | wer | Änderung
 *  !!------------------------------------------------------------------------------
 *  !! 2024-12-27 | sz  | Initialerstellung
 *  !!==============================================================================
 */
!!@ technicalContact=mailto:agi@bd.so.ch
!!@ furtherInformation=https://geo.so.ch/models/ARP/SO_ARP_SEin_Konfiguration_20241227.uml
!!@ shortDescription=&quot;Datenmodell für die (Teil-)Konfiguration der SEin-App&quot;
!!@ title=&quot;SEin-App Konfiguration&quot;
MODEL SO_ARP_SEin_Konfiguration_20241217 (de)
AT &quot;https://arp.so.ch&quot;
VERSION &quot;2024-12-27&quot;  =

  TOPIC SEin =

    CLASS Gruppe =
      Name : MANDATORY TEXT*500;
    END Gruppe;

    CLASS Thema =
      Name : MANDATORY TEXT*500;
      Karte : TEXT*500; !! Eigentlich Layer-ID
      !! TODO: Transparenz
    END Thema;

    CLASS Gemeinde =
      Name : MANDATORY TEXT*200;
      BFSNr : MANDATORY 2000 .. 3000;
      UNIQUE BFSNr;
    END Gemeinde;

    ASSOCIATION Gruppe_Thema =
      Gruppe_R -- {1} Gruppe;
      Thema_R -- {0..*} Thema;
    END Gruppe_Thema;

    ASSOCIATION Thema_Gemeinde =
      Thema_R -- {0..*} Thema;
      Gemeinde_R -- {0..*} Gemeinde;
      ist_betroffen : BOOLEAN;
    END Thema_Gemeinde;

  END SEin;

END SO_ARP_SEin_Konfiguration_20241217.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Prozess (aka GRETL-Job) sieht circa so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;DuckDB-Datei mit Konfigurations-INTERLIS-Datenmodell erstellen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hoheitsgrenzen importieren (z.B. von &lt;a href=&quot;https://files.geo.so.ch/ch.so.agi.av.hoheitsgrenzen/aktuell/&quot;&gt;hier&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pro Gemeinde einen Eintrag in der Klasse Gemeinde erstellen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loop über alle Gemeinden und prüfen, ob ein Amphibienlaichgebiet innerhalb der Gemeinde liegt und Eintrag in die Beziehungstabelle (Thema_Gemeinde) erstellen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der relevante Teil des GRETL-Jobs sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;tasks.register(&apos;downloadAmphibien&apos;, Download) {
    src &quot;https://data.geo.admin.ch/ch.bafu.bundesinventare-amphibien/data.zip&quot;
    dest pathToTempFolder
    overwrite true
}

tasks.register(&apos;unzipAmphibien&apos;, Copy) {
    dependsOn &apos;downloadAmphibien&apos;
    from zipTree(Paths.get(pathToTempFolder, &quot;data.zip&quot;))
    into file(&quot;$rootDir&quot;)
    include &quot;*LV95*/*.shp&quot;
    include &quot;*LV95*/*.dbf&quot;
    include &quot;*LV95*/*.shx&quot;
}

tasks.register(&apos;processInit&apos;, SqlExecutor) {
    database = [dbUri, dbUser, dbPwd]
    sqlFiles = [&quot;delete.sql&quot;]
}

def gemeinden = [2401,2402,2403,2404,2405,2406,2407,2408,2421,2422,2424,2425,2426,2427,2428,2430,2445,2455,2457,2461,2463,2464,2465,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2491,2492,2493,2495,2497,2499,2500,2501,2502,2503,2511,2513,2514,2516,2517,2518,2519,2520,2523,2524,2525,2526,2527,2528,2529,2530,2532,2534,2535,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2553,2554,2555,2556,2571,2572,2573,2574,2575,2576,2578,2579,2580,2581,2582,2583,2584,2585,2586,2601,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622]

gemeinden.each { gemeinde -&amp;gt;
    tasks.register(&quot;processGemeinde_$gemeinde&quot;, SqlExecutor) {
        dependsOn &apos;processInit&apos;
        database = [dbUri, dbUser, dbPwd]
        sqlFiles = [&quot;gemeinde.sql&quot;, &quot;amphibien.sql&quot;]
        sqlParameters = [bfsnr: gemeinde as String]
    }
}

task processAll() {
    description = &quot;Sql aggregation task.&quot;
    dependsOn {
        tasks.findAll { task -&amp;gt; task.name.startsWith(&apos;processGemeinde_&apos;) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die eigentliche Businesslogik (Ist die Gemeinde betroffen? Abfüllen der Konfig-Tabellen.) passiert in SQL:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;LOAD spatial;

CREATE TEMP TABLE t_betroffen AS
WITH gemeinde AS
(
    SELECT
        g2.t_id AS gemeinde_t_id,
        gemeindename,
        bfs_gemeindenummer,
        geometrie
    FROM
        agi_hoheitsgrenzen_pub.hoheitsgrenzen_gemeindegrenze AS g1
        INNER JOIN sein_konfig.sein_gemeinde AS g2
        ON g1.bfs_gemeindenummer = g2.bfsnr
    WHERE
        bfs_gemeindenummer = ${bfsnr}
)
,
themadaten AS
(
    SELECT
        ST_Multi(geom) AS geom
    FROM
    ST_Read(&apos;Amphibien_LV95/amphibLaichgebiet.shp&apos;, spatial_filter_box={min_x: 2590925, min_y: 1212325, max_x: 2645288, max_y: 1263441}::BOX_2D)

)
,
thema AS
(
    SELECT
        t_id AS thema_t_id
    FROM
        sein_konfig.sein_thema
    WHERE
        karte = &apos;ch.bafu.bundesinventare-amphibien&apos;
)
,
betroffen AS
(
    SELECT
        gemeinde.gemeinde_t_id,
        gemeinde.gemeindename,
        gemeinde.bfs_gemeindenummer,
        ist_betroffen,
        thema.thema_t_id
    FROM
    (
        SELECT
            count(*) &amp;gt; 0 AS ist_betroffen
        FROM
            gemeinde
            INNER JOIN themadaten
            ON ST_Overlaps(gemeinde.geometrie, themadaten.geom)
    ) AS foo
    LEFT JOIN gemeinde
    ON 1=1
    LEFT JOIN thema
    ON 1=1
)
SELECT
    *
FROM
    betroffen
;

INSERT INTO sein_konfig.sein_thema_gemeinde
(
    thema_r,
    gemeinde_r,
    ist_betroffen
)
SELECT
    thema_t_id,
    gemeinde_t_id,
    ist_betroffen
FROM
    t_betroffen
;

DROP TABLE
    t_betroffen
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee ist, dass die Klassen &amp;laquo;Gruppe&amp;raquo; und &amp;laquo;Thema&amp;raquo; durch das Fachamt in der zentralen Datenbank nachgeführt werden. Der GRETL-Job exportiert die Daten aus der zentralen Datenbank und importiert sie in die DuckDB-Datei (siehe Punkt 1 oben). Natürlich muss das SQL angepasst werden oder der GRETL-Job, wenn z.B. ein neues Thema hinzukommmt. Übrigens: Eleganter wäre, wenn wir die Klassen in unterschiedliche Topics packen würden. Dann kann ich beim Re-Import nur das &amp;laquo;Gemeinde&amp;raquo;-Topic berücksichtigen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt mit &lt;em&gt;DuckDB&lt;/em&gt; vielleicht sogar noch eine weniger aufwändige Variante. Man kann aus &lt;em&gt;DuckDB&lt;/em&gt; &lt;em&gt;PostgreSQL&lt;/em&gt; &lt;a href=&quot;https://duckdb.org/docs/extensions/postgres.html&quot;&gt;direkt ansprechen&lt;/a&gt; und auch Daten zurückschreiben. Somit müsste ich nicht einmal das INTERLIS-Modell in &lt;em&gt;DuckDB&lt;/em&gt; erstellen, sondern könnte direkt mit &lt;em&gt;PostgreSQL&lt;/em&gt; kommunizieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DuckDB, the next big thing, das FME-Boomer alt aussehen lässt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://drive.google.com/file/d/15T6FgsRXoN9-KNUIG60nScpLG24UWJ89/view?usp=sharing&quot;&gt;ili2duckdb-5.2.2-SNAPSHOT.zip&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #44 - ilishaper: shape your xtf</title>
      <link>http://blog.sogeo.services/blog/2024/11/18/interlis-leicht-gemacht-number-44.html</link>
      <pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/11/18/interlis-leicht-gemacht-number-44.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It&amp;#8217;s finally here: &lt;a href=&quot;https://downloads.interlis.ch/ilishaper/&quot;&gt;&lt;em&gt;ilishaper&lt;/em&gt; Version 1.0&lt;/a&gt;. Kurze &lt;a href=&quot;https://blog.sogeo.services/blog/2023/09/20/interlis-leicht-gemacht-number-38.html&quot;&gt;Rekapitulation&lt;/a&gt;, wozu brauchen wir &lt;a href=&quot;https://github.com/claeis/ilishaper&quot;&gt;&lt;em&gt;ilishaper&lt;/em&gt;&lt;/a&gt;? Die meisten unserer Daten sind öffentlich und &lt;a href=&quot;https://data.geo.so.ch&quot;&gt;frei verfügbar&lt;/a&gt;. Einige Themen beinhalten nicht-öffentliche Informationen. Das kann z.B. der Name einer Person sein oder ihre Telefonnummer. Diese Informationen sind nicht für die Öffentlichkeit bestimmt und nur für den internen Gebrauch resp. für berechtigte Personen gedacht. Im Kartendienst haben wird das applikatorisch gelöst: Wir veröffentlichen die Datenbanktabelle einfach zwei Mal. Einmal mit den geschützten Attributen für interne resp. angemeldete Benutzer und einmal ohne die geschützten Attribute für die Öffentlichkeit. Aber wir sind jedoch noch nicht in der Lage, den Datensatz als Datei zu veröffentlichen. Wir hatten zuerst einen Ansatz mit Vererbungen im INTERLIS-Modell verfolgt. Das erwies sich aber als zu &lt;a href=&quot;https://blog.sogeo.services/blog/2023/09/20/interlis-leicht-gemacht-number-38.html&quot;&gt;kompliziert&lt;/a&gt;. Und hier kommt nun &lt;em&gt;ilishaper&lt;/em&gt; ins Spiel. Die Idee ist folgende:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir erstellen zuerst nur ein flaches Publikationsdatenmodell mit allen Attributen (also auch den nicht-öffentlichen). Dieses Modell verwenden wir, um die Datenbanktabellen zu erstellen, welche z.B. der Kartendienst verwendet. Die Daten werden bei einer Publikation des Themas in eine XTF-Datei exportiert. Was wir jetzt benötigen ist eine Software, die aus dem Basismodell ein Derivatmodell ohne die nicht-öffentlichen Attribute erzeugt und aus der XTF-Daten entsprechend eine zusätzliche XTF-Datei ohne die nicht-öffentlichen Attribute erstellt (gemäss Derivatmodell). Das Derivatmodell muss natürlich nur einmalig erstellt werden und kann in ein &lt;a href=&quot;https://geo.so.ch/models&quot;&gt;INTERLIS-Repository&lt;/a&gt; eingecheckt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schauen wir uns ein Beispiel an. Gegeben das folgende Datenmodell:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;INTERLIS 2.3;

MODEL SO_ALW_Bienenstandorte_restricted_20241113 (de)
AT &quot;https://alw.so.ch&quot;
VERSION &quot;2024-11-13&quot;  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC Bienenstandorte =
    OID AS INTERLIS.UUIDOID;

    /** Bienenstandort
     */
    CLASS Bienenstandort =
      /** Nummer
       */
      Nummer : MANDATORY TEXT*16;
      /** Honigsorte
       */
      Honigsorte : (
        Bluetenhonig,
        Waldhonig
      );
      /** Standort
       */
      Standort : MANDATORY GeometryCHLV95_V1.Coord2;
      /** Bemerkung
       */
      Bemerkung : TEXT*200;
      /** Name des Imkers
       */
      Name : MANDATORY TEXT*255;
      /** Telefonnummer
       */
      Telefonnummer : TEXT*20;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_restricted_20241113.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die letzten beiden Attribute &lt;code&gt;Name&lt;/code&gt; und &lt;code&gt;Telefonnummer&lt;/code&gt; sind nicht-öffentlich und dürfen im Derivatmodell nicht mehr vorkommen. Für die Konfiguration von &lt;em&gt;ilishaper&lt;/em&gt; wird eine ini-Datei benötigt, die ziemlich selbsterklärend sein sollte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[SO_ALW_Bienenstandorte_restricted_20241113]
name=SO_ALW_Bienenstandorte_20241113
issuer=https://alw.so.ch
version=2024-11-13
doc=Datenmodell für die Bienenstandorte mit öffentlichen Daten

[SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort.Name]
ignore=true

[SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort.Telefonnummer]
ignore=true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem folgenden Befehl kann man das Derivatmodell erstellen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar ilishaper-1.0.1.jar --createModel --config bienenstandorte.ini --modeldir &quot;.;https://models.geo.admin.ch&quot; --out SO_ALW_Bienenstandorte_20241113.ili SO_ALW_Bienenstandorte_restricted_20241113.ili&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Derivatmodell sieht aus wie gewünscht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;INTERLIS 2.3;

/** Datenmodell für die Bienenstandorte mit öffentlichen Daten
 */
MODEL SO_ALW_Bienenstandorte_20241113 (de)
  AT &quot;https://alw.so.ch&quot;
  VERSION &quot;2024-11-13&quot;
  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC Bienenstandorte =

    /** Bienenstandort
     */
    CLASS Bienenstandort =
      /** Nummer
       */
      Nummer : MANDATORY TEXT*16;
      /** Honigsorte
       */
      Honigsorte : (
        Bluetenhonig,
        Waldhonig
      );
      /** Standort
       */
      Standort : MANDATORY GeometryCHLV95_V1.Coord2;
      /** Bemerkung
       */
      Bemerkung : TEXT*200;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_20241113.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber Achtung: Es sieht wahrscheinlich nur auf den ersten Blick so aus wie gewünscht. Es fehlt &lt;code&gt;OID AS INTERLIS.UUIDOID&lt;/code&gt;. Warum das aber korrekt ist, steht &lt;a href=&quot;https://github.com/claeis/ilishaper/issues/1&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn wir das Derivatmodell haben, können wir in &lt;a href=&quot;https://blog.sogeo.services/data/interlis-leicht-gemacht-number-44/bienenstandorte_restricted.xtf&quot;&gt;meinem Testdatensatz&lt;/a&gt; mit drei Bienenstandorten die nicht-öffentlichen Attribute gemäss Derivatmodell &amp;laquo;abstreifen&amp;raquo; und eine neue XTF-Datei erstellen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar ilishaper-1.0.1.jar --deriveData --config bienenstandorte.ini --modeldir &quot;.;https://models.geo.admin.ch&quot; --out bienenstandorte.xtf bienenstandorte_restricted.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Et voilà: Die &lt;a href=&quot;https://blog.sogeo.services/data/interlis-leicht-gemacht-number-44/bienenstandorte.xtf&quot;&gt;resultierende XTF-Datei&lt;/a&gt; hat kein &lt;code&gt;Name&lt;/code&gt;-Attribut und kein &lt;code&gt;Telefonnummer&lt;/code&gt;-Attribut.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber &lt;em&gt;ilishaper&lt;/em&gt; kann noch mehr. Wir haben zusätzlich die Anforderung, dass wir neben Spalten (Attribute) auch Zeilen filtern können. Nehmen wir an, dass die Öffentlichkeit nur die Bienenstandorte, die Blütenhonig produzieren, kennen darf. Dazu müssen wir die ini-Datei erweitern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[SO_ALW_Bienenstandorte_restricted_20241113]
name=SO_ALW_Bienenstandorte_20241113
issuer=https://alw.so.ch
version=2024-11-13
doc=Datenmodell für die Bienenstandorte mit öffentlichen Daten

[SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort.Name]
ignore=true

[SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort.Telefonnummer]
ignore=true

[SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort]
filter=&quot;Honigsorte==#Bluetenhonig&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die beiden letzten Zeilen sind dazugekommen. Erlaubt sind Expressions wie z.B. bei einem Mandatory-Constraint. Der Ausdruck muss jedoch pro Objekt auswertbar sein und darf also keine Rollen oder Referenzattribute enthalten. Das Derivatmodell müssen wir nicht mehr erstellen, da sich an diesem nichts ändert. Ein nochmaliger Aufruf für das Erstellen der XTF-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar ilishaper-1.0.1.jar --deriveData --config bienenstandorte.ini --modeldir &quot;.;https://models.geo.admin.ch&quot; --out bienenstandorte_filter.xtf bienenstandorte_restricted.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Konsolenoutput verrät uns bereits, dass nur noch zwei Objekte in der neuen XTF-Datei vorhanden sind:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;Info: bienenstandorte_restricted.xtf: SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte BID=SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte
Info:       3 objects in CLASS SO_ALW_Bienenstandorte_restricted_20241113.Bienenstandorte.Bienenstandort
Info: bienenstandorte_filter.xtf: SO_ALW_Bienenstandorte_20241113.Bienenstandorte BID=1
Info:       2 objects in CLASS SO_ALW_Bienenstandorte_20241113.Bienenstandorte.Bienenstandort
Info: ...conversion done&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Blick in die &lt;a href=&quot;https://blog.sogeo.services/data/interlis-leicht-gemacht-number-44/bienenstandorte_filter.xtf&quot;&gt;neue XTF-Datei&lt;/a&gt; bestätigt dies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sämtliche Dateien zum Rumspielen stehen &lt;a href=&quot;https://blog.sogeo.services/data/interlis-leicht-gemacht-number-44/bienenstandorte.zip&quot;&gt;hier&lt;/a&gt; zum Download bereit.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #43 - ilivalidator JVM-Benchmarking (revisited)</title>
      <link>http://blog.sogeo.services/blog/2024/10/20/interlis-leicht-gemacht-number-43.html</link>
      <pubDate>Sun, 20 Oct 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/10/20/interlis-leicht-gemacht-number-43.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Angespornt durch mein &lt;a href=&quot;https://blog.sogeo.services/blog/2024/10/15/geoserver_on_steroids.html&quot;&gt;GeoServer-Benchmarking&lt;/a&gt; wollte ich &lt;a href=&quot;https://blog.sogeo.services/blog/2021/11/28/interlis-leicht-gemacht-number-27.html&quot;&gt;ein weiteres Mal&lt;/a&gt; das Verhalten von &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;  mit verschiedenen JVM-Varianten unter die Lupe nehmen. Gibt es irgendwelche Kombinationen von JVM-Version, RAM-Zuteilung, Garbage-Collector, die besonders schnell oder langsam sind?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Benchmarking habe ich die Nutzungsplanung des Kantons Thurgau verwendet. Diese ist als einzelne XTF-Datei auf &lt;a href=&quot;https://geodienste.ch/downloads/npl_nutzungsplanung?data_format=xtf_canton&quot;&gt;geodienste.ch&lt;/a&gt; bereitgestellt und hat darum eine entsprechende Grösse und &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; hat einiges zu rechnen. Letztes Mal habe ich noch INTERLIS-1-Datensätze geprüft. Das lasse ich sein, das sollte langsam Geschichte sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Hardware dient mir der Dedicated-vCPU-Server aus dem &lt;a href=&quot;https://blog.sogeo.services/blog/2024/10/15/geoserver_on_steroids.html&quot;&gt;GeoServer-Benchmarking&lt;/a&gt; mit 32 GB RAM und 16 Kernen, wobei die Anzahl Kerne für dieses Benchmarking nebensächlich ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit einem &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;JBang&lt;/a&gt;-&amp;laquo;Java-Skript&amp;raquo; prüfe ich 100 Mal die Transferdatei inkl. Katalog und schreibe die benötigte Zeit in Sekunden in die Konsole:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;///usr/bin/env jbang &quot;$0&quot; &quot;$@&quot; ; exit $?
//REPOS mavenCentral,ehi=https://jars.interlis.ch/
//DEPS ch.interlis:ilivalidator:1.14.3

import org.interlis2.validator.Validator;
import ch.ehi.basics.settings.Settings;

import static java.lang.System.*;

import java.text.SimpleDateFormat;
import java.util.Date;

public class run_ilivalidator {

    public static void main(String... args) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;);

        long startTime = System.currentTimeMillis();
        Date startDate = new Date(startTime);
        out.println(&quot;Start Time: &quot; + dateFormat.format(startDate));

        Settings settings = new Settings();
        settings.setValue(Validator.SETTING_ALL_OBJECTS_ACCESSIBLE, Validator.TRUE);
        settings.setValue(Validator.SETTING_ILIDIRS, &quot;.&quot;);

        for (int i=0; i&amp;lt;100; i++) {
            boolean valid = Validator.runValidation(new String[] {&quot;Nutzungsplanung_Catalogue_CH_V1_2_20210901.xml&quot;, &quot;Nutzungsplanung_LV95_V1_2.xtf&quot;}, settings);
        }

        long endTime = System.currentTimeMillis();
        Date endDate = new Date(endTime);
        System.out.println(&quot;End Time: &quot; + dateFormat.format(endDate));

        long timeTaken = endTime - startTime;
        out.println(&quot;Time taken: &quot; + timeTaken / 1000 + &quot; seconds&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die INTERLIS-Datenmodelle lagen alle lokal vor und es mussten keine Requests zu anderen Repos gemacht werden. Die 100-fache Ausführung (= 100 x ~40s) der Validierung innerhalb der gleichen JVM sollte vorhandene Performanceunterschiede hervorheben. Für jede verwendete JVM habe ich den Benchmark 4 Mal durchgeführt. Verwendet wurden folgende JVM:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GraalVM 23&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GraalVM 21&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GraalVM 17&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 21&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 17&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 11&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 8&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat habe ich in einem Matplotlib-Chart zusammengestellt. Eigentlich wollte ich bloss Punkte zeichnen, das habe ich jedoch nicht hingekriegt. Darum gibts jetzt pseudo-wissenschaftliche Boxplots:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p43/ilivalidator-benchmark-jvm.png&quot; alt=&quot;Chart&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es scheint als gäbe es einen klaren Gewinner in meinem Testszenario: GraalVM mit ParallelGC. Wobei man davon ausgehen kann, dass ab OpenJDK 21 und GraalVM 17 alle gleich aufliegen (auch mit ParallelGC). Interessant ist die Verschlechterung von OpenJDK bis zur Version 17.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die JVM verwendete die Hälfte des vorhandenen RAM: 16 GB. Ich habe die Variante GraalVM 21 (ParallelGC) mit 4 GB Heap laufen lassen und komme auf ein minimal schlechteres Resultat. Der Versuch mit 2 GB war nicht erfolgreich und führte zu Out of Memory Fehlern. Der RAM-Verbrauch ist relativ gering, steigt jedoch rapide an bei AREA-Validierungen (soweit meine Beobachtung).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine allgemein gültige Empfehlung ist schwierig anhand dieses Testes. Letztes Mal hat INTERLIS 1 eine leicht andere Resultatcharakteristik gezeigt als INTERLIS 2. Eventuell führen stark unterschiedliche Datensätze (viele oder wenig Geometrien, AREA- oder keine AREA-Prüfung, viele oder wenig Constraints) zu unterschiedlichen Resultaten. Aber bis auf weiteres würde ich (für institutionaliserte Betreiber) eine aktuelle JVM empfehlen (ab 21) und ParallelGC.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ilivalidator-benchmark&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ilivalidator-benchmark&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Documentation as Code</title>
      <link>http://blog.sogeo.services/blog/2024/10/18/docs_as_code.html</link>
      <pubDate>Thu, 17 Oct 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/10/18/docs_as_code.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Documentation as Code: Und zwar in zweifacher Hinsicht. Einerseits will ich Dokumentation wie Code behandeln und andererseits steckt ein Grossteil der Informationen, die ich in der Dokumentation haben will, direkt beim Quellcode (also eher &lt;em&gt;from&lt;/em&gt; Code). Konkret geht es um die &lt;a href=&quot;https://gretl.app&quot;&gt;GRETL&lt;/a&gt;-Dokumentation. Zu Beginn gab es &amp;laquo;nur&amp;raquo; die &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md&quot;&gt;Referenzdokumentation&lt;/a&gt; als einzelne Markdown-Datei im GRETL-Git-Repository. Diese wird manuell nachgeführt. Es gibt eine Beschreibung des Tasks, die erklären soll für was der Task verwendet werden kann, gefolgt von Beispielen und der Auflistung sämtlicher für den Benutzer relevanten Properties (also z.B. der Name der Datei, die man mit &lt;em&gt;ilivalidator&lt;/em&gt; geprüft haben will) in einer Tabelle. Die Tabelle besteht aus Propertyname und Beschreibung (ggf. noch mit Defaultwert). Letztes Jahr folgte die Webseite &lt;a href=&quot;https://gretl.app&quot;&gt;gretl.app&lt;/a&gt;, die neben weiteren Informationen auch die Referenzdokumentation beinhaltet. Die Webseite ist mit &lt;a href=&quot;https://quarto.org/&quot;&gt;&lt;em&gt;Quarto&lt;/em&gt;&lt;/a&gt; gemacht und wird in einem von &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; separaten &lt;a href=&quot;https://github.com/sogis/gretl-docs&quot;&gt;Github-Repository&lt;/a&gt; vorgehalten. Das führt jetzt natürlich zum Problem, dass die Referenzdokumentation an zwei Orten vorgehalten wird und erst noch händisch nachgeführt werden muss. Aus dem Code-Repository möchte ich die Referenzdokumentation nicht entfernen, aber den Webseiten-Code auch nicht ins Code-Repository migrieren. Ein weiteres Problem ist das händische Nachführen der Referenzdokumentation. Die Prosabeschreibung des Tasks und die Beispiele tuen mir nicht weh, jedoch schmerzt mich die Tabelle mit den Properties, da diese Information bereits (fast vollständig) im GRETL-Quellcode bei den Custom Tasks vorhanden ist. Ausserdem stört mich, dass der Datentyp in der Tabelle nicht ersichtlich ist, ebenso fehlt die Information, ob ein Property optional ist oder nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für letzteres (technisches) Problem hatte ich relativ schnell eine Idee: Mit &lt;em&gt;Javadoc&lt;/em&gt; gibt es ein Werkzeug, dass aus den Java-Quelltexten automatisch HTML-Output generiert. Dazu gibt es ein &lt;a href=&quot;https://openjdk.org/groups/compiler/using-new-doclet.html&quot;&gt;Doclet API&lt;/a&gt; mit dem man eigene sogenannte Doclets schreiben kann und so Informationen aus den Java-Quelltexten in beliebige Formate schreiben kann. Die Idee war nun, dass ich mir ein Doclet schreibe, dass die Task-Properties in eine Markdown-Tabelle schreibt. Diese Markdown-Tabelle (pro Task) referenziere ich als externe Quelle im &amp;laquo;Rahmen-Referenzdokumentations-Markdown&amp;raquo; (mit der Prosa-Beschreibung und den Beispielen) und generiere so die fertige Referenzdokumentation resp. Webseite.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Doclet zu implementieren war nicht allzu schwierig. Trotzdem gab es eine Herausforderung zu meistern: Eigentlich ist sonnenklar, was von der Java-Klasse (welche den Task implementiert) in der Tabelle landen soll. Nämlich die Properties, die für den Anwender später relevant sind (wieder das Beispiel mit dem Namen der Datei, die geprüft werden soll). Die Java-Klasse (weil vererbt) hat noch viele andere Properties resp. Fields und Methoden, die für meinen Anwendungsfall nicht relevant sind. Im einfachsten Fall sieht der Quellcode eines Custum-Gradle-Tasks so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;public class S3Download extends DefaultTask {
    /**
    * AWS Access Key
    */
    @Input
    public String accessKey;

    @Input
    public String secretKey;

    @Input
    public String bucketName;

    @Input
    public String key;

    @OutputDirectory
    public File downloadDir;

    @Input
    @Optional
    public String endPoint = &quot;https://s3.eu-central-1.amazonaws.com&quot;;

    @Input
    public String region = &quot;eu-central-1&quot;;

    @TaskAction
    public void upload() {
        // do something
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man sieht, dass die Properties annotiert sind. Man findet dank den Annotationen genau die Properties, die man will. Hat das Property einen Kommentar, kann man auch diesen mit der Doclet-API auslesen. Leicht komplizierter wird es, weil es auch noch weitere Arten gibt einen Task zu schreiben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;public class S3Upload extends DefaultTask {
    private String accessKey;
    private String secretKey;
    private Object sourceDir;
    private Object sourceFile;
    private Object sourceFiles;
    private String bucketName;
    private String endPoint = &quot;https://s3.eu-central-1.amazonaws.com&quot;;
    private String region = &quot;eu-central-1&quot;;
    private String acl = null;
    private String contentType = null;
    private Map&amp;lt;String,String&amp;gt; metaData = null;

    /**
    * AWS Access Key
    */
    @Input
    public String getAccessKey() {
        return accessKey;
    }

    @Input
    public String getSecretKey() {
        return secretKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Annotationen stehen bei dieser Variante nicht mehr bei den Properties, sondern bei der Getter-Methode. Eine dritte Variante kennt gar keine Properties (im Quellcode) mehr, sondern es wird nur noch ein abstrakter Getter geschrieben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;public abstract class S3Upload extends DefaultTask {
    /**
    * AWS Access Key
    */
    @Input
    public abstract Property&amp;lt;String&amp;gt; getAccessKey();

    @Input
    public abstract Property&amp;lt;String&amp;gt; getSecretKey();
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Doclet muss mit diesen Varianten umgehen können und als Produkt sowas ausspucken:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;Parameter | Datentyp | Beschreibung | Optional
----------|----------|-------------|-------------
accessKey | `Property&amp;lt;String&amp;gt;` | AWS Access Key | nein
secretKey | `Property&amp;lt;String&amp;gt;` |  | nein&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das &lt;a href=&quot;https://github.com/edigonzales/gretl-doclet&quot;&gt;Doclet&lt;/a&gt;-Proof-of-Concept liefert und ich kann die Markdown-Tabellen in &lt;em&gt;Quarto&lt;/em&gt; einbinden. Das löst das technische Problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das organisatorische Problem (wo liegt welcher Code?) ist mit dem Doclet noch nicht gelöst. Für die Lösungfindung kommt erschwerend hinzu, dass ich auch die Dokumentation verschiedener GRETL-Versionen greifbar haben will. Wir verwenden bei uns häufig zwei Versionen parallel und für diese möchte ich die korrekte Dokumentation bereitstellen. Nach ein paar schlechten Lösungen bin ich glaub auf die bisher beste Variante gestossen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Referenzdokumentation verbleibt originär im GRETL-Repository. Das Release-Management von GRETL sieht vor, dass jeder Commit (in bestimmen Branches) zu einem Release führt. Die vollständige Version (major.minor.patch) von GRETL ist nur innerhalb der Pipeline (Github Action) bekannt, da die Patch-Nummer der Job-Run-Nummer entspricht (was aber auch nicht super genial ist, da diese nicht ewig stabil ist). D.h. dass während eines GRETL-Builds die Referenzdokumentation hergestellt wird, zuerst mit &lt;em&gt;Javadoc&lt;/em&gt; die Markdown-Tabellen, anschliessend wird mit &lt;em&gt;Quarto&lt;/em&gt; eine einzelne HTML-Seite hergestellt. Jede Version dieser HTML-Seite wird irgendwohin deployed (z.B. S3). So in etwa wie &lt;a href=&quot;https://docs.interlis.ch&quot;&gt;docs.interlis.ch&lt;/a&gt; (leicht &lt;em&gt;anderes&lt;/em&gt; CSS, verprochen):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/docs_as_code/referencedoc.png&quot; alt=&quot;Referenzdokumentation&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Webseite &lt;a href=&quot;https://gretl.app&quot;&gt;gretl.app&lt;/a&gt; zeigt weiterhin nur eine Version und wird auch weiterhin in einem separaten Repo verwaltet. Die Pipeline, welche die Webseite herstellt, wird nur bei Bedarf (manuell) ausgelöst (oder z.B. aus dem main-Branch von GRETL getriggert). Die Pipeline muss auch den GRETL-Quellcode auschecken und die Referenzdokumentation als Teil der Webseite herstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Soweit meine Idee zur Organisation. Gibt es weitere Vorschläge?&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GeoServer on steroids</title>
      <link>http://blog.sogeo.services/blog/2024/10/15/geoserver_on_steroids.html</link>
      <pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/10/15/geoserver_on_steroids.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.sogeo.services/tags/GraalVM.html&quot;&gt;Ich&lt;/a&gt; mag &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt;. Sei es um Java-Anwendungen zu einem Native Image runterzukompilieren oder Python mit Java zu verheiraten. Ein weiterer spannender Punkt ist der eigene &lt;a href=&quot;https://www.graalvm.org/latest/reference-manual/java/compiler/&quot;&gt;Graal JIT Compiler&lt;/a&gt;. Dieser kann zu einer Performancesteigerung der Java-Anwendung führen und zwar ohne, dass man an dieser überhaupt was ändern muss. Nur die Runtime (&amp;laquo;Java-Variante&amp;raquo;) austauschen und gut ist. So hat der Wechsel zum Graal JIT Compiler z.B. bei der &lt;a href=&quot;https://www.morling.dev/blog/one-billion-row-challenge/&quot;&gt;1BRC&lt;/a&gt; eine &lt;a href=&quot;https://x.com/gunnarmorling/status/1843649474545287202/photo/3&quot;&gt;Performancesteigerung von acht Prozent&lt;/a&gt; gebracht. Bei mir selber habe ich solche Unterschiede noch keine festgestellt. Vor &lt;a href=&quot;https://blog.sogeo.services/blog/2021/11/28/interlis-leicht-gemacht-number-27.html&quot;&gt;ein paar Jahren&lt;/a&gt; habe ich mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; ein paar Benchmarks gemacht, die aber nicht sonderlich aussagekräftig waren und nicht gezeigt hätten, dass &lt;em&gt;GraalVM&lt;/em&gt; bedeutend schneller ist. Weil wir uns zur Zeit überlegen von &lt;em&gt;QGIS Server&lt;/em&gt; auf &lt;a href=&quot;https://geoserver.org&quot;&gt;&lt;em&gt;GeoServer&lt;/em&gt;&lt;/a&gt; zu wechseln, wäre das jedenfalls ein Anwendungfall, den man untersuchen könnte, da der Server zu Bürozeiten relativ stark beansprucht wird und somit eine genügend hohe Dauerlast besteht. Obwohl wir bereits heute viele Java-Anwendungen in Betrieb haben, hat keine davon eine ähnliche Last zu tragen wie ein möglicher, zukünftiger Java-Kartendienst.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun benötige ich ein Testszenario, das aus mehr als bloss vier Flächen und zwei Punkten besteht. Ich habe mich dazu entschieden die Nutzungsplanung und ein Orthofoto (swissimage 2021) zu verwenden. Die Nutzungsplanung steht als Vektordatensatz in einzelnen INTERLIS-Transferdateien &lt;a href=&quot;https://files.geo.so.ch/ch.so.arp.nutzungsplanung.kommunal/aktuell/&quot;&gt;zur Verfügung&lt;/a&gt;, die ich &lt;a href=&quot;https://github.com/edigonzales/geoserver-benchmarks/blob/461afff02f2b9bf1e96dd9339eb39ddccc2a95da/gretl/build.gradle&quot;&gt;ruckzuck&lt;/a&gt; mit &lt;a href=&quot;https://gretl.app&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; in eine gedockerte PostgreSQL/PostGIS-Datenbank importieren kann. Die SLD-Datei hatte ich noch von früheren Spielereien rumliegen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/npl_wms.png&quot; alt=&quot;WMS-Bild Nutzungsplanung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Orthofoto liegt als 30 GB grosses cloud optimized GeoTIFF &lt;em&gt;lokal&lt;/em&gt; vor:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/ortho_wms.png&quot; alt=&quot;WMS-Bild Orthofoto&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das eigentliche Benchmarking verwendete ich &lt;a href=&quot;https://jmeter.apache.org/&quot;&gt;&lt;em&gt;JMeter&lt;/em&gt;&lt;/a&gt;. Man definiert in &lt;em&gt;JMeter&lt;/em&gt; sowas wie eine Basis-URL (also in unserem Fall einen WMS-GetMap-Request) mit Platzhaltern für die Parameter (WIDTH, HEIGHT, BBOX und LAYERS), die bei jedem von &lt;em&gt;JMeter&lt;/em&gt; gemachten Request dynamisch befüttert werden. Die Werte für die Platzhalter können in einer CSV-Datei gespeichert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;width;height;bbox;layer
2102;2119;2605738.274269104,1236815.4005975279,2605773.134554806,1236850.5428170345;ch.swisstopo.swissimage_2021.rgb
3308;1924;2619968.8471599706,1256153.9242204945,2632686.446437572,1263550.7383106109;ch.swisstopo.swissimage_2021.rgb
3320;1896;2610788.4627983514,1256404.5859801334,2617948.8869838137,1260493.7920812287;ch.swisstopo.swissimage_2021.rgb
3470;1797;2634212.7299694223,1248533.8556387443,2641987.561823341,1252560.1878697216;ch.swisstopo.swissimage_2021.rgb
1972;2126;2592921.414637569,1242392.7718350205,2601610.971095325,1251760.9244867398;nutzungsplanung_grundnutzung
3391;1803;2597536.2772151744,1211156.517151424,2632904.225935421,1229961.7107646957;ch.swisstopo.swissimage_2021.rgb
3551;1134;2604904.606863846,1237243.4070413096,2623471.839928261,1243172.7909599373;nutzungsplanung_grundnutzung
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie habe ich die CSV-Datei mit Random-Werten hergestellt? Als Grundlage diente mir ein &lt;a href=&quot;https://github.com/edigonzales-dumpster/geoserver-tests/blob/35e7010a6ca6eb246c4d5612b23c269904ed1afc/benchmark/scripts/wms_request.py&quot;&gt;Python-Skript&lt;/a&gt; von den legendären &lt;a href=&quot;https://wiki.osgeo.org/wiki/FOSS4G_Benchmark&quot;&gt;FOSS4G Performance Shoot-outs&lt;/a&gt;. Das Skript hat verschiedene Parameter mit dem man den Output steuern kann: So ist es möglich einen Range für WIDTH und HEIGHT anzugeben und die übergeordnete Region, in der die BBOX der einzelne GetMap-Requests liegen sollen. Zusätzlich lässt sich ein Auflösungsrange angeben, was nichts anderes ist als Massstabsbereiche. Als letztes Zückerchen kann man eine Shapedatei angeben, um sicherzustellen, dass die berechnete Bounding Box entweder komplett innerhalb der Shapefilegeometrien liegt oder diese mindestes schneidet (intersects), was in unserem Fall sinnvoll ist, da ich keine Requests will, die ins Leere zeigen. Für diese Geometrieoperationen verwendet das Python-Skript Bindings für OGR/GDAL. Das war mir ein (Installations-)Graus. Ich habe das Skript &lt;em&gt;ChatGPT&lt;/em&gt; geschickt mit der Bitte um eine Umwandlung nach &lt;a href=&quot;https://github.com/geoscript/geoscript-groovy&quot;&gt;&lt;em&gt;GeoScript Groovy&lt;/em&gt;&lt;/a&gt;. Das hat sogar &lt;a href=&quot;https://github.com/edigonzales/geoserver-benchmarks/blob/e7ee9c96372d67a0db8b862300fab824fdd99df6/scripts/wms_requests.groovy&quot;&gt;halbwegs gut&lt;/a&gt; funktioniert und weil es für &lt;em&gt;GeoScript&lt;/em&gt; eine &lt;a href=&quot;https://jericks.github.io/geoscript-groovy-cookbook/#uber-jar&quot;&gt;Uber Jar&lt;/a&gt; gibt, ist die Installation nur ein Download dieser Jar-Datei. Der Aufruf des Skriptes ist simpel: &lt;code&gt;java -jar geoscript-groovy-app-1.22.0.jar script wms_requests.groovy&lt;/code&gt;. Maximal so kompliziert sollte &amp;laquo;geo-scripting&amp;raquo; sein. Dünkt mich angenehmer als Python-Bindings installieren und auf Teufel komm raus die passenden OGR/GDAL-Libs (mit &lt;em&gt;Conda&lt;/em&gt; oder dergleichen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe mich dazu entschieden, dass ich für WIDTH und HEIGHT einen Range von 3840x2160 Pixel und 1920x1080 Pixel und einen Massstab von circa 1:350&apos;000 bis 1:350 zulasse. Da ich nur zwei Layer zur Auswahl habe, wählt das Skript wahlweise einen der beiden aus und schreibt ihn in die CSV-Datei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der Hardware habe ich mich für &lt;a href=&quot;https://www.hetzner.com/de/cloud/&quot;&gt;Hetzner-Server&lt;/a&gt; entschieden. Ich habe die Benchmarks auf zwei unterschiedlichen Servern durchgeführt. Einmal auf einem Server mit shared vCPU und einmal mit dedicated vCPU. Shared bedeutet, dass die vCPU mit anderen VM geteilt wird. Mit wie vielen ist nicht klar. Bei den VM mit dedicated vCPU muss sich die VM die vCPU nicht mit anderen teilen. Die Preise sind entsprechend sehr unterschiedlich. Ich habe mich jeweils für 16 Kerne und 32 GB RAM entschieden. Die Shared-Variante (ARM-Prozessoren) kostet 26.70 Euro, die Dedicated-Variante 103.77 Euro (pro Monat). Also viermal so viel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Datenbank läuft gedockert auf der gleichen VM. &lt;em&gt;GeoServer&lt;/em&gt; (2.26.0) läuft ungedockert in &lt;em&gt;Tomcat&lt;/em&gt;. Bezüglich CPU-Zuweisung habe ich weder bei der Datenbank noch bei &lt;em&gt;GeoServer&lt;/em&gt; etwas gemacht. Tomcats Heap Memory habe ich beim Start resp. als Maximum auf 4GB beschränkt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Benchmark habe ich mit 2, 4, 8, 12 und 16 Threads laufen lassen. Er sollte unbedingt nicht im GUI gestartet werden, sondern in der Konsole, z.B.:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./bin/jmeter -n -t benchmark.jmx  -l graal-17-g1gc-v1.jtl -e -o graal-17-g1gc-v1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Befehl erzeugt einen HTML-Report mit allerlei Grafiken. Leider ist mir zu spät in den Sinn gekommen, dass ich v.a. the Throughput auch gerne als Daten hätte, damit ich sie gemeinsam in einem Diagramm darstellen kann. So bleiben mir nur die automatisch generierten Grafiken pro Run, die man halt visuell vergleichen muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Getestet habe ich folgende Java-/GraalVM-Versionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GraalVM 17&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GraalVM 21&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 11&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 17&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der Dedicated-vCPU-VM habe ich den Benchmark für jede Version vier Mal laufen lassen. Zusätzlich kommen noch Varianten mit unterschiedlichen Garbage Collector (G1GC vs ParallelGC) hinzu. Auf der Shared-vCPU-VM habe ich nicht im gleichen Umfang getestet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und finally habe ich einen Anwendungsfall, bei dem &lt;em&gt;GraalVM&lt;/em&gt; signifikant schneller ist und mehr Throughput schafft. Wobei GraalVM 17 und 21 praktisch identisch sind mit leichten Vorteilen für GraalVM 21 (visuell verglichen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM 21 (dedicated, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/graal-21-g1gc_.png&quot; alt=&quot;Resultat GraalVM 21&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM 17 (dedicated, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/graal-17-g1gc_.png&quot; alt=&quot;Resultat GraalVM 17&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Sprünge zwischen der Anzahl Threads ist klar ersichtlich. Einzig beim Sprung von 12 auf 16 Threads ist mit nicht viel mehr Throughput zu rechnen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM 17 (dedicated, ParallelGC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/graal-17-parallelgc_.png&quot; alt=&quot;Resultat GraalVM 17 (parallelgc)&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Verwendung vom ParallelGC bringt hier (im Gegensatz zur &lt;a href=&quot;https://blog.sogeo.services/blog/2021/11/28/interlis-leicht-gemacht-number-27.html&quot;&gt;INTERLIS-Validierung mit &lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;) nichts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OpenJDK 17 (dedicated, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/temurin-17-g1gc_.png&quot; alt=&quot;Resultat OpenJDK 17&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn GraalVM bei 16 Threads immer um die 20 requests/second pendelt, so sind es bei OpenJDK klar weniger (circa 17.5 requests/second).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OpenJDK 11 (dedicated, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/temurin-11-g1gc_.png&quot; alt=&quot;Resultat OpenJDK 11&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OpenJDK 11 ist praktisch identisch zu OpenJDK 17.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant sind ebenfalls die Resultate der Shared-vCPU-VM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM 17 (shared, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/graal-17-g1gc-arm_.png&quot; alt=&quot;Resultat GraalVM 17 (shared)&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OpenJDK 17 (shared, G1GC):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver_on_steroids/temurin-17-g1gc-arm_.png&quot; alt=&quot;Resultat OpenJDK 17 (shared)&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einerseits zeigt sich das gleiche Bild: &lt;em&gt;GraalVM&lt;/em&gt; vs &lt;em&gt;OpenJDK&lt;/em&gt;. Spannend ist aber der Throughput bei 12 und 16 Threads. Da kommt &lt;em&gt;GraalVM&lt;/em&gt;  beinahe an die Resultate von &lt;em&gt;OpenJDK&lt;/em&gt; auf der Dedicated-vCPU-VM heran. Und sowieso sind die Resultate nicht übel, wenn man bedenkt, dass man nur einen Viertel bezahlt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fazit: Use &lt;em&gt;GraalVM&lt;/em&gt;! Grob geschätzt sind es 15% mehr Durchsatz. Aber Achtung: Den RAM-Verbrauch habe ich z.B. nicht angeschaut. Dazu kann ich gar keine Aussage machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/geoserver-benchmarks/tree/main/results&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/geoserver-benchmarks/tree/main/results&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>AI - Behördensprache ade</title>
      <link>http://blog.sogeo.services/blog/2024/06/24/ai_behoerdensprache_ade.html</link>
      <pubDate>Mon, 24 Jun 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/06/24/ai_behoerdensprache_ade.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ZiqWi6SpqOg&quot;&gt;&amp;laquo;Talent borrows, Genius steals&amp;raquo;&lt;/a&gt;: Das &lt;a href=&quot;https://www.zh.ch/de/direktion-der-justiz-und-des-innern/statistisches-amt.html&quot;&gt;Statistische Amt&lt;/a&gt; des Kantons Zürich hat einen sehr interessanten &lt;a href=&quot;https://github.com/machinelearningZH/simply-simplify-language&quot;&gt;Prototypen&lt;/a&gt; zur Vereinfachung von Texten in &lt;a href=&quot;https://www.edi.admin.ch/dam/edi/de/dokumente/gleichstellung/E-Accessibility/empfehlungen_informationen_leichtesprache_gebaerdensprache.pdf.download.pdf/Empfehlungen%20f%C3%BCr%20Verwaltungen%20zur%20Erstellung%20von%20Informationen%20in%20Leichter%20Sprache%20und%20Geb%C3%A4rdensprache.pdf&quot;&gt;einfache und leichte&lt;/a&gt; Sprache entwickelt. (Meine Eselsbrücke: Die leichte Sprache ist noch einfacher, als die einfache Sprache.) Für mich war klar, das will ich auch ausprobieren. Was mich jedoch besonders interessiert, ist, wie gut ein frei verfügbares LLM (&lt;em&gt;Llama3&lt;/em&gt;) die Aufgabe meistert. Das Ganze hat sich natürlich aufwändiger als geplant herausgestellt. Aber der Reihe nach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ZH-Prototyp ist mit Python und &lt;a href=&quot;https://streamlit.io/&quot;&gt;&lt;em&gt;Streamlit&lt;/em&gt;&lt;/a&gt; umgesetzt. Ich habe mit beidem wenig bis keine Erfahrung und darum dachte ich, dass ich den GUI-Teil schnell mit &lt;a href=&quot;https://vaadin.com/&quot;&gt;&lt;em&gt;Vaadin&lt;/em&gt;&lt;/a&gt; umsetzen kann. Das eigentlich GUI ging tatsächlich schnell, es stellte sich jedoch heraus, dass die Anwendung auch noch einen beträchtlichen Teil an Businesslogik beinhaltet: Nämlich die Analyse resp. Bewertung der Texte. Letzten Endes läuft es auf die Einstufung der Texte (vorher/nachher) in ein &lt;a href=&quot;https://en.wikipedia.org/wiki/Common_European_Framework_of_Reference_for_Languages&quot;&gt;CEFR-Level&lt;/a&gt; hinaus. Die Anwendung verwendet &lt;a href=&quot;https://spacy.io/&quot;&gt;&lt;em&gt;spaCy&lt;/em&gt;&lt;/a&gt;, eine NLP-Bibliothek, um &lt;a href=&quot;https://github.com/machinelearningZH/simply-simplify-language/blob/main/_streamlit_app/sprache-vereinfachen.py#L153&quot;&gt;bestimmte Parameter&lt;/a&gt; zu berechnen. Mit diesen Parametern wird anschliessend die &lt;a href=&quot;https://github.com/machinelearningZH/simply-simplify-language/blob/main/_streamlit_app/sprache-vereinfachen.py#L232&quot;&gt;Verständlichkeit&lt;/a&gt; eines Textes berechnet. Die Formel haben sie mittels eines Logistic Regression Modelles hergeleitet. Dieses spaCy-Teil gibt es natürlich nicht 1:1 in Java. Ich habe mit &lt;a href=&quot;https://dev.languagetool.org/&quot;&gt;languagetool&lt;/a&gt; eine Java-Bibliothek gefunden, die ähnliche Funktionen aufweist. Verschiedene Parameter weisen beim Ausprobieren die exakt gleichen Werte aus, andere - z.B. der &lt;a href=&quot;https://github.com/machinelearningZH/simply-simplify-language/blob/main/_streamlit_app/sprache-vereinfachen.py#L161&quot;&gt;common word score&lt;/a&gt; - nicht. Soweit ich es nachvollziehen kann, liegt es daran, dass unterschiedlich &amp;laquo;lemmatized&amp;raquo; wird. Das führt natürlich zur Frage, ob die erwähnte Formel auch in meinem Stack stimmt. Wahrscheinlich weniger gut, als im Original. Trotzdem führt es meines Erachtens zu plausiblen und sinnvollen (leicht unterschiedlichen) Punktzahlen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben dieser Analyse-Logik hat der Kanton Zürich sicher auch einiges an Zeit in die &lt;a href=&quot;https://github.com/edigonzales/simply-simplify-language-java/tree/main/src/main/resources/prompts&quot;&gt;Prompts&lt;/a&gt; investiert, die für die guten Resultat unabdingbar sind. Auch hier: Ehre, wem Ehre gebührt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein Prototyp mit Vaadin und einem Resultat mit GPT-4o:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_behoerdensprache_ade_p2/prototyp01.png&quot; alt=&quot;prototyp01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Resultate dünken mich ziemlich gut. Mit GPT-4o scheint mir schneller als GPT-4 zu sein und häufiger mit Aufzählungn zu arbeiten als GPT-4.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie sieht es mit &lt;em&gt;Llama3&lt;/em&gt; aus? Die Herausforderung war eine brauchbare Testumgebung zu finden. Lokal mit meinem M1-Prozessor macht es wirklich gar keinen Spass. Ein Mitarbeiter hat auf seinem Gamer-PC mit einer &lt;em&gt;RTX 4080 Super&lt;/em&gt; Grafikkarte gezeigt, dass die (gefühlte) Geschwindigkeit nahe an ChatGPT 4 ist. Machte ich mich also auf die Suche nach einem Cloud-Anbieter, der GPU-Rechner im Programm hat. Die wirklich grossen Hyperscaler waren mir entweder zu kompliziert (Azure, weil noch nie was gemacht mit) oder dermassen undurchschaubar im Angebot (AWS), dass ich nicht wusste, welche Instanz nun geeignet ist für meine Tests. Hetzner hat zwar einen GPU-Rechner im Angebot, jedoch mit hohen Setup-Gebühren. OVHCloud hätte zwar viele unterschiedliche GPU-Rechner im Angebot. Als Neukunde kann man diese jedoch, trotz hinterlegter Kreditkarte, nicht verwenden. Eine Anfrage wurde abgelehnt. Keine Ahnung warum. Als halber Freund von DigitalOcean bin ich über Paperspace gestolpert. Die wurden vor kurzem von DigitalOcean aufgekauft und haben GPU-Rechner im Angebot. Auch hier muss man sich mittels Ticket die Rechner freischalten lassen aber danach kann man frisch fröhlich verschiedenste GPU-Rechner auswählen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe mich für einen Rechner mit einer Ampere A4000 Nvidia Karte für $0.76/h entschieden. Die richtige Coolen würden A100-80Gx8 für $25.44/h wählen&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Ollama&lt;/em&gt; installieren geht eigentlich ganz passabel, es installiert jedoch einen neuen Kernel (soweit ich es verstanden habe) und dauert darum ein Weilchen und die VM muss gerebootet werden. Anschliessend reicht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ollama run llama3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das startet &lt;em&gt;Ollama&lt;/em&gt; mit &lt;em&gt;Llama3&lt;/em&gt; als LLM. Man muss &lt;em&gt;Ollama&lt;/em&gt; beibringen, dass er nicht bloss auf 127.0.0.1 hören soll, sondern auf allen lokalen Interfaces. Dazu muss die Env-Variable &lt;code&gt;OLLAMA_HOST=0.0.0.0&lt;/code&gt; in der Datei &lt;em&gt;/etc/systemd/system/ollama.service&lt;/em&gt; gesetzt werden. &lt;em&gt;Ollama&lt;/em&gt; ist nun auch von extern ansprechbar (so natürlich nie in Produktion gehen). Prüfen kann man es mit curl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl http://74.82.28.177:11434/api/generate -d &apos;{
  &quot;model&quot;: &quot;llama3&quot;,
  &quot;prompt&quot;: &quot;Why is the sky blue?&quot;
}&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Geschwindigkeit kann sich wirklich &lt;a href=&quot;https://youtu.be/V87j4nev-_Q&quot;&gt;sehen lassen&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie gut funktioniert es aber mit unserem Behördensprache-ade-Tool? Die Geschwindigkeit ist trotz umfangreicherer Prompts auf GPT-4o-Niveau. Die Qualität der vereinfachten Texte ist jedoch weniger gut resp. sie erreichen nur Punktzahlen zwischen 10 und 15 (der Ausgangstext hat eine Punktzahl von 1). Das ist zwar ein wenig ernüchternd aber soweit ich es nachvollziehen konnte, ist &lt;em&gt;Llama3&lt;/em&gt; nicht wirklich auf die deutsche Sprache trainiert. Es gibt auf HuggingFace Llama3-basierte Modelle, die mit &lt;a href=&quot;https://huggingface.co/DiscoResearch/Llama3-German-8B&quot;&gt;deutschen Token&lt;/a&gt; feingetuned wurden. Ob mit einem solchen Modell bessere Ergebnisse erzielt werden können, müsste man noch ausprobieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_behoerdensprache_ade_p2/prototyp02.png&quot; alt=&quot;prototyp02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nichtsdestotrotz finde ich es wichtig, dass es frei verfügbare LLM gibt. Für Organisationen werden sie spannend, wenn man &amp;laquo;KI machen&amp;raquo; will und keine Kompromisse beim Datenschutz und der digitalen Souveränität eingehen will/kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ausprobieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;docker run -p8080:8080 -e OPENAI_API_KEY=123456789 edigonzales/simply-simplify-language&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;em&gt;Llama3&lt;/em&gt; müssen die &lt;a href=&quot;https://github.com/edigonzales/simply-simplify-language-java/blob/main/src/main/resources/application.properties#L17&quot;&gt;zwei Env-Variablen&lt;/a&gt; gesetzt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Original: &lt;a href=&quot;https://github.com/machinelearningZH/simply-simplify-language/&quot; class=&quot;bare&quot;&gt;https://github.com/machinelearningZH/simply-simplify-language/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Shameless plug: &lt;a href=&quot;https://github.com/edigonzales/simply-simplify-language-java/&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/simply-simplify-language-java/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>AI - Let the journey begin</title>
      <link>http://blog.sogeo.services/blog/2024/06/03/ai_let_the_journey_begin.html</link>
      <pubDate>Mon, 3 Jun 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/06/03/ai_let_the_journey_begin.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als NVIDA-Aktionär muss ich das Thema jetzt pushen; die Investition soll sich ja lohnen. Wie jeder andere auch verwende ich hin- und wieder ChatGPT und freue mich über sinnvolle Antworten vor allem als Hilfe zum Programmieren. Der Zugang zum Thema fällt mir aber schwer: LLM, Tokens, RAG, Prompt Engineering, Fine Tuning&amp;#8230;&amp;#8203; WTF? Um das Thema nicht nur den Luftikussen und Verkäufern zu überlassen, muss ich mich hier auch minimal schlau machen. Und wie gelingt mir das am besten? Ich brauche eine (halb-)konkrete Fragestellung und Werkzeuge, mit denen ich was anstellen kann. Welcome &lt;a href=&quot;https://spring.io/projects/spring-ai&quot;&gt;&lt;em&gt;Spring AI&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://docs.langchain4j.dev/&quot;&gt;&lt;em&gt;LangChain4j&lt;/em&gt;&lt;/a&gt;. Beides sind Java-Frameworks, die das Arbeiten mit den unterschiedlichsten LLM und AI-Diensten vereinfachen, in dem sie als Abstraktionsschicht dienen. Das hilft ungemein in Zeiten, in denen gefühlt jeden Tag das neue, beste LLM entsteht. Man will also das LLM austauschen können, ohne den Code ändern zu müssen. &lt;em&gt;Spring AI&lt;/em&gt; - nomen est omen - dient vor allem im Spring-Umfeld und &lt;em&gt;LangChain4j&lt;/em&gt; ganz allgemein im Java-Umfeld. Die konkrete Fragestellung ist jetzt nicht so originell: Ich will mit eigenen Daten arbeiten und Antworten bekommen auf Fragen wie:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Wer ist Amtschef im Amt für Raumplanung im Kanton Solothurn?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Müssen Obstkulturen in der amtlichen Vermessung aufgenommen werden?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Was sind die Rechtsgrundlagen für eine Gemeindegrenzregulierung?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Welche Eigentumsbeschränkungen gibt es auf dem Grundstück 114 in der Gemeinde Zuchwil?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die LLM können aus verschiedenen Gründen diese Fragen meistens nicht beantworten. Entweder weil sie nicht mit diesen Daten trainiert wurden. Denke kaum, dass sie das &amp;laquo;Das Handbuch der amtlichen Vermessung Kanton Solothurn&amp;raquo; irgendwann zu Gesicht bekommen haben oder aber sie wurden anhand nicht mehr aktueller Daten trainiert (knowledge cut off date).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Folgende Beispiele sind mit dem gpt4-Modell durchgespielt. D.h. Informationen landen immer auf einem fremden Server. Aus diesem Grund verwende ich nur frei verfügbare Daten und mein Lieblingsbeispiel &amp;laquo;Wem gehört das Grundstück XY in der Gemeinde YZ?&amp;raquo; lasse ich vorderhand im Giftschrank. Möchte ja meinen Job behalten. Interessant wären in diesem Zusammenhang natürlich lokal / inhouse laufende Modelle wie z.B. &lt;a href=&quot;https://llama.meta.com/llama3/&quot;&gt;llama3&lt;/a&gt;. Auf meinem Notebook macht das keinen Spass und die &lt;a href=&quot;https://www.hetzner.com/de/dedicated-rootserver/matrix-gpu/&quot;&gt;200 Euro pro Monat für einen Hetzner-Server&lt;/a&gt; waren mir dann doch zu viel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine einfache Variante mit eigenen Daten zu arbeiten, ist &lt;em&gt;prompt stuffing&lt;/em&gt;. Dabei werden die Information einfach mit der Frage mitgeschickt. Ein paar Amtschefs habe ich in beispielhaft in einer Markdown-Datei erfasst:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;## Bau- und Justizdepartement (BJD)

- Amt für Umwelt (AfU): Gabriel Zenklusen
- Amt für Raumplaung (ARP): Sacha Peter
- Amt für Denkmalpflege und Archäologie (ADA): Stefan Blank
- Amt für Verkehr und Tiefbau (AVT): Roger Schibler
- Hochbauamt (HBA): Guido Keune
- Motorfahrzeugkontrolle (MFK): Kenneth Lützelschwab
- Amt für Geoinformation (AGI): Stefan Ziegler

## Volkwirtschaftsdepartement (VWD)

- Amt für Landwirtschaft (ALW): Felix Schibli
- Amt für Wald, Jagd und Fischerei (AWJF): Rolf Manser
- Amt für Gemeinden (AGEM): Grolimund André
- Amt für Militär und Bevölkerungsschutz (AMB): Diego Ochsner
- Amt für Wirtschaft und Arbeit (AWA): R. Frei

## Departement des Innern (DDI)

- Amt für Gesellschaft und Soziales (AGS): Sandro Müller
- Gesundheitsamt (GESA): Peter Eberhard
- Migrationsamt: Johanna Schwegler
- Amt für Justizvollzug (AJUV): Michael Leutwyler&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese Daten müssen irgendwie zum LLM gelangen. Dazu wird ein Prompt-Template mit Einleitung und Platzhaltern für den Kontext (aka unseren eigenen Daten) und der eigentliche Frage benötigt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-foo&quot; data-lang=&quot;foo&quot;&gt;Verwende den folgenden Inhalt, um die Frage am Ende zu beantworten.
Wnn du die Antwort nicht kennst, antworte mit
&quot;Ich kenne die Antwort leider nicht&quot;.

{context}

Frage: {question}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;Spring AI&lt;/em&gt; kann ich einen Restcontroller schreiben und aus dem Prompt-Template einen Prompt machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;@Value(&quot;classpath:/docs/organisation-kanton-solothurn.md&quot;)
private Resource docsToStuffResource;

....

@GetMapping(&quot;/api/output/stuff/organisation&quot;)
public String getOrganisationInfo(
        @RequestParam(value = &quot;message&quot;, defaultValue = &quot;Wer ist Amtschef oder Amtschefin im Amt für Umwelt des Kantons Solothurn&quot;) String message
        ) {

    PromptTemplate promptTemplate = new PromptTemplate(organisationKantonSolothurnResource);
    Map&amp;lt;String,Object&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
    map.put(&quot;question&quot;, message);
    map.put(&quot;context&quot;, docsToStuffResource);

    Prompt prompt = promptTemplate.create(map);
    ChatResponse response = chatClient.call(prompt);

    return response.getResult().getOutput().getContent();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Platzhalter werden mit der Frage (&lt;code&gt;message&lt;/code&gt;-Parameter) und dem Inhalt der Markdown-Datei (&lt;code&gt;docsToStuffResource&lt;/code&gt;) ersetzt und anschliessend wird das Ganze zu OpenAI hochgeladen und GPT-4 liefert mir die korrekte Antwort. Auch die Frage der Anzahl Departemente wird richtig beantwortet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was kann man machen, wenn wir nicht nur eine einzelne Markdown-Datei vorliegen haben, sondern 100 oder 1000 PDF, E-Mails, Word-Dateien etc.? Das Prinzip bleibt das gleiche, nur dass wir nicht alle Dokumente hochladen, sondern nur den (hoffentlich) relevanten Teil. Wir können/sollten aus zwei Gründen nicht alle Dokumente hochladen: Einerseits sind die sogenannten Tokens die Währung in diesem AI-Zirkus. D.h. es wird pro Input- und Output-Token abgerechnet. Es ist massiv teurer 100 PDF-Seiten hochzuladen als bloss einen Paragraphen daraus. Tokens entsprechend Wortteilen und man rechnet mit circa &lt;a href=&quot;https://platform.openai.com/tokenizer&quot;&gt;1.25 Token pro Wort&lt;/a&gt;. Andererseits können die LLM nicht mit unendlich vielen Tokens umgehen. Hier gibt es grosse Unterschiede zwischen den LLM und die maximale Anzahl wächst ständig mit neuen Generationen der LLM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Lösung für dieses Problem heisst RAG (Retrieval Augmented Generation) und Embeddings. Kann ich nicht richtig erklären, ergoogelt sich man besser. Kurzum: Unsere 1000 Dateien werden in einen Vektorstore importiert und bei einer Anfrage wird zuerst nach den zur Frage möglichst passendsten Inhalten gesucht und nur diese werden zum LLM geschickt. Vektorstores gibt es wie Sand am Meer. &lt;em&gt;Spring AI&lt;/em&gt; hat zum rumpröbeln einen filebasierten Store, ich verwende PostgreSQL mit der &lt;a href=&quot;https://github.com/pgvector/pgvector&quot;&gt;pgvector-Extension&lt;/a&gt;. Für viele Anwendungsfälle reicht PostgreSQL wohl. Bei meinen Beispielen war die Dokumentensuche in der Datenbank jedenfalls nicht der zeitlimitierende Faktor. &lt;em&gt;Spring AI&lt;/em&gt; bietet für den Import von Dateien verschieden Reader an. Ich habe für drei Dokumente der amtlichen Vermessung den PDF-Reader verwendet. Es handelt sich um das circa &lt;a href=&quot;https://s3.eu-central-1.amazonaws.com/ch.so.agi.av.lnf.handbuch/handbuch-2.6.zip&quot;&gt;190-seitige Handbuch der amtlichen Vermessung Kanton Solothurn&lt;/a&gt;, sowie um die &lt;a href=&quot;https://so.ch/fileadmin/internet/bjd/bjd-agi/pdf/AmtlicheVermessung/Weisungen/Weisung_Vermarkung_150122.pdf&quot;&gt;Vermarkungsweisung&lt;/a&gt; und die Weisung betreffend &lt;a href=&quot;https://so.ch/fileadmin/internet/bjd/bjd-agi/pdf/AmtlicheVermessung/Weisungen/Weisung_Regulierung_Gemeindegrenzen_230310.pdf&quot;&gt;Gemeindegrenzregulierungen&lt;/a&gt;. Es kann auch mal vorkommen, dass die PDF-Datei nicht gelesen werden kann oder dass komische Meldungen wegen Fonts im Log erscheinen. Warum ist mir nicht ganz klar. Entweder ist die PDF-Datei korrupt oder es liegt am PDF-Reader. &lt;em&gt;Spring AI&lt;/em&gt; verwendet zum Lesen der PDF-Dateien &lt;a href=&quot;https://pdfbox.apache.org/&quot;&gt;&lt;em&gt;Apache PDFBox&lt;/em&gt;&lt;/a&gt; und es gibt ergänzend einen &lt;a href=&quot;https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/reader/tika/TikaDocumentReader.html&quot;&gt;Tika-Reader&lt;/a&gt; mit dem man &lt;a href=&quot;https://tika.apache.org/2.9.0/formats.html&quot;&gt;alles mögliche lesen&lt;/a&gt; kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach dem Import, der bei mir circa zwei Minuten gedauert (dauert die Vektorberechnung so lange?) hat, sieht es in der Datenbank so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_journey_p1/vectorstore.png&quot; alt=&quot;vectorstore&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stelle ich eine Frage werden zuerst die &lt;a href=&quot;https://github.com/edigonzales/spring-ai-avdocs/blob/02214c8/src/main/java/dev/edigonzales/avdocs/SpringAssistantCommand.java#L55&quot;&gt;&lt;em&gt;n&lt;/em&gt; ähnlichsten Dokumente(-nteile) in der Datenbank gesucht&lt;/a&gt;. Das &lt;em&gt;n&lt;/em&gt; darf man selber wählen. Anschliessend wird diese Information wieder in ein &lt;a href=&quot;https://github.com/edigonzales/spring-ai-avdocs/blob/02214c8/src/main/java/dev/edigonzales/avdocs/SpringAssistantCommand.java#L39&quot;&gt;Prompt-Template gestopft&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-foo&quot; data-lang=&quot;foo&quot;&gt;Sie sind ein hilfreicher und freundlicher KI-Assistent,
der Fragen zur amtlichen Vermessung des Kantons Solothurn
beantworten kann. Verwenden Sie die Informationen aus dem
Abschnitt DOKUMENTE, um genaue Antworten zu geben.
Wenn sie unsicher sind oder die Antwort nicht im Abschnitt
DOKUMENTE zu finden ist, geben sie einfach an, dass sie
die Antwort nicht kennen.

Bitte verwenden sie in der Antwort keine &quot;ß&quot;, sondern &quot;ss&quot;.

FRAGE:
{input}

DOKUMENTE:
{documents}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anhand der Metadaten (siehe Spalte &lt;em&gt;metadata&lt;/em&gt; in der DB-Tabelle) weiss die Anwendung aus welchen Original-Dokumenten die Information für die Antwort stammen und kann diese dem Benutzer auch mitteilen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu korrekten Antworten haben folgende Fragen geführt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Müssen Obstkulturen aufgenommen werden?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Müssen Apfelbäume aufgenommen werden?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dürfen Kunststoffzeichen für die Vermarkung verwendet werden?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Was sind die Rechtsgrundlagen für eine Gemeindegrenzregulierung?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei anderen Fragen wurde halluziniert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Wie ist die Gebäudedefinition?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Was gehört bei einer Gemeindegrenzregulierung auf den Regulierungsplan?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wann kommt es zu einer Gemeindegrenzregulierung?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eingangs habe ich die Frage &amp;laquo;Welche Eigentumsbeschränkungen gibt es auf dem Grundstück 114 in der Gemeinde Zuchwil?&amp;raquo; beispielhaft aufgeführt. Ist sowas möglich? Ja. Verschiedene LLM kennen das Prinzip des &lt;a href=&quot;https://platform.openai.com/docs/guides/function-calling&quot;&gt;Function&lt;/a&gt;  &lt;a href=&quot;https://docs.spring.io/spring-ai/reference/api/functions.html&quot;&gt;Callings&lt;/a&gt;. Dabei können vom Entwickler beliebige Funktionen eingehängt werden. Die Funktion führt beliebigen Code aus und liefert eine Antwort zurück, die das LLM verwenden kann. In meinem Fall sind es sogar zwei Funktionen. Eine liefert den E-GRID eines Grundstückes anhand des Gemeindenamens und der Grundstücksnummer. Die zweite Funktion ruft mit dem E-GRID als Identifier den ÖREB-Webservice auf, liest aus dem XML die betroffenen Themen aus und liefert diese als Liste zurück. Die LLM sind dahingehend trainiert, dass sie möglichst gut wissen, wann sie die Funktion(en) aufrufen müssen. Das Ganze ist aber schon bisschen magisch. Mir ist noch nicht ganz klar, was und wie genau hier rein spielt. Die Funktionen werden &lt;a href=&quot;https://github.com/edigonzales/spring-ai-demo/blob/4d89fa7/src/main/java/dev/edigonzales/demo/functions/GrunstueckFunctionConfiguration.java#L12&quot;&gt;annotiert&lt;/a&gt; und es hilft sicher hier was sinnvolles reinzuschreiben (rede ich mir jedenfalls ein).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Frage nach den Eigentumsbeschränkungen konnte beantwortet werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ai_journey_p1/oereb-frage.png&quot; alt=&quot;oereb-frage&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anscheinend wird wirklich zuerst anhand des Gemeindenamens und der GB-Nummer die &lt;a href=&quot;https://github.com/edigonzales/spring-ai-demo/blob/4d89fa7/src/main/java/dev/edigonzales/demo/functions/GrundstueckService.java&quot;&gt;Grundstuecks-Funktion&lt;/a&gt; aufgerufen und anschliessend mit dem jetzt bekannten E-GRID die &lt;a href=&quot;https://github.com/edigonzales/spring-ai-demo/blob/4d89fa7/src/main/java/dev/edigonzales/demo/functions/OerebService.java&quot;&gt;ÖREB-Funktion&lt;/a&gt;, die nur eine &lt;a href=&quot;https://github.com/edigonzales/spring-ai-demo/blob/4d89fa7/src/main/java/dev/edigonzales/demo/functions/GrundstueckService.java#L21&quot;&gt;Java-Liste&lt;/a&gt; zurückliefert. Der schön formatierte Text stammt vom LLM. Faszinierend.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Viel Code stammt aus den &lt;a href=&quot;https://www.youtube.com/playlist?list=PLZV0a2jwt22uoDm3LNDFvN6i2cAVU_HTH&quot;&gt;Beispielen&lt;/a&gt; von Dan Vega.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/spring-ai-demo&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/spring-ai-demo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/spring-ai-avdocs&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/spring-ai-avdocs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #42 - INTERLIS over the wire</title>
      <link>http://blog.sogeo.services/blog/2024/05/31/interlis-leicht-gemacht-number-42.html</link>
      <pubDate>Fri, 31 May 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/05/31/interlis-leicht-gemacht-number-42.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit über 20 Jahren landen Informationen über Baugesuche ausserhalb der Bauzone in unserer GDI. Die erste Generation einer Geschäftskontrolle wurde noch selber programmiert. Interessant war dabei, dass die Lokalisierung quasi im Zentrum stand: Das Geschäft musste mit einem Klick in die Karte eröffnet werden. Heute mit einer externen Lösung ist das nicht mehr so gelöst. Nichtsdestotrotz landen die Koordinaten des Baugesuches und weitere Informationen in unserer Datenbank und werden als geschützter WMS-Layer publiziert. Die Daten werden von der externen Anwendung via unserem &lt;a href=&quot;https://geo.so.ch/api/data/v1/api/&quot;&gt;Dataservice (einfache Rest-API und JSON)&lt;/a&gt; in die Datenbank gespeichert. Ganz geglückt ist die Integration nicht, da das Fehlerhandling - warum auch immer - eher medioker ist: Falls es beim Zurückschreiben (PUT oder POST) einen Fehler gibt, war es das. Der Benutzer bemerkt diesen Fehler nicht und die Anwendung macht auch nichts. So weit, so gut. Ist jetzt noch nicht per se ein Negativpunkt für ein Rest-API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton realisiert zur Zeit das &lt;a href=&quot;https://so.ch/verwaltung/bau-und-justizdepartement/departementssekretariat/projekt-elektronisches-baubewilligungsverfahren-ebauso/&quot;&gt;Projekt &amp;laquo;Elektronisches Baubewilligungsverfahren (eBauSO)&amp;raquo;&lt;/a&gt; und auch hier ist das Zurückschreiben der Geoinformationen zum Baugesuch ein Thema, das behandelt werden muss. Entweder machen wir es in verbesserter Form mit dem Rest-API, dessen Signatur wir aber abgekündigt haben (und in Zukunft OGC API - Features verwenden wollen). Oder wir ziehen andere Lösungsvarianten in Betracht. Dazu muss man die Anforderungen kennen: Die externe Anwendung kennt immer die Lokalisierung des vom Benutzer (Baugesuchssteller oder Leitbehörde etc.) bearbeitenden Baugesuchs (ohne GDI, ohne WMS) und kann sie auf einer eigenen Karte darstellen. Für die Behörden ist ebenso wichtig zu wissen, ob es andere aktuelle oder ältere Baugesuche gibt, die sich auf dem Grundstücke oder in der Nähe befinden. Diese Information muss aber nicht zwingend real-time sein. Das heisst, wenn ein Baugesuchssteller ein neues Baugesuch eröffnet, müssen die Behörden nicht zum gleichen Zeitpunkt die Koordinaten im geschützten WMS-Layer sehen. Wenn wir wählen können, ist uns ein filebasierter Austausch mit INTERLIS lieber. Eine Variante wäre z.B. eine tägliche Gesamtlieferung aller Baugesuchsinformationen. Da jedoch pro Jahr circa 6&apos;000 Baugesuche erstellt werden, skaliert das dann doch nicht so gut. Eine andere Variante eines dateibasierten Austausches ist, dass man pro Geschäft eine Datei erstellt und nur jeweils bei Veränderungen im Geschäft die Datei exportiert und irgendwo hinkopiert, wo wir sie herunterladen können. Mit &lt;em&gt;ili2pg&lt;/em&gt; resp. &lt;a href=&quot;https://gretl.app&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; können wir die Daten einfach in die Datenbank importeren. Ein Geschäft = eine Datei = ein Dataset. Ganz fire and forget ist dieser Ansatz aber nicht: Man kann mit diesem Ansatz keine Geschäfte direkt löschen, sondern es wird ein Status-Attribut benötigt, das diese Information speichert und ein nachgelagerter Prozess müsste, so gewollt, den Punkt in der Datenbank löschen (oder in der Karte einfach nicht darstellen). Zudem spielt die Reihenfolge eine wichtige Rolle: Z.B. darf die Löschmeldung nicht vor einer anderen Änderung in die Datenbank importiert werden. Dieses Problem löst sich auf, falls die  externe Anwendung immer den gleichen Dateinamen für das Geschäft verwendet und die Dateien am Zielort einfach überschrieben werden. Die Geschichte des Geschäfts wird in unserer Datenbank nicht abgebildet, so dürfen einzelne Zwischenzustände also fehlen. Umsetzen würden wir es ganz simpel: Externe Anwendung speichert die Daten auf einem FTP-Server und wir machen bissle &lt;em&gt;GRETL&lt;/em&gt; und &lt;em&gt;Jenkins&lt;/em&gt; und gut ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Ansatz sind wir im Bereich von Messaging angekommen. Man könnte die letzte Variante auch mit einem Message Broker umsetzen, ohne Umweg über Dateien exportieren und rumschieben. Gesagt, getan: Spasseshalber &lt;a href=&quot;https://activemq.apache.org/&quot;&gt;&lt;em&gt;Apache ActiveMQ&lt;/em&gt;&lt;/a&gt; heruntergeladen und gestartet. &lt;em&gt;Apache ActiveMQ&lt;/em&gt; ist ein Open Source Message Broker, der das &lt;a href=&quot;https://projects.eclipse.org/projects/ee4j.messaging&quot;&gt;Jakarta Messaging API&lt;/a&gt; (JMS) implementiert. Damit er auch ausserhalb der Java-Welt einsetzbar ist, unterstützt &lt;em&gt;ActiveMQ&lt;/em&gt; auch andere Protokolle, z.B. REST, &lt;a href=&quot;https://stomp.github.io/&quot;&gt;STOMP&lt;/a&gt; oder WebSocket. &lt;em&gt;ActiveMQ&lt;/em&gt; kennt zwei Modi: Queue und Topic. Bei der Queue gibt es einen Producer und einen Verbraucher. Wurde die Nachricht vom Verbraucher empfangen, wird sie aus der Queue gelöscht. Topic entspricht einem Publisher/Subscriber-Modell. Hier können mehrere Subscriber die Nachricht empfangen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Faken wir also unseren Usecase mit einem Python-Producer und einem Java-Consumer. Der Python-Producer verschickt die Nachricht mittels STOMP:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import stomp

# Create a connection to ActiveMQ
conn = stomp.Connection12([(&apos;localhost&apos;, 61613)], auto_content_length=False) #https://github.com/jasonrbriggs/stomp.py/issues/216
conn.connect(login=&apos;admin&apos;, passcode=&apos;admin&apos;, wait=True)

# Send a message to a specific destination (queue)
destination = &apos;ch/so/dsbjd/ebau&apos;
message = &apos;&apos;&apos;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;ili:transfer xmlns:ili=&quot;http://www.interlis.ch/xtf/2.4/INTERLIS&quot; xmlns:geom=&quot;http://www.interlis.ch/geometry/1.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:SO_ARP_Baugis_20190612=&quot;http://www.interlis.ch/xtf/2.4/SO_ARP_Baugis_20190612&quot;&amp;gt;
  &amp;lt;ili:headersection&amp;gt;
    &amp;lt;ili:models&amp;gt;
      &amp;lt;ili:model&amp;gt;SO_ARP_Baugis_20190612&amp;lt;/ili:model&amp;gt;
    &amp;lt;/ili:models&amp;gt;
    &amp;lt;ili:sender&amp;gt;ili2gpkg-5.1.0-58fc980cc6639b2c16a3cff8a0fa19ef1484b11c&amp;lt;/ili:sender&amp;gt;
  &amp;lt;/ili:headersection&amp;gt;
  &amp;lt;ili:datasection&amp;gt;
    &amp;lt;SO_ARP_Baugis_20190612:Baugis ili:bid=&quot;SO_ARP_Baugis_20190612.Baugis&quot;&amp;gt;
      &amp;lt;SO_ARP_Baugis_20190612:Geschaeft ili:tid=&quot;E888E057-B203-4488-866F-0EA1D365AFEF&quot;&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:SobauID_sprechend&amp;gt;Neubau Zürihaus&amp;lt;/SO_ARP_Baugis_20190612:SobauID_sprechend&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:SobauID_system&amp;gt;08DE6290-A2EF-4044-9C49-3B9A1F280610&amp;lt;/SO_ARP_Baugis_20190612:SobauID_system&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Art_Text&amp;gt;foo&amp;lt;/SO_ARP_Baugis_20190612:Art_Text&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Art_Code&amp;gt;1&amp;lt;/SO_ARP_Baugis_20190612:Art_Code&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Vorhaben&amp;gt;Gaga&amp;lt;/SO_ARP_Baugis_20190612:Vorhaben&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Entscheid_Text&amp;gt;Ja&amp;lt;/SO_ARP_Baugis_20190612:Entscheid_Text&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Entscheid_Code&amp;gt;1&amp;lt;/SO_ARP_Baugis_20190612:Entscheid_Code&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Sistiert_Text&amp;gt;nein&amp;lt;/SO_ARP_Baugis_20190612:Sistiert_Text&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Sistiert_Code&amp;gt;2&amp;lt;/SO_ARP_Baugis_20190612:Sistiert_Code&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Sobau_Link&amp;gt;https://agi.so.ch&amp;lt;/SO_ARP_Baugis_20190612:Sobau_Link&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Projektleiter_Name&amp;gt;Lisa Liegenschaft&amp;lt;/SO_ARP_Baugis_20190612:Projektleiter_Name&amp;gt;
        &amp;lt;SO_ARP_Baugis_20190612:Geometrie&amp;gt;
          &amp;lt;geom:coord&amp;gt;
            &amp;lt;geom:c1&amp;gt;2607892.464&amp;lt;/geom:c1&amp;gt;
            &amp;lt;geom:c2&amp;gt;1228257.773&amp;lt;/geom:c2&amp;gt;
          &amp;lt;/geom:coord&amp;gt;
        &amp;lt;/SO_ARP_Baugis_20190612:Geometrie&amp;gt;
      &amp;lt;/SO_ARP_Baugis_20190612:Geschaeft&amp;gt;
    &amp;lt;/SO_ARP_Baugis_20190612:Baugis&amp;gt;
  &amp;lt;/ili:datasection&amp;gt;
&amp;lt;/ili:transfer&amp;gt;
&apos;&apos;&apos;

conn.send(body=message, destination=destination, headers={&apos;persistent&apos; :&apos;true&apos;})

# Disconnect from ActiveMQ
conn.disconnect()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Herstellen des INTERLIS-XML in Python würde für unseren Anwendungfalls reines Templating reichen. Man muss nur die Attribut- resp. Elementwerte austauschen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wird die Nachricht verschickt und es gibt noch keinen Consumer, kann man die Nachricht in der Enterprise-90er-Jahre-Vibes-Oberfläche von &lt;em&gt;ActiveMQ&lt;/em&gt; betrachten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p42/activemq_message_in_queue.png&quot; alt=&quot;activemq_message_in_queue&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanterweise überlebten die Nachrichten im Broker einen Restart zuerst nicht. Anscheinend ist es eine Eigenschaft der Nachricht selber, ob sie &lt;em&gt;persistent&lt;/em&gt; ist oder eben nicht. Mit STOMP muss das via Header gelöst werden (siehe Zeile 43).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Konsumenten bauen wir mit &lt;a href=&quot;https://camel.apache.org/&quot;&gt;&lt;em&gt;Apache Camel&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;Apache Camel&lt;/em&gt; ist ein in Java geschriebenes Open Source Integration Framework. Mit &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;JBang&lt;/a&gt;-Zuckerguss drüber, kann man mit einer einzigen Datei Java-Code sehr viel erreichen. Die sogenannte Route (siehe Zeile 27 &lt;code&gt;&amp;#8230;&amp;#8203; extends RouteBuilder&lt;/code&gt;) lädt vom Message Broker die Nachricht herunter (Zeile 38-40), speichert sie in einer Datei (Zeile 41-42) und validiert diese mit &lt;em&gt;ilivalidator&lt;/em&gt; (Zeile 46-51). Falls die Nachricht valide ist, wird sie mit &lt;em&gt;ili2pg&lt;/em&gt; importiert (nicht implementiert, Zeile 54-55), ansonsten wird eine entsprechende Meldung geloggt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;//REPOS mavencentral,ehi=https://jars.interlis.ch/
//DEPS ch.interlis:ilivalidator:1.14.1
//DEPS ch.ehi:ehibasics:1.4.1
//DEPS org.apache.camel:camel-bom:4.5.0@pom
//DEPS org.apache.camel:camel-main
//DEPS org.apache.camel:camel-activemq
//DEPS org.apache.camel:camel-debug
//DEPS org.apache.camel:camel-file
//DEPS org.apache.camel:camel-health
//DEPS org.apache.camel:camel-jms

import ch.ehi.basics.settings.Settings;
import org.interlis2.validator.Validator;

import org.apache.camel.main.Main;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.Exchange;
import org.apache.camel.Predicate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.activemq.ActiveMQComponent;

import static org.apache.activemq.ActiveMQConnection.DEFAULT_BROKER_URL;

import java.nio.file.Paths;
import java.util.UUID;

public class consume_messages extends RouteBuilder {
    private static final String TMP_DIR = &quot;/Users/stefan/tmp/&quot;;

    Main main = new Main();

    @Override
    public void configure() throws Exception {
        // Kann man sich sparen, falls Default-Url verwendet wird.
        main.bind(&quot;activemq&quot;, ActiveMQComponent.activeMQComponent(DEFAULT_BROKER_URL));
        main.bind(&quot;activemqConnectionFactory&quot;, ActiveMQConnectionFactory.class);

        from(&quot;activemq:queue:ch/so/dsbjd/ebau&quot; +
            &quot;?username=user&quot; +
            &quot;&amp;amp;password=1234&quot;)
        .setHeader(&quot;CamelFileName&quot;, method(consume_messages.class, &quot;generateFileName&quot;))
        .to(&quot;file:&quot;+TMP_DIR)
        .choice()
            .when(new Predicate() {
                @Override
                public boolean matches(Exchange exchange) {
                    Settings settings = new Settings();
                    settings.setValue(Validator.SETTING_ILIDIRS, &quot;.;&quot;+Validator.SETTING_DEFAULT_ILIDIRS);
                    String fileName = (String) exchange.getIn().getHeader(&quot;CamelFileName&quot;);
                    boolean valid = Validator.runValidation(Paths.get(TMP_DIR, fileName).toString(), settings);
                    return valid;
                }
            }).process(exchange -&amp;gt; {
                System.out.println(&quot;File is valid and will be imported: &quot; + exchange.getIn().getHeader(&quot;CamelFileName&quot;));
                // ili2pg...
            })
            .otherwise().log(&quot;File is NOT valid.&quot;)
        .end();
    }

    public static String generateFileName() {
        UUID uuid = UUID.randomUUID();
        return uuid.toString() + &quot;.xtf&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Dateinamen (Zeile 139) ist im Codebeispiel einfach eine random UUID. Damit könnte ich für den ili2pg-Import nicht auf den Dateinamen als Dataset zurückgreifen, sondern müsste z.B. zuerst die TID aus der XML-Nachricht rauslesen (siehe Anforderungen oben).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bringt INTERLIS in solchen Fällen etwas? Mich dünkt ja. Ich kann die Nachricht vor der Weiterverarbeitung mit dem vollen INTERLIS-Arsenal prüfen. Die Prüfung bekomme ich mit &lt;em&gt;ilivalidator&lt;/em&gt; geschenkt. Anschliessend kann ich die Daten mit &lt;em&gt;ili2db&lt;/em&gt; in die Datenbank importieren. Diesen Schritt bekomme ich ebenfalls geschenkt. Es fallen praktisch keine Zeilen Businesslogik an. Ich muss keine Zeile Code ändern, wenn das Datenmodell ändert oder wenn ich den Messaging-Ansatz für ein komplett anderes Thema wähle. Zudem interessieren mich hier beim Empfangen der Nachricht die Information über das Geschäft nicht (also der einzelne Record / das einzelne Objekt). D.h. ich muss gar nicht auf diese Stufe runter. Ansonsten könnte die Validierung mit &lt;a href=&quot;https://beanvalidation.org/3.0/&quot;&gt;Beans Validiation&lt;/a&gt; erfolgen und der Import mit einem &lt;a href=&quot;https://jakarta.ee/specifications/persistence/&quot;&gt;ORM&lt;/a&gt; gemacht werden. Aber da würde massiv mehr Code anfallen und man müsste es für jedes Thema / jedes Modell separat lösen. Und falls das Modell umfangreicher wird (mit Assoziationen etc.) wird das schnell hässlich. Wenn wir bei XML bleiben aber nicht INTERLIS machen wollen, könnte man die Validierung mit XSD machen. Das ist definitiv einige Stufen weniger praktikabel und elegant als mit INTERLIS. Zudem fehlt mir etwas für den generischen Import in die Datenbank. Das ähnliche Problem hatten wir bei der Realisierung des neuen Meldewesens für die amtlichen Vermessung. Wir bekommen die Nachrichten im Standard &lt;a href=&quot;https://www.ech.ch/de/ech/ech-0132/2.1.0&quot;&gt;eCH-0132&lt;/a&gt;. Ist im Prinzip dead on arrival, weil wir als erstes die XML-Datei in eine INTERLIS-Datei gemäss &lt;a href=&quot;https://geo.so.ch/models/AGI/SO_AGI_SGV_Meldungen_20221109.ili&quot;&gt;einem eigenen Modell&lt;/a&gt; &lt;a href=&quot;https://github.com/sogis/gretljobs/blob/main/agi_av_meldewesen/xml2xtf.xsl&quot;&gt;umtransformieren&lt;/a&gt; (müssen), damit wir möglichst viel Aufwand und Code sparen. Das als einer der verschiedenen Kritikpunkte an den eCH-Objektwesen-Standards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/message-broker-playground&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/message-broker-playground&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #41 - Hello Datahub</title>
      <link>http://blog.sogeo.services/blog/2024/05/29/interlis-leicht-gemacht-number-41.html</link>
      <pubDate>Wed, 29 May 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/05/29/interlis-leicht-gemacht-number-41.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit knapp &lt;a href=&quot;https://blog.sogeo.services/blog/2016/11/09/interlis-leicht-gemacht-number-14.html&quot;&gt;acht Jahren&lt;/a&gt; bieten wir einen &lt;a href=&quot;https://geo.so.ch/ilivalidator/&quot;&gt;INTERLIS-Webservice&lt;/a&gt; in einfacher Form an: Datei hochladen und gut ist. In seiner letzten Iteration wurden sogenannte Prüfprofile eingeführt. Der Benutzer soll/muss das entsprechende Prüfprofil in einer Comobox oder via Query-Parameter auswählen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p41/ilivalidator-web-service.png&quot; alt=&quot;ilivalidator-web-service&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Prüfprofil ist letzten Endes nur eine &lt;code&gt;metaConfig&lt;/code&gt;-Konfiguration, die für ein bestimmtes Thema die Konfigurationen der Software und der Prüfung steuert. Die &lt;code&gt;metaConfig&lt;/code&gt;-Datei muss in diesem Fall nicht auf dem Server vorliegen, sondern es wird automatisch und selbständig in den INTERLIS-Data-Repositories (aka ilidata.xml) nach ihr gesucht (analog der Modelle). Diese Laufzeitabhängigkeit von anderen Repositories ist zwar nicht das Gelbe vom Ei aber weil wir die Konfigurationen an verschiedenen Stellen benötigen, gehen wir diesen Deal ein. Letzten Endes war es ein bewusster Entscheid. Vorteilhaft bei dieser Lösung dünkt mich, dass ein Anwender so die genau gleiche Validierung bei sich lokal durchführen kann, ohne die Daten zuerst hochladen zu müssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Bedürfnis, dass nach einer erfolgreichen Prüfung eine automatische Anlieferung der Daten erfolgt, hatten wir bis jetzt nicht. Einzig die Daten der amtlichen Vermessung werden mindestens wöchentlich an den Kanton geliefert. Hier setzen wir immer noch auf das Infogrips-Universum. Das Einpflegen neuer Daten in die Nutzungsplanung machen wir selber (ausser bei kompletten Ortsplanungsrevisionen) und Leitungskataster machen wir noch nicht. Andere Themen fallen nicht ins Gewicht. Mit der Einführung des neuen Datenmodelles in der amtlichen Vermessung ist jedoch der richtige Zeitpunkt gekommen, den Anlieferungsprozess der amtlichen Vermessung zu überdenken und wir sind zum Schluss gekommen, dass wir hier ebenfalls die &amp;laquo;ilivalidator-Schiene&amp;raquo; fahren wollen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anforderungen an den Prozess sind grob:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&amp;laquo;ftp put&amp;raquo;- oder &amp;laquo;curl -X POST&amp;raquo;-Niveau für einfache Automatisierung auf Seiten Datenlieferant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Passwort muss durch Datenlieferant veränderbar sein.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beliebige Datenmodelle: Auch wenn wir zuerst nur den DMAV-Anlieferungsprozess implementieren werden, muss es für beliebige andere Modell funktionieren.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Daten werden geprüft und in einem Verzeichnis abgelegt. Weitere Prozessierungsschritte sind (momentan) nicht Aufgabe des Datahubs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Übersicht Lieferstatus für Datenlieferanten und AGI (GUI und Rest-API)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach reiflicher Überlegung und einigen Diskussionen haben wir uns bei der Authentifizierung auf einen API-Key geeinigt. Dieser wird als Header mitgeschickt und wir können dadurch die Anlieferung mit einem simplen &lt;code&gt;curl&lt;/code&gt;-Befehl umsetzen. Der API-Key kann durch die Anwender selbständig widerrufen werden, ebenso können neue oder zusätzliche API-Keys angefordert werden. Das führt - auch zwecks besserem Verständnis - zum Datenmodell, welches die Konfiguration des Datahubs beschreibt. Selbstverständlich wurde es mit INTERLIS gemacht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p41/datenmodell.png&quot; alt=&quot;datenmodell&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es ist bewusst sehr einfach gehalten: Es gibt ein Thema, z.B. die Grundstücke der amtlichen Vermessung. Der Name ist der Identifikatior, z.B. &lt;code&gt;DMAV_Grundstuecke&lt;/code&gt;. Auf Anhieb nicht ganz einfach zu verstehen, ist der Fakt, dass &lt;em&gt;ilivalidator&lt;/em&gt; eine &lt;code&gt;config&lt;/code&gt;- als auch eine &lt;code&gt;metaConfig&lt;/code&gt;-Option &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/docs/ilivalidator.rst#aufruf-syntax&quot;&gt;kennt&lt;/a&gt;. Die &lt;code&gt;config&lt;/code&gt;-Option steuert die Prüfung, die &lt;code&gt;metaConfig&lt;/code&gt;-Option steuert die Software. In der &lt;code&gt;metaConfig&lt;/code&gt;-Konfiguration wiederum kann man auf eine &lt;code&gt;config&lt;/code&gt;-Konfiguration verweisen&amp;#8230;&amp;#8203; Klingt alles sau-kompliziert, ist es aber eigentlich gar nicht so. Vielleicht hilft ein Blick in eine &lt;a href=&quot;https://geo.so.ch/models/AFU/VSADSSMINI_2020_LV95_IPW_20230605-meta.ini&quot;&gt;Beispiel-metaConfig-Datei&lt;/a&gt; und eine &lt;a href=&quot;https://geo.so.ch/models/AFU/VSADSSMINI_2020_LV95_IPW_20230605.ini&quot;&gt;Beispiel-config-Datei&lt;/a&gt;. D.h. auch, dass die Theme-Klasse sogar noch abgespeckt werden könnte, da der Verweis auf eine &lt;code&gt;metaConfig&lt;/code&gt;-Konfiguration reichen würde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als zweite Klasse gibt es das Operat. Das Operat entspricht einem ili2db-Dataset, in der amtlichen Vermessung ist das die Gemeinde. Wahrscheinlich kommt früher oder später das Bedürfnis, dass Operate eines gleichen Themas unterschiedliche Validierungskonfigurationen haben dürfen. Dann müsste auch die Klasse Operat ein &lt;code&gt;metaConfig&lt;/code&gt;-Attribut aufweisen. Jedes Operat ist einem Thema zugeordnet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die dritte Klasse ist die Organisation. Hier geht es um die Autorisierung: Wer darf welches Operat zu welchem Thema liefern. Als letztes Puzzleteil gibt es die ApiKey-Klasse. Jeder API-Key ist einer Organisation zugewiesen ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Langer Rede, kurzer Sinn. Hier ein curl-Aufruf einer Anlieferung der Grundstücke der amtlichen Vermessung der Gemeinde Solothurn:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -v -X POST --header &quot;X-API-KEY:c0bb04eb-789b-4063-95ad-bd86a06c6aff&quot; \
-F &apos;file=@Grundstuecke_Solothurn.xtf&apos; -F &apos;theme=DMAV_Grundstuecke&apos; -F &apos;operat=2601&apos; \
http://localhost:8080/api/deliveries&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Antwort liefert der Datahub sofort (nachdem die Datei hochgeladen wurde) einen Statuscode 202 (= Accepted) zurück und einen Link, um den Status des Jobs einsehen zu können:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;http://localhost:8080/api/jobs/B0820978-B325-4C66-87F1-7F21C8DA4819&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Gegensatz zu unserer heutigen Lösung spielen die Dateinamen keine Rolle. Die Dateien werden nach erfolgreicher Prüfung automatisch entsprechend dem Operatsnamen umbenannt und in das Themenverzeichnis kopiert. Dort stehen sie für nachgelagerte Prozesse, in diesem Fall für den Import in die Datenbank, zur Verfügung. &lt;em&gt;Ili2db&lt;/em&gt; resp. &lt;a href=&quot;https://gretl.app/&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; kann so automatisch die Datei dem richtigen Dataset zuweisen (nämlich dem Dateinamen ohne Extension). Hier ein Beispiel mit den Themen &lt;code&gt;IPW_2020&lt;/code&gt; und &lt;code&gt;LKMAP_2015&lt;/code&gt; und einigen Operaten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p41/target_directory.png&quot; alt=&quot;target_directory&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird - auch bewusst - nicht geprüft, ob der Inhalt der Daten dem angegebenen Operat/Dataset entspricht. Wir hatten in fast 20 Jahren ein oder zwei Mal ein solches Malheur in der amtlichen Vermessung. Ein Fehler, der anscheinend nicht häufig vorkommt. Die Auswirkungen wären heute (noch) nicht schlimm und schnell zu beheben. Zudem eine robuste und generische Umsetzung mich herausfordernd dünkt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Anwendern steht, zusätzlich zur Abfrage via Jobs-API, eine einfache filterbare Job-Übersichtstabelle zur Verfügung. Ebenfalls werden die Resultate der Validierung per E-Mail verschickt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p41/uebersicht.png&quot; alt=&quot;uebersicht&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zukünftig werden API-Keys nach einer gewissen Zeit automatisch ungültig. Die Organisation wird frühzeitig informiert werden, dass sie einen neuen Key lösen muss. Zudem wird ein API-Key sofort von der Anwendung selber widerrufen, wenn anstelle &amp;laquo;https&amp;raquo; nur &amp;laquo;http&amp;raquo; in der Datahub-URL verwendet wurde. Ein neuer resp. zusätzlicher Key kann mit der Key-API erzeugt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -i -X POST --header &quot;X-API-KEY:c0bb04eb-789b-4063-95ad-bd86a06c6aff&quot; \
http://localhost:8080/api/keys&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anwendung kennt aufgrund des mitgelieferten API-Keys die Organisation und kann den neuen Key dieser zuordnen. Der neue Key wird nicht in der Antwort mitgeschickt, sondern an die hinterlegte E-Mail-Adresse geschickt. Damit wird verhindert, dass ein kompromittierter Key für zuviel Schabernack verwendet werden kann. Die Nachführung der E-Mail-Adressen übernehmen wir selber und kann von Benutzern nicht selber verändert werden. Erstens gibt das einen zusätzlichen Schutz und zweitens kommt das sehr selten vor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Keys können auch gelöscht/widerrufen werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -i -X DELETE --header &quot;X-API-KEY:c0bb04eb-789b-4063-95ad-bd86a06c6aff&quot; \
http://localhost:8080/api/keys/e2b76dce-5160-429b-96ec-0c64ed7c5027&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Umgesetzt wurde der &lt;a href=&quot;https://github.com/sogis/datahub&quot;&gt;Datahub&lt;/a&gt; mit &lt;em&gt;Spring Boot&lt;/em&gt;. Wir verwenden, wie bereits beim ilivalidator-web-service, die &lt;a href=&quot;https://www.jobrunr.io/&quot;&gt;JobRunr-Bibliothek&lt;/a&gt; für das interne Jobmanagement. Die Anwendung nimmt eine Lieferung entgegen, speichert die Daten in einem temporären Verzeichnis ab und macht einen Eintrag in die JobRunr-Queue. Die Queue kann im Memory sein oder in einer Datenbank (wie in unserem Fall). Es können beliebig viele Worker registriert werden, um die Queue abzuarbeiten. Der Worker muss nicht einmal im gleichen Netzwerk liegen, sondern muss einzig Zugriff auf die Queue haben und in unserem Fall auf die XTF-Datei zugreifen können (was z.B. mit Objectstorage leicht möglich wäre). Die Anwendung ist so konzipiert, dass sie sowohl Frontend wie auch Worker sein kann (steuerbar über Env-Variablen). Der Overhead des Frontends scheint mir zu gering, um die Trennung in zwei separate Anwendungen (Frontend inkl. Rest-API und Worker) zu rechtfertigen. Dafür gewinne ich einfacheres Entwickeln und für minimale Anforderungen reicht das Deployment einer einzelnen Anwendung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Authentifizierung und Authorisierung ist mit &lt;em&gt;Spring Security&lt;/em&gt; gemacht, das &amp;laquo;Frontend&amp;raquo; (also die einzelne, filterbare Tabelle) mit &lt;em&gt;Jakarta Faces&lt;/em&gt; (der Screenshot zeigt noch einen Mockup).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Datenmodell wurde mit &lt;em&gt;ili2pg&lt;/em&gt; in der PostgreSQL-Datenbank implementiert, um mit &lt;a href=&quot;https://cayenne.apache.org/&quot;&gt;&lt;em&gt;Apache Cayenne&lt;/em&gt;&lt;/a&gt; mit dem Database First Approach die Java-Klassen erzeugen zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As simple as it gets: Einfache Gesamtarchitektur, eine Programmiersprache, eine Modellierungsprache.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sogis/datahub&quot;&gt;https://github.com/sogis/datahub&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>HTTP-Statuscode 206 - Cloud Optimized GeoTIFF Benchmark</title>
      <link>http://blog.sogeo.services/blog/2024/05/13/statuscode-206-cogtiff.html</link>
      <pubDate>Mon, 13 May 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/05/13/statuscode-206-cogtiff.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Mach&apos; Cloud Optimized GeoTIFF!&amp;raquo; sagen sie. &amp;laquo;Dann brauchst du keinen WMS mehr!&amp;raquo; behaubten sie. Gesagt, &lt;a href=&quot;https://blog.sogeo.services/blog/2023/12/29/statuscode-206-letsgetstarted.html&quot;&gt;getan&lt;/a&gt;. &amp;laquo;Das macht alles viel einfacher!&amp;raquo; beteuern sie. Zumindest letzteres ist leider nur die halbe Wahrheit, wenn man das Ganze auch in einer kantonalen Infrastruktur aufbauen will. Aber natürlich bleibe ich trotzdem grosser Freund von diesem cloud native / cloud optimizeten Geozeugs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo hakt es nun?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Testen habe ich auf einem Hetzner-Cloud-Server unsere Rasterdaten als Cloud Optimized GeoTIFF deployed und mittels &lt;a href=&quot;https://caddyserver.com/&quot;&gt;&lt;em&gt;Caddy&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://stac.sogeo.services/files/raster/&quot;&gt;öffentlich verfügbar&lt;/a&gt; gemacht. In QGIS bisschen rumgespielt und mit der Performance absolut zufrieden gewesen. Ok, dann lass&apos; es uns in der kantonalen Infrastruktur umsetzen. Wir haben einen einen &amp;laquo;dummen&amp;raquo; Webserver, der die Vektor- und Rasterdaten ähnlich wie bei meinem Hetzner-Test &lt;a href=&quot;https://files.geo.so.ch/&quot;&gt;öffentlich zugänglich&lt;/a&gt; macht. Ich freute mich schon die E-Mail mit den Neuigkeiten rauszuhauen und mit bestem Digitaliserungsboomer-Marketingsprech über unsere Cloud-Errungschaften und so zu schreiben. Gott sei Dank noch vorher mit dem Orthofoto (swissimage 2021 kantonale Abdeckung, circa 30GB) getestet und mich gefragt warum das interessanterweise vor allem beim schnellen Zoomen sehr käsig ist. Es ist signifikant langsamer als die Hetzner-Variante, die erst noch weiter entfernt ist. Der Super-GAU ist jedoch, dass QGIS manchmal die Rasterdatei gar nicht als solche erkennt und mit einer Fehlermeldung quittiert. Ob beide Probleme irgendwie zusammenhängen wissen wir, wie so vieles, noch nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Letzeres scheint einen Zusammenhang zu haben mit den &lt;a href=&quot;https://trac.osgeo.org/gdal/wiki/CloudOptimizedGeoTIFF#HowtoreaditwithGDAL&quot;&gt;GDAL-Umgebungsvariablen&lt;/a&gt;, die verhindern, dass unnötige HTTP-Requests gemacht werden. Und/oder mit dem Umstand, dass unser Webserver keinen HEAD-Request unterstützt. Who knows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ziemlich nervig ist ebenfalls die schlechtere Performance gegenüber der Hetzner-Variante. Das kann doch fast nicht sein, müsste man meinen. Um es aber der zentralen IT, die ausnahmweise diesen File-Webserver für uns betreibt, schwarz auf weiss darlegen zu können, muss ein sauberes Benchmarking her. Grundsätzlich natürlich sofort an &lt;a href=&quot;https://jmeter.apache.org/&quot;&gt;&lt;em&gt;jMeter&lt;/em&gt;&lt;/a&gt; gedacht. Wie simuliert man nun aber solche Range Requests? Ein Range Request ist bloss ein normaler HTTP-GET-Request mit einem &lt;code&gt;Range&lt;/code&gt;-Header, der - nomen est omen - den gewünschten Byterange angibt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -v -X GET -H &quot;Range: bytes=26201587777-26202626445&quot; https://files.geo.so.ch/ch.swisstopo.swissimage_2021.rgb/aktuell/ch.swisstopo.swissimage_2021.rgb.tif&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe mir mit Java und &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;&lt;em&gt;jBang&lt;/em&gt;&lt;/a&gt; ein &lt;a href=&quot;https://github.com/edigonzales/cogtiff_benchmark/blob/dda77de/sampler/cogtiff_request_sampler.java&quot;&gt;Java-Skript&lt;/a&gt; geschrieben, dass mir innerhalb der Grösse der Zieldatei einen Byterange zwischen 200KB und 2MB ausrechnet. Man müsste im Webserver-Logfile schauen, ob das circa der Realität entspricht. Diese Range Requests sind nicht mehr ganz so einfach zum Faken wie WMS-Requests mit Boundingbox und Pixel-Anzahl. Gehen wir aber davon aus, dass meine Ranges mehr oder weniger mit der Realität vergleichbar sind. Diese Byteranges werden in einer CSV-Datei gespeichert und können von &lt;em&gt;jMeter&lt;/em&gt; als dynamischen Header-Wert verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erster Versuch war von zu Hause aus, also mit langsamerer Internetverbindung. Ich habe hier nur mit einem Thread getestet. Mit dem Hetzner-Server erreiche ich durchschnittlich einen Throughput von 3.6 Requests pro Sekunde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/home_hetzner.png&quot; alt=&quot;Hetzner Home&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Inhouse-Variante erreiche ich nur lausige 1.9 Requests pro Sekunde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/home_aio.png&quot; alt=&quot;AIO Home&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Gegenprobe habe ich das Gleiche mit Objectstorage von Exoscale probiert. Dort erreiche ich sehr ähnliche Werte wie mit dem Hetzner-Rechner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und nun noch im Büro mit schnellerer Internetverbindung und mit mehreren Threads (1, 2, 4 und 8). Mit Hetzner erreiche ich durchschnittlich 18.8 Requests pro Sekunde mit einem Maximum von fast 40 Requests pro Sekunde bei 8 Threads:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/office_hetzner.png&quot; alt=&quot;Hetzner Office&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/office_hetzner_throughput.png&quot; alt=&quot;Hetzner Office Throughput&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Inhouse-Variante sieht es düster aus. Im Durchschnitt 3.1 Requests pro Sekunde und ein Maximum von knapp 5 Requests pro Sekunde. Mit einem Thread erreiche ich ziemlich exakt den gleichen Throughput wie von zu Hause aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/office_aio.png&quot; alt=&quot;AIO Office&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p3/office_aio_throughput.png&quot; alt=&quot;AIO Office Throughput&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch hier die Gegenprobe mit S3 von Exoscale mit sehr ähnlichen (leicht besseren) Resultaten als mit Hetzner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo ist das Problem mit der Inhouse-Variante? Man weiss es nicht. Irgendwo im Netzwerk? WAF? Zu wenig Ressourcen dem Webserver zugewiesen? Zu langsames Filesystem? Frustrierend sind die vielen Meetings ohne Outcome. Es liegt ja auf dem Tisch, dass etwas faul ist im Staate Dänemark oder mindestens Solothurn und dass es offensichtlich mit dem 5 Euro-Rechner von Hetzner massiv schneller geht. Obwohl die Cloud Optimized GeoTIFF Sache sehr simpel erscheint und &amp;laquo;eigentlich&amp;raquo; auch ist, gibt es ein paar Stolpersteine auf dem Weg zur Glückseligkeit.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Oops! I accidentally made a report server</title>
      <link>http://blog.sogeo.services/blog/2024/04/02/reportingserver.html</link>
      <pubDate>Tue, 2 Apr 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/04/02/reportingserver.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Erstellen von sogenannten Objektblättern verwenden wir &lt;em&gt;JasperReports&lt;/em&gt;. Unter Objektblatt verstehen wir vor allem ein PDF mit Informationen zu einem &lt;a href=&quot;https://geo.so.ch/api/v1/document/kantonsgrenzsteine?feature=22460&amp;amp;x=2639079.084623869&amp;amp;y=1256177.19874576&amp;amp;crs=EPSG%3A2056&quot;&gt;einzelnen Objekt&lt;/a&gt; oder zu einem Standort. Standardmässig werden die Feature-ID, die Koordinaten und das Koordinatensystem dem Report-Server übermittelt. Es können aber beliebige Parameter dem Jasper-Report übermittelt werden. Diese dienen meistens der Filterung via SQL-Query. Immer häufiger kommt vor, dass neben Objektblättern &lt;a href=&quot;https://geo.so.ch/api/v1/document/arp_uebersicht_massnahmen_agglomerationsprogramm.xlsx?feature=382121&amp;amp;x=2606750.801906189&amp;amp;y=1228100.4484159572&amp;amp;crs=EPSG%3A2056&quot;&gt;(Excel-)Listen&lt;/a&gt; gewünscht werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum gefällt uns die jetzige Situation nicht?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der eigentliche Server, den wir einsetzen, ist nicht JasperReports-Server, sondern wurde im Rahmen des &amp;laquo;neuen&amp;raquo; Web GIS Clients auf Basis der JasperReports-Bibliothek und &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; durch den Lieferanten 2017/2018 selber entwickelt. Ein Update hat der Server nie erfahren. Sowohl bei der JasperReports-Bibliothek als auch bei &lt;em&gt;Spring Boot&lt;/em&gt; (Version 1.5&amp;#8230;&amp;#8203;) nicht. Funktional gibt es ebenfalls einige Unschönheiten. So sind maximal Verbindungen zu fünf verschiedenen Datenbanken möglich und dann war da noch was mit Jasper-Subreports, was nicht funktionierte. Richtig Spass macht die Arbeit mit &lt;em&gt;JasperReports&lt;/em&gt; auch nicht. Das Studio ist so ein GUI-Moloch und nicht intuitiv. Zwar beherrscht &lt;em&gt;JasperReports&lt;/em&gt; viele Ausgabeformate (PDF, DOCX, XLSX, HTML) aber richtig brauchbar dünkt mich nur die PDF-Augabe. Bereits die XLSX-Ausgabe ist ein Gefrickel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So richtig zufrieden waren wir also nie. Ab und wann schaut man über den Gartenzaun und entdeckt, dass die kantonale Informatik auch einen Report-Server im Angebot haben: &lt;a href=&quot;https://www.dox42.com/&quot;&gt;&lt;em&gt;dox42&lt;/em&gt;&lt;/a&gt;. Für ein Thema setzen wir nun auf dieses Pferd: Der Klick in die Karte löst eine SQL-Query aus und die Resultate werden mittels &lt;a href=&quot;https://dox42.so.ch/dox42restservice.ashx?Operation=GenerateDocument&amp;amp;ReturnAction.Format=pdf&amp;amp;DocTemplate=c%3a%5cdox42Server%5ctemplates%5cAFU%5cEWS_moeglich.docx&amp;amp;InputParam.p_koordinate_x=2607907&amp;amp;InputParam.p_koordinate_y=1228277&amp;amp;InputParam.p_grundstueck=1125%20(Solothurn)&amp;amp;InputParam.p_gemeinde=Solothurn&amp;amp;InputParam.p_tiefe=100&amp;amp;InputParam.p_tiefe_gruende=Instabiler_UG&amp;amp;InputParam.p_gw=true&quot;&gt;GET-Request&lt;/a&gt; an den dox42-Server geschickt. Das Gute daran ist, dass wir es nicht selber betreiben müssen und dass man die Report-Templates direkt in Word oder Excel machen kann. Ein heikler Punkt sind halt die Organisationsgrenzen. Für uns ist es sehr wichtig, dass wir durcharbeiten können. Wir können es uns nicht leisten, dass der Sachbearbeiter, der das Thema / das Projekt umsetzt, ein Ticket machen muss, um irgendein Detail am Report zu ändern und/oder um diesen in die Produktion zu deployen. Ein No-Go.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesen Rahmenbedingungen haben wir uns entschlossen ein Projekt zur Ablösung oder Weiterentwicklung unseres Report-Servers zu machen. An einer Brainstorming-Sitzung kam plötzlich die Frage auf, wie der &lt;a href=&quot;https://geo.so.ch/api/oereb/extract/pdf/?EGRID=CH857632820629&quot;&gt;ÖREB-Katasterauszug&lt;/a&gt; gemacht wird und warum man das nicht für alle Reports so machen will/kann. Unser ÖREB-Katasterauszug wird mit &lt;a href=&quot;https://blog.sogeo.services/blog/2018/12/31/xslt-xslfo-2-pdf4oereb.html&quot;&gt;XSL-FO&lt;/a&gt; aus dem ÖREB-XML-Auszug hergestellt. Und das macht er tatsächlich &lt;a href=&quot;https://monitoring.oereb.services/detail.xhtml?identifier=SO&amp;amp;probe=extract&quot;&gt;gut&lt;/a&gt; und stabil. Rein funktional betrachtet, dürfte damit wohl fast jede Anforderung an ein Objektblatt abgedeckt sein (nicht die Excelliste). Als Input benötigt man jedoch eine XML-Datei, deren Schema beim ÖREB-Kataster spezifiert ist. Diesen Weg (ein XML-Schema definieren), möchte man kaum  für jedes Thema beschreiten, weil aufwändig und schlichtweg nicht nötig. Die XML-Datei wäre ja nur dazu da, die PDF-Datei herzustellen und es bliebe alles in der gleichen Organisation und niemand anderes muss sich auf die Struktur der XML-Datei verlassen können. Es reduziert sich nun auf die Frage: Kann man eine XML-Datei praktikabel herstellen? Ja. Weil die Informationen zum Objektblatt sowieso alle in einer Datenbank vorliegen, kann mir die &lt;a href=&quot;https://www.postgresql.org/docs/16/functions-xml.html&quot;&gt;Datenbank&lt;/a&gt; auch das XML herstellen und ich brauche dazu &lt;a href=&quot;https://blog.jooq.org/stop-mapping-stuff-in-your-middleware-use-sqls-xml-or-json-operators-instead/&quot;&gt;keine Middleware&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Deal ist der folgende: Es gibt pro Objektblatt eine &lt;a href=&quot;https://github.com/edigonzales/dox43/blob/3a93a81/src/main/resources/grundstuecksbeschrieb/grundstuecksbeschrieb.ini&quot;&gt;ini-Datei&lt;/a&gt;. In dieser steht momentan nur die Datenbank-ID, damit die Anwendung weiss, an welche Datenbank sie die SQL-Query aus der &lt;a href=&quot;https://github.com/edigonzales/dox43/blob/3a93a81/src/main/resources/grundstuecksbeschrieb/grundstuecksbeschrieb.sql&quot;&gt;zweiten Datei&lt;/a&gt; schicken muss. Die Query kann von aussen mit beliebigen Parametern (aus GET- und/oder POST-Request) alimentiert werden. Die Query liefert einen String zurück, der gültiges XML ist. Wichtig dabei ist, dass nur genau ein einziger Record mit einer Spalte zurückgeliefert wird. Diese temporäre XML-Datei wird dann mittels der XSL-Transformation aus der &lt;a href=&quot;https://github.com/edigonzales/dox43/blob/3a93a81/src/main/resources/grundstuecksbeschrieb/grundstuecksbeschrieb.xsl&quot;&gt;dritten Datei&lt;/a&gt; zu einem XSL-FO-Dokument und dieses zu einem PDF gemacht. Fertig. Zum vielleicht besseren Verständnis das Ablaufschema visualisiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/reportingserver/ablauf.png&quot; alt=&quot;ablauf&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Aufruf bleibt im Prinzip gleich wie bei der bestehenden Lösung: &lt;code&gt;&lt;a href=&quot;http://localhost:8080/reports/grundstuecksbeschrieb?feature=22460&amp;amp;x=2639079&amp;amp;y=1256177&amp;amp;format=pdf&quot; class=&quot;bare&quot;&gt;http://localhost:8080/reports/grundstuecksbeschrieb?feature=22460&amp;amp;x=2639079&amp;amp;y=1256177&amp;amp;format=pdf&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Tolle an dieser Lösung dünkt mich, dass man die Schritte sehr bequem einzeln entwickeln kann. So kann ich mir zuerst ein Dummy-XML händisch machen und damit die XSL-Transformation entwickeln. Oder ich sehe es als Zielstruktur meiner SQL-Query und kümmere mich zuerst um diese. GUI braucht es dazu auch keines. Ebenfalls habe ich das Gefühl, dass die Lösung transparenter ist. Bei den &lt;em&gt;JasperReports&lt;/em&gt; passiert das SQL irgendwo unübersichtlich in den Reports (=Debugging-Hölle). Bei meiner Lösung sammle ich die Daten zuerst und übergebe sie dem Arbeitsschritt, der die Daten rendern soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Resultate können sich sehen lassen. Als Beispiel habe ich das Erdwärmesondestandortblatt umgesetzt. Die XML-Datei, die ich mittels SQL-Query herstellen muss, ist simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&amp;gt;
&amp;lt;ewsstandort&amp;gt;
  &amp;lt;gbnr&amp;gt;3317&amp;lt;/gbnr&amp;gt;
  &amp;lt;grundbuch&amp;gt;Solothurn&amp;lt;/grundbuch&amp;gt;
  &amp;lt;gemeinde&amp;gt;Solothurn&amp;lt;/gemeinde&amp;gt;
  &amp;lt;easting&amp;gt;2605775&amp;lt;/easting&amp;gt;
  &amp;lt;northing&amp;gt;1228417&amp;lt;/northing&amp;gt;
  &amp;lt;tiefe&amp;gt;100&amp;lt;/tiefe&amp;gt;
  &amp;lt;tiefe_grund&amp;gt;Instabiler_UG&amp;lt;/tiefe_grund&amp;gt;
  &amp;lt;grundwasser&amp;gt;true&amp;lt;/grundwasser&amp;gt;
  &amp;lt;getmap&amp;gt;https://geo.so.ch/ows/somap?SERVICE=WMS&amp;amp;amp;VERSION=1.3.0&amp;amp;amp;REQUEST=GetMap&amp;amp;amp;FORMAT=image%2Fpng&amp;amp;amp;TRANSPARENT=false&amp;amp;amp;LAYERS=ch.so.agi.hintergrundkarte_ortho,ch.so.agi.av.grundstuecke&amp;amp;amp;STYLES=&amp;amp;amp;SRS=EPSG%3A2056&amp;amp;amp;CRS=EPSG%3A2056&amp;amp;amp;TILED=false&amp;amp;amp;OPACITIES=255&amp;amp;amp;DPI=96&amp;amp;amp;WIDTH=600&amp;amp;amp;HEIGHT=480&amp;amp;amp;BBOX=2607821.625%2C1228212.5%2C2607980.375%2C1228339.5&amp;amp;amp;MARKER=X-%3E2607901%7CY-%3E1228276&amp;lt;/getmap&amp;gt;
&amp;lt;/ewsstandort&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apache FOP macht übrigens aus dem GetMap-Request tatsächlich ein Bild im PDF:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/reportingserver/ews01.png&quot; alt=&quot;erdwärmesonden standortblatt seite 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/reportingserver/ews02.png&quot; alt=&quot;erdwärmesonden standortblatt seite 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also: PDF ist gegessen. Was ist jedoch mit unseren Excellisten? Da verfolge ich in Teilen den gleichen Ansatz. Mit dem Unterschied, dass ich hier mit einer Excel-Template-Datei arbeiten muss. Mit &lt;a href=&quot;https://jxls.sourceforge.net/&quot;&gt;&lt;em&gt;Jxls&lt;/em&gt;&lt;/a&gt; gibt es eine gute Excel-Templating-Engine. D.h. man schreibt in der Excel-Datei, die später als Template dienen soll, gewisse Anweisungen in die Zellen und als Notiz zu Zellen. Vom Wesen her nicht ganz unterschiedlich zu z.B. Jinja2-Templates. Im Gegensatz zum Objektblatt interessiert mich beim Listenreport in der Regel mehr als bloss ein Objekt. Entsprechend darf die &lt;a href=&quot;https://github.com/edigonzales/dox43/blob/3a93a81/src/main/resources/avmeldewesen/avmeldewesen-grundstuecke.sql&quot;&gt;SQL-Query&lt;/a&gt; auch mehr als einen Record und eine Spalte zurückliefern. Es werden mehrere SQL-Queries (eine pro Datei) für ein Listenreport unterstützt, damit z.B. mehrere Sheets abgefüllt werden können:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/reportingserver/av01.png&quot; alt=&quot;av meldewesen sheet 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/reportingserver/av02.png&quot; alt=&quot;av meldewesen sheet 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ob man mit &lt;em&gt;Jxls&lt;/em&gt; elaborierte Exellisten (Grafiken o.ä.) erstellen kann, weiss ich anhand meines ersten Gehversuches noch nicht. Unser Projekt wird zeigen müssen, ob unsere Anforderungen mit diesen beiden Ansätzen plusminus erfüllt werden können.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>DuckDB - Lizenzgebühren zu hoch? Try DuckDB!</title>
      <link>http://blog.sogeo.services/blog/2024/01/28/duckdb-lizenzgebuehren.html</link>
      <pubDate>Sun, 28 Jan 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/01/28/duckdb-lizenzgebuehren.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus aktuellem Anlass zwei anschauliche Beispiele warum &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt; eine tolle Sache ist. Im Grundsatz geht es um sogenannte &amp;laquo;federated queries&amp;raquo;, also die Fähigkeit, Daten von mehreren, verteilten oder heterogenen Datenquellen abzufragen / zu analysieren / umzubauen und so zu tun, als wäre es eine einzige einheitliche Quelle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Beispiel 1&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich möchte an einer bestimmten Koordinate den Zonentyp kennen, die Gemeindenummer und den Steuerfuss der Gemeinde. Gut, das Beispiel ist brutal gefaked, aber egal. Gehen wir davon aus, dass die Nutzungsplanung in der &lt;em&gt;DuckDB&lt;/em&gt; vorliegt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2duckdb-5.1.1-SNAPSHOT.jar --dbfile arp_npl_2549.duckdb \
--disableValidation --defaultSrsCode 2056 --nameByTopic \
--models SO_ARP_Nutzungsplanung_Publikation_20201005 \
--preScript init.sql --doSchemaImport \
--import ilidata:2549.ch.so.arp.nutzungsplanung.kommunal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gentle Reminder, dass die ilidata-Unterstützung in allen ili2db-Varianten ein &lt;a href=&quot;https://blog.sogeo.services/blog/2023/05/10/interlis-leicht-gemacht-number-35.html&quot;&gt;geniales Feature&lt;/a&gt; ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Gemeindegrenzen sind in einer PostgreSQL-Datenbank gespeichert. Die Steuerfüsse liegen in einer Parquet-Datei auf S3 vor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die PostgreSQL-Datenbank müssen wir in der &lt;em&gt;DuckDB&lt;/em&gt; registrieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;ATTACH &apos;dbname=pub user=ddluser password=ddluser host=127.0.0.1 port=54322&apos; AS pg (TYPE postgres);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und dann geht es bereits los mit goold old SQL:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    npl.typ_bezeichnung,
    npl.bfs_nr,
    gem.gemeindename,
    taxes.steuerfuss_in_prozent
FROM
    nutzungsplanung_grundnutzung AS npl
    LEFT JOIN pg.agi_hoheitsgrenzen.hoheitsgrenzen_gemeindegrenze AS gem
    ON npl.bfs_nr = gem.bfs_gemeindenummer
    LEFT JOIN &apos;https://s3.myregion.amazonaws.com/xxxxxxxxx/ch.so.agem.steuerfuesse.natuerliche_personen.parquet&apos; AS taxes
    ON gem.gemeindename = taxes.gemeinde
WHERE
    ST_Intersects(ST_Point(2611477,1233990), npl.geometrie)
    AND
    taxes.jahr = 2022
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann das Resultat auch als DuckDB-Tabelle persistieren oder eine View erstellen: &lt;code&gt;CREATE VIEW v1 AS &amp;#8230;&amp;#8203;&lt;/code&gt;. Das Resultat als Excel-Datei exportieren, ist auch nur ein &lt;code&gt;COPY&lt;/code&gt;-Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;COPY (SELECT * FROM v1) TO &apos;output.xlsx&apos; WITH (FORMAT GDAL, DRIVER &apos;xlsx&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Beispiel 2&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieses Beispiel ist tatsächlich ein realer Usecase. Jedoch kann ich ihn in der Realität noch nicht bis zum bitteren Ende durchspielen. Das bittere Ende ist das Exportformat: &lt;a href=&quot;http://giswiki.org/wiki/Generate&quot;&gt;Arc GENERATE&lt;/a&gt;. WTF!? Wahrscheinlich von Jack Dangermond persönlich in Assembler geschrieben. Ein Müsterchen gefällig:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;OBJECTID	FLAECHE	DK_HABS	DK_GEBTYP	DK_GEBNUTZDK_EMPFST	DK_OBERFL	BESCHAEFT	OIDDS	HOEHE
478362209		1	1		501			7.10
2611228.895	1227828.112	7.10
2611230.401	1227836.795	7.10
2611242.775	1227834.644	7.10
2611240.585	1227822.074	7.10
2611240.642	1227822.064	7.10
2611240.619	1227821.936	7.10
2611232.357	1227823.369	7.10
2611233.055	1227827.390	7.10
2611228.895	1227828.112	7.10
END&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Scheint mir aber sehr simpel zu sein. Mir fehlt nur der passende &lt;a href=&quot;https://github.com/claeis/iox-wkf&quot;&gt;IoxWriter&lt;/a&gt;, den ich dann in &lt;a href=&quot;https://gretl.app&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; ansteuern kann und Daten via SQL exportieren kann. Womit wir wieder bei SQL angekommen sind. PostGIS kann auch Raster und kann auch Raster mit cloud optimized geotiff. Das hat den Vorteil, dass die Datenbank nicht ins Unermessliche wächst. Für meinen vorliegenden Fall benötige ich das Geotiff mit den Gebäudehöhen (abgleitet aus LiDAR-Daten). Das &lt;a href=&quot;https://stac.sogeo.services/files/raster/ch.so.agi.lidar_2019.ndsm_buildings.tif&quot;&gt;GeoTIFF&lt;/a&gt; ist 2.1GB gross. Wenn ich die Daten aber nicht komplett in die DB importiere, ist die &amp;laquo;Raster-&amp;raquo;Tabelle bloss 67MB gross.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ich benötige ist der Grundriss eines Gebäude aus der amtlichen Vermessung und die durchschnittliche Gebäudehöhe. Also ein Verschnitt zwischen Vektor und Raster. In PostgreSQL sieht das z.B. so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH buildings AS
(
    SELECT
        t_id,
        geometrie AS geometrie
    FROM
        agi_dm01avsoch24.bodenbedeckung_boflaeche
    WHERE
        art = &apos;Gebaeude&apos;
    AND
        ST_Intersects(ST_SetSRID(ST_MakePoint(2611306, 1228084), 2056), geometrie)
)
,
clipped_tiles AS
(
  SELECT
    ST_Clip(elev.rast, buildings.geometrie) AS rast,
    elev.rid,
    buildings.t_id
  FROM
    agi_lidar_2019_ndsm.buildings AS elev
  JOIN
    buildings
    ON ST_Intersects(buildings.geometrie, ST_ConvexHull(elev.rast))
)
,
stats AS
(
    SELECT
        t_id,
        (ST_SummaryStatsAgg(rast, 1, true)).*
    FROM
        clipped_tiles
    GROUP BY
        t_id
)
SELECT
    buildings.t_id,
    buildings.geometrie,
    stats.mean AS hoehe
FROM
    buildings
    LEFT JOIN stats
    ON buildings.t_id = stats.t_id
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und mit &lt;em&gt;DuckDB&lt;/em&gt;? &lt;em&gt;DuckDB&lt;/em&gt; kann auch Queries direkt in der attachten PostgreSQL-Datenbank ausführen, z.B.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT * FROM postgres_query(&apos;pg&apos;, &apos;SELECT postgis_full_version()&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anstelle &lt;code&gt;SELECT postgis_full_version()&lt;/code&gt; muss ich meine CTE von oben ausführen. Ich erstelle ein View und exportiere sie in eine Shapedatei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;CREATE VIEW v2 AS
SELECT
    *
FROM
    postgres_query(&apos;pg&apos;, &apos;...&apos;)
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;COPY (SELECT * EXCLUDE geometrie, ST_GeomFromWkb(geometrie) FROM v2) TO &apos;gebaeudehoehen.shp&apos; WITH (FORMAT GDAL, DRIVER &apos;ESRI Shapefile&apos;, SRS &apos;EPSG:2056&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beweis in QGIS:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/duckdb-lizenzgebuehren/qgis-gebaeudehoehen.png&quot; alt=&quot;qgis-gebaeudehoehen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das Beste: &lt;em&gt;DuckDB&lt;/em&gt; ist &lt;a href=&quot;https://github.com/duckdb/duckdb&quot;&gt;Open Source&lt;/a&gt; und man bezahlt keine Lizenzgebühren.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #40 - Let there be ili2duckdb</title>
      <link>http://blog.sogeo.services/blog/2024/01/15/interlis-leicht-gemacht-number-40.html</link>
      <pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2024/01/15/interlis-leicht-gemacht-number-40.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gleich ein zweifaches Jubiläum: 40. INTERLIS-Blogpost und &lt;a href=&quot;https://blog.sogeo.services/blog/2014/01/03/smoothe-hoehenkurven.html&quot;&gt;10 Jahre Blogging&lt;/a&gt; (Neudeutsch: Content Creation). Und ja, immer noch mit &lt;a href=&quot;https://blog.sogeo.services/feed.xml&quot;&gt;RSS-Feed&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich &lt;a href=&quot;https://blog.sogeo.services/blog/2023/12/30/statuscode-206-duckdbundparquet.html&quot;&gt;mag&lt;/a&gt; &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt;. Und weil es wirklich eine coole Sache ist und die Möglichkeiten, was man damit anstellen kann, beinahe grenzenlos sind, muss natürlich ili2duckdb her. Ganz generell lässt sich behaupten: Wo ein &lt;a href=&quot;https://de.wikipedia.org/wiki/Java_Database_Connectivity&quot;&gt;JDBC-Treiber&lt;/a&gt;, da ein ili2db-Flavor. &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;Ili2db&lt;/em&gt;&lt;/a&gt; ist so aufgebaut, dass man relativ einfach eine neue Datenbankvariante hinzufügen kann. Voraussetzung ist eben das Vorhandensein eines &lt;a href=&quot;https://duckdb.org/docs/api/java&quot;&gt;JDBC-Treibers&lt;/a&gt; und das Schreiben einiger Java-Klassen. In den Java-Klassen muss man vendor-spezifisches Zeugs regeln. Also z.B. wie die Datentypen der spezifischen Datenbank heissen: Ist es &lt;code&gt;BIGINT&lt;/code&gt; oder &lt;code&gt;INT8&lt;/code&gt;? Oder wie muss ein Datum formatiert sein, damit ich es mit einem Insert-Befehl importieren kann? Solche Geschichten halt. Da es bereits verschiedenste Flavor gibt, konnte ich viel spicken resp. vor allem verstehen, welche Klassen und Methoden welche Bedeutung haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein &lt;a href=&quot;https://github.com/edigonzales/ili2db/commit/6c94c3853c64ac9db313902378a6fa340824e097&quot;&gt;initialer Commit&lt;/a&gt; habe ich gemacht. Die Arbeit war soweit ziemlich straight forward aber trotzdem lehrreich: Man lernt &lt;em&gt;ili2db&lt;/em&gt;, &lt;em&gt;DuckDB&lt;/em&gt; und die Fähigkeiten des JDBC-Treibers besser kennen. Im Commit fehlen unter anderem noch Tests. Ich habe &lt;em&gt;ili2duckdb&lt;/em&gt; nur manuell, also mit eigenen XTF, getestet. Was sind meine Erkenntnisse, was ist der Stand?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;JDBC-Treiber:&lt;/strong&gt;
Es handelt sich um einen sogenannten Typ-2-Treiber. D.h. neben Java-Code braucht es eine zusätzliche Bibliothek, die jedoch &amp;laquo;versteckt&amp;raquo; in der Jar-Datei ist. Die Bibliothek ist in der Regel betriebssystem-spezifisch, was bedeutet, dass der JDBC-Treiber nur auf kompatiblen Betriebssytemen funktioniert. Sowohl als Programmierer wie auch als Anwendert merkt man das in den allermeisten Fällen gar nicht. Die DuckDB-Lib läuft auf Linux, macOS und Windows. Soweit alles kein Problem. Das gleiche gilt übrigens auch für &lt;em&gt;ili2gpkg&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;JDBC-Test-Code:&lt;/strong&gt;
Damit man überhaupt versteht, was der JDBC-Treiber kann, lohnte es sich für mich eine eigene Java-Klasse zum Rumspielen zu haben, die nur Inserts und Selects macht. Die ganze ili2db-Magie gibt es da nicht und ich gewann Klarheit warum ein Fehler auftritt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Dateigrösse:&lt;/strong&gt;
Momentan dünken mich die resultierenden DuckDB-Dateien gross. Ein Testdatensatz als GeoPackage ist 220KB gross, die DuckDB-Variante 4.2MB. Ob ich noch was besser machen kann, oder das eher auf Seiten DuckDB zu suchen ist, weiss ich nicht. Sie schwärmen jedenfalls von ihrer &lt;a href=&quot;https://duckdb.org/2022/10/28/lightweight-compression.html&quot;&gt;Compression&lt;/a&gt;. Was verlinkter Beitrag nicht berücksichtigt, sind die Geometrien. Soweit für mich nachvollziehbar, sind die Geometrien in DuckDB nicht komprimiert. Vielleicht liegt es alleine daran.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;init.sql:&lt;/strong&gt;
Die spatial skills von &lt;em&gt;DuckDB&lt;/em&gt; sind in eine &lt;a href=&quot;https://duckdb.org/docs/extensions/spatial&quot;&gt;Extension&lt;/a&gt; ausgelagert. Diese Extension muss man installieren und laden. Das Installieren muss nur einmalig gemacht werden und &lt;em&gt;ili2db&lt;/em&gt; bietet dazu Möglichkeiten. Beim Laden wirds komplizierter. Dies muss - soweit ich es verstehe - immer gemacht werden, wenn eine neue DB-Verbindung gemacht wird. Nun ist es so, dass &lt;em&gt;ili2db&lt;/em&gt; nicht alles mit der gleichen Verbindung macht. D.h. nach dem Installieren der Extension wird die DB-Verbindung geschlossen, das Laden bringt hier nichts (mehr). Es muss also ein Weg gefunden werden, wie beim Anlegen der Tabellen oder beim Importieren der Daten die Extension mit der gleichen Verbindung, die auch für die nachfolgenden SQL-Befehle verwendet wird, vorgängig geladen werden kann. Mir ist nur die Variante mit der &lt;code&gt;--preScript&lt;/code&gt;-Option eingefallen. Aber vielleicht hat &lt;em&gt;ili2db&lt;/em&gt; noch irgendwo was anderes, wo man sich einhaken kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;ST_GeomFromWKB:&lt;/strong&gt;
&lt;em&gt;DuckDB&lt;/em&gt; kennt die Funktion &lt;code&gt;ST_GeomFromWKB(BLOB)&lt;/code&gt; und so dachte ich, dass man das identisch wie bei &lt;em&gt;PostGIS&lt;/em&gt; machen kann: &lt;em&gt;Ili2db&lt;/em&gt; parst das XTF und wandelt die Geometrie nach WKB um und übergibt die WKB-Geometrie anschliessend dem SQL-Insert-Statement. Nur hat das - warum auch immer - nicht funktioniert. Abhilfe schafft die Verwendung von &lt;code&gt;ST_GeomFromHEXWKB()&lt;/code&gt; und die vorgängige Umwandlung in einen HEX-String.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;ADD CONSTRAINT:&lt;/strong&gt;
&lt;em&gt;Ili2db&lt;/em&gt; erzeugt zuerst die Tabelle und fügt anschliessend mit &lt;code&gt;ADD CONSTRAINT&lt;/code&gt;-Befehlen die Constraints hinzu. Da &lt;em&gt;DuckDB&lt;/em&gt; diese Synax nicht kennt, sondern man Constraints nur beim Anlegen der Tabellen erzeugen kann, gibt es momentan keine Constraints.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Geometrie-Support:&lt;/strong&gt;
Ja, der Geometrie-Support in &lt;em&gt;DuckDB&lt;/em&gt; ist schon nicht ganz auf PostGIS-Niveau. So gehen M- und Z-Werte nicht und SRID werden auch keine unterstützt. Compound Curves (Kreisbogen) habe ich schon gar nicht probiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat kann sich sehen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p40/ili2duckdb.png&quot; alt=&quot;ili2duckdb&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;More to come &lt;a href=&quot;https://www.youtube.com/watch?v=8BX70FI7hfI&quot;&gt;&amp;#8230;&amp;#8203;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>HTTP-Statuscode 206 - Parquet und DuckDB</title>
      <link>http://blog.sogeo.services/blog/2023/12/30/statuscode-206-duckdbundparquet.html</link>
      <pubDate>Sat, 30 Dec 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/12/30/statuscode-206-duckdbundparquet.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie im vorangegangen &lt;a href=&quot;http://blog.sogeo.services/blog/2023/12/29/statuscode-206-letsgetstarted.html&quot;&gt;Blogpost&lt;/a&gt; aufgezeigt, wird WFS dank Cloud Native Formate für gewisse Anwendungsfällte ziemlich überflüssig. Die Frage ist, ob z.B. &lt;a href=&quot;https://geoparquet.org/&quot;&gt;GeoParquet&lt;/a&gt; auch für Realtime-Datenanalysen, im einfachsten Fall für Filterabfragen, geeignet ist. Unter Filterabfragen verstehe ich sowas wie ein WMS-GetFeatureInfo-Request und/oder ein klassischer GIS-Nadelstich für Fachanwendungen mittels WFS/Featureservice. Als Abfragesprache eignet sich SQL und als Engine dazu &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt;. Gehen tut es natürlich schon, die Frage ist, ob es performant genug ist, ohne die GeoParquet-Datei lokal vorzuhalten. Weil (Geo)Parquet und &lt;em&gt;DuckDB&lt;/em&gt; zusammen irgendwie Magie ist und momentan die Antwort auf fast jede Frage, erhoffte ich mir in brutaler Naivität natürlich eine ansprechende Performance. Dank guter &lt;a href=&quot;https://duckdb.org/docs/extensions/spatial&quot;&gt;Dokumentation&lt;/a&gt; hat man die Syntax für die spezifisichen Geokniffe schnell heraus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    typ_kt
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v.parquet&apos; AS grundnutzung
WHERE
    ST_Intersects(ST_Point(2596651,1226670), ST_GeomFromWkb(grundnutzung.geometry))
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Abfrage dauert circa entäuschende 7 Sekunden. Das entspricht mehr oder weniger der Downloadzeit der gesamten Parquet-Datei, was tatsächlich - gemäss Logdatei des Webservers - auch gemacht wurde: &lt;code&gt;Content-Range=[ &quot;bytes 4-63990494/64006068&quot; ]&lt;/code&gt;. Nach professioneller Recherche (also Ausprobieren bis die Ohren wackeln) &lt;em&gt;glaube&lt;/em&gt; ich langsam ein gewisses Wissen davon zu haben, was hinter den Kulissen abgeht (vor allem auch Dank &lt;a href=&quot;https://medium.com/radiant-earth-insights/the-admin-partitioned-geoparquet-distribution-59f0ca1c6d96&quot;&gt;Chris Holmes Beitrag&lt;/a&gt;) und wo man dran schrauben kann resp. muss, damit das schneller geht. Ob es dann tatsächlich einmal gleich schnell wie eine GetFeatureInfo-Abfrage wird, wage ich heute zu bezweiflen. Die GetFeatureInfo-Abfrage dauert bei uns für Grundnutzung und Grundstücke jeweils circa 200ms. Gemeinsam in einem Request circa 300ms. Es sind einfach andere Prinzipien mit anderen Rahmenbedingungen. Aber der Reihe nach. Am einfachsten bekommt man vielleicht ein Verständnis wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was passiert, wenn ich einen einzelnen Record mit einer WHERE-Clause anfordere?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    t_ili_tid
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v.parquet&apos; AS grundnutzung
WHERE
    t_ili_tid = &apos;e9732511-dbe5-4625-98e1-535c47793fb8&apos;
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Query dauert circa 0.5 Sekunden. Im Webserver-Log sehe ich vier Einträge. Der erste ist ein HEAD-Request. Die drei folgenden sind Range-Requests, wobei auf den ersten Blick nur der letzte ins Gewicht fällt. Sowohl bei der Antwortzeit wie auch bei der Datenmenge. Wie sieht es aus, wenn ich noch ein weiteres (Sach-)Attribut anfordere:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    t_ili_tid,
    typ_kt
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v.parquet&apos; AS grundnutzung
WHERE
    t_ili_tid = &apos;e9732511-dbe5-4625-98e1-535c47793fb8&apos;
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Query wird &lt;em&gt;nicht&lt;/em&gt; spürbar langsamer, im Logfile sieht man jedoch einen zusätzlichen Range-Request. Es ist also relevant, wie viele und welche Attribute man in der Query anfordert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich nun noch das Geometrieattribut anfordere, dauert die Query aber wieder knapp 7 Sekunden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    t_ili_tid,
    geometry
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v.parquet&apos; AS grundnutzung
WHERE
    t_ili_tid = &apos;e9732511-dbe5-4625-98e1-535c47793fb8&apos;
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Parquet-Dateien sind in &lt;em&gt;row groups&lt;/em&gt; partitioniert. Gute Erklärung dazu in einem &lt;a href=&quot;https://duckdb.org/2021/06/25/querying-parquet.html&quot;&gt;DuckDB-Blogbeitrag&lt;/a&gt;. Es müssen also mindestens alle &lt;em&gt;row groups&lt;/em&gt; heruntergeladen werden, die meiner Query resp. den Filterkriterien entsprechen. Und so wie ich es mir nun zurecht gelegt habe, kann man innerhalb einer &lt;em&gt;row groups&lt;/em&gt; die Attribute mittels Range-Request ansprechen. Darum gibt es bei der zweiten und dritten Query einen weiteren Range-Request (für das zweite Attribut). Die vorangegangen Range-Requests dienen dem Herunterladen der Metadaten (also v.a. Statistiken zu den &lt;em&gt;row groups&lt;/em&gt;). Die Metadaten einer Parquet-Datei kann man in &lt;em&gt;DuckDB&lt;/em&gt; anschauen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    *
FROM
    parquet_metadata(&apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v.parquet&apos;)
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In meiner Parquet-Datei gibt es genau eine &lt;em&gt;row group&lt;/em&gt; (mit allen 37&apos;731 Records drin). Interessant sind die beiden letzten Spalten, die den Speicherplatzbedarf der Attributwerte innerhalb der &lt;em&gt;row group&lt;/em&gt; ausweist. Wenn ich mir die komprimierte Grösse des Attributes &lt;code&gt;t_ili_tid&lt;/code&gt; anschaue (1&apos;353&apos;462), entspricht das exakt der Grösse einer Antwort eines Range-Requests (für &lt;code&gt;t_ili_tid&lt;/code&gt;). Es müssen also zwingend bereits bei der einfachsten Query mit nur einem angeforderten Attribut 1.3MB Daten heruntergeladen werden. Wenn ich das Geometrieattribut anfordere, werden zusätzliche 61MB Daten runtergesaugt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun gibt es die Möglichkeit die Daten innerhalb der Parquet-Datei in mehrere &lt;a href=&quot;https://duckdb.org/docs/data/parquet/tips&quot;&gt;mehrere &lt;em&gt;row groups&lt;/em&gt;&lt;/a&gt; aufzuteilen (resp. die Menge der Records einer &lt;em&gt;row group&lt;/em&gt; zu definieren). Dann müsste nur noch die &lt;em&gt;row group&lt;/em&gt;, die meine Geometrie enthält, heruntergeladen werden. Gesagt, getan (Achtung: siehe &lt;code&gt;&amp;#8230;&amp;#8203;_bbox_v3.parquet&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    t_ili_tid,
    geometry
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v_bbox_v3.parquet&apos; AS grundnutzung
WHERE
    t_ili_tid = &apos;e9732511-dbe5-4625-98e1-535c47793fb8&apos;
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In dieser Datei-Variante gibt es 18 &lt;em&gt;row groups&lt;/em&gt; mit jeweils 2048 Records. Die Query dauert nicht mehr 7 Sekunden, sondern nur noch circa 900ms. Es werden aber 23 Requests losgeschickt und nicht mehr bloss 5 Requests wie mit der Original-Datei. &lt;em&gt;DuckDB&lt;/em&gt; muss nun anhand der Metadaten/Statistik die passenden &lt;em&gt;row groups&lt;/em&gt; finden. Was passiert wenn ich die allererste Query (den Nadelstich) mit dieser Datei ausführe? Es dauert genau gleich lang oder sogar noch ein klein wenig länger. Es werden 27 Requests abgesetzt und weil die Geometriespalte keine Statistiken ausweist, muss &lt;em&gt;DuckDB&lt;/em&gt; sämtliche Geometrie-&lt;em&gt;row groups&lt;/em&gt; herunterladen und die Intersects-Bedingung prüfen. Das ist Stand heute mit GeoParquet Version 1.0.0 einfach so. Auf der &lt;a href=&quot;https://github.com/opengeospatial/geoparquet&quot;&gt;Roadmap&lt;/a&gt; für Version 1.1.0 stehen glücklicherweise Dinge wie &amp;laquo;spatial optimization, spatial indices and spatial partitioning to improve performance reading spatial subsets&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann sich selber eine BBOX-Spalte basteln und diese in die WHERE-Clause miteinbeziehen. Das sind dann circa so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    t_ili_tid
FROM
    &apos;https://stac.sogeo.services/files/test/nutzungsplanung_grundnutzung_v_bbox_v3.parquet&apos; AS grundnutzung
WHERE
    bbox.minx &amp;lt; 2596651 AND bbox.maxx &amp;gt; 2596651 AND bbox.miny &amp;lt; 1226670 AND bbox.maxy &amp;gt; 1226670
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das performt solange man die Geometriespalte &lt;em&gt;nicht&lt;/em&gt; anfordert. Ohne Geometriespalte circa 700ms, mit Geometriespalte 7.5 Sekunden. Das Intersects brauche ich schon gar nicht mehr zu versuchen anzuhängen. Mit Geometriespalte sind es zwar &amp;laquo;bloss&amp;raquo; 24 Requests, die gemacht werden müssen aber es sind teilweise auch grössere Antworten dabei, die &lt;em&gt;DuckDB&lt;/em&gt; zuerst runterladen muss. Es wird somit auch ein Abwägen sein zwischen der Menge von &lt;em&gt;row groups&lt;/em&gt; und der Grösse der &lt;em&gt;row groups&lt;/em&gt;. Weniger &lt;em&gt;row groups&lt;/em&gt; bedeuten weniger Requests aber grössere Downloads (und umgekehrt). Und es ist natürlich ein Unterschied zwischen 2&apos;048 Punkten oder 2&apos;048 Flächen (mit tausenden Vertexpunkten) in einer &lt;em&gt;row group&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber genug des Jammerns: Wenn die Dateien lokal vorliegen, flutschen die Queries natürlich nur so und das Duo &lt;em&gt;DuckDB&lt;/em&gt; und GeoParquet wird noch viel Freude bereiten. Zu guter Letzt noch ein Hinweis auf ein nettes Goodie: das &lt;a href=&quot;https://duckdb.org/docs/data/partitioning/hive_partitioning&quot;&gt;&lt;em&gt;hive partitioning&lt;/em&gt;&lt;/a&gt;. Das funktioniert lokal oder mit S3. Grundsätzlich erlaubt es &lt;em&gt;DuckDB&lt;/em&gt; mehrere Parquet-Dateien als eine anzusprechen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    count(t_ili_tid)
FROM
    read_parquet(&apos;s3://xxxxxxxxxxxxx/ch.so.arp.nutzungsplanung.kommunal/*.parquet&apos;, hive_partitioning = 1)
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gilt die Annahme, dass für jede Gemeinde eine Datei vorliegt und im Verzeichnis/Bucket &lt;em&gt;ch.so.arp.nutzungsplanung.kommunal&lt;/em&gt; liegt. Hat man sehr viele (z.B ganze Schweiz oder so) Dateien, kann man das beschleunigen, indem man für jede Gemeinde einen Unterordner macht und diesen korrekt benennt: &lt;code&gt;bfs_nr=&amp;lt;xxxx&amp;gt;&lt;/code&gt;. Wenn man in einer Query nur noch an Daten einer spezifischen Gemeinde interessiert ist, sucht &lt;em&gt;DuckDB&lt;/em&gt; auch nur noch in diesem Unterordner (und muss nicht mehr sämtliche Dateien durchsuchen):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    count(t_ili_tid)
FROM
    read_parquet(&apos;s3://xxxxxxxxxxxxx/ch.so.arp.nutzungsplanung.kommunal/*/grundnutzung.parquet&apos;, hive_partitioning = 1)
WHERE
    bfs_nr = 2503
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>HTTP-Statuscode 206 - Let&apos;s get started</title>
      <link>http://blog.sogeo.services/blog/2023/12/29/statuscode-206-letsgetstarted.html</link>
      <pubDate>Fri, 29 Dec 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/12/29/statuscode-206-letsgetstarted.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Des GIS-Menschen liebster HTTP-Statuscode sollte 206 sein. Warum? 206 heisst Partial Content und bedeutet, dass der angeforderte Teil erfolgreich übertragen wurde. D.h. es wird nicht die komplette Ressource angefordert, sondern nur Teile davon. Der Client teilt dem Server den gewünschten Teil mittels Content-Range-Header mit. Im Geo-Bereich kann das interessant werden, um mit Daten zu arbeiten ohne sie vollständig (z.B. die 50GB grosse Rasterdatei) herunterladen zu müssen. Die Daten müssen jedoch gewisse Bedingungen erfüllen, damit die 206-Magie auch greifen kann. Sie müssen also &amp;laquo;cloud native&amp;raquo; oder &amp;laquo;cloud optimized&amp;raquo; sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes ist mir vor ein 1-2 Jahren das &lt;a href=&quot;https://www.cogeo.org/&quot;&gt;Cloud Optimized GeoTIFF&lt;/a&gt; begegnet. Damals ein wenig rumgespielt und wieder sein gelassen. Wie wahrscheinlich andere auch halten wir unsere Rasterdaten (Orthofotos, LiDAR-Derivate) in einzelnen Kacheln vor. Dazu eine VRT-Datei und für kleinere Massstäbe eine einzelne Übersichtsdatei. Das Handling ist immer ein wenig mühsam: Es sind teilweise mehrere tausend Dateien und man braucht zwingend die Übersichtsdatei für die kleineren Massstäbe. Ich denke, dass man bereits vor dem Cloud Optimized GeoTIFF einfach eine einzige grosse BigTIFF-Datei hätte herstellen können. Damit wäre zumindest unser Handling-Problem kleiner geworden. Andere Benefits hätten wir nicht gehabt. Insbesondere möchten unsere Kunden nicht wirklich 50GB runterladen, wenn sie bloss an einem Ausschnitt interessiert sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil das Cloud Optimized GeoTIFF aber wirklich ziemlich genial ist, habe ich nochmals einen Anlauf unternommen und aus unseren tausenden Rasterdaten mit &lt;a href=&quot;https://gdal.org&quot;&gt;&lt;em&gt;GDAL&lt;/em&gt;&lt;/a&gt; jeweils ein einzelnes GeoTIFF erzeugt. Seit geraumer Zeit gibt es sogar ein spezielles &lt;a href=&quot;https://gdal.org/drivers/raster/cog.html&quot;&gt;&lt;code&gt;COG&lt;/code&gt;-Profil&lt;/a&gt;. Damit ist die Herstellung kinderleicht. Es werden sogar die internen Overviews erstellt (falls gewünscht). Die resultierenden GeoTIFFs habe ich auf einen Hetzner-Server geschmissen: &lt;a href=&quot;https://stac.sogeo.services/files/raster/&quot;&gt;https://stac.sogeo.services/files/raster/&lt;/a&gt; und zugleich als &lt;a href=&quot;https://radiantearth.github.io/stac-browser/#/external/stac.sogeo.services/catalog.json?.language=en&quot;&gt;STAC-Katalog exponiert&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In QGIS kann man seit längerem GeoTIFFs auch mittels URL laden. Zum Ausprobieren lade ich so z.B das 13GB grosse Orthofoto &lt;a href=&quot;https://stac.sogeo.services/files/raster/ch.so.agi.orthofoto_2007.rgb.tif&quot;&gt;ch.so.agi.orthofoto_2007.rgb.tif&lt;/a&gt; in QGIS. Die Performance ist genial und insbesondere in einem Bereich, in dem bereits relativ viele einzelne Kacheln geladen werden müssen, schneller als der WMS (der immer noch das VRT mit den vielen einzelnen TIFFs verwendet). Mit einer schnellen Internetverbindung beinahe so schnell als würde die Datei lokal vorliegen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was passiert im Hintergrund? Dazu schaue ich mir die Logdatei des Webservers an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/statuscode_206_p1/log01.png&quot; alt=&quot;Logfile 206&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man sieht sehr schön den Statuscode (= 206), den Content-Range-Header und die ausgelieferte Grösse der Antwort. Es ist natürlich auch relativ performant, weil die Orthofotos ordentlich komprimiert sind. Wenn man &amp;laquo;richtige&amp;raquo; Rasterdaten (z.B. &lt;a href=&quot;https://stac.sogeo.services/files/raster/ch.bl.agi.lidar_2018.dtm_slope.tif&quot;&gt;ch.bl.agi.lidar_2018.dtm_slope.tif&lt;/a&gt;) verwendet, wird die Menge der herunterzuladenden Daten bei einer Teilanfrage grösser, weil man die Ausgangsdaten nicht gleich stark komprimieren kann wie das Orthofoto.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein wenig störend ist das erstmalige Laden der Datei. Das scheint lange zu dauern. In der Webserver-Logdatei sieht man viele 404er. QGIS (GDAL?) sucht nach verschiedenen Hilfs-/Metadateien (*.aux.xml etc.), die es nicht gibt. Bei mir sind das insgesamt 75 Requests ins Leere. Lokal spielt das weniger eine Rolle. Aber wenn 75 (ich kanns immer noch nicht ganz glauben) HTTP-GET-Requests gemacht werden müssen&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie sieht es mit Vektordaten aus? Eigentlich ziemlich ähnlich. Es gibt verschiedene Formate, die von sich behaupten (oder andere behaupten es) &amp;laquo;cloude native&amp;raquo; zu sein. Siehe Übersicht unter &lt;a href=&quot;https://guide.cloudnativegeo.org/&quot;&gt;cloudnativegeo.org&lt;/a&gt;. Mit &lt;code&gt;ogr2ogr&lt;/code&gt; habe ich unsere &lt;a href=&quot;https://data.geo.so.ch&quot;&gt;frei verfügbaren Vektordaten&lt;/a&gt; nach FlatGeobuf und GeoParquet umgewandelt und ebenfalls auf den &lt;a href=&quot;https://stac.sogeo.services/files&quot;&gt;Hetzner-Server&lt;/a&gt; kopiert und als &lt;a href=&quot;https://radiantearth.github.io/stac-browser/#/external/stac.sogeo.services/catalog.json?.language=en&quot;&gt;STAC-Katalog&lt;/a&gt; exponiert. Bei einigen, wenigen Umwandlungen aus der GeoPackage-Datei kam es zu Fehlern. Stand heute ist mir der Grund noch nicht klar. Analog zu Rasterdaten können in QGIS auch Vektordaten mittels URL geladen werden. In der Webserver-Logdatei erscheinen wieder die 206-Statuscodes. FlatGeobuf funktioniert bei mir im Gegensatz zu GeoParquet wunderbar. GeoParquet wird in QGIS unter macOS noch nicht unterstützt, unter Windows ist mir damit QGIS abgestürzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun muss man aber auf den grundlegenden Unterschied zu den Rasterdaten aufmerksam machen, der bei kleinen Dateigrössen und schneller Internetverbindung zuerst gar nicht auffällt. Im Gegensatz zum Cloud Optimized GeoTIFF haben weder FlatGeobuf noch GeoParquet Overviews. Das bedeutet, dass ich je nach Zoomstufe relativ viele Daten - sprich irgendeinmal alle - herunterladen muss. Abhilfe schafft nur ein Format, dass auch Overviews unterstützt, z.B. &lt;a href=&quot;https://github.com/protomaps/PMTiles&quot;&gt;PMTiles&lt;/a&gt;. Wobei mir nicht klar ist, ob das auch wieder zu verschnittenen Flächengeometrien (weil &amp;laquo;Tile&amp;raquo;) führt und wie man damit gut umgehen soll. Ich meine, wer will verschnittene Grundstücke?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei &amp;laquo;anständigen&amp;raquo; Zoomlevels ist die Performance von FlatGeobuf sehr gut. Insbesondere im Vergleich zu einer GeoPackage-Datei, die man ebenfalls als HTTP-Ressource in QGIS anzapfen kann. Zum Ausprobieren habe ich im &lt;a href=&quot;https://stac.sogeo.services/files/test/&quot;&gt;Test-Ordner&lt;/a&gt; sowohl die AV-Bodenbedeckung wie auch die Grundnutzung der Nutzungsplanung in verschiedenen Formaten abgelegt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was nun? Im Prinzip kann der WMS für Rasterdaten einpacken. Wobei es vielleicht doch nicht ganz so einfach ist. Es würde zwar zu einer Vereinfachung des technischen Systems führen (kein Server mehr, nur Webspace), aber vielleicht will man die Rasterdaten mit einem Defaultstyle anbieten, oder einen Layer (z.B. Landeskarte) aus verschiedenen einzelnen Layern? Und für den Endbenutzer ist es wohl einfacher (wenn es bloss um die Darstellung geht) alles an einem Ort (also dem WMS) abzuholen. Aber als Ergänzung muss man es allemal anbieten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der WFS als dumber Dateidownload hat ausgedient. Ich sehe hier keine Vorteile mehr zur Variante FlatGeobuf/GeoParquet mit STAC (und einfacher und brauchbarer Integration im  QGIS Data Source Manager). Wenn ich die gesamte Bodenbedeckung via WFS herunterlade, klemmt es garantiert irgendwo. Sei es die Last beim WFS selber oder dann irgendwo in QGIS. Als Kunde weiss ich ja nicht einmal, ob der WFS-Server alle angeforderten Features ausgeliefert hat oder bloss die serverseitig eingestellte, maximale Anzahl. Bissle broken by design. Darum lieber FlatGeobuf o.ä. mit STAC.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanter wird es bei gefilterten Requests, also z.B beim klassischen Nadelstich: Welche Grundnutzung gilt an der Koordinate X/Y? Wäre natürlich genial, wenn man auch hier auf einen Featureservice/WFS verzichten könnte und man bloss ein paar Parquet-Files bereitstellen müsste. Der Client könnte mit &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt; und SQL die Abfragen machen. Ob und wie das gehen könnte, gibt es in einem weiteren Blogpost zu lesen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #39 - &quot;Dear BFS, I fixed it for you.&quot;</title>
      <link>http://blog.sogeo.services/blog/2023/11/20/interlis-leicht-gemacht-number-39.html</link>
      <pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/11/20/interlis-leicht-gemacht-number-39.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie heisst es so schön: &amp;laquo;Wer den Schaden hat, braucht für den Spott nicht zu sorgen&amp;raquo;. Man weiss zwar (noch) nicht genau, wie das Wahl-Statistik-Debakel passiert ist, aber ein wenig frotzeln lässt sich schon. Die meisten Informationen stehen meines Erachtens in einem &lt;a href=&quot;https://www.nzz.ch/visuals/exceldateien-und-fehlerhafte-programme-eine-rekonstruktion-wie-es-zum-falschen-wahlergebnis-kam-ld.1763604&quot;&gt;NZZ-Artikel&lt;/a&gt;. Lassen wir mal die Microservices (&amp;laquo;Für jedes mögliche (kantonale) Datenformat existiert ein solches Programm.&amp;raquo;: Ojeeeee&amp;#8230;&amp;#8203;) beiseite und widmen uns den &amp;laquo;Datenlieferungen&amp;raquo; in Form einer Exceldatei. Ich weiss wirklich nicht, wie man auf eine solche Idee kommt. Aber Rohdaten (wenn man den Begriff verwenden darf) in einer Exceldatei als Transferformat? Ballsy! Ein weiterer Kritikpunkt ist die Datenstruktur: Nicht alles auf dieser Welt lässt sich als einfache Tabelle abbilden. Manchmal wäre ein normalisiertes Datenmodell definitiv die bessere Wahl. Dann zählt man die Gemeinden auch nicht dreifach.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also: man nehme den &lt;a href=&quot;https://img.nzz.ch/2023/11/01/d87c3470-f598-4966-a291-30e5b2a79db7.png?width=2240&amp;amp;height=569&amp;amp;fit=bounds&amp;amp;quality=75&amp;amp;auto=webp&amp;amp;crop=2577,654,x0,y0&quot;&gt;Screenshot&lt;/a&gt; aus dem NZZ-Artikel und generiere daraus ein INTERLIS-Modell:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p39/datenmodell.png&quot; alt=&quot;Datenmodell&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Modell hat keinen Anspruch auf Vollständigkeit und Genialität. Wahrscheinlich fehlen noch ein paar Attribute, insbesondere müsste es sowohl Majorz- und Proporzwahlen abbilden können. Zuerst wollte ich mich an den &lt;a href=&quot;https://www.ech.ch/de/ech/ech-0110/4.1&quot;&gt;eCH-Standard&lt;/a&gt; anlehnen. Den verstehe ich aber nicht so wirklich. Ich fühle mich damit genauso unwohl wie mit den eCH-Objektwesen-Standards. Ich finde sie massiv schlechter lesbar als INTERLIS-Modelle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/bfs-fix/f0235d35280177b9f9eb38f60b90e91a74794a5d/CH_BFS_Wahlresultate_20231109.ili&quot;&gt;INTERLIS-Modell&lt;/a&gt; ist sehr einfach gehalten. Trotzdem ist es natürlich nicht ganz so einfach wie die flache Exceldatei. Es gibt zwei Klassen: Gemeinde und Kandidaten. Die Gemeinde-Klasse beinhaltet Informationen zu einer Gemeinde, die Kandidaten-Klasse Informationen zu einem Kandidaten. Kandidat und Gemeinde stehen in einer Beziehung zueinander. Jeder Kandidat kann in jeder Gemeinde gewählt werden. Aus diesem Grund hat die Beziehung ein Stimmen-Attribut. Es beschreibt die Anzahl Stimmen, die ein Kandidat in einer Gemeinde geholt hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;ili2gpkg&lt;/em&gt; habe ich eine leere GeoPackage-Datei erstellt und die Zahlen aus dem Screenshot mit &lt;a href=&quot;https://dbeaver.io/&quot;&gt;&lt;em&gt;dbeaver&lt;/em&gt;&lt;/a&gt; übertragen. Das ging interessanterweise trotz der Beziehungen noch ganz entspannt, da &lt;em&gt;dbeaver&lt;/em&gt; bei Fremdschlüsseln die möglichen Beziehungs-Records auflistet. Man kann anschliessend den zu verknüpfenden Record bequem mit einem Mausklick auswählen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p39/dbeaver.png&quot; alt=&quot;dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nicht viel anstrengender als eine Exceldatei auszufüllen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was gewinnt das BFS damit? Es kann sowohl seine Microservices wie auch seine Excel-Importskripts wegwerfen. Wenn es die Geowelt hinkriegt, sollte auch das BFS eine standardisierte Lieferung hinkriegen. Trotz oder genau wegen des Föderalismus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant sind nun die Prüfmöglichkeiten, die dank INTERLIS gratis mitkommen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Einfachsten sind die UNIQUE-Constraints. So darf die Gemeindenummer und die Kandidatenummer nur einmal pro Lieferung vorkommen (Kandidatenummer müsste wohl in Kombination mit Kanton geprüft werden). Die UNIQUE-Constraints helfen auch dabei, dass man in der Hitze des Gefechts eine Gemeinde nicht x-fach importiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spannender sind die Constraints, die prüfen, ob das mitgelieferte Total gleich der Summe einzelner Werte ist. So bei den Stimmberechtigten, die als Total wie auch aufgeteilt in Frauen- und Männerstimmen, geliefert werden. Und bei bei den Wahlzetteln: Hier wird das Total geliefert, wie auch ungültige, leere etc. Die Constraints sind straight forward:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;!!@ name=korrekteAnzahlStimmen
!!@ ilivalid.msg=&quot;Eingelegte Wahlzettel ungleich Summe leere, ungültige und in Betracht gezogene Wahlzettel.&quot;
MANDATORY CONSTRAINT Math.add(Math.add(leere_Wahlzettel, ungueltige_Wahlzettel), in_Betracht_Wahlzettel) == eingelegte_Wahlzettel;

!!@ name=korrekteSummeMaennerFrauen
!!@ ilivalid.msg=&quot;Anzahl Stimmberechtigte ungleich Summe Maenner und Frauen.&quot;
MANDATORY CONSTRAINT Math.add(Maenner, Frauen) == wahlberechtigt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Rechnen mit &lt;code&gt;Math.add&lt;/code&gt; etc. ist ziemlich noisy und es wird schnell unübersichtlich. Mit INTERLIS 2.4 geht dann auch &lt;code&gt;+&lt;/code&gt; usw.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das sind alles Constraints, die für jede kantonale Lieferung in identischer Form gelten würden. Was macht man, wenn man spezifisch pro Kanton etwas prüfen will. Eine einfache Variante sind &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/bfs-fix/f0235d35280177b9f9eb38f60b90e91a74794a5d/CH_BFS_Wahlresultate_20231109_Validierung_20231112.ili&quot;&gt;Validierungsmodelle&lt;/a&gt;. Besagter Kanton schickt einen Nationalrat nach Bern. D.h. das Attribut &lt;em&gt;gewaehlt&lt;/em&gt; in der Klasse &lt;em&gt;Kandidat&lt;/em&gt; darf nur ein einziges Mal den Wert &lt;em&gt;true&lt;/em&gt; aufweisen. Der für die Prüfung notwendige Constraint sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;!!@ name = korrekteAnzahlGewaehlter
!!@ ilivalid.msg = &quot;Anzahl gewählter Personen ist nicht korrekt.&quot;
SET CONSTRAINT WHERE gewaehlt : INTERLIS.objectCount(ALL) == 1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine andere, &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/383&quot;&gt;zukünftige&lt;/a&gt; Variante ist die Verwendung von &lt;a href=&quot;http://blog.sogeo.services/blog/2021/11/01/interlis-leicht-gemacht-number-26.html&quot;&gt;Runtime-Parameter&lt;/a&gt;. Man könnte den Constraint im Hauptmodell belassen und beim Prüfaufruf den für den Kanton korrekten Wert als Paramter mitliefern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;java -jar ilivalidator.jar \
--runtimeParams &quot;CH_BFS_Wahlresultate_20231109.anzahlGewaehlteKandidaten=1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Constraint hat Zugriff auf den Runtime-Parameter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;!!@ name = korrekteAnzahlGewaehlter
!!@ ilivalid.msg = &quot;Anzahl gewählter Personen ist nicht korrekt.&quot;
SET CONSTRAINT WHERE gewaehlt : INTERLIS.objectCount(ALL) == PARAMETER CH_BFS_Wahlresultate_20231109.anzahlGewaehlteKandidaten;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wer hätte das gedacht! INTERLIS hilft sogar bei der Publikation korrekter Wahlresultate. Spass beiseite. Ich bin auf den Bericht gespannt, der die ganze Sache untersuchen soll. Jedenfalls dünkt mich viel Optimierungspotenzial vorhanden. Die Vorteile mit INTERLIS liegen auf der Hand. Ein nicht ganz offensichtlicher Vorteil ist die gute Testbarkeit des gesamten Systems. Alles kann mit Testdaten bequem getestet werden und die Businesslogik ist nicht irgendwie verpackt in Skripts und Microservices, sondern in einem bestehenden Kommandozeilen-Werkzeug. Brauche ich wirklich Hipster-Microservices, kann ich die INTERLIS-Programmbibliotheken verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die XTF-Datei zum Rumspielen und Provozieren von Fehlern kann &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/bfs-fix/f0235d35280177b9f9eb38f60b90e91a74794a5d/fubar.xtf&quot;&gt;heruntergeladen&lt;/a&gt; werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;INTERLIS: ein Angebot auch für das BFS.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GeoServer Magie #2 - More GetFeatureInfo witchcraft</title>
      <link>http://blog.sogeo.services/blog/2023/11/03/geoserver-magie-2.html</link>
      <pubDate>Fri, 3 Nov 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/11/03/geoserver-magie-2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://geoserver.org/&quot;&gt;&lt;em&gt;GeoServer&lt;/em&gt;&lt;/a&gt; war schon immer der WMS-Server der Herzen, also meines Herzens. Ganz zu Beginn (muss 2001 oder früher gewesen sein) hatten wir &lt;a href=&quot;https://mapserver.org/&quot;&gt;&lt;em&gt;MapServer&lt;/em&gt;&lt;/a&gt; erfolgreich im Einsatz. &lt;em&gt;GeoServer&lt;/em&gt; haben wir ein paar Jahre später für das Herstellen von grossformatigen PDF verwendet. Zu guter Letzt sind wir dem Marketing von QGIS-Server erlegen und haben auch diesen WMS-Server in Betrieb genommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein wichtiges Ziel bei der Einführung des neuen Web GIS Clients (2016 - 2018) war die Reduktion der WMS-Server auf einen. Die Wahl fiel auf QGIS-Server. Weil wir früher oder später beim Web GIS Client und sämtlichen Services und Umsystemen über die Bücher müssen, darf man sich jetzt &lt;em&gt;GeoServer&lt;/em&gt; wieder ins Gedächtnis rufen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich fand bei &lt;em&gt;GeoServer&lt;/em&gt; immer gut, dass bereits viel mitkommt, das wir bei unserer Lösung zusätzlich programmieren lassen mussten, z.B.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Identity and Access Management&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GUI für die Administration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Layergruppen zu einem Einzellayer gruppiert&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mächtige Konfigurationsmöglichenkeiten bei der GetFeatureInfo-Funktion&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und um diese Konfigurationsmöglichenkeiten geht es hier und jetzt. Ich habe bereits vor &lt;a href=&quot;http://blog.sogeo.services/blog/2018/09/10/geoserver-magie-1.html&quot;&gt;fünf Jahren davon geschwärmt&lt;/a&gt; und bin immer noch begeistert. Ich habe unsere Anforderungen an die Objektabfrage (die plusminus der GetFeatureInfo-Abfrage entspricht) rausgekramt und will wissen, wie das mit &lt;em&gt;GeoServer&lt;/em&gt; ginge?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anforderungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Aliasnamen: Die Attributnamen aus der Datenbank sollen überschrieben werden können.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Attribute sollen ausgeblendet resp. nicht angezeigt werden können.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reihenfolge der Attribute muss konfigurierbar sein&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Attributewerte sollen formatiert werden können. Z.B. &amp;laquo;true&amp;raquo; soll zu &amp;laquo;Ja&amp;raquo; formatiert werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Neue Attribute sollen hinzugefügt werden können, z.B. ein Link auf eine Fachanwendung. Der Link ist nicht in der Datenquelle vorhanden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Der hinzukonfigurierte Link soll zudem verwendet werden können, um einen Report-Service aufzurufen. Dem Report-Service muss die Objekt-ID und die geklickte Koordinate mitgeliefert werden können.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fotos (Link auf URL) sollen automatisch dargestellt werden (nur HTML-Output).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allfällige JSON-Inhalte als Attributwert sollen als Key-Value gerendert werden (nur HTML-Output).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Templating Engine für sehr spezifische Outputs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beliebige SQL-Query: Das Resultat einer beliebigen SQL-Query wird als Key-Value in der GetFeatureInfo-Antwort zurückgeliefert.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beliebige Businesslogik: Es wird selbst programmierter Code ausgeführt und das Resultat wird als Key-Value in der GetFeatureInfo-Antwort zurückgeliefert. Haben wir wenig überraschend wirklich nicht so gerne und noch weniger gerne als die SQL-Query.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Templating Engine&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wahrscheinlich das Wichtigste gleich zu Beginn: GetFeatureInfo-Antworten werden in &lt;em&gt;GeoServer&lt;/em&gt; für die Formate GeoJSON und HTML mittels &lt;a href=&quot;https://freemarker.apache.org/&quot;&gt;Freemarker Templates&lt;/a&gt; &lt;a href=&quot;https://docs.geoserver.org/stable/en/user/tutorials/freemarker.html&quot;&gt;konfiguriert&lt;/a&gt;. Somit ist die Basis für viel Einflussnahme gelegt. Die Templates können global, pro Workspace oder für einen spezifischen Layer definiert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ziel ist natürlich ein möglichst generisches, globales Template herstellen zu können und nur für die absoluten Spezialfälle ein zusätzliches.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Aliasnamen / Attribute ausblenden / Reihenfolge&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese drei Anforderungen können seit &lt;a href=&quot;https://geoserver.org/announcements/2022/05/24/geoserver-2-21-0-released.html&quot;&gt;Version 2.21.0&lt;/a&gt; leicht umgesetzt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p2/attributes01.png&quot; alt=&quot;Customize attributes&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Attributnamen &amp;laquo;schoener_Stein&amp;raquo; wird zu &amp;laquo;Schöner Stein&amp;raquo;. Mit den Pfeilen kann man die Reihenfolge ändern und mit dem roten Button, kann man Attribute entfernen. Hat jetzt noch Luft nach oben, was die Benutzerfreundlichkeit betrifft: Ich würde lieber ein Attribut visuell &amp;laquo;disablen&amp;raquo; und nicht löschen. Und die Reihenfolge möchte ich mit Drag &apos;n&apos; Drop verändern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man die Attributnamen verändert, muss man unbedingt wissen, dass der XML/GML-Output nicht mehr zwingend funktioniert. &lt;em&gt;GeoServer&lt;/em&gt; sendet eine WFS-FeatureCollection zurück. Attribute landen in XML-Tags. Was natürlich mit Umlauten und Leerzeichen nicht mehr funktioniert. Die erlaubten Antwortformate lassen sich aber in &lt;em&gt;GeoServer&lt;/em&gt; konfigurieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Attributwerte formatieren&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Inbesondere bei Boolean-Werten hat man das Bedürfnis diese anders als true/false zu rendern. Hier helfen uns die &lt;a href=&quot;https://docs.geoserver.org/main/en/user/filter/function_reference.html&quot;&gt;Filter-Funktionen&lt;/a&gt;. Mit diesen lassen sich die Attributwerte verändern. Im folgenden Beispiel wird zuerst geprüft, ob der Werte null ist und anschliessend findet die eigentliche Umwandlung in den &amp;laquo;schönen&amp;raquo; String statt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p2/attributes02.png&quot; alt=&quot;Customize attributes&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Neue Attribute&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wozu braucht man das? Wir verlinken regelmässig in der Antwort einer Objektabfrage zu anderen Fachanwendungen. Ein gutes Beispiel sind die Grundstücke. Klickt man auf das Grundstück, wird zusätzlich ein Link auf die Grundbuch-Webanwenwendung (&lt;em&gt;Intercapi&lt;/em&gt;) gerendert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p2/attributes04.png&quot; alt=&quot;Customize attributes&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Link soll nicht Bestandteil der eigentlichen Daten sein und er muss dynamisch sein. D.h. der Query-Parameter des Links ändert sich bei jedem Grundstück (z.B. &lt;code&gt;?egrid=CH270670353285&lt;/code&gt;), damit man direkt beim angewählten Grundstück in der Fachanwendung landet. Stand heute machen wird das mit einem zusätzlichen, speziellen Jinja-Template. Mit &lt;em&gt;GeoServer&lt;/em&gt; geht das einfacher: neue Attribute können mit &amp;laquo;Add attribute&amp;raquo; hinzugefügt werden. Mit den bereits erwähnten Filterfunktionen und dem Zugriff auf andere Attribute (hier &amp;laquo;egrid&amp;raquo;) kann der Link einfach zusammengestöpselt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p2/attributes03.png&quot; alt=&quot;Customize attributes&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Objekt-ID und geklickte Koordinate&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir haben einen eigenen Report-Service, der Zusatzinformationen zu Objekten als PDF oder Excel zurückliefert (siehe Objektblatt-Link).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p2/attributes05.png&quot; alt=&quot;Customize attributes&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dem Report-Service muss man die Objekt-ID (des geklickten Objekts) und/oder die geklickte Koordinate übergeben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;https://geo.so.ch/api/v1/document/afu_bodenprofilstandorte?feature=10632&amp;amp;x=2604491.665269116&amp;amp;y=1228356.537479993&amp;amp;crs=EPSG%3A2056&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was der Report-Service mit diesen Informationen macht, ist ihm überlassen. Heute können wir in unserer Administrationsanwendung die (Jasper-)Reports direkt einem WMS-Layer zuweisen. Mit &lt;em&gt;GeoServer&lt;/em&gt; würde man den Link wie oben als zusätzliches Attribut erfassen. Was aber noch fehlt, ist die geklickte Koordinate. Diese muss als Platzhalter im Link erfasst werden und kann im &lt;em&gt;Freemarker Template&lt;/em&gt; zur Laufzeit berechnet und der Platzhalter ersetzt werden. Im &lt;em&gt;Freemarker Template&lt;/em&gt; stehen gewisse Informationen immer zur Verfügung. So z.B. die GetFeatureInfo-Request-URL. Aus dieser lässt sich die geklickte Koordinate berechnen und dem Link hinzufügen. Das kann im globalen Template gemacht werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Fotos rendern&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im globalen Template kann geprüft werden, ob ein Link auf ein Foto zeigt. Mehr oder weniger muss nur die Endung geprüft werden. Ist es kein Foto, wird nur der Link gerendert, sonst das Bild.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;JSON-Inhalte rendern&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Objekte weisen bei uns manchmal Attribute auf, deren Inhalt JSON ist. Das rührt daher, dass einem Objekt z.B. mehrere Dokumente (mit den Attributen Link, Titel, offizielle Nummer, &amp;#8230;&amp;#8203;) zugewiesen sind. Das lösen wir so, dass wir diese unbekannte Anzahl an Dokumenten als JSON im Attribut kodieren. In der GetFeatureInfo-Antwort soll aber kein JSON-String stehen, sondern wie alle anderen Attribute im Key-Value-Stil gerendert werden. Auch diese Anforderung kann direkt in einem globalen Template gelöst werden. Herausfordernd ist, herauszufinden, ob es sich um JSON handelt oder um einen normalen String. Freemarker sieht in beiden Fällen nur einen String (weil &lt;em&gt;GeoServer&lt;/em&gt; das bereits umwandelt).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Beliebige SQL-Query / Businesslogik&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In ganz wenigen Ausnahmefällen müssen wir Informationen zurückliefern, die nicht direkt vom geklickten Layer stammen. Für diesen Use Case haben wir zwei Lösungsvarianten: Wir können beliebiges SQL ausführen oder beliebigen Python-Code. Bei beiden Varianten gibt es sowas wie ein Interface, das implementiert werden muss, damit es generisch funktioniert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;em&gt;GeoServer&lt;/em&gt; prototypisch umgesetzt habe ich nur die SQL-Variante. Vom Prinzip her ist die Python-Variante (in unserem Fall natürlich Java) sehr ähnlich. &lt;em&gt;Freemarker Templates&lt;/em&gt; erlauben das Ausführen von beliebigen statischen Java-Methoden. Diese müssen aber vorgängig explizit (aus Sicherheitsgründen) freigeschaltet werden. Wir schreiben einmalig eine Java-Klasse mit einer statischen Methode, die eine SQL-Query ausführt. Die SQL-Query steht in einer Datei, die z.B. in das GeoServer-Data-Directory kopiert wird. Die Java-Methode sucht anhand der Parameter die Datei und führt die SQL-Query aus und retourniert die Antwort als Liste von Maps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gratis bekommt man zudem das DB-Connection-Pooling, weil man die Datasource via JNDI definiert. Wir teilen uns somit mit &lt;em&gt;GeoServer&lt;/em&gt; den Connection Pool.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man könnte sogar soweit gehen, diese Logik in das generische, globale &lt;em&gt;Freemarker Template&lt;/em&gt; zu integrieren. Ist meines Erachtens wahrscheinlich nicht sinnvoll, weil somit &lt;em&gt;jeder&lt;/em&gt; GetFeatureInfo-Request zuerst prüft, ob eine solche SQL-Query ausführt werden muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;GeoServer&lt;/em&gt; bietet sehr mächtige Konfigurationsmöglichenkeiten im Bereich des GetFeatureInfo-Requests an. Einiges, das wir benötigen und speziell entwickeln liessen, gibt es hier out-of-the-box.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #38 - ilishaper: shape your ili</title>
      <link>http://blog.sogeo.services/blog/2023/09/20/interlis-leicht-gemacht-number-38.html</link>
      <pubDate>Wed, 20 Sep 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/09/20/interlis-leicht-gemacht-number-38.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt (bald) ein neues INTERLIS-Werkzeug und es heisst &lt;a href=&quot;https://github.com/claeis/ilishaper&quot;&gt;&lt;em&gt;ilishaper&lt;/em&gt;&lt;/a&gt; (jaja, ich höre die Häme bereits: &amp;laquo;shape&amp;#8230;&amp;#8203;&amp;raquo;). &lt;em&gt;Ilishaper&lt;/em&gt; hat zwei Funktionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die erste Funktion betrifft die INTERLIS-Modelldatei. Man kann aus einem Modell ein weiteres, reduziertes Modell ableiten. Dabei lassen sich Attribute, ganze Klassen und ganze Topics abstreifen. Dazu benötigt es eine Konfigurationsdatei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[Basismodell]
name=Derivatmodell
issuer=mailto:test@host
version=2023-01-01
doc=Neu generiertes Modell

[Basismodell.TopicT1.ClassA.Attr1]
ignore=true

[Basismodell.TopicT1.ClassB]
ignore=true

[Basismodell.TopicT2]
ignore=true

[Basismodell.TopicT1.ClassA]
filter=Attr5==#rot&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Syntax dünkt mich mehr oder weniger selbsterklärend. Die ersten paar Zeilen betreffen die Modellinformationen: Man kann den Namen des Modells bestimmen und weitere Meta-Attribute. Interessant sind die letzten beiden Zeilen: Mit diesen lassen sich auch Objekte filtern. Im vorliegenden Fall werden sämtliche Objekte gefiltert, die den Aufzähltypwert &lt;code&gt;#rot&lt;/code&gt; aufweisen. Das führt zur zweiten Funktion von &lt;em&gt;ilishaper&lt;/em&gt;. Das Filtern hat ja keine Auswirkung beim Herstellen des Derivatmodelles, sondern erst wenn auch die INTERLIS-Transferdatei vom Basismodell ins Derivatmodell von &lt;em&gt;ilishaper&lt;/em&gt; &amp;laquo;geshaped&amp;raquo; wird. Somit wird es möglich sein sowohl Zeilen wie auch Spalten abzustreifen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum brauchen wir so was?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die meisten unserer Geodaten sind öffentlich und frei verfügbar. Einige der Themen weisen jedoch teilweise geschützte Daten auf. Der Klassiker: eine Telefonnummer. Diese Information darf die Öffentlichkeit nicht sehen und ist nur für den internen Gebrauch. Auf Service-Stufe (also WMS etc.) haben wir das applikatorisch gelöst (im QGIS-Server). Bei der Bereitstellung der Geodaten als Datei (XTF, GeoPackage etc.) hatten wir keine Lösung. Der erste Ansatz, den wir verfolgten, war Vererbung. Wir modellierten das Modell mit den öffentlichen Informationen und dazu ein erweitertes Modell mit den zusätzlichen Informationen, die nicht öffentlich sind. Man ist nun in der Lage mit &lt;em&gt;ili2pg&lt;/em&gt; Daten gemäss beiden Modellen zu exportieren (was eigentlich recht abgefahren ist). Ein Beispiel aus dem Fundus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;INTERLIS 2.3;

MODEL SO_ALW_Bienenstandorte_20210529 (de)
AT &quot;https://alw.so.ch&quot;
VERSION &quot;2021-05-26&quot;  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC Bienenstandorte =
    OID AS INTERLIS.UUIDOID;

    /** Bienenstandort (public)
     */
    CLASS Bienenstandort =
      /** Nummer
       */
      Nummer : MANDATORY TEXT*16;
      /** Standort
       */
      Standort : MANDATORY GeometryCHLV95_V1.Coord2;
      /** Bemerkung
       */
      Bemerkung : TEXT*200;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_20210529.

MODEL SO_ALW_Bienenstandorte_protected_20210529 (de)
AT &quot;mailto:stefan@localhost&quot;
VERSION &quot;2021-05-29&quot;  =
  IMPORTS SO_ALW_Bienenstandorte_20210529;

  TOPIC Bienenstandorte
  EXTENDS SO_ALW_Bienenstandorte_20210529.Bienenstandorte =

    /** Bienenstandort mit allen (inkl. nicht-öffentlichen) Attributen.
     */
    CLASS Bienenstandort (EXTENDED) =
      /** Name des Imkers
       */
      Name : MANDATORY TEXT*255;
      /** Telefonnummer
       */
      Telefonnummer : TEXT*20;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_protected_20210529.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn wir das Modell mit &lt;em&gt;ili2pg&lt;/em&gt; in der Datenbank abbilden, entsteht zwar trotz Vererbung mit passender Abbildungsregel nur eine Tabelle. Jedoch entstehen auch zwei zusätzliche Spalten &lt;code&gt;t_basket&lt;/code&gt; und &lt;code&gt;t_type&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p38/dbeaver01.png&quot; alt=&quot;bienenschema&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;code&gt;t_type&lt;/code&gt;-Spalte schmerzt mich nicht sonderlich, das ist eine Konstante. Was mich mehr stört und nervt ist die &lt;code&gt;t_basket&lt;/code&gt;-Spalte, um die ich mich kümmern muss. Es ist ein Fremdschlüssel zur &lt;code&gt;t_ili2db_basket&lt;/code&gt;-Tabelle. Ich kann also nicht irgendwas reinschreiben. Und hier liegt der wirkliche Hund begraben: Zu den oben dargestellten Modellen (sogenannte flache Publikationsmodelle) gibt es bei uns in der Regel auch immer ein sauber normalisiertes Erfassungsmodell. Die Publikation vom Erfassungs- in das Publikationsmodell machen wir mit SQL. Und nun muss man sich plötzlich ziemlich mühsam (v.a. bei umfangreicheren Modellen) um diesen Fremdschlüssel kümmern. Und warum? Weil ich die Telefonnummer nicht publizieren will. Man muss also einen ziemlichen Handstand machen (Vererbung, zwei Modelle, komplizierter Datenumbau), um ein Attribut loszuwerden. Die Idee von &lt;em&gt;ilishaper&lt;/em&gt; ist eben auch, diesen Prozess viel transparenter zu machen: man konfiguriert das Abstreifen eines Attributes in der Konfig-Datei und muss sich nicht um Baskets kümmern. Scheint mir so eine viel &amp;laquo;expressivere&amp;raquo; Lösung für das Problem zu sein. Zudem kann man mit dem Vererbungsmeccano nicht Zeilen filtern, was bei uns ebenfalls ab und wann verlangt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir werden zukünftig ein Erfassungmodell machen, das nicht weiss, ob z.B. ein Attribut öffentlich oder nicht-öffentlich ist. Weiter werden wir ein Publikationsmodell mit öffentlichen und nicht-öffentlichen Daten erstellen. Dieses Publikationsmodell persistieren wir in der Datenbank und es dient ebenfalls für die Publikation der Daten als Service (siehe oben, Problem applikatorisch gelöst). Aus diesem Publikationsmodell erstellen wir &lt;em&gt;einmalig&lt;/em&gt; das reduzierte Publikationsmodell mit den öffentlichen Daten. Das Modell verschwindet natürlich nicht wieder, sondern wird genauso, wie alle anderen Modellen in der &lt;a href=&quot;https://geo.so.ch/models/&quot;&gt;INTERLIS-Modellablage&lt;/a&gt; publiziert. Nach dem Export der Daten in das Publikationsmodell kommt der &lt;em&gt;ilishaper&lt;/em&gt; zum Zuge und wandelt die Transferdatei in das reduzierte Publikationsmodell um, das jedermann zur Verfügung stehen wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Klar, es entsteht auch wieder Konfiguration aber trotzdem scheint mir dieses Vorgehen transparenter, einfacher und auch skalierbarer (unterschiedliche Derivatmodelle) zu sein.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #15 - ÖREB-Proxy-Service</title>
      <link>http://blog.sogeo.services/blog/2023/08/14/oereb-kataster-richtig-gemacht-15.html</link>
      <pubDate>Mon, 14 Aug 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/08/14/oereb-kataster-richtig-gemacht-15.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Update 2023-08-17:&lt;/em&gt; Die Suche eines EGRID mittels Adresse funktioniert. Was nicht geht ist die Suche mittels NBIdent und GB-Nummer. Das scheint mir die Api nicht herzugeben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt &lt;a href=&quot;https://s.geo.admin.ch/a120486ae5&quot;&gt;27 ÖREB-Webservices&lt;/a&gt;. Jeder Webservice ist unter einer anderen URL erreichbar. Wenn ich als Kunde nicht nur in einem Kanton ÖREB-Katasterauszüge beziehen wollte, fände ich diesen Umstand bemühend. Ich muss 27 Endpunkte verwalten und vor allem muss ich bei jeder Koordinate oder jedem EGRID irgendwie herausfinden, welchen Service/Kanton ich nun verwenden muss. Es muss ein ÖREB-Proxy her, der die standardisierten Requests entgegennimmt und nachdem er herausgefunden hat, welcher Kanton betroffen ist, den Request ausführt und die Antwort an den Client zurückliefert. Ein erster Prototyp (oder im Solothurnischen Neusprech: Rocksolide Top-of-the-Pops-Application, also Champions League) steht: &lt;a href=&quot;https://proxy.oereb.services&quot;&gt;https://proxy.oereb.services&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein paar Testrequests:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://proxy.oereb.services/getegrid/xml/?EN=2600595,1215629&quot; class=&quot;bare&quot;&gt;https://proxy.oereb.services/getegrid/xml/?EN=2600595,1215629&lt;/a&gt; (Solothurn)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://proxy.oereb.services/getegrid/xml/?EN=2757735,1224129&quot; class=&quot;bare&quot;&gt;https://proxy.oereb.services/getegrid/xml/?EN=2757735,1224129&lt;/a&gt; (Liechtenstein)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://proxy.oereb.services/getegrid/xml/?GNSS=7.44646,47.09171&quot; class=&quot;bare&quot;&gt;https://proxy.oereb.services/getegrid/xml/?GNSS=7.44646,47.09171&lt;/a&gt; (Solothurn)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://proxy.oereb.services/extract/xml/?EGRID=CH807306583219&quot; class=&quot;bare&quot;&gt;https://proxy.oereb.services/extract/xml/?EGRID=CH807306583219&lt;/a&gt; (Solothurn)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://proxy.oereb.services/extract/xml/?EGRID=CH767982496078&quot; class=&quot;bare&quot;&gt;https://proxy.oereb.services/extract/xml/?EGRID=CH767982496078&lt;/a&gt; (Basel-Landschaft)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Quellcode ist auf &lt;a href=&quot;https://github.com/edigonzales/oereb-proxy&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wird unterstützt?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird nur XML und PDF als Format unterstützt. XML ist im Gegensatz zu JSON zwingend und es gibt mindestens zwei Kantone, die JSON nicht anbieten. Es werden die Requests &amp;laquo;GetEgrid&amp;raquo; und &amp;laquo;Extract&amp;raquo; angeboten. Die beiden anderen könnte man aber problemlos noch umsetzen. Es funktionieren auch nicht ganz alle Aufrufe. Der GetEgrid-Request mit einer Adresse geht nicht. Dazu später gleich mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es sollten alle Kantone freigeschaltet sein, die den Kataster in der Version 2 anbieten (und Fürstentum Liechtenstein). Falls der Request ein Kanton betrifft, der nicht gefunden wird, wird der Statuscode 204 zurückgeliefert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Umgesetzt habe ich es mit &lt;em&gt;Spring Boot&lt;/em&gt;. Das Ganze mit &lt;em&gt;GraalVM&lt;/em&gt; in ein Native Binary runterkompiliert. So startet die Java-Anwendung in 0.06 Sekunden und braucht massiv weniger RAM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Service selber ist bewusst &lt;a href=&quot;https://github.com/edigonzales/oereb-proxy/blob/main/src/main/java/ch/so/agi/oereb/MainController.java#L72&quot;&gt;dumm&lt;/a&gt; gehalten. Er soll nur Requests entgegennehmen und beim betroffenen Kanton den Auszug anfordern. Die Antwort wird nicht verändert. Es gibt zusätzlich einen &amp;laquo;redirect&amp;raquo;-Modus. In diesem Fall wird der betroffene Kanton eruiert und anschliessend wird zum kantonalen ÖREB-Webservice &lt;em&gt;weitergeleitet&lt;/em&gt;. Das hat den Nachteil, dass Geschichten wie &lt;a href=&quot;http://blog.sogeo.services/blog/2023/01/11/oereb-kataster-richtig-gemacht-13.html&quot;&gt;CORS&lt;/a&gt; wieder Probleme machen könnten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Hauptschwierigkeit liegt eigentlich darin herauszufinden, welcher Kanton betroffen ist. Wenn man bei der GetEgrid-Anfrage beginnt, muss man den Kanton anhand einer Koordinate herausfinden. Ich habe das früher für meinen &lt;a href=&quot;https://map.oereb.services/&quot;&gt;Viewer&lt;/a&gt; mit sequenzieller und auch paralleler Abfrage aller Kantonsservices probiert. Das geht viel zu lange. Wenn ich schon von 27 Services abhängig bin, kann ich mich auch noch von einem weiteren Service abhängig machen: Die GeoAdmin API. Man muss zwar im vielfälltigen Angebot suchen, bis man den passenden Dienst hat. Aber danach klappt es einwandfrei. Die Koordinate beim GetEgrid-Request kann auch als WGS84 geliefert werden. Dafür gibt es von Swisstopo ebenfalls einen &lt;a href=&quot;https://geodesy.geo.admin.ch/reframe/wgs84tolv95&quot;&gt;Dienst&lt;/a&gt;. Warum dieser jedoch nicht unter der &lt;a href=&quot;https://api3.geo.admin.ch/services/sdiservices.html&quot;&gt;GeoAdmin API&lt;/a&gt; dokumentiert ist, weiss ich nicht. Für die Variante mit der Adresse (Ortschaft, Strasse, Hausnummer) habe ich keine Variante gefunden, die mir eindeutig den EGRID zurückliefert. Anyone?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man den EGRID hat und den eigentlichen Auszug bestellen will, muss man anhand des EGRID den betroffenen Kanton eruieren. Auch das geht mit der GeoAdmin API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das wars eigentlich auch schon. Mit geodienste.ch[geodienste.ch] kann ich an einer Stelle die MGDM (Daten und Dienste) beziehen. Warum soll das nicht auch für den ÖREB-Kataster möglich sein?&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>OGD made easy #3 - Visualisierung und Data Crunching</title>
      <link>http://blog.sogeo.services/blog/2023/08/13/ogd-made-easy-03.html</link>
      <pubDate>Sun, 13 Aug 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/08/13/ogd-made-easy-03.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Teil &lt;a href=&quot;http://blog.sogeo.services/blog/2023/07/10/ogd-made-easy-01.html&quot;&gt;1&lt;/a&gt; und &lt;a href=&quot;http://blog.sogeo.services/blog/2023/07/20/ogd-made-easy-02.html&quot;&gt;2&lt;/a&gt; behandeln die Integration und Bereitstellung von OGD-Daten. Viele Dienststellen möchten ihre Daten aber auch visualisiert haben. Insbesondere wenn es darum geht die Gemeindeflächen z.B. nach Steuerlast einzufärben (Choroplethenkarte), gelangen sie zu uns. Das ist bei uns jeweils immer bisschen Handarbeit und wir wünschten uns was Effizienteres. Wir hatten früher eine Lösung mit Excel, welche aber Maintenance-Hell war und wieder verschwunden ist. Eins vorneweg: Gefunden habe ich die perfekte Lösung noch nicht, sondern es ging mir ums Kennenlernen und Ausloten bestehender Lösungen für klassische (nicht-kartografische) Visualisierungen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich bin Anfang des Jahres über &lt;a href=&quot;https://www.dashbuilder.org/&quot;&gt;&lt;em&gt;Dashbuilder&lt;/em&gt;&lt;/a&gt; gestolpert. Jetzt im Rahmen dieser Rumspielerei war der passende Anlass es auszuprobieren. Das Ausprobieren gestaltete sich aber relativ schwierig. Ich hatte extrem Mühe den Einstieg zu finden. Anscheinend ist da sehr viel im Fluss und m.E. leidet darunter die Dokumentation, was es natürlich für Aussenstehende wiederum schwieriger macht (z.B. Ankündigugen von Neuerungen in Blogs, die schon wieder passé sind). Zuerst gab es sowas wie einen visuellen Dashboardbuilder. Dieser ist - soweit ich es eben verstanden habe - Geschichte. Es brauchte auch eine Runtime, auf die man die zusammengebastelten Visualisierungen deployen konnte (musste?). Auch diese ist nicht mehr nötig. Neu schreibt man sein Zeugs nur noch mit &lt;a href=&quot;https://noyaml.com/&quot;&gt;YAML&lt;/a&gt;. Diese Kröte könnte man ja noch Schlucken, wenn es top-notch dokumentiert wäre. Ist es aber nicht. Ich habe die Übung mit &lt;em&gt;Dashbuilder&lt;/em&gt; abgebrochen und bin umgeschwenkt auf was Anderes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jeder Kubernetes- oder OpenShift-Leidgeplagte kennt es wohl: &lt;a href=&quot;https://grafana.com/oss/&quot;&gt;&lt;em&gt;Grafana&lt;/em&gt;&lt;/a&gt;. Grafana sagt von sich: &amp;laquo;Grafana is the leading open source visualization and dashboarding platform that allows you to query, visualize, alert on, and understand your data no matter where it’s stored.&amp;raquo; Klingt schon mal nicht schlecht. Vorsicht ist immer geboten, wenn ein OSS-Produkt nur von einer Firma bewirtschaftet wird. Das kann auch böse enden oder zumindest zu Unsicherheiten führen (siehe &lt;a href=&quot;https://www.hashicorp.com/blog/hashicorp-adopts-business-source-license&quot;&gt;HashiCorp&lt;/a&gt;). Da ist z.B. &lt;a href=&quot;https://qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; bezüglich OSS schon ein anderes Kaliber.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unsere OGD-Daten haben wir von CSV nach Parquet und Excel umformatiert. Wie sieht es damit in &lt;em&gt;Grafana&lt;/em&gt; aus? Eher mau. Es gibt zwar unzählige Datenquellen, die man anzapfen kann. Aber Parquet ist nicht &lt;a href=&quot;https://grafana.com/docs/grafana/latest/datasources/&quot;&gt;darunter&lt;/a&gt;. Wenn man nicht gleich &amp;laquo;all in&amp;raquo; via &lt;a href=&quot;https://grafana.com/docs/grafana/latest/datasources/postgres/&quot;&gt;PostgreSQL&lt;/a&gt; gehen will, bleibt fast nur noch CSV mit dem &lt;a href=&quot;https://grafana.com/grafana/plugins/yesoreyeram-infinity-datasource/&quot;&gt;Infinity-Plugin&lt;/a&gt;. Hier natürlich sehr nervig, weil man entweder explizit den Datentyp casten muss oder aufpassen muss, dass Grafana resp. das Plugin den Datentyp aus dem CSV korrekt erkennt. Die Gestaltungsmöglichkeiten sind umfrangreich und die Dokumentation ist gut:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/grafana-example.png&quot; alt=&quot;Grafana Beispiel&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Vergleich dazu die Grafik aus der heute bereitgestellten Exceldatei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/grafik-excel-afu.png&quot; alt=&quot;Grafix aus Excel&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann Grafana-Dashboards der Öffentlichkeit ohne Login bereitstellen (Beta-Feature) oder es besteht die Möglichkeit, dass Teile davon als iFrame von anderen Anwendungen eingebunden werden können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein ebenso spannender Aspekt ist das Data Crunching. Wir sind bei uns in der Geoinformation SQL-zentrisch unterwegs. GIS-Analysen etc. finden immer in der Datenbank statt (Dank Docker auch mal in einer Jenkins-Pipeline). Geht  SQL-mässig auch was, ohne die Daten in PostgreSQL zu importieren?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil technisch faszinierend, habe ich zuerst &lt;a href=&quot;https://drill.apache.org/&quot;&gt;Apache Drill&lt;/a&gt; ausprobiert. Man kann damit praktisch jede erdenkliche NoSQL-Datenbank mit richtigem &lt;a href=&quot;https://drill.apache.org/docs/sql-reference/&quot;&gt;SQL&lt;/a&gt; (nicht irgendein Pseudo-Dialekt) ansprechen und zudem die Quellen verwalten. Es ist also möglich z.B. Parquet-Dateien auf S3 mit SQL abzufragen, wenn sie in &lt;em&gt;Apache Drill&lt;/em&gt; freigeschaltet (oder je nach Sichtweise registriert) sind. Kann man gut finden oder auch schlecht. Kommt wohl auf die Anforderungen darauf an. Als Einzelbenutzer wäre mir so eine Verwaltung egal. Wenn jemand &lt;em&gt;Apache Drill&lt;/em&gt; als Software in einer Organisation zur Verfügung stellt, will man wahrscheinlich sowas. Damit nicht zu viel Schabernack gemacht wird. &lt;em&gt;Drill&lt;/em&gt; lässt sich Standalone betreiben oder in einem mehr oder weniger aufwändigen Setup mit Zookeeper. Dann sind mehrere &amp;laquo;Drillbits&amp;raquo; möglich, um die Last besser zu verteilen. Interessant ist &lt;em&gt;Drill&lt;/em&gt; auch, weil es einen JDBC- und ODBC-Treiber gibt. Ich kann somit sämtliche registrierten Datenquellen in beliebigen Anwendungen mit SQL abfragen (z.B. &lt;em&gt;dbeaver&lt;/em&gt; oder klassische BI-Werkzeuge). Neben all diesem steht auch eine einfache Konsole zur Verfügung, in der ich die SQL-Statements absetzen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT * FROM s3.`ch.so.afu.abfallmengen_gemeinden.parquet`;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wobei &lt;code&gt;s3&lt;/code&gt; hier nur für den Namen des Storages dient und es nicht zwingend &lt;code&gt;s3&lt;/code&gt; heissen muss für Daten auf S3. Man kann sich das vielleicht wie einen Schemanamen vorstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Antwort wird in 0.8 Sekunden zurückgeliefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/drill-result-01.png&quot; alt=&quot;Apache Drill Result 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann auch zwischen beliebigen Quellen joinen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    *
FROM
    s3.`ch.so.afu.abfallmengen_gemeinden.parquet` AS abfall
    LEFT JOIN s3.`ch.so.agi.amtliche_vermessung_statistik.umsatz.parquet` AS av_umsatz
    ON av_umsatz.jahr = abfall.jahr
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Und ja, die Query hier ist sinnfrei.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Ausführung der Query dauert circa 1.5 Sekunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/drill-result-02.png&quot; alt=&quot;Apache Drill Result 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Exports von Datanalysen in verschiedene Formate sind möglich. Erwähnenswert ist, dass man neben der Vielzahl von NoSQL-Datenbanken eben auch normale relationale Datenbanken ansprechen kann (solange es einen JDBC-Treiber gibt). Damit liessen sich Abfragen zwischen Parquet-Daten in einem Data Swamp und einem klassischen Datawarehouse machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein klein wenig mulmiges Gefühl bekomme ich, wenn ich mir die Performance anschaue. Sollten die Queries komplexer werden, weiss ich nicht, ob die Antwortzeiten komplett durch die Decke gehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wäre Data Crunching mit SQL, wenn man nicht auch &lt;a href=&quot;https://duckdb.org/&quot;&gt;&lt;em&gt;DuckDB&lt;/em&gt;&lt;/a&gt; anschauen würde? &lt;em&gt;DuckDB&lt;/em&gt; ist quasi die Datenbank to go. Ein einziges Binary und fertig. Standardmässig werden erzeugte Tabellen nicht auf die Harddisk persistiert und man kann verschiedene Formate prozessieren, ohne sie importieren zu müssen. Es gibt auch einen JDBC-Treiber, um z.B. &lt;a href=&quot;https://duckdb.org/docs/guides/sql_editors/dbeaver.html&quot;&gt;&lt;em&gt;dbeaver&lt;/em&gt; als Client&lt;/a&gt; zu verwenden. Es ist aber kein reiner Java-Treiber, sondern es werden für verschiedene Betriebssystem Binaries in die Jar-Datei gepackt (wie beim SQLite-Treiber). Darum ist sie über 50MB gross. Aber es funktioniert tadellos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/duckdb-jdbc-treiber.png&quot; alt=&quot;DuckDB JDBC Treiber&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die letzten vier Dateien in der Auflistung sind die Binaries für Linux (amd und arm), Windows und macOS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um Parquet-Dateien auf S3 abzufragen, muss man zuerst einmalig die HTTPFS-Extension installieren und laden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;INSTALL httpfs;
LOAD httpfs;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend kann man beliebige Dateien, die via HTTP greifbar sind, abfragen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-03/duckdb-query-dbeaver-01.png&quot; alt=&quot;DuckDB JDBC Treiber&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Abfrage dauert circa 0.2 Sekunden. Der unsinnige Join mit der Statistik der amtlichen Vermessung sieht nun so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT
    *
FROM
    read_parquet(&apos;https://s3.eu-central-1.amazonaws.com/ch.so.data-dev/ch.so.afu.abfallmengen_gemeinden.parquet&apos;) AS abfall
    LEFT JOIN read_parquet(&apos;https://s3.eu-central-1.amazonaws.com/ch.so.data-dev/ch.so.agi.amtliche_vermessung_statistik.umsatz.parquet&apos;) AS av_umsatz
    ON av_umsatz.jahr = abfall.jahr
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Query dauert nur 0.3 Sekunden. Was fast Faktor 5 schneller ist als mit &lt;em&gt;Apache Drill&lt;/em&gt;. Woran es liegt, weiss ich nicht. Ich würde darauf tippen, das bereits die Umgehung des S3-Protokolls einen gewissen Einfluss hat. Nomen est omen: OGD-Daten sind frei verfügbar. Das ganze S3-Berechtigungs-Gedöns ist in diesem Fall nicht notwendig und es sind schlicht Dateien, die via HTTP lesbar sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten (als erzeugte Tabellen) können exportiert (lokal in &lt;a href=&quot;https://duckdb.org/docs/guides/import/parquet_export&quot;&gt;verschiedene&lt;/a&gt; &lt;a href=&quot;https://duckdb.org/docs/guides/import/csv_export&quot;&gt;Formate&lt;/a&gt; oder auch nach &lt;a href=&quot;https://duckdb.org/docs/guides/import/s3_export&quot;&gt;S3&lt;/a&gt;) werden. Es gibt eine &lt;a href=&quot;https://duckdb.org/docs/extensions/spatial.html&quot;&gt;Spatial-Extension&lt;/a&gt;, die GIS-Operationen implementiert. Und man kann sogar GDAL-Formate verwenden. Ziemlich abgefahren alles. Ganz frisch ist &lt;a href=&quot;https://motherduck.com/blog/announcing-motherduck-duckdb-in-the-cloud/&quot;&gt;Motherduck&lt;/a&gt;, wo Kollaboration und Cloud eine Rolle spielen sollen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.r-project.org/&quot;&gt;&lt;em&gt;R&lt;/em&gt;&lt;/a&gt; würde sich für Data Crunching auch noch eignen. Hier fehlt mir irgendwie immer noch der Zugang. Für coole Dashboards könnte/müsste man sich zudem &lt;a href=&quot;https://superset.apache.org/&quot;&gt;&lt;em&gt;Apache Superset&lt;/em&gt;&lt;/a&gt; anschauen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>OGD made easy #2 - Datenintegration und -publikation</title>
      <link>http://blog.sogeo.services/blog/2023/07/20/ogd-made-easy-02.html</link>
      <pubDate>Thu, 20 Jul 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/07/20/ogd-made-easy-02.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;http://blog.sogeo.services/blog/2023/07/10/ogd-made-easy-01.html&quot;&gt;ersten Teil&lt;/a&gt; ging es um die Umwandlung von CSV-Dateien in das Parquet-Format und um die Prüfung der CSV-Dateien mit einem &lt;a href=&quot;https://github.com/edigonzales/csv2parquet&quot;&gt;CLI-Tool&lt;/a&gt;. Nun kann das CLI-Tool zwar zweckdienlich sein, aber für die automatisierte Integration in eine Dateninfrastruktur reicht es nicht und passt nicht in unseren Stack. Wir benötigten die Funktionalitäten als &lt;a href=&quot;https://gradle.org&quot;&gt;Gradle&lt;/a&gt;-Task in unserem ETL-Werkzeug &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt;. Gesagt, getan: es gibt nun drei neue GRETL-Tasks:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#csv2parquet-incubating&quot;&gt;Csv2Parquet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#csv2excel-incubating&quot;&gt;Csv2Excel&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#ogdmetapublisher-incubating&quot;&gt;OgdMetaPublisher&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ersten Beiden wandeln eine CSV-Datei in eine Parquet- resp. Excel-Datei um. Dabei wird - falls vorhanden - ein INTERLIS-Modell zwecks Bestimmung der Datentypen berücksichtigt. Der dritte Task erstellt aus einer TOML-Datei mit minimalen Metainformationen (Titel, Beschreibung, Datumsangaben, Zuständigkeiten) und dem Datenmodell eine Datei mit sämtlichen Metainformationen im INTERLIS-Format. Das &lt;a href=&quot;https://github.com/sogis/gretl/blob/e6c97d76ffc5c8fdd20d333cd2d05429e60e38d8/gretl/src/main/resources/ogdmetapublisher/ili/SO_OGD_Metadata_20230629.ili&quot;&gt;Datenmodell&lt;/a&gt; ist angelehnt an &lt;a href=&quot;https://www.dcat-ap.ch/&quot;&gt;DCAT-AP CH&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Intergrations-GRETL-Job sieht folgendermassen aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;import ch.so.agi.gretl.tasks.*
import java.nio.file.Files
import java.nio.file.Paths
import de.undercouch.gradle.tasks.download.Download

apply plugin: &apos;ch.so.agi.gretl&apos;

defaultTasks &quot;runIntegrationJob&quot;

def pathToTempFolder = System.getProperty(&quot;java.io.tmpdir&quot;)
def resultDir = file(&quot;./result&quot;)
resultDir.mkdirs()

def csvFileName = &quot;ch.so.afu.abfallmengen_gemeinden.csv&quot;
def baseName = &quot;ch.so.afu.abfallmengen_gemeinden&quot;
def csvFileObj = file(Paths.get(resultDir.toString(), csvFileName))
def parquetFileName = baseName + &quot;.parquet&quot;
def excelFileName = baseName + &quot;.xlsx&quot;
def tomlFileName = baseName + &quot;.toml&quot;
def bucket = &quot;ch.so.data-dev&quot;
def modelName = &quot;SO_AFU_Abfallmengen_Gemeinden_20230629&quot;
def defaultModelDir = projectDir.toString()+&quot;;https://geo.so.ch/models&quot;

// Könnte auch Upload durch Benutzer sein.
tasks.register(&apos;downloadCsv&apos;, Download) {
    src &quot;https://s3.eu-central-1.amazonaws.com/ch.so.data.ingress-demo/$baseName/$csvFileName&quot;
    dest csvFileObj
    overwrite true

    doLast {
        println &quot;File downloaded to: &quot; + pathToTempFolder
    }
}

tasks.register(&apos;validateCsv&apos;, CsvValidator) {
    dependsOn &apos;downloadCsv&apos;
    dataFiles = [csvFileObj.toString()]
    firstLineIsHeader = true
    valueDelimiter = null
    valueSeparator = &quot;;&quot;
    models = modelName
    modeldir = defaultModelDir
}

tasks.register(&apos;createParquet&apos;, Csv2Parquet) {
    dependsOn &apos;validateCsv&apos;
    csvFile = csvFileObj
    firstLineIsHeader = true
    valueDelimiter = null
    valueSeparator = &quot;;&quot;
    models = modelName
    modeldir = defaultModelDir
    outputDir = file(resultDir)
}

tasks.register(&apos;createExcel&apos;, Csv2Excel) {
    dependsOn &apos;createParquet&apos;
    csvFile = csvFileObj
    firstLineIsHeader = true
    valueDelimiter = null
    valueSeparator = &quot;;&quot;
    models = modelName
    modeldir = defaultModelDir
    outputDir = file(resultDir)
}

tasks.register(&apos;createMeta&apos;, OgdMetaPublisher) {
    dependsOn &apos;createExcel&apos;
    configFile = file(tomlFileName)
    outputDir = resultDir
}

tasks.register(&apos;uploadFiles&apos;, S3Upload) {
    dependsOn &apos;createMeta&apos;
    accessKey = awsAccessKeyAgi
    secretKey = awsSecretAccessKeyAgi
    sourceFiles = fileTree(resultDir) { include &quot;*.parquet&quot; include &quot;*.xlsx&quot; include &quot;*.csv&quot; include &quot;*.xtf&quot; }
    endPoint = &quot;https://s3.eu-central-1.amazonaws.com&quot;
    region = &quot;eu-central-1&quot;
    bucketName = bucket
    acl = &quot;public-read&quot;
}

tasks.register(&apos;runIntegrationJob&apos;) {
    dependsOn &apos;uploadFiles&apos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die einzelnen Tasks führen überschaubare Aufgaben aus. Also z.B. Datei herunterladen oder CSV-Datei nach Excel umwandeln. Grundsätzlich gestalten wir unsere GRETL-Tasks immer so. Für die Geodatenpublikation haben wir jedoch einen sehr umfassenden &amp;laquo;Publisher&amp;raquo;-Task entwickeln lassen. Dieser exportiert die Daten aus der Datenbank, prüft diese, wandelt sie in unterschiedliche Format um und lädt die Dateien auf einen SFTP-Server. Dieser Task ist zwar sehr umfangreich aber er muss für unsere circa 150 Geodaten-Themen immer das Gleiche machen. Uns so kann es durchaus sinnvoll sein, einen sehr spezifischen und umfangreichen Task vorzuhalten. Auch im vorliegenden Fall könnte man früher oder später auf die Idee kommen, die Umwandlung der CSV-Datei, die Erstellung der Meta-Datei und das Hochladen zu kombinieren, da immer gleich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Ausführungsplattform und Orchestrator von GRETL-Jobs verwenden wir bei uns &lt;a href=&quot;https://www.jenkins.io/&quot;&gt;&lt;em&gt;Jenkins&lt;/em&gt;&lt;/a&gt;. Wir sind immer noch sehr zufrieden. Insbesondere mit der Flexibilität und den vielen Möglichkeiten, die man mit &lt;em&gt;Jenkins&lt;/em&gt; hat. Andere verwenden z.B. &lt;a href=&quot;https://gitlab.com/&quot;&gt;GitLab&lt;/a&gt;. Das ist so einfach möglich, weil &lt;em&gt;GRETL&lt;/em&gt; letzten Endes nichts anderes ist als ein Gradle-Build-Job. D.h. man benötigt &lt;em&gt;Gradle&lt;/em&gt; und &lt;em&gt;Java&lt;/em&gt;. Am besten packt man sämtliche Abhängigkeiten in ein Docker-Image und schon hat man eine eigene GRETL-Runtime, die nur Docker benötigt. Um den Integrationsprozess zu testen (gut, könnte man auch lokal) und weil &lt;em&gt;Jenkins&lt;/em&gt; und &lt;em&gt;GitLab&lt;/em&gt; durch sind, probiere ich es noch mit &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt;. Dazu mache ich mir ein GitHub-Repo mit &lt;a href=&quot;https://github.com/edigonzales/ogd-jobs&quot;&gt;sämtlichen Integrationsjobs&lt;/a&gt;. Zusätzlich benötige ich die &lt;a href=&quot;https://github.com/edigonzales/ogd-jobs/blob/main/.github/workflows/main.yaml&quot;&gt;Konfiguration&lt;/a&gt; der Action (aka Pipeline). Die Action-Konfiguration ist einfach und besteht aus einem parametrisierten Gradle-Job-Aufruf (Zeile 25). Der Benutzer muss beim Start den Job-Namen eintippen und dieser wird zur Laufzeit in der Action verwendet und es wird der gewünschte Integrationsjob ausgeführt. Im Job muss ein Standardtask (&lt;code&gt;defaultTask&lt;/code&gt;) definiert werden. Die Actionkonfiguration sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;name: ogd-job
on:
  workflow_dispatch:
    inputs:
      version:
        description: &apos;identifier?&apos;
        required: true

jobs:
  dataIntegration:
    env:
      ORG_GRADLE_PROJECT_awsAccessKeyAgi: ${{secrets.AWS_ACCESS_KEY_ID}}
      ORG_GRADLE_PROJECT_awsSecretAccessKeyAgi: ${{secrets.AWS_SECRET_ACCESS_KEY}}

    runs-on: ubuntu-latest

    container:
      image: sogis/gretl:latest

    steps:
      - uses: actions/checkout@v3

      - name: Run GRETL job
        run: |
          gradle -b ${{ github.event.inputs.version }}/build.gradle --init-script /home/gradle/init.gradle --no-daemon&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Interessante sind die Zeilen 17 und 18. Hier wird definiert &lt;em&gt;in&lt;/em&gt; welchem Container der Job laufen soll. Wir wählen unser GRETL-Image. Nachfolgende Action-Steps werden direkt in diesem Container ausgeführt. Wir könnten auch ohne Dockerimage auskommen. Dann müssen aber die Abhängigkeiten von GRETL (als Gradle-Plugin) aus verschiedenen Maven-Repositories herunterladen werden, d.h. die Repositories müssen online sein. Mit Docker-Container dünkt es mich eleganter und zuverlässiger.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Daten und die Meta-Dateien liegen nach der Integration auf einer öffentlich zugänglichen Ablage (hier S3). Um den Zugang niederschwelliger zu gestalten, ist ein kleines Frontend sicher nicht verkehrt. Dazu verwende ich als Grundlage einfach die Anwendung, die Rahmen unseres &lt;a href=&quot;https://data.geo.so.ch&quot;&gt;neuen Datenbezuges&lt;/a&gt; entstand. Et voilà: &lt;a href=&quot;https://sogis-sodata-ogd-vli2k.ondigitalocean.app/&quot; class=&quot;bare&quot;&gt;https://sogis-sodata-ogd-vli2k.ondigitalocean.app/&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-02/sodata-ogd-01.png&quot; alt=&quot;sodata ogd frontend&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anwendung wurde mit &lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;&lt;em&gt;GWT&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; umgesetzt. Die während der Integration hergestellten Meta-Dateien werden geparsed und die notwendigen Informationen landen in einem &lt;a href=&quot;https://lucene.apache.org/&quot;&gt;Lucene-Index&lt;/a&gt; (man geniesse und schätze das old school Webseiten-Layout!) für die Suche. Weil es sich bei den Meta-Dateien um INTERLIS-Transferdateien handelt, kann ich zum Parsen &lt;a href=&quot;https://github.com/claeis/iox-ili&quot;&gt;&lt;em&gt;iox-ili&lt;/em&gt;&lt;/a&gt; verwenden. Damit habe ich Zugriff auf die einzelnen Transferobjekte. Die Kommunikation (also die Antwort auf eine Suchanfrage) zwischen Client und Server basiert auf JSON. Aus diesem Grund muss ich aus dem geparsten XTF JSON-Objekte resp. -Strings herstellen können. Kostet mich nicht mehr als:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;IoxEvent event = xtfReader.read();
while (event instanceof IoxEvent) {
    if (event instanceof ObjectEvent) {
        ObjectEvent objectEvent = (ObjectEvent) event;
        IomObject iomObj = objectEvent.getIomObject();
        IomObject[] iomObjects = new IomObject[] {iomObj};
        Writer writer = new StringWriter();
        JsonGenerator jg = objectMapper.createGenerator(writer);
        Iox2jsonUtility.write(jg, iomObjects, td);
        jg.flush();
        jg.close();
        String jsonString = writer.toString();
        // do something with jsonString
    }
    event = xtfReader.read();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Umwandlung eines Iom-Objektes in einen JSON-String gibt es die Klasse &lt;a href=&quot;https://github.com/claeis/ili2db/blob/d30ee04aa484a803a3c352da91690495cf2fa3ae/src/ch/ehi/ili2db/json/Iox2jsonUtility.java#L27&quot;&gt;&lt;code&gt;Iox2jsonUtility&lt;/code&gt;&lt;/a&gt;. Es wird immer ein zusätzliche Attribut &lt;code&gt;@type&lt;/code&gt; erzeugt, welches der INTERLIS-Klasse (oder -Struktur) entspricht. Bei Objekten (im Gegensatz zu Strukturen) gibt es ein weiteres Zusatzattribut &lt;code&gt;@id&lt;/code&gt;, welches der &lt;code&gt;TID&lt;/code&gt; entspricht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;[
  {
    &quot;@type&quot;: &quot;SO_OGD_Metadata_20230629.Datasets.Dataset&quot;,
    &quot;@id&quot;: &quot;ch.so.agi.amtliche_vermessung_statistik&quot;,
    &quot;Identifier&quot;: &quot;ch.so.agi.amtliche_vermessung_statistik&quot;,
    &quot;Title&quot;: &quot;Statistische Kennzahlen der amtlichen Vermessung&quot;,
    &quot;Description&quot;: &quot;Statistische Kennzahlen der amtlichen Vermessung über Personal und Umsatz in den Jahren 1983 bis 2022.&quot;,
    &quot;Publisher&quot;: {
      &quot;@type&quot;: &quot;SO_OGD_Metadata_20230629.Office_&quot;,
      &quot;AgencyName&quot;: &quot;Amt für Geoinformation&quot;,
      &quot;Abbreviation&quot;: &quot;AGI&quot;,
      &quot;OfficeAtWeb&quot;: &quot;https://agi.so.ch&quot;,
      &quot;Email&quot;: &quot;mailto:agi@bd.so.ch&quot;,
      &quot;Phone&quot;: &quot;032 627 75 92&quot;
    },
    &quot;Theme&quot;: &quot;Statistik,Amtliche Vermessung&quot;,
    &quot;Keywords&quot;: &quot;Statistik,Amtliche Vermessung&quot;,
    &quot;StartDate&quot;: &quot;1983-01-01&quot;,
    &quot;EndDate&quot;: &quot;2022-12-31&quot;,
    &quot;Resources&quot;: [
      {
        &quot;@type&quot;: &quot;SO_OGD_Metadata_20230629.Resource&quot;,
        &quot;Identifier&quot;: &quot;ch.so.agi.amtliche_vermessung_statistik.umsatz&quot;,
        &quot;Title&quot;: &quot;Umsatz pro Jahr&quot;,
        &quot;Description&quot;: &quot;Umsatz pro Jahr. Anzahl Gebäudemutationen und Grundstücksmutationen und Gesamtumsatz in Franken.&quot;,
        &quot;Model&quot;: {
          &quot;@type&quot;: &quot;SO_OGD_Metadata_20230629.ModelLink&quot;,
          &quot;Name&quot;: &quot;SO_AGI_Amtliche_Vermessung_Statistik_Umsatz_20230625&quot;,
          &quot;LocationHint&quot;: &quot;https://geo.so.ch/models&quot;
        },
     ....&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Integration und Publikation geschafft. Als nächstes kommt Visualisierung und Data Crunching.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>OGD made easy #1 - csv2parquet</title>
      <link>http://blog.sogeo.services/blog/2023/07/10/ogd-made-easy-01.html</link>
      <pubDate>Mon, 10 Jul 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/07/10/ogd-made-easy-01.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ob &lt;a href=&quot;https://so.ch&quot;&gt;wir&lt;/a&gt; überhaupt mal OGD machen werden, steht noch in den Sternen. Es schadet aber wohl nichts, sich ein paar Gedanken zu den Abläufen und dem Einsatz und Zusammenspiel einzelner Komponenten zu machen. Synergien zu den Prozessen und den Werkzeugen unserer &lt;a href=&quot;https://geo.so.ch/&quot;&gt;GDI&lt;/a&gt; gibt es mit genügend grosser Wahrscheinlichkeit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;CSV-Dateien spielen anscheinend eine &lt;a href=&quot;https://www.stadt-zuerich.ch/portal/de/index/ogd/werkstatt/csv.html&quot;&gt;grosse&lt;/a&gt; &lt;a href=&quot;https://www.zh.ch/de/politik-staat/opendata/leitlinien.html#-932898780&quot;&gt;Rolle&lt;/a&gt;. Gehen wir also davon aus, dass Fachstellen ihre offenen Daten im CSV-Format anliefern. Ein paar Fragen, die sich stellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Wie beschreibt man CSV-Daten? (z.B. Was bedeutet das Attribut &amp;lt;xxx&amp;gt; genau?)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wie kontrolliert man CSV-Daten?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Soll man neben CSV (und dem Klassiker XLSX) noch weitere, &amp;laquo;bessere&amp;raquo; Formate bereitstellen?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ersten beiden Fragestellungen lassen sich mit INTERLIS erledigen. Eine Antwort auf die letzte Frage könnte &lt;a href=&quot;https://en.wikipedia.org/wiki/Apache_Parquet&quot;&gt;Parquet&lt;/a&gt; sein. Parquet ist ein spaltenorientiertes Dateiformat. Die Spezifikation ist quelloffen. Es eignet sich gut für die Abfrage und Verarbeitung von Daten. Und vielleicht am Profansten: Es gibt Datentypen, Rätselraten war gestern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was nun CSV mit INTERLIS zu tun hat und wie man eine CSV-Datei in eine Parquet-Datei umwandeln kann, zeige ich anhand meines kleinen Prototypes &lt;a href=&quot;https://github.com/edigonzales/csv2parquet&quot;&gt;&lt;em&gt;csv2parquet&lt;/em&gt;&lt;/a&gt;. Es handelt sich dabei um eine Java-Kommandozeilenanwendung und dient momentan vor allem Anschauungs- und Testzwecken. Früher oder später würde die Umwandlung bei uns wohl in unserem ETL-Werkzeug &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; integriert werden. Heruntergeladen werden kann die aktuellste Version &lt;a href=&quot;https://github.com/edigonzales/csv2parquet/releases/&quot;&gt;hier&lt;/a&gt;. Die Zip-Datei entpacken und das Shell-Skript resp. die Batch-Datei auführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;./bin/csv2parquet --help&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Konsole sollte der Hilfetext erscheinen. Die Anforderungen an die Anwendung sind moderat (Java 8 reicht). Leider muss man gefühlt das halbe Internet herunterladen. Die Zip-Datei ist circa 70MB gross. Der Grund dafür sind die - für mich und viele anderen - unnötigen Hadoop-Abhängigkeiten der &lt;a href=&quot;https://github.com/apache/parquet-mr&quot;&gt;Parquet-Bibliothek&lt;/a&gt;. Es gibt einen &lt;a href=&quot;https://issues.apache.org/jira/browse/PARQUET-1822?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel&quot;&gt;Issue&lt;/a&gt; dazu. Vergessen wir das aber wieder und konzentrieren uns auf die Funktionen und erstellen die erste Parquet-Datei aus einer CSV-Datei. Ausgewählt habe ich die &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/csv2parquet/b9172dd298f7b55a45eb89e4deb0b5009de58300/src/test/data/bewilligte_erdwaermeanlagen/bewilligte_erdwaermeanlagen.csv&quot;&gt;bewilligten Erdwärmeanlagen&lt;/a&gt;. Das &lt;a href=&quot;https://afu.so.ch&quot;&gt;Amt für Umwelt&lt;/a&gt; stellt viele Daten bereits heute freundlicherweise &lt;a href=&quot;https://so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-umwelt/umweltdaten/&quot;&gt;online&lt;/a&gt;. Der einfachst mögliche Aufruf ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;./bin/csv2parquet -i bewilligte_erdwaermeanlagen.csv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit das einfach so funktioniert, werden von der Software Annahmen bezüglich des Trennzeichens (Separator) und des Feldtrenners (Delimiter) getroffen. Standardwert für das Trennzeichen ist ein Semikolon und für den Feldtrenner ein leeres Zeichen (also kein Feldtrenner). Das entspricht dem Output von Excel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;jahr;anfragen;bewilligte_anlagen;durchschnittlicher_oelpreis_pro_1000_liter;bewilligte_erdsonden;bewilligte_erdkollektoren;sondenlaenge_km;heizleistung_kw;internet_clicks_durchschnitt_pro_monat
1991;28;28;;26;2;;;
1992;67;33;;26;7;;;
1993;99;26;;24;2;;;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn kein Zielverzeichnis (&lt;code&gt;--output&lt;/code&gt;) angegeben wird, wird versucht die Parquet-Datei in das Quellverzeichnis zu schreiben. Jetzt haben wir zwar eine Parquet-Datei aber wie kann man sie anschauen? Für ganz rasches Quick &apos;n&apos; Dirty reicht mir (eher aus Entwicklersicht) ein Online-Viewer, z.B. &lt;a href=&quot;https://www.parquet-viewer.com/&quot; class=&quot;bare&quot;&gt;https://www.parquet-viewer.com/&lt;/a&gt;. Der Viewer hat anscheinend gerade in den letzten paar Tagen ein Update erfahren und einige Features sind nicht mehr gratis. Es gibt aber auch noch andere Möglichkeiten relativ einfach eine Parquet-Datei anzuschauen: Und zwar mit &lt;a href=&quot;https://dbeaver.io/&quot;&gt;&lt;em&gt;dbeaver&lt;/em&gt;&lt;/a&gt;. Wenn man &lt;em&gt;dbeaver&lt;/em&gt; sowieso in Betrieb hat, muss man einmalig einen zusätzlichen &lt;a href=&quot;https://duckdb.org/docs/guides/sql_editors/dbeaver.html&quot;&gt;Treiber installieren&lt;/a&gt; (basierend auf &lt;em&gt;DuckDB&lt;/em&gt;). Wurde der Treiber installiert, kann man mit einem simplen &lt;a href=&quot;https://duckdb.org/docs/guides/import/parquet_import&quot;&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/a&gt; die Daten anzeigen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-01/dbeaver01.png&quot; alt=&quot;Parquet in dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;DESCRIBE SELECT&lt;/code&gt; kann man Informationen über die Attribute anzeigen und es fällt auf, dass der Datentyp immer Text ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-01/dbeaver02.png&quot; alt=&quot;Describe Parquet in dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie man das ändern kann, zeige ich gleich. Vorher möchte ich erläutern, wie man mit unterschiedlichen Trennzeichen und Feldtrennern umgeht. Als Beispiel verwende ich den gleichen &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/csv2parquet/8a8b611928eb03be56d50f30a39ca31360dbfa24/src/test/data/bewilligte_erdwaermeanlagen/bewilligte_erdwaermeanlagen_komma_anfuehrungszeichen.csv&quot;&gt;Datensatz&lt;/a&gt; aber mit einem Komma als Trennzeichen und Anführungszeichen als Feldtrenner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-csv&quot; data-lang=&quot;csv&quot;&gt;&quot;jahr&quot;,&quot;anfragen&quot;,&quot;bewilligte_anlagen&quot;,&quot;durchschnittlicher_oelpreis_pro_1000_liter&quot;,&quot;bewilligte_erdsonden&quot;,&quot;bewilligte_erdkollektoren&quot;,&quot;sondenlaenge_km&quot;,&quot;heizleistung_kw&quot;,&quot;internet_clicks_durchschnitt_pro_monat&quot;
&quot;1991&quot;,&quot;28&quot;,&quot;28&quot;,&quot;&quot;,&quot;26&quot;,&quot;2&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;
&quot;1992&quot;,&quot;67&quot;,&quot;33&quot;,&quot;&quot;,&quot;26&quot;,&quot;7&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;
&quot;1993&quot;,&quot;99&quot;,&quot;26&quot;,&quot;&quot;,&quot;24&quot;,&quot;2&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich &lt;em&gt;csv2parquet&lt;/em&gt; gleich wie im ersten Aufruf verwende, wird das mit einem Fehler (&amp;laquo;org.apache.avro.SchemaParseException&amp;raquo;) quittiert. Das Problem ist eben, dass die Zeilen nicht geparsed werden können. Wir müssen &lt;em&gt;csv2parquet&lt;/em&gt; mit dem richtigen Feld- und Trennzeichen konfigurieren. Das geschieht mit einer TOML-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;[&quot;ch.so.afu.bewilligte_erdwaermeanlagen&quot;]
firstLineIsHeader=true
valueSeparator=&quot;,&quot;
valueDelimiter=&quot;\&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was zwischen den eckigen Klammern steht, spielt momentan noch keine Rolle. Wichtig ist jedoch, dass der Text dazwischen immer zwischen Anführungszeichen steht. Die drei Optionen sind selbsterklärend. Das Anführungszeichen als Feldtrenner muss escaped werden. Der Aufruf ändert sich zu:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;./bin/csv2parquet -c config.toml -i bewilligte_erdwaermeanlagen_komma_anfuehrungszeichen.csv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es resultiert wiederum eine Parquet-Datei, die aus reinen Text-Attributen besteht. Als weitere Option steht &lt;code&gt;encoding&lt;/code&gt; zur Verfügung, um z.B. &lt;code&gt;ISO-8859-1&lt;/code&gt; encodierte CSV-Dateien korrekt lesen zu können (Standard ist &lt;code&gt;UTF-8&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was augenfällig sein sollte, ist die Tatsache, dass man immer den Feldtrenner verwenden &lt;em&gt;muss&lt;/em&gt;. Man kann also keine CSV-Dateien verwenden, in der z.B. Texte mit Anführungszeichen begrenzt werden, numerische Werte aber nicht. Zuerst fand ich das nicht gut, dann sehr gut, nun bin ich eher hin- und hergerissen. Malesh, ist nun mal so und müsste in der darunterliegenden Bibliothek geändert werden und verliert vor allem den Schrecken, wenn wir endlich INTERLIS ins Spiel bringen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man will wahrscheinlich die Daten, die geliefert werden, für die Anwender relativ gut beschreiben. Beschreibe ich die Daten akkurat, kann ich diese entsprechend der Beschreibug auch prüfen. Dank der Beschreibung der Daten kann ich ebenfalls den Datentyp in der Parquet-Datei definieren und muss mich nicht mit &amp;laquo;Text&amp;raquo; zufrieden geben. Für das alles kann man INTERLIS und sein Tool-Ökosystem verwenden. Auf der Hand liegt, dass für die Beschreibung ein INTERLIS-Modell verwendet wird. In unserem Fall mit den bewilligten Erdwärmeanlagen kann das so aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

MODEL SO_AFU_Bewilligte_Erdwaermeanlagen_20230616 (de)
AT &quot;https://agi.so.ch&quot;
VERSION &quot;2023-06-16&quot;  =

  TOPIC Bewilligte_Erdwaermeanlagen =

    CLASS Bewilligte_Erdwaermeanlagen =
      /** Erhebungsjahr
       */
      jahr : MANDATORY INTERLIS.GregorianYear;
      /** Anzahl Anfragen
       */
      anfragen : MANDATORY 0 .. 100000;
      /** Anzahl bewilligte Anlagen
       */
      bewilligte_anlagen : 0 .. 100000;
      /** Durchschnittlicher Erdölpries pro 1000 Liter
       */
      durchschnittlicher_oelpreis_pro_1000_liter : 0 .. 10000;
      /** Bewilligte Erdsonden
       */
      bewilligte_erdsonden : 0 .. 100000;
      /** Bewilligte Erdkollektoren
       */
      bewilligte_erdkollektoren: 0 .. 100000;
      /** Gesamte Bohrmeter / Sondenlänge in Kilometer
       */
      sondenlaenge_km : 0.000 .. 10000.000;
      /** Gesamte Heizleistung in Kilowatt
       */
      heizleistung_kw : 0.000 .. 10000.000;
      /** Anzahl Onlineanfragen via Web GIS Client
       */
      internet_clicks_durchschnitt_pro_monat : 0 .. 100000;
    END Bewilligte_Erdwaermeanlagen;

  END Bewilligte_Erdwaermeanlagen;

END SO_AFU_Bewilligte_Erdwaermeanlagen_20230616.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ins Auge springen zwei Dinge: die Beschreibung der Attribute und natürlich die Definition der Datentypen. Im vorliegenden Fall nicht sonderlich spannend, aber immerhin gibt es &amp;laquo;Jahr&amp;raquo;, &amp;laquo;Integer&amp;raquo; und sowas wie &amp;laquo;Double&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schön und gut aber wie hilft mir das weiter? Hier kommt die &lt;a href=&quot;https://github.com/claeis/iox-ili&quot;&gt;iox-ili&lt;/a&gt;- resp. &lt;a href=&quot;https://github.com/claeis/iox-wkf&quot;&gt;iox-wkf&lt;/a&gt;-Bibliothek zum Zuge. Sie werden zwar vor allem in &lt;em&gt;ili2db&lt;/em&gt; und &lt;em&gt;ilivalidator&lt;/em&gt; eingesetzt, man kann damit eben auch z.B einen CSV-Reader oder Parquet-Writer implementieren. Ersteres haben wir vor Jahren bereits für &lt;a href=&quot;https://github.com/sogis/gretl/&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; gemacht. Hat man einen solchen IOX-Reader, ist es nicht mehr weit zum CSV-Validator, der sich ebenfalls leicht implementieren lässt und somit gleich/ähnlich wie &lt;em&gt;ilivalidator&lt;/em&gt; funktioniert. Der Clou ist, dass das alles mit einem INTERLIS-Modell gesteuert wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für mich hiess das, ich musste vor allem zuerst den &lt;a href=&quot;https://github.com/edigonzales/iox-parquet&quot;&gt;Parquet-IOX-Writer&lt;/a&gt; programmieren. Den CSV-IOX-Reader und den CSV-Validator gab es in &lt;em&gt;GRETL&lt;/em&gt; bereits (bissle copy/paste mit Anpassungen&amp;#8230;&amp;#8203;). Der Parquet-IOX-Writer hat mich einiges an Nerven gekostet: Der ganze Umgang mit Datum und Zeit scheint mir für Normalsterbliche mühsam. Grundsätzlich kennt Parquet UTC-berichtigte und lokale Zeiten. Ich wollte lokale Zeiten verwenden. Leider gab es einen hässlichen Bug. Aber Open Source to the Rescue: &lt;a href=&quot;https://github.com/apache/parquet-mr/pull/1115&quot;&gt;Man fixe es halt&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die TOML-Datei muss nun um eine Zeile erweitert werden. Es muss definiert werden, welches Modell verwendet werden soll. Die Modelldatei darf dabei in einem Modellrepository sein oder im gleichen Verzeichnis wie die CSV-Datei vorliegen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;[&quot;ch.so.afu.bewilligte_erdwaermeanlagen&quot;]
firstLineIsHeader=true
valueSeparator=&quot;;&quot;
models=&quot;SO_AFU_Bewilligte_Erdwaermeanlagen_20230616&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich verwende wieder die erste CSV-Datei (Export aus Excel). Aus diesem Grund habe ich &amp;laquo;valueDelimiter&amp;raquo; entfernt. Der Aufruf bleibt gleich wie vorhin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;./bin/csv2parquet -c config.toml -i bewilligte_erdwaermeanlagen.csv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Konsole erscheint der bekannte Output von &lt;em&gt;ilivalidator&lt;/em&gt;. Die resultierende Parquet-Datei schauen wir uns in &lt;em&gt;dbeaver&lt;/em&gt; nochmals an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ogd-made-easy-01/dbeaver03.png&quot; alt=&quot;Describe Parquet with data types in dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und siehe da: plötzlich sind da unterschiedliche Datentypen. Die Konfig-Datei könnte zukünftig in einem &lt;a href=&quot;http://blog.sogeo.services/blog/2023/05/10/interlis-leicht-gemacht-number-35.html&quot;&gt;Datenrepository bereitgestellt&lt;/a&gt; werden. Dann würde ein Aufruf à la &lt;code&gt;-c ilidata:&amp;lt;identifier&amp;gt;&lt;/code&gt; reichen und sie müsste nicht lokal vorliegen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Verwendung von &lt;em&gt;ilivalidator&lt;/em&gt; und INTERLIS als Validierungskomponente steht nun die Türe offen für alles was die beiden hergeben. Ein anderer &lt;a href=&quot;https://raw.githubusercontent.com/edigonzales/csv2parquet/2b1e930754e0f618c705f9b929bb01c59167747b/src/test/data/abfallmengen_gemeinden/abfallmengen_gemeinden.csv&quot;&gt;Test-Datensatz&lt;/a&gt; beinhaltet die Abfallmengen pro Gemeinde pro Jahr und pro Abfallart. Das Modell sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

MODEL SO_AFU_Abfallmengen_Gemeinden_20230629 (de)
AT &quot;https://afu.so.ch&quot;
VERSION &quot;2023-06-29&quot;  =

  TOPIC Abfallmengen_Gemeinden =

    CLASS Abfallmengen_Gemeinden =
      /** Erhebungsjahr
       */
      jahr : MANDATORY INTERLIS.GregorianYear;
      /** Art des Abfalls
       */
      abfallart : MANDATORY TEXT*100;
      /** Kilogramm Abfall pro Einwohner
       */
      abfall_kg_pro_einwohner : MANDATORY 0.00 .. 1000.00;
      /** Was bedeutet das genau?
       */
      wiederverwertung : MANDATORY (ja, nein);
      /** Aufzählabfallartersatz und Showcase für Constraints.
       */
      !!@ ilivalid.msg = &quot;Falscher Wert im Attribut &apos;abfallart&apos;: &apos;{abfallart}&apos;&quot;
      MANDATORY CONSTRAINT abfallart==&quot;Kehricht&quot; OR abfallart==&quot;Kehricht / Sperrgut&quot; OR abfallart==&quot;Papier / Karton&quot; OR abfallart==&quot;Grüngut&quot; OR abfallart==&quot;Textil&quot; OR abfallart==&quot;Weissblech&quot; OR abfallart==&quot;Aluminium&quot; OR abfallart==&quot;Metalle&quot; OR abfallart==&quot;Motoren / Speiseöl&quot; OR abfallart==&quot;Sonderabfälle&quot; OR abfallart==&quot;Strassensammlerschlamm&quot; OR abfallart==&quot;Wischgut&quot; OR abfallart==&quot;Glas (Glasbruch + Glassand)&quot;;
    END Abfallmengen_Gemeinden;

  END Abfallmengen_Gemeinden;

END SO_AFU_Abfallmengen_Gemeinden_20230629.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Modell verwendet für das Attribut &lt;code&gt;wiederverwendung&lt;/code&gt; einen Aufzähltyp, d.h. es darf nur &lt;code&gt;ja&lt;/code&gt; oder &lt;code&gt;nein&lt;/code&gt; als Wert verwendet werden. Sehr hässlich aber wirkungsvoll ist der &lt;code&gt;MANDATORY CONSTRAINT&lt;/code&gt;. Er dient als Ersatz für einen Aufzähltyp und prüft, ob nur erlaubte Abfallarten vorhanden sind. Man ist mit der Prüfung nicht auf das einzelne Objekte / den einzelnen Record eingeschränkt. Mit einem &lt;code&gt;SET CONSTRAINT&lt;/code&gt; lassen sich Dinge über verschiedene Objekte / den ganzen Datensatz hinweg prüfen. So als spontanes Beispiel: Die Gesamtzahl der gelieferten Objekte darf einen bestimmten Wert nicht überschreiten: &lt;code&gt;SET CONSTRAINT INTERLIS.objectCount(ALL)==100;&lt;/code&gt;. Oder noch bisschen exotischer ein Plausiblity Constraint, der prüft, ob ein Prozentteil der Objekte eine Bedingung erfüllen (z.B. &lt;code&gt;abfall_kg_pro_einwohner&lt;/code&gt; muss in mindestens 30 Prozent der Fälle kleiner als 500 Kilogramm sein). Alles Dank INTERLIS frei Haus: korrekte Datentypen und eine sehr mächtige Datenprüfung.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #37 - The State of INTERLIS native (Summer 2023)</title>
      <link>http://blog.sogeo.services/blog/2023/06/22/interlis-leicht-gemacht-number-37.html</link>
      <pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/06/22/interlis-leicht-gemacht-number-37.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.sogeo.services/blog/2022/11/01/interlis-leicht-gemacht-number-31.html&quot;&gt;Letzten Herbst&lt;/a&gt; habe ich &lt;a href=&quot;https://github.com/edigonzales/ili2c-native/releases&quot;&gt;&lt;em&gt;ili2c&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/edigonzales/ili2pg-native/releases&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-native/releases&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; mit &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; zu einem &lt;a href=&quot;https://www.graalvm.org/latest/reference-manual/native-image/&quot;&gt;Native Image&lt;/a&gt; kompiliert. Daraus resultiert - im Gegensatz zu den &lt;a href=&quot;https://downloads.interlis.ch&quot;&gt;offiziell publizierten&lt;/a&gt; Versionen - eine betriebssystemabhängige Variante, die jedoch keine Java-Installation benötigt. Das Kompilieren übernimmt eine Github Action und somit können mindestens drei Betriebssysteme (Windows, Ubuntu-Linux, macOS) problemlos angeboten werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einen Haken hat die ganze Sache aber: Die Performance ist &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/364&quot;&gt;massiv schlechter&lt;/a&gt; als bei den Java-Varianten. Dies wird vor allem bei der Prüfung mit ilivalidator zum Problem. Es gab bereits relativ lange eine Enterprise-Version von GraalVM, die sich diesem Problem annahm. Die Java-Variante kann in unserem Fall anscheinend massiv vom JIT-Compiler profitieren und während der Laufzeit immer mehr den Code optimieren. Das ist mit einem Native Image nicht mehr möglich. Das muss alles vorher passieren. Dies ist bei der Enterprise-Variante mit &lt;a href=&quot;https://www.graalvm.org/22.0/reference-manual/native-image/PGO/&quot;&gt;&amp;laquo;Profile-Guided Optimizations&amp;raquo;&lt;/a&gt; möglich. Die Anwendung wird zuerst geprofiled und dabei Informationen gesammelt. Mit diesen Informationen wird  anschliessend der Native Image Builder gefüttert und daraus sollte eine bessere Performance resultieren. Eine zweite Baustelle ist der Garbager Collector. Die Community-Variante verfügt nur über den Serial GC, die Enterprise-Variante zusätzlich über den Garbage First Garbage Collector (G1GC), jedoch nur unter Linux. Nun hat aber auch die Enterprise-Variante einen Haken und man vermutet wohl richtig: Enterprise == $.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem neusten GraalVM-Release (Version 23) gibt es keine Enterprise-Variante mehr. Der Nachfolger heisst &amp;laquo;Oracle GraalVM&amp;raquo; und ist &lt;a href=&quot;https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5&quot;&gt;frei&lt;/a&gt;. Man sollte sich sicherheitshalber wohl die &lt;a href=&quot;https://www.oracle.com/downloads/licenses/graal-free-license.html&quot;&gt;&amp;laquo;GraalVM Free Terms and Conditions (GFTC) license&amp;raquo;&lt;/a&gt; und die &lt;a href=&quot;https://www.oracle.com/java/technologies/javase/jdk-faqs.html#GraalVM-licensing&quot;&gt;FAQ&lt;/a&gt; gut durchlesen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit all den Enterprise-Feature, die nun frei verfügbar sind, steht einem Performance-Vergleich nichts mehr im Wege. Geprüft wird ilivalidator 1.13.3 mit unserem &lt;a href=&quot;https://data.geo.so.ch/proxy?file=https://files.geo.so.ch/ch.so.alw.fruchtfolgeflaechen/aktuell/ch.so.alw.fruchtfolgeflaechen.xtf.zip&quot;&gt;Fruchtfolgeflächen-Datensatz&lt;/a&gt;. Zuerst habe ich nur die einzelnen Objekte geprüft (&lt;code&gt;--singlePass&lt;/code&gt;). Das hat zur Folge, dass die AREA-Prüfung nicht ausgeführt wird. Als Testrechner habe einen Hetzner-Cloud-Server verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Variante&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Standard&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;1:33&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;1:00&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO und G1GC&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;1:04&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JVM&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:33&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine spürbare Verbesserung vor allem dank PGO ist sichtbar. Gegen die JVM-Variante haben die Native Images aber immer noch keine Chance. Wie sieht es aus, wenn ich das XTF komplett prüfe (also v.a. inklusive der AREA-Prüfung):&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Variante&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Standard&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;13:32&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;10:13&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO und G1GC&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;8:19&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JVM&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;6:08&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Unterschiede werden grösser. Die PGO+G1GC-Variante ist fünf Minuten schneller als die Standardvariante. Es gibt zwar immer noch eine Lücke zur reinen Java-Variante zu schliessen, aber absolut faszinierend was sich rausholen lässt mit einem anderen Garbage Collector und den PGO.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie sieht es bei &lt;em&gt;ili2pg&lt;/em&gt; aus? Die Datenbank läuft in einem Docker-Container. Die Prüfung des Datensatzes wurde ausgeschaltet (&lt;code&gt;--disableValidation&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Variante&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Standard&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:40&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:35&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PGO und G1GC&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:19&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JVM&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:20&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es bräuchte wohl grössere Datensätze, um hier grössere Abstände zu sehen. Jedenfalls scheint PGO+G1GC auch hier gut mithalten zu können. Der Einfluss der Anwendung dürfte auf die reine Ausführungszeit geringer sein, da die Datenbank natürlich viel abarbeiten muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;em&gt;ilitools&lt;/em&gt; als Native Image werden immer interessanter und dank der frei verfügbaren Oracle GraalVM Distribution ist die Herstellung auch kein Problem mehr.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #36 - Mapping rules matter und weitere Performancebeobachtungen</title>
      <link>http://blog.sogeo.services/blog/2023/05/14/interlis-leicht-gemacht-number-36.html</link>
      <pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/05/14/interlis-leicht-gemacht-number-36.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich bin zur Zeit am &lt;a href=&quot;https://github.com/edigonzales/sodata-stac&quot;&gt;Entwickeln einer Anwendung&lt;/a&gt;, welche die Geodaten des Kantons als &lt;a href=&quot;https://stacspec.org&quot;&gt;STAC-Katalog&lt;/a&gt; bereitstellt. Im Prinzip ist sie in einer ersten, einfachen Form fertig, nur konnte ich sie bei uns noch nicht in Betrieb nehmen, weil der OPTIONS-Request in der Firewall gesperrt ist. Dieser wird benötigt, wenn der STAC-Browser unseren Katalog anzapfen will. Was bei uns aufgrund der Umstände eine Woche geht, dauert bei Digitalocean eine Stunde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JSON: &lt;s&gt;https://sogis-sodata-stac-fvxwq.ondigitalocean.app/catalog.json&lt;/s&gt; &lt;a href=&quot;https://data.geo.so.ch/stac/catalog.json&quot; class=&quot;bare&quot;&gt;https://data.geo.so.ch/stac/catalog.json&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;STAC-Browser: &lt;s&gt;https://radiantearth.github.io/stac-browser/#/external//sogis-sodata-stac-fvxwq.ondigitalocean.app/catalog.json?.language=en&lt;/s&gt; &lt;a href=&quot;https://radiantearth.github.io/stac-browser/#/external//data.geo.so.ch/stac/catalog.json?.language=en&quot; class=&quot;bare&quot;&gt;https://radiantearth.github.io/stac-browser/#/external//data.geo.so.ch/stac/catalog.json?.language=en&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Input dient mir die XML-Datei, welche aus unserer Metadatenbank exportiert wird. Die erste Frage, die sich mir stellte: Wie kommen die Daten in eine Datenbank? Am einfachsten schien mir der Weg über ein INTERLIS-Modell. Ich schreibe ein Modell, das sehr nahe an der STAC-Spezifikation ist (JSON-Schemas) und bringe mit XSLT die XML-Datei in die korrekte Form. Anschliessend importiere ich die Daten mit &lt;em&gt;ili2pg&lt;/em&gt; in eine PostgreSQL-Datenbank. Den Ansatz verwenden wir übrigens auch für die eCH-Objektwesen-Meldungen. Die kommen ebenfalls in einer XML-Datei daher und müssten mit einer 1:1-Schnittstelle importiert werden. Verstehe wer will.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein work-in-progress &lt;a href=&quot;https://geo.so.ch/models/AGI/SO_AGI_STAC_20230426.ili&quot;&gt;INTERLIS-STAC-Datenmodell&lt;/a&gt; ist sehr einfach und besteht aus einer Klasse. In der Klasse gibt es Attribute, die wiederum aus Listen von Strukturen bestehen: So enthält eine &amp;laquo;Collection&amp;raquo; ein oder mehrere &amp;laquo;Items&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Entwickeln habe ich eine XTF-Datei verwendet, die drei Collections enthält. Der Import in die Datenbank ging sehr schnell, so dass es nicht störte, dass dies bei jedem live reload (d.h. nach jeder Codeänderung) gemacht wurde. Als es darum ging das System mit einer kompletten XTF-Datei zu testen, war ich verwirrt. Wir haben 93 Collections, der Import ging aber gefühlt ewig. Ich kam aus dem Staunen nicht mehr raus. Erst die &lt;code&gt;--trace&lt;/code&gt;-Option hat mir geholfen. Ich sah sehr viele SQL-Befehle und dann ging mir ein Licht auf: Obwohl ich nur 93 Objekte in die Datenbank importieren muss, sind viel mehr SQL-Inserts notwendig. Es gibt insgesamt circa 70&apos;000 Items. Items sind im INTERLIS-Datenmodell als Struktur modelliert. Strukturen werden standardmässig als Tabelle in der Datenbank abgebildet. Neben den Items sind noch viele weitere Attrbute als Strukturen modelliert. Insbesondere schmerzen natürlich Attribute der Item-Struktur, weil diese  wiederum sehr viele separate Insert-Statements auslösen (weil es viele Items gibt).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was machen? JSON to the rescue. &lt;em&gt;Ili2db&lt;/em&gt; hat die Fähigkeit Strukturen nicht als Tabellen in der Datenbank abzubilden, sondern als JSON-Objekt. Die JSON-Objekte sind in der &amp;laquo;Mutter-Tabelle&amp;raquo; in einem JSON-Feld (oder je nach Datenbank: Text-Feld) gespeichert. Wichtig scheint mir der Hinweis, dass es mit dieser Abbildungvariante für Strukturen keinen Informationsverlust gibt. Die Daten können auch wieder exportiert werden. Damit die Strukturen als JSON-Objekt abgebildet werden, muss das Attribut im Datenmodell entsprechend annotiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;STRUCTURE Item =
    Identifier : MANDATORY TEXT;
    Title : TEXT;
    Date: MANDATORY FORMAT INTERLIS.XMLDate &quot;1990-1-1&quot; .. &quot;2100-12-31&quot;;
    !!@ili2db.mapping=JSON
    Boundary : BoundingBox;
    Geometry : MTEXT; !! FIXME
    !!@ili2db.mapping=JSON
    Assets : BAG {1..*} OF Asset;
END Item;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige Attribute hatte ich bereits annotiert gehabt. Nur die BoundingBox des Items nicht. Und allein dieses führte zur gefühlt ewigen Importdauer. Die nachfolgende Tabelle zeigt die Dauer eines Imports in Abhängigkeit der Strukturabbildungsregeln (ohne JSON, alles mit JSON, alles mit JSON ausser Items):&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Abbildungsregel&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ohne JSON&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;9:39&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;alles mit JSON&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0:10&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;alles mit JSON (ausser Items)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;2:45&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Zeiten sind mit Vorsicht zu geniessen und würden in der Produktion nicht ganz so extrem ausfallen. Die Tests habe ich auf macOS mit einer gedockerten Datenbank gemacht. In dieser Kombi ist Datenbank-I/O relativ lahm.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie erwähnt hatte ich bereits einige der Strukturen bewusst als JSON-Objekt abbilden lassen. Der Grund dafür war, dass ich mich entschieden hatte, sämtliche Logik zum Herstellen der JSON-Dateien (gemäss STAC-Spezifikation) in der Datenbank mit den &lt;a href=&quot;https://www.postgresql.org/docs/15/functions-json.html&quot;&gt;PostgreSQL-JSON-Funktionen&lt;/a&gt; zu machen. Zu dieser Thematik gibt es &lt;a href=&quot;https://www.crunchydata.com/blog/generating-json-directly-from-postgres&quot;&gt;interessante&lt;/a&gt; &lt;a href=&quot;https://blog.jooq.org/stop-mapping-stuff-in-your-middleware-use-sqls-xml-or-json-operators-instead/&quot;&gt;Artikel&lt;/a&gt;. Die Businesslogik in der Anwendung selber ist nun praktisch inexistent. Es werden nur Controller benötigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil die Anzahl der DB-Queries dermassen matchentscheidend ist, habe ich ein paar weitere Vergleiche gemacht. Wenn die Anzahl der Queries wichtig ist, ist entsprechend die Geschwindigkeit der Query auch entscheidend. Weniger die Dauer direkt auf dem DB-Server, sondern die Dauer vom Absetzen des Requests in der Anwendung bis zur Antwort. Dazu habe ich bei Digitalocean eine gemanagte Datenbank erstellt und verglich wie lange ein Import meiner 93 Objekte dauerte. Als Abbildungsregel habe ich &amp;laquo;alles mit JSON (ausser Items)&amp;raquo; verwendet. Einmal importierte ich die Daten vom lokalen Rechner und einmal von einem Digitalocean-Server im gleichen Rechenzentrum.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3334%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Ort&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;ping (ms)&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;lokal&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;24&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;46:52&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Rechenzentrum&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0.7&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;01:51&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ping-Spalte zeigt die Dauer eines PING-Requests. Unterschied ist krass aber ziemlich plausibel. Netzwerkrequests sind halt einfach teuer. Dazu ein &lt;a href=&quot;https://blog.jooq.org/the-cost-of-jdbc-server-roundtrips/&quot;&gt;guter Artikel&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei &lt;em&gt;ili2db&lt;/em&gt; kommt noch hinzu, dass für jeden Record, der in die Datenbank geschrieben wird, ein &lt;code&gt;SELECT nextval($1)&lt;/code&gt; gemacht wird. De fakto eine Verdoppelung der Requests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt in &lt;em&gt;ili2db&lt;/em&gt; die Option &lt;code&gt;--importBatchSize rows&lt;/code&gt;. Mit diesem können die INSERT-Befehle gebündelt werden (analog dazu &lt;code&gt;--exportFetchSize rows&lt;/code&gt;). Vergleich mit/ohne batch-Option, jeweils vom lokalen Rechner zur Digitalocean-DB:&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;importBatchSize&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Dauer (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;n/a&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;69:25&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;5000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;35:14&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Zeiten sind nicht mit den Tests (lokal vs Rechenzentrum) vergleichbar, da ich eine andere Internetverbindung verwendet habe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Conclusion (aus der Sicht eines möglichst schnellen Imports):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Es muss eine sehr schnelle (latenzarme) Verbindung zwischen &lt;em&gt;ili2db&lt;/em&gt; und der Datenbank vorhanden sein.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Falls möglich sollten Strukturen mit JSON abgebildet werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Requests sollten gebatched werden. Dies würde ich aber noch vertiefter prüfen wollen, ggf. hat das auch unter gewissen Umständen einen negativen Impact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SELECT nextval($1)&lt;/code&gt;: Vielleicht hat jemand eine gute Idee.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Entwicklung einer &amp;laquo;smart3Inheritance&amp;raquo;-Methode, die Assoziationen als JSON-Objekte abbildet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu &amp;laquo;smart3Inheritance&amp;raquo;: Keine Ahnung, ob das im grösseren Kontext sinnvoll ist und/oder gut umsetzbar ist. Wenn ich aber z.B. an eine Klasse mit einem Geometrieattribut denke, die eine Beziehung zu Dokumenten hat, könnten die Dokumente als JSON-Objekt in einem JSON-Feld in der Tabelle mit der Geometrie abgebildet werden. Ist wohl sowieso das, was man will, wenn man die Daten bloss anzeigen will (und nicht editieren will).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #35 - Automatisierung steigern mit dem Daten-Repository</title>
      <link>http://blog.sogeo.services/blog/2023/05/10/interlis-leicht-gemacht-number-35.html</link>
      <pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/05/10/interlis-leicht-gemacht-number-35.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt; 4.10.0 gibt es neue Möglichkeiten seine Prozesse nochmals einfacher zu automatisieren: Man kann mit einem einzigen ili2db-Befehl Daten aus einem Daten-Repository herunterladen und in eine Datenbank importieren. Das Daten-Repository ist das Äquivalent zum Modell-Repository: Es werden beliebige Daten in strukturierter Form bereitgestellt. Die strukturierte Form ist in diesem Fall natürlich wieder eine INTERLIS-Transferdatei. Im Rahmen unseres neuen &lt;a href=&quot;https://data.geo.so.ch/&quot;&gt;Datenbezuges&lt;/a&gt; habe ich ein solches &lt;a href=&quot;https://data.geo.so.ch/ilidata.xml&quot;&gt;Daten-Repository&lt;/a&gt; erstellt. Die oberste Ebene eines Daten-Repositories besteht aus &quot;DatasetMetadata&quot;-Objekten. Unsere Hoheitsgrenzen als Beispiel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;DatasetIdx16.DataIndex.DatasetMetadata TID=&quot;33&quot;&amp;gt;
    &amp;lt;id&amp;gt;ch.so.agi.av.hoheitsgrenzen&amp;lt;/id&amp;gt;
    &amp;lt;version&amp;gt;current&amp;lt;/version&amp;gt;
    &amp;lt;model&amp;gt;
        &amp;lt;DatasetIdx16.ModelLink&amp;gt;
        &amp;lt;name&amp;gt;SO_Hoheitsgrenzen_Publikation_20170626&amp;lt;/name&amp;gt;
        &amp;lt;locationHint&amp;gt;https://geo.so.ch/models&amp;lt;/locationHint&amp;gt;
        &amp;lt;/DatasetIdx16.ModelLink&amp;gt;
    &amp;lt;/model&amp;gt;
    &amp;lt;epsgCode&amp;gt;2056&amp;lt;/epsgCode&amp;gt;
    &amp;lt;publishingDate&amp;gt;2023-05-13&amp;lt;/publishingDate&amp;gt;
    &amp;lt;owner&amp;gt;https://agi.so.ch&amp;lt;/owner&amp;gt;
    &amp;lt;boundary&amp;gt;
        &amp;lt;DatasetIdx16.BoundingBox&amp;gt;
        &amp;lt;westlimit&amp;gt;7.340693492284002&amp;lt;/westlimit&amp;gt;
        &amp;lt;southlimit&amp;gt;47.074299169536175&amp;lt;/southlimit&amp;gt;
        &amp;lt;eastlimit&amp;gt;8.03269288687543&amp;lt;/eastlimit&amp;gt;
        &amp;lt;northlimit&amp;gt;47.50119805032911&amp;lt;/northlimit&amp;gt;
        &amp;lt;/DatasetIdx16.BoundingBox&amp;gt;
    &amp;lt;/boundary&amp;gt;
    &amp;lt;title&amp;gt;
        &amp;lt;DatasetIdx16.MultilingualText&amp;gt;
        &amp;lt;LocalisedText&amp;gt;
            &amp;lt;DatasetIdx16.LocalisedText&amp;gt;
            &amp;lt;Language&amp;gt;de&amp;lt;/Language&amp;gt;
            &amp;lt;Text&amp;gt;Hoheitsgrenzen&amp;lt;/Text&amp;gt;
            &amp;lt;/DatasetIdx16.LocalisedText&amp;gt;
        &amp;lt;/LocalisedText&amp;gt;
        &amp;lt;/DatasetIdx16.MultilingualText&amp;gt;
    &amp;lt;/title&amp;gt;
    &amp;lt;shortDescription&amp;gt;
        &amp;lt;DatasetIdx16.MultilingualMText&amp;gt;
        &amp;lt;LocalisedText&amp;gt;
            &amp;lt;DatasetIdx16.LocalisedMText&amp;gt;
            &amp;lt;Language&amp;gt;de&amp;lt;/Language&amp;gt;
            &amp;lt;Text&amp;gt;&amp;lt;![CDATA[Als Hoheitsgrenzen werden die Landesgrenzen, Kantonsgrenzen und die Grenzen der politischen Gemeinden bezeichnet. Die Hoheitsgrenzen liegen auf Grundstücksgrenzen. Sie sind Bestandteil der amtlichen Vermessung.]]&amp;gt;&amp;lt;/Text&amp;gt;
            &amp;lt;/DatasetIdx16.LocalisedMText&amp;gt;
        &amp;lt;/LocalisedText&amp;gt;
        &amp;lt;/DatasetIdx16.MultilingualMText&amp;gt;
    &amp;lt;/shortDescription&amp;gt;
    &amp;lt;keywords&amp;gt;Landesgrenzen,Kantonsgrenzen,Gemeindegrenzen,Bezirksgrenzen,schöne Steine,Inventar Hoheitsgrenzsteinen&amp;lt;/keywords&amp;gt;
    &amp;lt;technicalContact&amp;gt;https://agi.so.ch&amp;lt;/technicalContact&amp;gt;
    &amp;lt;furtherInformation&amp;gt;https://so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-geoinformation/amtliche-vermessung/hoheitsgrenzen/&amp;lt;/furtherInformation&amp;gt;
    &amp;lt;knownWMS&amp;gt;
        &amp;lt;DatasetIdx16.WebService_&amp;gt;
        &amp;lt;value&amp;gt;https://geo.so.ch/api/wms&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.WebService_&amp;gt;
    &amp;lt;/knownWMS&amp;gt;
    &amp;lt;knownWFS&amp;gt;
        &amp;lt;DatasetIdx16.WebService_&amp;gt;
        &amp;lt;value&amp;gt;https://geo.so.ch/api/wfs&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.WebService_&amp;gt;
    &amp;lt;/knownWFS&amp;gt;
    &amp;lt;furtherWS&amp;gt;
        &amp;lt;DatasetIdx16.WebService_&amp;gt;
        &amp;lt;value&amp;gt;https://geo.so.ch/api/data/v1&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.WebService_&amp;gt;
    &amp;lt;/furtherWS&amp;gt;
    &amp;lt;knownPortal&amp;gt;
        &amp;lt;DatasetIdx16.WebSite_&amp;gt;
        &amp;lt;value&amp;gt;https://geo.so.ch/map?l=ch.so.agi.bezirksgrenzen&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.WebSite_&amp;gt;
    &amp;lt;/knownPortal&amp;gt;
    &amp;lt;files&amp;gt;
        &amp;lt;DatasetIdx16.DataFile&amp;gt;
        &amp;lt;fileFormat&amp;gt;application/interlis+xml;version=2.3&amp;lt;/fileFormat&amp;gt;
        &amp;lt;file&amp;gt;
            &amp;lt;DatasetIdx16.File&amp;gt;
            &amp;lt;path&amp;gt;files/ch.so.agi.av.hoheitsgrenzen.xtf&amp;lt;/path&amp;gt;
            &amp;lt;/DatasetIdx16.File&amp;gt;
        &amp;lt;/file&amp;gt;
        &amp;lt;/DatasetIdx16.DataFile&amp;gt;
    &amp;lt;/files&amp;gt;
&amp;lt;/DatasetIdx16.DataIndex.DatasetMetadata&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In diesem Kontext interessant sind die Attribute &quot;id&quot; und &quot;files&quot;. Das &quot;id&quot;-Attribut benötige ich später zum eindeutigen Ansprechen des Datensatzes, den ich bei mir importieren will. Das &quot;files&quot;-Atttribut zeigt auf die Datei (resp. Dateien), welche zu diesem Datensatz vorhanden sind. Das &quot;path&quot;-Attribut ist zwingend ein relativer Pfad (siehe &lt;a href=&quot;https://models.interlis.ch/core/DatasetIdx16.ili&quot;&gt;dazugehöriges Datenmodell&lt;/a&gt;). D.h. man kann nicht auf irgendeine Datei auf irgendeinem Server zeigen. Sondern die Datei muss auf dem gleichen Server liegen, wie die &lt;em&gt;ilidata.xml&lt;/em&gt;-Datei. Dies ist bewusst so gewählt: Damit wird verhindert, dass man auf etwas zeigt, das man nicht unter Kontrolle hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bevor ich die Datei importieren kann, erstelle ich in einem separaten Schritt das Schema mit den leeren Tabellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg-4.11.0.jar --dbhost localhost --dbport 54321 --dbdatabase pub --dbusr ddluser --dbpwd ddluser --dbschema agi_hoheitsgrenzen_pub_v1 --models SO_Hoheitsgrenzen_Publikation_20170626 --nameByTopic --defaultSrsCode 2056 --createFk --createFkIdx --createMetaInfo --createUnique --createNumChecks  --createTextChecks --createDateTimeChecks --createEnumTabs --strokeArcs --schemaimport&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Datensatz aus einem Daten-Repository spreche ich beim Import über &lt;code&gt;ilidata:&amp;lt;id&amp;gt;&lt;/code&gt; an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg-4.11.0.jar --dbhost localhost --dbport 54321 --dbdatabase pub --dbusr ddluser --dbpwd ddluser --dbschema agi_hoheitsgrenzen_pub_v1 --models SO_Hoheitsgrenzen_Publikation_20170626 --import ilidata:ch.so.agi.av.hoheitsgrenzen&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Konsole sieht man anhand der Logeinträge (&lt;code&gt;Info: search in repository &lt;a href=&quot;http://models.interlis.ch/&quot; class=&quot;bare&quot;&gt;http://models.interlis.ch/&lt;/a&gt; for BID &amp;lt;ch.so.agi.av.hoheitsgrenzen&amp;gt;&lt;/code&gt; etc. pp.), dass &lt;em&gt;ili2pg&lt;/em&gt; den Datensatz &quot;ch.so.agi.av.hoheitsgrenzen&quot; in den Daten-Repositories sucht und im Kanton Solothurn findet und anschliessend importiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das funktioniert natürlich auch mit z.B. &lt;em&gt;ili2gpkg&lt;/em&gt; und mit einem Befehl, also ohne vorgängiges Anlegen des Schemas / der Tabellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2gpkg-4.11.0.jar --dbfile hoheitsgrenzen.gpkg --models SO_Hoheitsgrenzen_Publikation_20170626 --nameByTopic --defaultSrsCode 2056 --createFk --createFkIdx --createMetaInfo --createUnique --createNumChecks  --createTextChecks --createDateTimeChecks --createEnumTabs --strokeArcs --doSchemaImport --import ilidata:ch.so.agi.av.hoheitsgrenzen&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beinahe fast schon Feenstaub. INTERLIS mal wieder dem Rest um Längen voraus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was passiert, wenn ich Datensätze importieren will, die aus verschiedenen Dateien bestehen? Z.B. die kommunalen Nutzungsplanung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;files&amp;gt;
    &amp;lt;DatasetIdx16.DataFile&amp;gt;
    &amp;lt;fileFormat&amp;gt;application/interlis+xml;version=2.3&amp;lt;/fileFormat&amp;gt;
    &amp;lt;file&amp;gt;
        &amp;lt;DatasetIdx16.File&amp;gt;
        &amp;lt;path&amp;gt;files/2503.ch.so.arp.nutzungsplanung.kommunal.xtf&amp;lt;/path&amp;gt;
        &amp;lt;/DatasetIdx16.File&amp;gt;
        &amp;lt;DatasetIdx16.File&amp;gt;
        &amp;lt;path&amp;gt;files/2514.ch.so.arp.nutzungsplanung.kommunal.xtf&amp;lt;/path&amp;gt;
        &amp;lt;/DatasetIdx16.File&amp;gt;
        &amp;lt;DatasetIdx16.File&amp;gt;
        &amp;lt;path&amp;gt;files/2463.ch.so.arp.nutzungsplanung.kommunal.xtf&amp;lt;/path&amp;gt;
        &amp;lt;/DatasetIdx16.File&amp;gt;
        &amp;lt;DatasetIdx16.File&amp;gt;
        &amp;lt;path&amp;gt;files/2542.ch.so.arp.nutzungsplanung.kommunal.xtf&amp;lt;/path&amp;gt;
        &amp;lt;/DatasetIdx16.File&amp;gt;
        ....
    &amp;lt;/file&amp;gt;
    &amp;lt;/DatasetIdx16.DataFile&amp;gt;
&amp;lt;/files&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird nur die erste Datei importiert. Scheint mir noch nicht ganz ausgereift zu sein. Hier würde sich wohl auch die Frage nach einer Unterstützung von &lt;code&gt;--dataset&lt;/code&gt; stellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann nicht nur Daten in einem Daten-Repository referenzieren und bereitstellen, sondern auch Konfigurationen. Also mit welchen Paramatern ein Schema angelegt werden soll. Eine solche Konfigurationsdatei (&quot;ch.so.agi.hoheitsgrenzen.ini&quot;) sieht für unser Beispiel so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;[ch.ehi.ili2db]
models=SO_Hoheitsgrenzen_Publikation_20170626
nameByTopic=true
defaultSrsCode=2056
createFk=true
createFkIdx=true
createMetaInfo=true
createUnique=true
createNumChecks=true
createTextChecks=true
createDateTimeChecks=true
createEnumTabs=true
strokeArcs=true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der dazugehörige Eintrag in der &lt;em&gt;ilidata.xml&lt;/em&gt;-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;DatasetIdx16.DataIndex.DatasetMetadata TID=&quot;4&quot;&amp;gt;
    &amp;lt;id&amp;gt;ch.so.agi.hoheitsgrenzen_config&amp;lt;/id&amp;gt;
    &amp;lt;version&amp;gt;1&amp;lt;/version&amp;gt;
    &amp;lt;owner&amp;gt;mailto:agi@bd.so.ch&amp;lt;/owner&amp;gt;
    &amp;lt;categories&amp;gt;
        &amp;lt;DatasetIdx16.Code_&amp;gt;
            &amp;lt;value&amp;gt;http://codes.interlis.ch/type/metaconfig&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.Code_&amp;gt;
        &amp;lt;DatasetIdx16.Code_&amp;gt;
            &amp;lt;value&amp;gt;http://codes.interlis.ch/model/SO_Hoheitsgrenzen_Publikation_20170626&amp;lt;/value&amp;gt;
        &amp;lt;/DatasetIdx16.Code_&amp;gt;
    &amp;lt;/categories&amp;gt;
    &amp;lt;files&amp;gt;
        &amp;lt;DatasetIdx16.DataFile&amp;gt;
            &amp;lt;fileFormat&amp;gt;text/plain&amp;lt;/fileFormat&amp;gt;
            &amp;lt;file&amp;gt;
                &amp;lt;DatasetIdx16.File&amp;gt;
                    &amp;lt;path&amp;gt;ch.so.agi.hoheitsgrenzen.ini&amp;lt;/path&amp;gt;
                &amp;lt;/DatasetIdx16.File&amp;gt;
            &amp;lt;/file&amp;gt;
        &amp;lt;/DatasetIdx16.DataFile&amp;gt;
    &amp;lt;/files&amp;gt;
&amp;lt;/DatasetIdx16.DataIndex.DatasetMetadata&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;code&gt;Code_&lt;/code&gt;-Einträge sind - soweit ich die Dokumentation verstehe - freiwillig. Die Konfigurationsdatei mit den Optionen wird in den &quot;file&quot;-Attributen referenziert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ili2pg-Befehl verkürzt sich jetzt stark, da viele Optionen in der ini-Datei vorhanden sind. Weil ich für diese Konfigurationsdatei noch kein Repository haben, muss ich sie direkt ansprechen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg-4.11.0.jar --dbhost localhost --dbport 54321 --dbdatabase pub --dbusr ddluser --dbpwd ddluser --dbschema agi_hoheitsgrenzen_pub_v1 --metaConfig ch.so.agi.hoheitsgrenzen.ini --schemaimport&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Würde die ini-Datei in einem Daten-Repository liegen, müsste die metaConfig-Option um ein &quot;ilidata&quot; ergänzt werden: &lt;code&gt;--metaConfig ilidata:ch.so.agi.hoheitsgrenzen.ini&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ili2gpkg-Befehl von vorhin wird ebenfalls massiv kürzer:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2gpkg-4.11.0.jar --dbfile hoheitsgrenzen.gpkg --metaConfig ch.so.agi.hoheitsgrenzen.ini --doSchemaImport --import ilidata:ch.so.agi.av.hoheitsgrenzen&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was bringt uns das nun alles?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das automatische Herunterladen und Importieren von Daten ist für Endbenutzer interessant, die regelmässig und automatisch Daten bei sich integrieren. Man muss jedoch ID des Datensatzes kennen, um von dieser Automatisierung zu profitieren, was wohl z.B. bei Lisa Liegenschaft nicht der Fall sein dürfte. Für uns wiederum könnte es für das lokale Entwickeln von Datenumbauten etc. interessant sein. Wir kennen plusminus die ID der Datensätze und können so Entwicklungsumgebungen auf Knopfdruck herstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Bereitstellung der Konfigurationsdateien für die Schemaerstellung machen wir heute bereits ähnlich, jedoch mit einem selbstgestrickten Werkzeug. Dieses können wir mit ili2pg-pur ersetzen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten-Repository:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Daten-Repository stelle ich innerhalb einer &lt;a href=&quot;https://github.com/sogis/sodata-api&quot;&gt;Spring Boot-Anwendung&lt;/a&gt; her. Zwei von drei Dateien (&lt;em&gt;ilisite.xml&lt;/em&gt; und &lt;em&gt;ilimodels.xml&lt;/em&gt;) sind statische Dateien und nicht interessant. Die &lt;em&gt;ilidata.xml&lt;/em&gt;-Datei stelle ich beim Hochfahren der Anwendung aus einer XML-Datei her, die aus unserer Metadatenbank exportiert wird. Zu Beginn habe ich erwähnt, dass die Dateien auf dem gleichen Server liegen müssen wie die &lt;em&gt;ilidata.xml&lt;/em&gt;-Datei. Hier musste ich klein wenig bescheissen. Das ist bei uns nicht der Fall und ich habe einen einfachen Proxy in die Anwendung eingebaut, welcher die Datei zuerst von einem anderen Server herunterlädt. Ein weiterer Grund für den Proxy ist der Umstand, dass die Dateien bei uns als Zipdateien abgelegt sind. Diese müssen aber als XTF-Datei vorliegen, damit es funktioniert. Dieses Entzippen hat zu einer interessante Frage geführt: Wenn das Repository nun rege genützt würde, werden sehr viele Daten produziert beim Entzippen. Wie gehe ich damit um? Ich kann die XTF-Datei erst löschen, wenn sie vollständig an den Benutzer, der sie angefordert hat, geschickt wurde. Dann kann ich sie aber nicht mehr löschen, weil der Request fertig ist. Erste, einfache Idee war ein eingebauter Cronjob, der alle XX Minuten das Verzeichnis mit den heruntergeladenen und entzippten Daten löscht. Das fand ich aber doch nicht so prickelnd, weil man entweder viel zu oft aufräumt oder zu wenig oft. Eine meines Erachtens gute Variante ist Erweiterung der &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/InputStreamResource.html&quot;&gt;Spring InputStreamResource-Klasse&lt;/a&gt;. Diese InputStreamResource wird als Body dem Client zurückgeliefert. Man kann diese &lt;a href=&quot;https://github.com/sogis/sodata-api/blob/main/src/main/java/ch/so/agi/sodata/CleanupInputStreamResource.java&quot;&gt;Klasse dahingehend erweitern&lt;/a&gt;, indem man die &quot;close&quot;-Methode überschreibt und in dieser die unnötigen Dateien löscht. So kümmert sich jeder Request um seine Artefakte.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #14 - A quick look aufs PDF</title>
      <link>http://blog.sogeo.services/blog/2023/01/18/oereb-kataster-richtig-gemacht-14.html</link>
      <pubDate>Wed, 18 Jan 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/01/18/oereb-kataster-richtig-gemacht-14.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ok, schauen wir noch ein bisschen die PDF (aka statischer Auszug) an. Sicher nicht die systemkritischste Komponente. Aber man will einen einheitlichen Auftritt und die Frage ist, kriegt man ihn?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe mich auf Dinge beschränkt, die einem mehr oder weniger direkt ins Auge springen. Bei einigen dieser Dinge ist die Frage wie man es formal nachprüfen kann. Mich dünkt z.B. die Schrift an einer Stelle zu gross. Und nun? Strichlupe? Ich habe mich für den Weg via Import in &lt;a href=&quot;https://inkscape.org/&quot;&gt;&lt;em&gt;Inkscape&lt;/em&gt;&lt;/a&gt; entschieden. &lt;em&gt;Inkscape&lt;/em&gt; hat einen brauchbaren PDF-Import und Texte bleiben als Texte erhalten. Weil ich nicht weiss, wie gut und korrekt dieser letzten Endes ist, bleibt natürlich eine gewisse Unsicherheit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe die Analysen nach ÖREB-PDF-Generator zu gruppieren versucht. In der Hoffnung, dass die Gruppierung stimmt. Verwendet ein Kanton Generator X, gelten die Aussagen oftmals auch für einen anderen Kanton, der denselben Generator einsetzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben den rein visuellen Dingen, habe ich die Konformität des verlangten PDF-Formates &lt;em&gt;versucht&lt;/em&gt; zu überprüfen. Die Weisung verlangt, dass das PDF entweder ein PDF/A-1 oder PDF/A-2a ist. Die Überprüfung ist leider nicht ganz trivial. &lt;a href=&quot;https://de.wikipedia.org/wiki/PDF/A&quot;&gt;Wikipedia&lt;/a&gt; beschreibt das Problem sehr gut: &amp;laquo;Eine Validierung von gültigem PDF/A ist über entsprechende Prüfwerkzeuge möglich (siehe Weblinks). Diese Software-Tools sind sich jedoch häufig uneinig darüber, ob eine erzeugte Datei entsprechend PDF/A gültig ist. Der Grund dafür ist, dass die zugrundeliegenden Normen unterschiedlich interpretiert werden.&amp;raquo; Ich habe &lt;a href=&quot;https://avepdf.com/de/pdfa-validation&quot;&gt;zwei&lt;/a&gt; &lt;a href=&quot;https://www.slub-dresden.de/veroeffentlichen/open-access-publizieren/pdfa-erstellung/slub-pdfa-validator?tx_slubpdfavalidator_pdfavalidator%5Baction%5D=show&amp;amp;tx_slubpdfavalidator_pdfavalidator%5Bcontroller%5D=Validator&amp;amp;cHash=6d6ea1dc278612daa71841856e34536b&quot;&gt;Online-Validatoren&lt;/a&gt; verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;pyramid-oereb (mapfish print)&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes fällt auf, dass das Gemeindewappen mit dem Gemeindenamen ergänzt wird. Meines Erachtens steht nichts davon in der Weisung. Und weil auf dem Titelblatt explizit die Gemeinde aufgelistet wird, finde ich das mindestens überflüssig.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der Titelseite ist die erste Inhaltszeile von &amp;laquo;Katasterverantwortliche Stelle&amp;raquo; nicht bündig und ist zu Nahe am der Trennlinie.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der zweiten Seite (dem Inhaltsverzeichnis) stimmt die Grösse des Textes nicht. Die jeweiligen Titel sind in der Schriftgrösse 8pt, die Auflistung der Themen in Schriftgrösse 9pt. Die Schriftgrösse der Themen muss aber 8pt sein. Die Ergänzung des Themas mit &amp;laquo;(Rechtsgültig)&amp;raquo; sollte nicht sein. Nur &amp;laquo;nicht-rechtsgültige&amp;raquo; müssen mit dem Rechtsstatus in Klammer ergänzt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Beschreibung eines Typs auf den einzelnen ÖREB-Seiten ist zu gross: sie darf nur 8pt gross ein und nicht 9pt. Es scheint auch nicht sauber vertikal zentriert zu sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Zug verwendet auch Mapfish Print aber lustigerweise liegt das PDF in der Version 1.7 vor, was nicht PDF/A-1-konform, jedoch PDF/A-2-konform sein könnte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF ist gemäss beiden Validatoren nicht formatkonform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;pdf4oereb&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir im Kanton Solothurn verwenden eine XSL/XSL-FO-Tranformation bei der Herstellung des PDF aus dem XML. Was mich nicht ganz korrekt dünkt, ist der eigentliche Namen des Inhaltsverzeichnis-Eintrags eines Themas. Gemäss Weisung ist die Logik: &amp;laquo;Thema: Subthema&amp;raquo;. Bei uns steht nur der Name des Subthemas. Ich frage mich, ob sich hier DATA-Extract und PDF-Weisung nicht widersprechen. Entweder wir benennen unsere Subthemen um oder man müsste im XML noch irgendwo den sprechenden Namen des Themas verwalten. Oder man müsste die ganze Table of Contents- vs. Themen-Geschichte bei den einzelnen ÖREB anders leben. Aber wie? Ist wohl wirklich nicht ganz widerspruchsfrei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF validert mit beiden Validatoren gegen PDF/A-1a.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Kantone NW, OW, UR&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die drei PDF dünken mich sehr ähnlich, es gibt aber einige Unterschiede.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Uri weisen die Gemeindewappen den Gemeindenamen auf. Die Symbole sind zu klein. Im Kanton Uri noch kleiner als z.B. im Kanton NW. Die Trennlinie ist zu breit (1pt statt 0.2pt). Der Abstanz zwischen Bild und Auflistung der Typen ist zu klein. Zwischen dem Anteil und dem Prozentzeichen darf es keinen Leerschlag geben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Schriftgrösse im Inhaltsverzeichnis ist zu klein. Sie darf nicht 6pt sein, sondern muss 8pt sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF ist gemäss beiden Validatoren nicht formatkonform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Kanton Zürich&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanterweise kann ich das PDF nicht gleich gut wie andere in Inkscape importieren. Es fehlen die Texte (was aber noch rein gar nichts über die Qualität des PDF aussagt). Was jedoch sofort auffällt ist, dass im Kanton Zürich praktisch alles kursiv geschrieben ist. Warum?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei den einzelnen Typen steht manchmal ein Asterix hinter den Quadratmeter und Prozent. Mir auch nicht klar. Ebenfalls steht ein überflüssiger Leerschlag zwischen Anteil und dem Prozentzeichen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Liste auf der letzten Seite bei &amp;laquo;Begriffe und Abkürzungen&amp;raquo; stammt wohl noch aus der ersten Version. Sie müsste umfangreicher und länger sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF ist gemäss beiden Validatoren nicht formatkonform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Kanton Wallis&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Wallis hat noch einige Version-1-Restanzen: Der Titel &amp;laquo;Allfällige Eigentumsbeschränkungen, zu denen noch keine Daten vorhanden sind&amp;raquo; ist nicht mehr korrekt. Ebenso gibt es keine vollständige Legende mehr. Die Abstände zwischen verschiedenen Elementen stimmt oftmals nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das ÖREB-Katasterlogo ist nicht rechtsbündig und verzerrt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was mich ein wenig irritiert ist, dass man anscheinend die falsche Farbe der Links nicht erkennt, resp. noch niemand erkannt hat. Auch die Schriftart  ist nicht korrekt. Bei den Links wird anstelle Cadastra Helvetica verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF ist gemäss beiden Validatoren nicht formatkonform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Kanton Luzern&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Luzern verwendet ebenfalls eine eigene Lösung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der Titelseite fehlt der Stand der amtlichen Vermessung. Die Schriften sind zu gross: 8.5pt statt 8pt. Im Inhaltsverzeichnis wird die alte Bezeichung &amp;laquo;Allfällige&amp;#8230;&amp;#8203;&amp;raquo; verwendet. Das Gemeindelogo weist den Gemeindenamen auf.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei den einzelnen ÖREB-Seiten fehlt der kleine Untertitel, der Auskunft über den Rechtsstatus liefert. Die Legende ist &amp;laquo;komisch&amp;raquo; aufgebaut. Irgendwie scheint es Unterkapitel zu geben, z.B. gibt es beim Zonenplan in der Legend das Unterkapitel &amp;laquo;Grundnutzung&amp;raquo; und &amp;laquo;Überlagerungen&amp;raquo;. Der eigentliche Legendentext, z.B. &amp;laquo;Zone für öffentliche Zwecke&amp;raquo; wird ebenfalls mit Subkontext versehen (&amp;laquo;Zonentyp Gemeinde: Zone für öffentliche Zwecke&amp;raquo;). Scheint mir nicht der Weisung zu entsprechen. Die Einrückungen der Links auf die Dokumente fehlt und deren Farbe ist nicht korrekt. Die weiteren Informationen und Hinweise folgen fälschlicherweise nach den zuständigen Stellen. Sie sollten vor den zuständigen Stellen gelistet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das PDF ist gemäss beiden Validatoren nicht formatkonform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wahrlich kein Weltuntergang aber irgendwie, auf einem anderen Level, trotzdem leicht verstörend. Wir können es uns leisten mindestens 5 Mal ein Werkzeug für den identischen Arbeitsschritt zu bauen (dafür aber keines wirklich korrekt). Zu viel Geld? Zu viele Programmierer, die sich verwirklichen wollen? Zu viele Leute, die reinreden, was alles konfigurierbar sein muss? Fehlanreize? Ich weiss es nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Nachtrag 2023-01-23&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe eine &lt;a href=&quot;https://verapdf.org/&quot;&gt;Java-Bibliothek&lt;/a&gt; gefunden, die PDF/A-Dateien validiert. Auf Basis dieser Bibliothek habe ich einen zusätzlichen Check im &lt;a href=&quot;https://github.com/edigonzales/oereb-cts&quot;&gt;ÖREB-Kataster-Validierungstool&lt;/a&gt; erstellt. Erfreulicherweise validiert das PDF der Kantone &lt;a href=&quot;https://monitoring.oereb.services/details/extract/NW&quot;&gt;Nidwalden&lt;/a&gt; und Obwalden. Uri interessanterweise nicht, auch wenn ich immer noch denke, dass sie mit der gleichen Software hergestellt werden. Leider werden wohl die &lt;a href=&quot;https://monitoring.oereb.services/details/extract/BL&quot;&gt;pyramid-oereb-Kantone&lt;/a&gt; ein Problem &lt;a href=&quot;https://github.com/openoereb/pyramid_oereb/issues/876#issuecomment-884716001&quot;&gt;bekommen&lt;/a&gt;. Wobei mir nicht ganz klar ist, ob es im verlinkten Ticket nur um die Transparenz in den Symbolen der Bundesdaten geht oder um ein gröberes Problem bei der eingesetzten Lösung und was das &lt;a href=&quot;https://github.com/openoereb/pyramid_oereb/issues/876#issuecomment-1353127808&quot;&gt;Telefongespräch&lt;/a&gt; genau &amp;laquo;resolved&amp;raquo; hat. PDF/A bleibt aus Anbietersicht unbefriedigend: Support in Libs nicht so toll und Prüfung des Resultates nicht gerade Glückssache aber auch nicht non-trivial. Ich verstehe, dass man PDF/A anbieten will aber anscheinend überfordert diese Anforderung das Gesamtsystem ÖREB-Kataster Schweiz.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #13 - map.oereb.services und weitere Beobachtungen</title>
      <link>http://blog.sogeo.services/blog/2023/01/11/oereb-kataster-richtig-gemacht-13.html</link>
      <pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/01/11/oereb-kataster-richtig-gemacht-13.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um die URL ein wenig einprägsamer zu machen, habe ich die Resultate der Prüfungen der kantonalen ÖREB-Kataster-Dienste unter &lt;a href=&quot;https://monitoring.oereb.services&quot;&gt;monitoring.oereb.services&lt;/a&gt; publiziert (Digitalocean 10-Dollar-Deployment. Und irgendwie verstehe ich nicht wie genau das Restarten des Containers funktioniert, wenn der Health Check fehl schlägt. Oder übersehe ich was?). Ein Kanton hat sich bereits bewegt: Der Kanton Zürich hat &amp;laquo;Versions&amp;raquo; und &amp;laquo;GetEGRID&amp;raquo; korrigiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ÖREB-Spezifikationen und Weisungen sind gut, insbesondere die des ÖREB-Webservices und des DATA-Extracts. Mit dem XML-Output lässt sich z.B. das PDF erzeugen. Man muss (und soll) nicht zwei unterschiedliche Wege für die Herstellung des XML und des PDF beschreiten. Sondern das XML herstellen und daraus das PDF ableiten. Somit lassen sich bereits viele Fehler vermeiden, z.B. Inhalt XML != Inhalt PDF usw.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine andere Anwendungsmöglichkeit des XML-Auszuges ist die Aufbereitung/Ableitung zum dynamischen Auszug, sprich zu einer Webanwendung. Die Webanwendung braucht bloss den XML-Auszug für ein Grundstück abzurufen und kann aus den Informationen eine mehr oder weniger schicke Anwendung konfigurieren. Ich denke, einige der Clients der Kantone funktionieren (hoffentlich) so. Macht man das nun mit dem Anspruch, dass es schweizweit funktionieren soll - wir sind ja standardisiert - gewinnt man wieder Erkenntnisse über die Spezialitäten der Kantone und wohl auch noch über Fehler, die man sonst weniger gut entdecken würde. Gesagt, getan: &lt;a href=&quot;https://map.oereb.services&quot;&gt;map.oereb.services&lt;/a&gt;. Es gibt sicher noch das eine oder andere Grundstück, wo es nicht funktioniert. Mal was Null, was man abfangen sollte o.ä. Und es fehlen genügend funktionierende Dienste, die Änderungen mit und ohne Vorwirkung publizieren, um die Gruppierung / Reihenfolge des Clients zu validieren. Oder ich habe die betroffenen Grundstücke nicht gefunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst ein paar nicht-ÖREB-Bemerkungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anwendung ist komplett in Java mit &lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;&lt;em&gt;GWT&lt;/em&gt;&lt;/a&gt; geschrieben. Als Toolkit wird &lt;a href=&quot;https://demo.dominokit.org/home&quot;&gt;&lt;em&gt;Domino Kit&lt;/em&gt;&lt;/a&gt; verwendet. Das führt zu einem sehr angenehmen Entwickeln, da man sich im gleichen Ökosystem (Sprache, IDE, Build Tools) bewegt wie sonst auch. Mit &lt;a href=&quot;https://graalvm.org&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; zu einem Native Image runterkompilieren und die &amp;laquo;Java&amp;raquo;-Anwendung startet 0.06 Sekunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man etwas wie eine Adressen- und Grundstücksuche anbieten, reichen die Möglichkeiten des ÖREB-Webservices nicht mehr aus. Es fehlt schlichtweg die Möglichkeit mittels Freitext zu suchen. Soweit auch nicht schlimm und wohl auch nicht sinnvoll. Der &lt;a href=&quot;https://api3.geo.admin.ch/services/sdiservices.html#search&quot;&gt;Search-Service&lt;/a&gt; der GeoAdmin API hilft uns. Der Dienst liefert eine Koordinate zurück. Mit dieser kann man einen EGRID beim ÖREB-Webservice abfragen. Nun müsste man die Anfrage an den Service eines jeden Kantons machen. Das wird nicht performen. Abhilfe schafft auch hier wieder die GeoAdmin API indem man den &lt;a href=&quot;https://api3.geo.admin.ch/services/sdiservices.html#feature-resource&quot;&gt;Feature Resource&lt;/a&gt; Dienst verwendet und wissen will, in welchem Kanton (&lt;em&gt;ch.swisstopo.swissboundaries3d-kanton-flaeche.fill&lt;/em&gt;) die soeben erhaltene Koordinate liegt. Mit diesem Wissen kann man direkt den Service des betroffenen Kantons mit dem GetEGRID-Request beglücken. Der vermeintlich unnötige GetEGRID-Request ist notwendig, um zu eruieren, ob es sich um eine Liegenschaft oder um ein Baurecht handelt, wenn man einen Auszug mittels Klick in die Karte anfordert und es an dieser Stelle &lt;a href=&quot;https://map.oereb.services/?egrid=CH527354320619&quot;&gt;mehrere Grundstücke&lt;/a&gt; gibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Hintergrundkarte verwende ich den ÖREB-Situationsplan von &lt;a href=&quot;https://geodienste.ch/services/av/info&quot;&gt;geodienste.ch&lt;/a&gt;. Leider fehlt ein schweizweiter WMTS dieser Hintergrundkarte. Aus diesem Grund wirkt das Zoomen und Pannen nicht so &amp;laquo;snappy&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun zu den ÖREB-Kataster spezifischen Bemerkungen und Beobachtungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Inkompatible Kantone&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von den momentan vorhandenen Versions-2-Kantonen, können folgende Kantone nicht für diesen Anwendungsfall verwendet werden (oder es wäre nur mit unnötigem Geknorze möglich):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AI: WMS verlangt Authentifizierung, &lt;a href=&quot;https://www.geoportal.ch/services/wms/ktai?SERVICE=WMS&amp;amp;VERSION=1.3.0&amp;amp;REQUEST=GetMap&amp;amp;FORMAT=image%2Fpng&amp;amp;TRANSPARENT=true&amp;amp;LAYERS=ch.geoportal.raumplanung_grundstueckskataster.1478.0.oereb_zonenplan_kt_ai&amp;amp;MAPID=1478&amp;amp;CRS=EPSG%3A2056&amp;amp;WIDTH=493&amp;amp;HEIGHT=280&amp;amp;BBOX=2748370.5620040814%2C1243979.8562142858%2C2748756.2509959186%2C1244198.9087857143&amp;amp;AUTHENTICATE=true&amp;amp;EPOCH=2022-10-28T20%3A00%3A19&amp;amp;SRS=EPSG%3A2056&quot;&gt;Beispielrequest&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AR: WMS verlangt Authentifizerung&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BS: GEOMETRY=true beim &lt;a href=&quot;https://api.oereb.bs.ch/getegrid/xml/?EN=2612855,1267223&amp;amp;GEOMETRY=true&quot;&gt;GetEGRID-Request&lt;/a&gt; führt zu Fehlern.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FR: Geometrie fehlt beim &lt;a href=&quot;https://geo.fr.ch/RDPPF_ws/RdppfSVC.svc/getegrid/xml/?EN=2578478,1183785&amp;amp;GEOMETRY=true&quot;&gt;GetEGRID-Request&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LU: Liefert keinen Referenz-WMS im Auszug mit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NW: Liefert keinen Referenz-WMS im Auszug mit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OW: Liefert keinen Referenz-WMS im Auszug mit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SG: WMS verlangt Authentifizerung.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UR: Die Geometrien weisen ein falsches Koordinatensystem auf.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VS: Anstelle eines WMS wird eine ESRI-Irgendwas-Dienste-URL mitgeliefert.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es funktionieren die Kantone AG, BE, BL, GR, JU, NE, SO, TG, TI, ZG, ZH.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;CORS&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich wollte ohne Umwege über ein Backend den XML-Auszug anfordern und im Browser verarbeiten. D.h. das XML wird vom Browser angefordert. Nun geht das leider bei vielen Kantonen nicht, da die &amp;laquo;CORS-Header&amp;raquo; nicht gesetzt werden. Die KGK hat vor geraumer Zeit die Kantone darüber informiert. Damals ging es um die GetCapabilities-Antwort eines WMS, die vom Browser ohne die Header nicht verarbeitet werden kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p13/corsheader.png&quot; alt=&quot;cors header&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Funktioniert hat es (ohne alle zu prüfen) in den Kantonen BE, SO und TG. Um dennoch an den XML-Auszug zu kommen, musste ein einfacher Proxy her, der serverseitig das XML anfordert und an den Browser zurückschickt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Layer-Opazität&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt das Attribut &lt;em&gt;layerOpacity&lt;/em&gt;, das die Deckkraft des Kartenelements regelt. In den Kantonen JU und ZH ist der Wert &amp;laquo;1&amp;raquo;. Das bedeutet, dass die Karte komplett deckend ist. Wahrscheinlich nicht so gewollt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Reihenfolge&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Reihenfolge der Themen wie auch der Dokumente ist in der Weisung definiert.
Beim Kanton Zürich ist mir aufgefallen, dass die Reihenfolge der gesetzlichen Grundlagen nicht stimmt. Das RPG wird ganz am Ende aufgelistet und sollte eigentlich als erstes geführt werden. Der Kanton Aargau verwendet hier &lt;a href=&quot;https://api.geo.ag.ch/v2/oereb/extract/xml/?EGRID=CH832377520646&quot;&gt;&amp;laquo;None&amp;raquo;&lt;/a&gt;. Das habe ich netterweise im Code abgefangen. Die Reihenfolge der Themen habe mir nicht angeschaut.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die gerenderte Reihenfolge meines Clients müsste ich auch vertieft verifizieren, ob sie stimmt. Im ZH-Fall habe ich es im XML nachgeprüft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vielleicht ein heikles Thema. Mich dünkt, dass einige Dienste langsam sind. Ein denkbare Erklärung kann auch der &lt;a href=&quot;http://blog.sogeo.services/blog/2022/10/16/oereb-kataster-richtig-gemacht-9.html&quot;&gt;&amp;laquo;ÖREB-Habasch&amp;raquo;&lt;/a&gt; sein, der im betroffenen Kanton wütet. Auffallend ist jedoch, dass Kantone mit der gleichen Software eher zu den langsameren gehören. Als Extrembeispiel dient der Kanton Aargau. Die GetEGRID-Anfrage dauert für ein &lt;a href=&quot;https://map.oereb.services/?egrid=CH467223527107&quot;&gt;Einfamilienhaus-Grundstück&lt;/a&gt; zwischen 1 und 6 Sekunden. Das ist m.E. nicht mehr nachvollziehbar. Warum soll eine Point-in-Polygon-Abfrage so viel Zeit in Anspruch nehmen? Der Extract nimmt zwischen 5 und 12 Sekunden in Anspruch. Im &lt;a href=&quot;https://map.oereb.services/?egrid=CH756746873539&quot;&gt;Kanton Bern&lt;/a&gt; ist es circa 1 Sekunde für den GetEGRID-Request und 3 Sekunden für den Extract. Die Kantone &lt;a href=&quot;https://map.oereb.services/?egrid=CH338277496924&quot;&gt;BL&lt;/a&gt; und &lt;a href=&quot;https://map.oereb.services/?egrid=CH548749776707&quot;&gt;NE&lt;/a&gt; sind ähnlich. Im Issue-Tracker findet man Meldungen zu einer &lt;a href=&quot;https://github.com/openoereb/pyramid_oereb/issues/1508&quot;&gt;Performance-Verschlechterung&lt;/a&gt; in den neueren Versionen. Ich habe jedenfalls in Erinnerung, dass diese Kantone in der Version 1.0 schneller waren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Am anderen Ende der Skala sind die Kantone TG, ZH und SO: Da sind die GetEGRID-Requests im tieferen 3-stelligen Millisekundenbereich und der Extract gibt es für nicht allzu grosse Grundstücke im Subsekunden-Bereich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Url-Encoding&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was ich echt nicht verstehe, sind gewisse Url-Encodings. Warum macht man sowas: &amp;laquo;https%3A%2F%2Fwww.oereb2.apps.be.ch%2Fimage%2Fsymbol%2Fch.Nutzungsplanung%2Flegend_entry.png%3Fidentifier%3D3bcf516e-6419-4657-9190-a075e60c9512&amp;raquo;? Warum encodiert man den Doppelpunkt und die beiden Slashs des Url-Schemas? Das ist doch nie nötig? Und ist bloss mühsam wenn man damit arbeiten will.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Identische Namen für unterschiedliche Dokumente&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Kanton Aargau erkennt man das &lt;a href=&quot;https://oereblex.ag.ch/app/de/decrees&quot;&gt;ÖREBlex-Datenmodell&lt;/a&gt; wieder. Dort gibt es pro Entscheid ein Dokument, das Anhänge haben kann (oder so ähnlich). Das führt dazu, dass im XML unterschiedliche Dokumente den gleichen Titel aufweisen. Ob das sinnvoll ist, weiss ich nicht so recht. Tendiere zu nein. Der Kanton Aargau hat das jedenfalls visuell ansprechend im &lt;a href=&quot;https://apps.geo.ag.ch/oereb/client/?lang=de&amp;amp;map_x=2646295.685&amp;amp;map_y=1248707.667&amp;amp;map_zoom=12&amp;amp;egrid=CH807752802322&quot;&gt;dynamischen&lt;/a&gt; Auszug gelöst (siehe Nutzungsplanung). Dito im PDF. Ganz streng genommen, sieht das wahrscheinlich die &lt;a href=&quot;https://www.cadastre.ch/de/services/publication.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/Weisung-OEREB-statischer-Auszug-de.pdf.html&quot;&gt;Weisung zum statischen Auszug&lt;/a&gt; nicht vor. In &lt;a href=&quot;https://map.oereb.services/?egrid=CH807752802322&quot;&gt;meinem Client&lt;/a&gt; wird jedes Dokument aufgelistet, egal ob es den gleichen Titel trägt. Also auch wieder ein Spezialität, die man als Entwickler kennen müsste, weil mindestens nicht offensichtlich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was man auch automatisch durch eine Maschine testen sollte, ist der &lt;em&gt;layerIndex&lt;/em&gt; der Map-Elemente. Wenn sämtliche Elemente, also auch die Hintergrundkarte, den gleichen Wert aufweisen, weiss man nicht in welcher Reihenfolge die Bilder geschichtet werden müssen. Und wohl müsste im Regelfall (oder immer) die Hintergrundkarte - nomen est omen - tatsächlich im Hintergrund sein, also den kleinsten Wert aufweisen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #12 - Revisited: ÖREB-Kataster Compliance Test Suite</title>
      <link>http://blog.sogeo.services/blog/2023/01/01/oereb-kataster-richtig-gemacht-12.html</link>
      <pubDate>Sun, 1 Jan 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2023/01/01/oereb-kataster-richtig-gemacht-12.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe die &lt;a href=&quot;https://github.com/edigonzales/oereb-cts&quot;&gt;Test Suite&lt;/a&gt; um die Validierung der Endpunkte  &amp;laquo;versions&amp;raquo; und &amp;laquo;capabilities&amp;raquo; &lt;a href=&quot;https://sogis-oereb-cts-remdc.ondigitalocean.app/&quot;&gt;erweitert&lt;/a&gt; und verschiedene Testresultate ein wenig unter die Lupe genommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;versions&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Versions-Output prüfe ich neben der Schemakonformität tatsächlich auch den korrekten Wert der Version. Dies muss gemäss Weisung &amp;laquo;extract-2.0&amp;raquo; sein und nicht bloss &amp;laquo;2.0&amp;raquo;. Loben darf man den Kanton Luzern (und Solothurn), der das korrekt macht, im Gegensatz zu allen anderen. Beim Kanton Zürich kommt gar nix resp. 404 zurück. Der Kanton Wallis ist auch hier wieder auf Kriegsfuss mit den Namensräumen. In diesem Fall steht nicht mal eine XML-Deklaration (&lt;code&gt;&amp;lt;?xml version=&apos;1.0&apos;?&amp;gt;&lt;/code&gt;) am Anfang des Dokumentes. Was zwar streng genommen nicht zwingend ist aber, nun ja, gute Praxis wäre (&amp;laquo;should&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;capabilities&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier wird nur die Schemakonformität geprüft. Das Capabilities-Dokument bekommen die meisten hin. Der Kanton Freiburg leitet jedoch mit Status Code 307 weiter, was den Test fehl schlagen lässt. Die Kantone NW, OW und UR sind nicht schemakonform, weil der Topic-Code mit einer Zahl beginnt. Wieso sie das so machen, ist mir nicht klar. Der Topic-Code sollte doch gleich sein, wie später im Extract? Mein Lieblingskanton, der Kanton Tessin, hat zu lange Gemeindenummern (z.B. &lt;code&gt;531002&lt;/code&gt;) und der Kanton Wallis hat wiederum Namespace-Probleme: Einerseits ist das Root-Element &lt;code&gt;GetCapabilitiesResponse&lt;/code&gt; einem falschen Namespace zugewiesen (&lt;code&gt;&lt;a href=&quot;http://schemas.geo.admin.ch/swisstopo/OeREBK/15/Extract&quot; class=&quot;bare&quot;&gt;http://schemas.geo.admin.ch/swisstopo/OeREBK/15/Extract&lt;/a&gt;&lt;/code&gt;), was die Prüfung sofort abbrechen lässt und andererseits sind Topics vom Typ &amp;laquo;Theme&amp;raquo;, die einem anderem Namespace zugewiesen werden müssen, was hier nicht gemacht wird. Das Kantonskürzel bei kantonalen Themen muss in Grossbuchstaben geschrieben sein und nicht in Kleinbuchstaben (&lt;code&gt;ch.vs.Ueberlagernde_Nutzungsplanung&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Plan für das Grundbuch&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Kränzchen muss man den Kantonen BE und BL winden. Diese verwenden für die Hintergrundkarte den Situationsplan (aka Plan für das Grundbuch) von &lt;a href=&quot;https://geodienste.ch/services/av/info&quot;&gt;geodienste.ch&lt;/a&gt;. Das hat mich natürlich getriggert und ich musste den Dienst auch &lt;a href=&quot;https://geo.so.ch/map/?k=7935c2a96&quot;&gt;bei uns testen&lt;/a&gt;. Leider werden die Gebäudenummern dargestellt, was im Kanton Solothurn dazu führt, dass Platzhalter-Werte dargestellt werden. Wir kennen keine eigentlichen Gebäudenummern und darum füllen die NF-Geometer Platzhalter ab, damit überhaupt ein EGID erfasst werden kann. Ist mir momentan noch nicht ganz klar, wie wir das lösen können. Sinnvoll wäre die Benutzung des geodienste.ch Services allemal, da er für teures Geld erstellt wurde und nun verwenden die aller meisten Kantone ihn nicht&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Stabilität&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant zu beobachten ist die Stabilität der Dienste, soweit dies mit solchen einfachen Tests überhaupt möglich ist. Die sonstigen Musterschüler BL und NE kämpfen hier ein wenig. Der Kanton BL hat nachweislich Aussetzer, die oftmals einen Auszug mit eingebetteten Bildern betreffen. Weil ich zuerst am eigenen Code zweifelte, habe ich bei &lt;a href=&quot;https://statuscake.com&quot;&gt;Statuscake&lt;/a&gt; ein paar Tests am Laufen, die alle 5 Minuten einen Request machen. Statuscake bestätigt meine eigenen Beobachtungen. Das passiert maximal ein paar Mal pro Tag und kann natürlich irgendwas sein. Nicht zwingend der Dienst selber, sondern auch was unterwegs zwischen Eingang ins Kantonsnetz und ÖREB-Service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Kanton Neuenburg funktionieren plötzlich die Auszüge mit Bildern nicht mehr. Der Server wirft einen 500er Status Code. Interessanterweise funktioniert das PDF noch, jedoch im dynamischen Auszug werden keine Karten mehr dargestellt, obwohl die restliche Logik noch zu funktionieren scheint. Einmal mehr: Was sind gute Health-Checks für Kartendienste?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Embedded Images&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt habe ich die eingebetteten Bilder angeschaut. Die Kantone AR, BS und SG funktionieren nicht, wenn man die Bilder anfordert und können nicht getestet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine leider nicht ganz geklärte Frage ist, ob die Bilder mit 300 dpi ausgeliefert werden müssen. Die Weisung nimmt nur bei den Symbolen Stellung. Meines Erachtens ist es sinnvoll die Bilder in einer &amp;laquo;brauchbaren&amp;raquo; Qualität auszuliefern. Damit mit dem XML auch das PDF erstellt werden kann. Oder als Kunde will man vielleicht die Bilder in einer Word-Datei oder ähnlichem verwenden. Auch für solche Anwendungsfälle sind die 96 dpi wohl nicht wirklich genügend. Die Kanton AG, BE, BL, GR, LU, TI, ZG liefern keine 300 dpi aus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine eher eigenwillige Darstellung für den Plan für das Grundbuch verwenden die Kantone LU (blaue Gewässer), UR (eher &amp;laquo;einfach&amp;raquo;) und ZG (düster und dicke Linien):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/LU_landregister_main.png&quot; alt=&quot;canton lucerne land register main page&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/UR_landregister_main.png&quot; alt=&quot;canton uri land register main page&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/ZG_landregister_main.png&quot; alt=&quot;canton zug land register main page&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Getreu dem Motto &amp;laquo;Quadratisch, praktisch, gut&amp;raquo; liefert der Kanton Luzern zudem seine Bilder in einem quadratischen Format aus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir im Kanton Solothurn sind meines Erachtens auch nicht ganz korrekt unterwegs beim Plan für das Grundbuch der Titelseite. Auf Wunsch der Nachführungsgeometer stellen wir bei uns den sogenannten &amp;laquo;zukünftigen&amp;raquo; Stand der Grundstücke dar. D.h. bei einer Mutation werden die immer noch rechtsgültigen (schwarzen) Grenzen nicht dargestellt, sondern in rot die zukünftigen Grenzen. Beim Plan für das Grundbuch für die ÖREB wird der rechtsgültige Zustand dargestellt (und keine projektierten Grundstücke).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Wallis liefert keine eingebetteten Bilder aus und liefert auch bei &lt;code&gt;WITHIMAGES=true&lt;/code&gt; nur einen Verweis auf einen ESRI-Webdienst (!= WMS). Die Bilder der einzelnen ÖREB haben fälschlicherweise bereits den Plan für das Grundbuch als Hintergrund:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/VS_restriction.png&quot; alt=&quot;canton wallis restriction&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Bild resp. den Esri-Kartendienst-Request für die Titelseite (&lt;code&gt;PlanForLandRegisterMainPage&lt;/code&gt;) wird nicht ausgeliefert und im Plan für das Grundbuch für die ÖREB (&lt;code&gt;PlanForLandRegister&lt;/code&gt;) sind die Gebäude ausgefüllt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Zürich liefert wie bereits in der ÖREB-Kataster Version 1.0 die Bilder der ÖREB mit Plan für das Grundbuch, Nordpfeil, Massstab und Bandierung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/ZH_restriction.png&quot; alt=&quot;canton zurich restriction&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Plan für das Grundbuch für die ÖREB (&lt;code&gt;PlanForLandRegister&lt;/code&gt;) ist irgendwie merkwürdig:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/ZH_landregister.png&quot; alt=&quot;canton zurich land register&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich weiss nicht genau welche Kataster in welchem Abnahme-Status sind. Die gezeigte Folie an der letzten &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb.detail.event.html/cadastre-internet/2022/OEREB2022.html&quot;&gt;ÖREB-Kataster-Veranstaltung&lt;/a&gt; war ähm irgendwie eher ein Zufallsprodukt (sorry). Übrigens: Kann jemand &lt;a href=&quot;https://www.cadastre.ch/content/dam/cadastre-internet/de/divers-rdppf/Info-Veranstaltung-2022-de.zip&quot;&gt;diese Zip-Datei&lt;/a&gt; öffnen? In einer dieser Präsentationen sollte die Übersichtskarte mit den Status der Kantone sein. Leider kann ich sie nicht öffnen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Trotz allem Erreichten im &amp;laquo;Gesamtsystem&amp;raquo; ÖREB-Kataster darf man sich nicht zu stark auf die Schultern klopfen. Anscheinend ist auch die technische Umsetzung des ÖREB-Katasters für die Kantone non-trivial. Trotzdem will man es 26 Mal umsetzen. Viele Kantone verwenden den gleichen Software-Stack. Abgesehen von tendenziell marginalen Fehlern, scheint es hier also eher ein Konfigurations- und Betriebsproblem zu sein? Ist der Software-Stack zu kompliziert? Fehlt Wissen in den Kantonen? Ich weiss es nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei ganz wenigen dünkt mich die Qualität schlichtweg ungenügend (teilweise wegen eines vermeintlichen Details). Wie soll ein Benutzer damit umgehen, wenn er nicht bloss einen Knopf im dynamischen Auszug drückt (anscheinend fokussiert sich unser aller Aufmerksamkeit da drauf), sondern wirklich mit den Schnittstellen arbeiten will und muss? Das ist in diesen Fällen schlichtweg nicht möglich oder verschiedentlich nur mit grausigen Workarounds (= Zusatz-Code = Zusatzaufwand. Ja ja und technisch ist immer alles möglich&amp;#8230;&amp;#8203; technical debs und so). Hier sollte sowohl der Kanton und auch die Aufsicht besser hinschauen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Abnahme resp. die Validierung des Katasters darf nicht unterschätzt werden. Klar kann man gegen das Schema prüfen. Jedoch ist das Schema relativ tolerant: D.h. eine einfache Schemaprüfung validiert positiv auch wenn man eingebettete Bilder anfordert aber keine zurück bekommt. Die Prüfung ist also abhängig vom Request und es sind sehr viele Kombinationen möglich. Ich prüfe z.B. nur XML-Auszüge. Man wollte als Option unbedingt noch JSON als (optionales) Outputformat, das bedeutet nun aber auch erhöhter Prüfungsaufwand und erhöhte Fehleranfälligkeit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Leicht kritisch darf man zukünftige Weiterentwicklungen (weitere Rechtszustände, behördenverbindliche Daten, Grundstücksinformationsystem, &amp;#8230;&amp;#8203;) hinterfragen: Einfacher wird es nicht und wir kriegen bereits ein relativ einfaches Konstrukt nicht wirklich sauber auf die Reihe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alhamdulillah ist der ÖREB-Kataster nicht systemkritische Infrastruktur.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich bleibe dabei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p12/twitter.png&quot; alt=&quot;tweet öreb at kgk&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #34 - ilivalidator for Python 0.0.2</title>
      <link>http://blog.sogeo.services/blog/2022/12/31/interlis-leicht-gemacht-number-34.html</link>
      <pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/12/31/interlis-leicht-gemacht-number-34.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe ein paar Features implementiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em class=&quot;https://github.com/claeis/ilivalidator&quot;&gt;Ilivalidator&lt;/em&gt; erlaubt es mehrere INTERLIS-Transferdateien gleichzeitig und gemeinsam zu prüfen. Sollen mehrere Dateien &lt;em&gt;gleichzeitig&lt;/em&gt; (sprich mit einem Aufruf) geprüft werden, reicht es, wenn man die Dateien beim Kommandozeilenaufruf aneinander reiht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar ilivalidator.jar OeREBKRM_V2_0_Gesetze.xml OeREBKRM_V2_0_Themen.xml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Dateien werden unabhängig voneinander geprüft. Im vorliegenden Fall sind die ÖREB-Themen und die Gesetze miteinander verknüpft (Assoziation mit externen Rollen). Ob diese Verknüpfung stimmig ist, möchte man in der Regel wohl prüfen. D.h. die Dateien müssen &lt;em&gt;gemeinsam&lt;/em&gt; geprüft werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar ilivalidator.jar --allObjectsAccessible OeREBKRM_V2_0_Gesetze.xml OeREBKRM_V2_0_Themen.xml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Ilivalidator&lt;/em&gt; meldet auch mit dieser Option keine Fehler. Wenn man im Themen.xml nun einen Verweis auf ein Gesetz verfälscht, z.B. &lt;code&gt;&amp;lt;Gesetz REF=&quot;mich.gibt.es.nicht&quot;/&amp;gt;&lt;/code&gt;, meldet &lt;em&gt;ilivalidator&lt;/em&gt; einen Fehler:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Error: line 54: OeREBKRMkvs_V2_0.Thema.ThemaGesetz: No object found with OID mich.gibt.es.nicht.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wird &lt;code&gt;--allObjectsAccessible&lt;/code&gt; nicht verwendet, wird korrekterweise kein Fehler gefunden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neu kann man auch mit &amp;laquo;ilivalidator for Python&amp;raquo; mehrere Dateien prüfen. Der zwingende Methodenparameter &lt;code&gt;data_file_names&lt;/code&gt; ist eine Liste:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Python&quot; data-lang=&quot;Python&quot;&gt;from ilivalidator import Ilivalidator

valid = Ilivalidator.validate([&apos;OeREBKRM_V2_0_Gesetze.xml&apos;,&apos;OeREBKRM_V2_0_Themen.xml&apos;])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man wie eingangs erklärt die Dateien gemeinsam prüfen, muss der Parameter &lt;code&gt;--allObjectsAccessible&lt;/code&gt; übergeben werden. Dies funktioniert mit einem Dictionary, das optional ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Python&quot; data-lang=&quot;Python&quot;&gt;from ilivalidator import Ilivalidator

settings = {Ilivalidator.SETTING_ALL_OBJECTS_ACCESSIBLE: True}
valid = Ilivalidator.validate([&apos;OeREBKRM_V2_0_Gesetze.xml&apos;,&apos;OeREBKRM_V2_0_Themen.xml&apos;], settings)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Namen der Parameter sind als &amp;laquo;Konstanten&amp;raquo; verfügbar. Im Original ist &lt;code&gt;--allObjectsAccessible&lt;/code&gt; eine Option ohne Argument. Hier muss (falls es verwendet wird) entweder True oder False gesetzt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neu sind folgende Optionen implementiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_ILIDIRS&lt;/code&gt;: &lt;code&gt;--modeldir&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_MODELNAMES&lt;/code&gt;: &lt;code&gt;--models&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_ALL_OBJECTS_ACCESSIBLE&lt;/code&gt;: &lt;code&gt;--allObjectsAccessible&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_LOGFILE&lt;/code&gt;: &lt;code&gt;--log&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_LOGFILE_TIMESTAMP&lt;/code&gt;: &lt;code&gt;--logtime&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SETTING_XTFLOG&lt;/code&gt;: &lt;code&gt;--xtflog&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;trace&lt;/code&gt;: &lt;code&gt;--trace&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Optionen, die im Original ohne Argument funktionieren, müssen hier immer mit einem Boolean-Werte verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich habe ich mit &lt;em&gt;pytest&lt;/em&gt; angefangen Tests zu schreiben. Bei der Native Shared Library ist mir noch nicht klar was und wie genau getestet werden kann/soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein einfachster ilivalidator-Webservice mit &lt;a href=&quot;https://flask.palletsprojects.com/&quot;&gt;&lt;em&gt;Flask&lt;/em&gt;&lt;/a&gt; (ohne Grüsel-Systemcalls):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;pip install Flask
pip install ilivalidator==0.0.2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;ilivalidator-webservice.py&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Python&quot; data-lang=&quot;Python&quot;&gt;import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename
from ilivalidator import Ilivalidator
import tempfile

UPLOAD_FOLDER = &apos;/tmp&apos;
ALLOWED_EXTENSIONS = {&apos;xtf&apos;, &apos;xml&apos;, &apos;itf&apos;}

app = Flask(__name__)
app.config[&apos;UPLOAD_FOLDER&apos;] = UPLOAD_FOLDER

def allowed_file(filename):
    return &apos;.&apos; in filename and \
           filename.rsplit(&apos;.&apos;, 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route(&apos;/&apos;, methods=[&apos;GET&apos;, &apos;POST&apos;])
def upload_file():
    if request.method == &apos;POST&apos;:
        # check if the post request has the file part
        if &apos;file&apos; not in request.files:
            flash(&apos;No file part&apos;)
            return redirect(request.url)
        file = request.files[&apos;file&apos;]
        # If the user does not select a file, the browser submits an
        # empty file without a filename.
        if file.filename == &apos;&apos;:
            flash(&apos;No selected file&apos;)
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            filepath = os.path.join(app.config[&apos;UPLOAD_FOLDER&apos;], filename)
            file.save(filepath)

            temp_dir = tempfile.TemporaryDirectory()
            log_file = os.path.join(temp_dir.name, &quot;mylog.log&quot;)

            settings = {Ilivalidator.SETTING_LOGFILE: log_file}
            valid = Ilivalidator.validate([filepath], settings)

            with open(log_file,&quot;r&quot;) as f:
                content = f.read()
                return content

            return &quot;should not reach here&quot;
    return &apos;&apos;&apos;
    &amp;lt;!doctype html&amp;gt;
    &amp;lt;title&amp;gt;Upload new File&amp;lt;/title&amp;gt;
    &amp;lt;h1&amp;gt;Upload new File&amp;lt;/h1&amp;gt;
    &amp;lt;form method=post enctype=multipart/form-data&amp;gt;
      &amp;lt;input type=file name=file&amp;gt;
      &amp;lt;input type=submit value=Upload&amp;gt;
    &amp;lt;/form&amp;gt;
    &apos;&apos;&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Grundgerüst copy/paste von &lt;a href=&quot;https://flask.palletsprojects.com/en/2.2.x/patterns/fileuploads/&quot;&gt;hier&lt;/a&gt;.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Flask-Anwendung starten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;flask --app ilivalidator-webservice run&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und mit &lt;code&gt;curl&lt;/code&gt; eine INTERLIS-Transferdatei hochladen (oder mit dem Browser):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;curl -X POST -F file=@OeREBKRM_V2_0_Gesetze.xml http://127.0.0.1:5000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Output sollte der Inhalt des Logfiles der Validierung sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p34/flask_output.png&quot; alt=&quot;flask validation output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What&amp;#8217;s next? Keine Ahnung. Ich persönlich habe keinen grossen Bedarf an den ilitools in Python. Trotzdem finde ich es eine sinnvolle Sache und es könnte der Verbreitung und Akzeptanz von INTERLIS helfen. Dann müssten aber die Python Aficionados und geostandards.ch aus dem &lt;a href=&quot;https://geostandards.ch/&quot;&gt;Sonnenaufgang&lt;/a&gt; hervor geritten kommen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #11 - ÖREB-Kataster Compliance Test Suite Webservice</title>
      <link>http://blog.sogeo.services/blog/2022/12/22/oereb-kataster-richtig-gemacht-11.html</link>
      <pubDate>Thu, 22 Dec 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/12/22/oereb-kataster-richtig-gemacht-11.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Oktober habe ich einen Prototypen eines ÖREB-Kataster-Checkers &lt;a href=&quot;http://blog.sogeo.services/blog/2022/10/02/oereb-kataster-richtig-gemacht-8.html&quot;&gt;vorgestellt&lt;/a&gt;. Das Ganze habe ich als &lt;a href=&quot;https://sogis-oereb-cts-remdc.ondigitalocean.app/&quot;&gt;Webservice deployed&lt;/a&gt;, der alle paar Stunden die Prüfungen macht. Geprüft wird lange nicht alles was geprüft werden könnte, sondern nur einige Aspekte, die mir wichtig scheinen (oder einfach zu implementieren waren):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HTTP Status Code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Response Content Type&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Schemakonformität&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gibt es Geometrie-Elemente, wenn solche vorhanden sein müssen (resp. umgekehrt)?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sind die Geometrien im Bezugsrahmen LV95?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gibt es eingebettete Bilder, wenn solche vorhanden sein müssen (resp. umgekehrt)?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sind alle Bundesthemen-Codes im XML-Inhaltsverzeichnis vorhanden? (ConcernedTheme, NotConcernedTheme, ThemeWithoutData)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Response Content Type lasse ich &lt;code&gt;application/xml; charset=UTF-8&lt;/code&gt; als korrekt durchgehen, auch wenn die Weisung nur von &lt;code&gt;application/xml&lt;/code&gt; spricht. Ob alle Bundesthemen-Codes vorhanden sein müssen, ist mir selber nicht ganz klar. Wenn man z.B. die neuen Themen noch nicht umgesetzt hat, könnten sie fehlen. Aber in diesem Fall sollten sie sinnvollerweise bei den Themen ohne Daten erscheinen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige erwähnenswerte Beobachtungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Verschiedene Kantone (BS, GR, NW, OW, UR, VS, ZH) verwenden einen falschen Status Code bei der Weiterleitung zum dynamischen Auszug. Entweder wird 301 oder 302 verwendet, korrekt ist 303. Der Kanton Wallis ist ein Spezialfall. Dazu später mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige Kantone (AG, BS, GR, TI) scheinen noch nicht eine aktuelle pyramidoereb-Version zu verwenden. Da gab es noch ein &lt;a href=&quot;https://github.com/openoereb/pyramid_oereb/issues/1601&quot;&gt;Problem mit der Schemakonformität und dem QRCode/QRCodeRef-Element&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Content Type ist in seltenen Fällen falsch (und kein Folgefehler): Die Kantone Obwalden, Nidwalden und Uri verwenden beim Extract &lt;code&gt;text/xml; charset=utf-8&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei vielen Kantonen fehlen &lt;em&gt;meines Erachtens&lt;/em&gt; verschiedene Bundesthemen-Codes im Extract. Es gibt zwei Fälle zu unterscheiden: Das sind die Kantone (AG, AR, BE, SG, ZH), welche die neuen Themen noch nicht umgesetzt haben. Ich finde, wenn man schon das Rahmenmodell Version 2 verwendet, sollten diese Themen unter &lt;code&gt;ThemeWithoutData&lt;/code&gt; gelistet werden. Dann gibt die Kantone (AI, BS, LU, NW, OW, TG, UR), die zusätzlich (oder nur) bestehende Themen in keinem der drei &amp;laquo;Themen&amp;raquo; im Inhaltsverzeichnis auflisten. So gibt es z.B. in keinem dieser Kantone eine Nutzungsplanung (&lt;code&gt;ch.Nutzungsplanung&lt;/code&gt;). Sondern es gibt das kantonale Derivat davon (&lt;code&gt;ch.KT.xxxxx&lt;/code&gt;). Das dünkt mich falsch. Wenn man die Nutzungsplanung unterteilen will, sollte das mit Subthemen gemacht werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kommen wir zu den exotischeren Fällen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Aargau scheint es im Kontext des ÖREB-Katasters keine NBIdent zu geben. Im Extract steht &lt;code&gt;&amp;lt;data:IdentDN&amp;gt;N/A&amp;lt;/data:IdentDN&amp;gt;&lt;/code&gt;. Wenn ich den (hoffentlich korrekten) NBIdent aus den Daten der amtlichen Vermessung verwende, funktioniert der GetEGRID-Request jedoch trotzdem nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Appenzell Innerrhoden funktionieren &lt;a href=&quot;https://oereb.ai.ch/ktai/wsgi/oereb/extract/xml/?EGRID=CH967759413925&amp;amp;WITHIMAGES=true&quot;&gt;Aufrufe&lt;/a&gt; mit &lt;code&gt;WITHIMAGES=true&lt;/code&gt; nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Freiburg werden beim &lt;a href=&quot;https://geo.fr.ch/RDPPF_ws/RdppfSVC.svc/getegrid/xml/?EN=2578478,1183785&amp;amp;GEOMETRY=true&quot;&gt;GetEGRID-Aufruf&lt;/a&gt; mit &lt;code&gt;GEOMETRY=true&lt;/code&gt; keine Geometrie zurückgeliefert. Im Kanton Basel-Stadt funktioniert ein Aufruf mit &lt;code&gt;GEOMETRY=true&lt;/code&gt; weder bei &lt;a href=&quot;https://api.oereb.bs.ch/getegrid/xml/?EN=2612855,1267223&amp;amp;GEOMETRY=true&quot;&gt;GetEGRID&lt;/a&gt; noch beim &lt;a href=&quot;https://api.oereb.bs.ch/extract/xml/?EGRID=CH356489796755&amp;amp;GEOMETRY=true&quot;&gt;Extract&lt;/a&gt;. In beiden Fällen werden Fehler zurückgeliefert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Uri verwendet konsequent &lt;a href=&quot;https://prozessor-oereb.ur.ch/oereb/getegrid/xml/?EN=2694124,1180546&amp;amp;GEOMETRY=true&quot;&gt;keine LV95-Koordinaten&lt;/a&gt; in den Geometrien, sondern Pseudo-Mercator (EPSG:3857). Ich bin mir nicht sicher, ob das überhaupt irgendwo explizit geregelt ist. Aber man darf schon davon ausgehen, dass man LV95 ausspucken sollte. Zudem im &lt;a href=&quot;https://prozessor-oereb.ur.ch/oereb/capabilities/xml&quot;&gt;Capabilities-Dokument&lt;/a&gt; auch der Kanton Uri dies so bestätigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Zürich weist das &lt;code&gt;Limit&lt;/code&gt;-Element beim &lt;a href=&quot;https://maps.zh.ch/oereb/v2/getegrid/xml/?EN=2683914,1248184&amp;amp;GEOMETRY=true&quot;&gt;GetEGRID-XML&lt;/a&gt; dem falschen Namespace zu, was zu Schemakonformitätsfehlern führt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Luzern liefert leider immer &lt;a href=&quot;https://svc.geo.lu.ch/oereb/extract/xml/?EGRID=CH695089003582&amp;amp;WITHIMAGES=false&quot;&gt;eingebettete Bilder&lt;/a&gt; mit, auch wenn er dies explizit nicht sollte. Dafür fehlt der Verweis auf den WMS. Das ist sehr ärgerlich und wahrscheinlich der mühsamste Fehler. Die Schnittstelle wird so ad absurdum geführt: Die Anfragezeit ist deutlich länger (weil ja die Bilder produziert werden müssen) und für Aussenstehende ist der Dienst unbrauchbar, weil die WMS-Referenzen fehlen. Man kann keinen dynamischen Auszug bedienen. Ein Schelm wer Böses denkt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein anderes Sorgenkind ist der Kanton Wallis: Er leitet die Anfragen konsequent mit 307 (Temporary Redirect) weiter. Der Grund ist, soweit ich das nachvollziehen kann, der &amp;laquo;trailing slash&amp;raquo; im Pfad der Url: &lt;code&gt;&amp;#8230;&amp;#8203;/getegrid/xml/?EN=2643445,1130616&lt;/code&gt;. Ein solcher Request wird zu &lt;code&gt;&amp;#8230;&amp;#8203;/getegrid/xml?EN=2643445,1130616&lt;/code&gt; weitergeleitet (ohne den letzten Slash). Ich finde das nicht ein gutes Vorgehen: In der Weisung gibt es explizit eine Weiterleitung (zum dynamischen Auszug). D.h. Weiterleitungen haben eine gewisse Semantik. Ein Anwender der Schnittstelle muss seinen HTTP-Client nun so konfigurieren, dass er für die statischen Auszüge weiterleitet (Browser macht das automatisch, &lt;em&gt;curl&lt;/em&gt; nicht), wahrscheinlich aber für den dynamischen Auszug nicht, weil das sowieso anders gehabt werden muss. Als weiteres Ungemach wartet dann eine &lt;a href=&quot;https://rdppfvs.geopol.ch/getegrid/xml?EN=2643445,1130616&amp;amp;GEOMETRY=true&quot;&gt;XML-Datei&lt;/a&gt; &lt;em&gt;ohne&lt;/em&gt; Namespaces und &lt;em&gt;ohne&lt;/em&gt; Geometrien. Die &lt;a href=&quot;https://rdppfvs.geopol.ch/extract/xml?EGRID=CH595290323059&quot;&gt;WMS-Referenzen&lt;/a&gt; zeigen auf ArcGIS-REST-Services und nicht auf WMS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Manchmal kommt es vor, dass ein Request nicht funktioniert. Entweder ist der Dienst noch nicht wirklich stabil oder der Hund liegt bei mir begraben. Klar ist, dass ich keine Retries mache. Das muss ich beobachten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fehler in der Analyse bitte melden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #33 - Python loves INTERLIS</title>
      <link>http://blog.sogeo.services/blog/2022/12/11/interlis-leicht-gemacht-number-33.html</link>
      <pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/12/11/interlis-leicht-gemacht-number-33.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;a href=&quot;https://geobeer.ch/&quot;&gt;GeoBeer-Events&lt;/a&gt; sind eine tolle Sache. Wenn man aber den Mund mit zunehmender Menge Bier immer voller nimmt, ist man halt selber schuld: Für einen Github-Stern versprach ich, dass ich einen Proof of Concept der Java-INTERLIS-Werkzeuge als Python-Package mache, weil das mit &lt;a href=&quot;https://graalvm.org&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; &amp;laquo;ganz einfach und schnell geht&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee ist, dass man z.B. &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; wie praktisch jedes andere Python Package installieren kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;pip install ilivalidator&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und natürlich ohne den ganzen Java-Zauber, d.h. es soll ganz ohne Java funktionieren. Über die Jahre habe ich bereits einiges mit &lt;em&gt;GraalVM&lt;/em&gt; in dieser Richtung gemacht. Darum auch die Euphorie, dass das alles kein Problem sei. Die grösste Unbekannte war für mich eher wie man ein Python Package macht (inkl. Shared Library) und das zum Download für &lt;em&gt;pip&lt;/em&gt; bereitstellt. Das Ganze soll natürlich ebenfalls als Github Action in einer OS-Matrix laufen. Aber der Reihe nach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;GraalVM&lt;/em&gt; lassen sich nicht bloss die INTERLIS-Werkzeuge zu einem &lt;a href=&quot;http://blog.sogeo.services/blog/2022/11/01/interlis-leicht-gemacht-number-31.html&quot;&gt;Native Image&lt;/a&gt; kompilieren, sondern es lassen sich auch sogenannte Native Shared Libraries machen. Das sind Bibliotheken, die man in einem C/C++-Programm verwenden kann (aka *.so und *.dll). Diese lassen sich auch in einem Python-Skript gut ansteuern und verwenden. Wenn man nicht das komplette Programm zu einem Native Image kompilieren will, sondern zu einer Shared Library, muss man ein paar Zeilen zusätzlichen Code schreiben. Nämlich eine statische Java-Methode:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Java&quot; data-lang=&quot;Java&quot;&gt;package ch.so.agi.ilivalidator.libnative;

import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.interlis2.validator.Validator;

import ch.ehi.basics.settings.Settings;

public class IlivalidatorLib {

    @CEntryPoint(name = &quot;ilivalidator&quot;)
    public static boolean validate(IsolateThread thread, CCharPointer dataFilename) {
        var settings = new Settings();
        boolean valid = Validator.runValidation(CTypeConversion.toJavaString(dataFilename), settings);
        return valid;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese statische Methode muss mit &lt;code&gt;@CEntryPoint&lt;/code&gt; annotiert werden, damit sie im C/C++-Programm aufrufbar wird. Es gibt ein wenig GraalVM-Boilerplate (&lt;code&gt;IsolateThread thread&lt;/code&gt;), der uns aber nicht gross stören soll. Weil es ein Proof of Concept ist, ist die Methode sehr einfach gehalten. Es kann nur der Dateinamen der zu prüfenden INTERLIS-Transferdatei übergeben werden. Als Typ muss &lt;code&gt;CCharPointer&lt;/code&gt; verwendet werden, der später zu einem Java-String umgewandelt wird. Es können nur wenige Typen verwenden werden. Es wird als nicht möglich sein z.B. eine &lt;code&gt;Settings&lt;/code&gt;-Klasse zu übergeben. Das ist aber weniger schlimmer als zuerst befürchtet. Die Settings können z.B. als JSON-String übergeben werden und anschliessend in der Methode zu ihren korrekten Optionen gemappt werden. Das Resultat nach dem Kompilieren mit &lt;em&gt;GraalVM&lt;/em&gt; sind Header-Dateien und die Shared Library. Im Gegensatz zu Java-Bibliothekn muss die Shared Library für jedes Betriebssystem kompiliert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Python bietet mit &lt;code&gt;ctypes&lt;/code&gt; eine Bibliothek, die Funktionen in einer Shared Library aufrufen kann. Die Python-Ilivalidator-Klasse sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Python&quot; data-lang=&quot;Python&quot;&gt;import platform

from ctypes import *
from importlib_resources import files

if platform.uname()[0] == &quot;Windows&quot;:
    lib_name = &quot;libilivalidator.dll&quot;
elif platform.uname()[0] == &quot;Linux&quot;:
    lib_name = &quot;libilivalidator.so&quot;
else:
    lib_name = &quot;libilivalidator.dylib&quot;

class Ilivalidator:
    def validate(data_file_name):
        lib_path = files(&apos;ilivalidator.lib_ext&apos;).joinpath(lib_name)
        # str() seems to be necessary on windows: https://github.com/TimDettmers/bitsandbytes/issues/30
        dll = CDLL(str(lib_path))
        isolate = c_void_p()
        isolatethread = c_void_p()
        dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))
        dll.ilivalidator.restype = bool

        result = dll.ilivalidator(isolatethread, c_char_p(bytes(data_file_name, &quot;utf8&quot;)))
        return result

Ilivalidator.validate = staticmethod(Ilivalidator.validate)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst musst die Library geladen werden. Anschliessend wird in Zeile 23 die &lt;code&gt;ilivalidator&lt;/code&gt;-Funktion in der Shared Library aufgerufen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun kam der wirklich schwere Teil: Wie macht man ein Python Package und wie stelle ich dieses bereit? Als totaler Python-Fremdling war das eine echte Herausforderung. Einfach weil man es nicht kennt und aus einer völlig anderen Welt kommt. Nach ein wenig Einlesen und Googeln hatte ich mein Package soweit und konnte es auf test.pypi.org veröffentlichen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine weitere Herausforderung war das Testen unter Windows. Vorallem fehlte mir die Windows-Testumgebung. Klar kann ich relativ einfach den Code mit einer Github Action auf einem Windows-Runner testen. Das Debuggen ist jedoch so ziemlich schwierig. Auf meinem Macbook Air mit &lt;a href=&quot;https://mac.getutm.app/&quot;&gt;&lt;em&gt;UTM&lt;/em&gt;&lt;/a&gt; geht das wegen des ARM-Prozessors schon mal nicht (so gut), da es keinen Github Runner für Windows ARM gibt, der die Shared Library kompilieren könnte. Darum musste das alte Intel-Macbook her. Netterweise bietet Microsoft &lt;a href=&quot;https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/&quot;&gt;Windows 11 Images&lt;/a&gt; für verschiedene Hypervisoren. Die Kombination mit &lt;em&gt;Virtualbox&lt;/em&gt; erwies sich aber als unbrauchbar, da Windows nach kurzer Zeit nicht mehr auf Eingaben reagierte. Mit dem leider nicht freien &lt;em&gt;Parallels&lt;/em&gt; funktioniert es aber sehr gut. Somit konnte ich das Python Package auch unter Windows 11 testen und paar Fehler ausmerzen. Klammerbemerkung: Warum man einfach mal &lt;code&gt;str()&lt;/code&gt; verwenden soll, damit es unter Windows läuft, weiss nur der liebe Gott oder ein Python-Guru. Erinnert mich an meine C-Experimente während des Studiums: Hat man das Prinzip mit den Zeigern nicht so wirklich verstanden, probiert man es mit &lt;code&gt;*foo&lt;/code&gt; und wenn das nicht funktioniert, versucht man &lt;code&gt;&amp;amp;foo&lt;/code&gt;. Profis am Werk.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun denn, here it is: &lt;a href=&quot;https://pypi.org/project/ilivalidator/&quot;&gt;https://pypi.org/project/ilivalidator/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Package lässt sich mit &lt;em&gt;pip&lt;/em&gt; installieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;pip install ilivalidator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Innerhalb eine Python-Skripts kann ich &lt;em&gt;ilivalidator&lt;/em&gt; wie folgt aufrufen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-Python&quot; data-lang=&quot;Python&quot;&gt;from ilivalidator import Ilivalidator

valid = Ilivalidator.validate(&apos;tests/data/254900.itf&apos;)
print(&quot;The file is valid: {}&quot;.format(valid))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es sind keine ilivalidator-Optionen exponiert und somit ist es wirklich nur ein Proof of Concept. Aber nun steht alles und man müsste Fleissarbeit leisten. Die anderen Java-INTERLIS-Werkzeuge lassen sich analog als Python Package bereitstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil es kein pures Python Package ist (sondern abhängig von Native Shared Libraries ist), muss man für jedes Betriebssystem, Betriebssystemvariante und Prozessor-Architektur das Package herstellen. Momentan lässt sich das einfach für folgende Schnittmenge bewerkstelligen: Die Betriebssysteme und Prozessor-Architekturen, die GraalVM Native Image unterstützt und die frei verfügbaren Github Action Runner. In meiner Github Action kompiliere ich auf Ubuntu 22.04, macOS 12 und Windows 2022 jeweils auf x86_64 (also nicht ARM). Man könnte z.B. für Linux ARM bei Oracle Cloud gratis einen Self-Hosted Runner erstellen oder analog für Apple Silicon bei Hetzner einen Mac mini mieten. Die verfügbaren Kombinationen finden sich auf pypi.org unter &lt;a href=&quot;https://pypi.org/project/ilivalidator/#files&quot;&gt;&amp;laquo;Download files&amp;raquo;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, jetzt will ich meinen &lt;a href=&quot;https://github.com/pylitools/ilivalidator&quot;&gt;Github-Stern&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GraalVM Polyglot Superpowers #1 - SpatioTemporal Asset Catalogs (Stac)</title>
      <link>http://blog.sogeo.services/blog/2022/11/14/graalvm-polyglot-superpowers-p1.html</link>
      <pubDate>Mon, 14 Nov 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/11/14/graalvm-polyglot-superpowers-p1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://stacspec.org/&quot;&gt;SpatioTemporal Asset Catalogs&lt;/a&gt; sind der neue heisse Scheiss in der (Cloud-Native-)Geowelt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;At its core, the SpatioTemporal Asset Catalog (STAC) specification provides a common structure for describing and cataloging spatiotemporal assets.&amp;raquo;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Umgangssprachlich heisst das: JSON-Dateien mit Links auf Datensätze mit bisschen Metainformation und der Möglichkeit verschiedene Zeitstände anzubieten. Eigentlich gibt es das dank &lt;a href=&quot;https://inspire.ec.europa.eu/documents/Network_Services/Technical_Guidance_Download_Services_v3.1.pdf&quot;&gt;INSPIRE&lt;/a&gt; bereits auf Basis von Atomfeed und OpenSearch (mit einer Helvetisierung in &lt;a href=&quot;https://ech.ch/de/ech/ech-0056/3.0-0&quot;&gt;eCH-0056&lt;/a&gt;). So richtig durchgesetzt hat sich das nicht, was schade ist, da man damals bereits darauf geachtet hat das Rad nicht mehr neu zu erfinden, sondern vorhandene Nicht-GIS-Technologien zu verwenden. Die Helvetisierung war ein Totalflop, was man sich im Nachhinein leider eingestehen muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Stac-Spezifikation besteht aus vier &amp;laquo;semi-unabhängigen&amp;raquo; Spezifikationen, was zeitweilig ein wenig verwirrend sein kann. Die Chose ist aber auf &lt;a href=&quot;https://github.com/radiantearth/stac-spec&quot;&gt;Github&lt;/a&gt; relativ gut beschrieben. Nur manchmal wünschte ich mir ein INTERLIS-Modell zwecks besserer Lesbarkeit. Etwas was mich eben verwirrt, sind Ankündigungen, dass ein Release Candidate einer Stac-Spez veröffentlicht wurde. Hä, warum, die ist noch schon fertig? Nein, es gibt vier davon. Die ersten drei beschreiben meines Erachtes nur statische Dinge (Item, Catalog und Collection), also im Prinzip die JSON-Schemen. Die Vierte im Bunde, die Stac API Spezifikation, spezifiert eine Rest-API. Um diese geht es hier nicht, sondern um die Herstellung der statischen Dateien und das Bereitstellen dieser.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir verwalten sämtliche Informationen zu einem Datenthema in einer Metadatenbank. Aus diesen Informationen erzeugen wir unterschiedliche Konfigurationsdateien, um die verschiedenen Dienste zu bedienen, die dann zur Laufzeit nicht auf die Metadatenbank zugreifen müssen, sondern nur auf diese Konfigurationsdatei. Einer dieser Dienste ist die sogenannten Datensuche, sprich unsere zukünftige Datenabgabe. Für diese stellen wir eine XML-Datei aus der Metadatenbank her mit den benötigten Informationen aller zum Download stehenden Geodaten. Die Datensuche-Anwendung liest beim Start diese XML-Datei, erstellt einen Suchindex und füllt das GUI ab. Auf dieser Basis könnte man auch gleich den Stac-Catalog erzeugen: XML-Datei als Input und die Stac-JSON-Dateien als Output via Webserver bereitstellen. Für das Herstellen von Stac-Catalogen dünkt mich vor allem die Python-Bibliothek &lt;a href=&quot;https://stacspec.org/en/about/tools-resources/&quot;&gt;&lt;em&gt;pystac&lt;/em&gt;&lt;/a&gt; tauglich. Das würde aber bedeuten, dass ich eine komplette Anwendung mit Python machen müsste. &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; to the rescue.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;GraalVM&lt;/em&gt; bietet unter anderem eine &lt;a href=&quot;https://www.graalvm.org/python/&quot;&gt;Python-Implementierung&lt;/a&gt; an. Das Spannende für mich ist, dass man polyglot programmieren kann. D.h. man kann zwischen Java und Python Objekte austauschen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;class StacCreator:
    def create(self, collection_file_path, theme_publication, files_server_url, root_url):
        collection_id = theme_publication.getIdentifier()
        ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Python-Methode &lt;code&gt;create&lt;/code&gt; wird eine &lt;code&gt;theme_publication&lt;/code&gt; übergeben. Dies ist ein POJO, das aus dem Parsen der XML-Datei in Java erzeugt wurde. In der Python-Methode kann ich zum Beispiel auf die Getter-Methode &lt;code&gt;getIdentifier()&lt;/code&gt; zugreifen, welche mir den Identifier der Themenbereitstellung liefert usw.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein Ziel ist es also möglichst wenig Python schreiben zu müssen. Nur gerade das, was notwendig ist, um die Stac-Kataloge zu erzeugen. Kein Parsen der XML-Dateien, keinen Webserver hochfahren etc. Müsste ich alles lernen und habe ich bereits in meiner Spring Boot Anwendung vorliegen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Grundlage diente mir Tim Felgentreffs ausgezeichneter Blogbeitrag &lt;a href=&quot;https://medium.com/graalvm/supercharge-your-java-apps-with-python-ec5d30634d18&quot;&gt;&amp;laquo;Supercharge Your Java Apps with Python&amp;raquo;&lt;/a&gt;. Interessanter als das Geschriebene zu wiederholen, ist zu erwähnen, was mir nicht gelungen ist resp. nur über Umwege.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst wollte ich die Anwendung zu einem Native Image kompilieren, damit man &lt;em&gt;GraalVM&lt;/em&gt; nicht installieren muss. Das geht mit Spring Boot leider &lt;a href=&quot;https://github.com/oracle/graal/issues/4473&quot;&gt;noch nicht&lt;/a&gt;. Weil die Anwendung, wenn überhaupt, im Regelfall in einem Dockercontainer laufen würde, ist das nicht das Ende der Welt. Die grössere Herausforderung ist/war der Umgang mit dem &lt;em&gt;venv&lt;/em&gt;-Ordner. &lt;em&gt;GraalVM&lt;/em&gt; erstellt diesen und speichert die notwendigen Python-Bibliotheken in dieser virtuellen Umgebung. Wie paketiert man das in der Java-Welt? Am elegantesten wäre natürlich, wenn wir den &lt;em&gt;venv&lt;/em&gt;-Ordner als normale Ressource behandeln können und in die Spring Boot Fat-Jar packen könnten. Die Enttäuschung kommt beim Starten der Anwendung: Es findet die PyStac-Bibliothek nicht. Grund dafür ist, dass das unterliegende Truffle-Filesystem kein Jar-Filesystem implementiert, sondern &amp;laquo;nur&amp;raquo; das normale Filesystem. Wenn die Anwendung nur in einem Docker-Container laufen würde, könnte man den &lt;em&gt;venv&lt;/em&gt;-Ordner beim Builden des Images reinkopieren. Das finde ich einschränkend. Ich mag immer noch den Gedanken, dass eine Java-Awendung, jetzt halt eine polyglote Java-Anwendung, mit &lt;code&gt;java -jar sodata-stac.jar&lt;/code&gt; gestartet werden kann. Ich habe mich dazu entschlossen den &lt;em&gt;venv&lt;/em&gt;-Ordner zu zippen und die Zip-Datei in die Jar-Datei zu kopieren. Diese Zip-Datei wird beim Hochfahren der Anwendung aus den Ressourcen kopiert und entpackt. Vielleicht kann man das eleganter lösen. Mir ist aber nichts besseres in den Sinn gekommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein ein weiteres Problem ist die Kompatibilität der Python-Bibliotheken. Diese ist nicht immer garantiert und man ist in diesem Fall aufgeschmissen. Bei mir lief aber alles rund mit PyStac.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist polyglotes Programmieren sinnvoll? It depends. Ich sehe schon Vorteile (auch wenn jetzt gleich die Microservice-Marktschreier ums Eck kommen werden): Ich bin froh, wenn ich in der Java-Welt, wo ich einiges kenne (Build-Tools, IDE, Frameworks, Paketierung &amp;#8230;&amp;#8203;), verbleiben kann und nur das absolut nötigste auslagern muss. Und welche GIS-Abteilung hat tatsächlich die notwendige Grösse und hat verschiedene Teams, die an verschiedenen Microservices arbeiten?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es funktioniert übrigens auch umgekehrt, d.h. mittels Graalpython auf Java-Klassen zugreifen. Das habe ich in einem älteren Blogbeitrag am Beispiel &lt;em&gt;ili2gpkg&lt;/em&gt; &lt;a href=&quot;http://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html&quot;&gt;gezeigt&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nachfolgend Links zum Code und zu interessanten Webseiten, die mir weitergeholfen haben. Wer es ausprobieren will:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;docker run -p 8080:8080 -v ~/tmp:/stac sogis/sodata-stac-jvm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im lokalen &lt;em&gt;~/tmp&lt;/em&gt;-Ordner liegen die Stac-Dateien. Die Links auf die Assets funktionieren nicht, da es sich um Fantasie-Daten handelt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/sodata-stac&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/sodata-stac&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/graalvm/supercharge-your-java-apps-with-python-ec5d30634d18&quot; class=&quot;bare&quot;&gt;https://medium.com/graalvm/supercharge-your-java-apps-with-python-ec5d30634d18&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://technology.amis.nl/languages/create-a-native-image-binary-executable-for-a-polyglot-java-application-using-graalvm/&quot; class=&quot;bare&quot;&gt;https://technology.amis.nl/languages/create-a-native-image-binary-executable-for-a-polyglot-java-application-using-graalvm/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blogs.oracle.com/javamagazine/post/java-graalvm-polyglot-python-r&quot; class=&quot;bare&quot;&gt;https://blogs.oracle.com/javamagazine/post/java-graalvm-polyglot-python-r&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.graalvm.org/22.3/reference-manual/embed-languages/&quot; class=&quot;bare&quot;&gt;https://www.graalvm.org/22.3/reference-manual/embed-languages/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #32 - The State of INTERLIS native</title>
      <link>http://blog.sogeo.services/blog/2022/11/01/interlis-leicht-gemacht-number-31.html</link>
      <pubDate>Tue, 1 Nov 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/11/01/interlis-leicht-gemacht-number-31.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Titel ist (zu) bedeutungsschwanger und schlaumeierisch. Es geht weniger um INTERLIS selbst, sondern ob die Lieblingswerkzeuge eines jeden Geoinformatikers (&lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ili2c&quot;&gt;&lt;em&gt;ili2c&lt;/em&gt;&lt;/a&gt;) zu einem Native Image kompilierbar sind und diese sinnvoll zu gebrauchen sind. Ein Native Image ist ahead-of-time kompilierter Java-Code. Dieses Native Image benötigt keine Java Virtuelle Maschine mehr. Dafür muss es für jedes Betriebssystem eigens kompiliert werden. Ich fand die Idee ziemlich cool, als ich das erste Mal davon hörte. Meine Einstiegsdroge war ein &lt;a href=&quot;https://chrisseaton.com/truffleruby/tenthings/&quot;&gt;Blogbeitrag&lt;/a&gt; von Chris Seaton. Damals wohl Mitarbeiter bei Oracle Labs. Ein Native Image kann man nicht mit dem normalen JDK herstellen, sondern man benötigt (noch) &lt;a href=&quot;https://graalvm.org&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt;. Ein Teil der GraalVM wird wieder zurück in das OpenJDK-Projekt fliessen, wo es &lt;a href=&quot;https://openjdk.org/jeps/295&quot;&gt;glaub ursprünglich sogar herkam&lt;/a&gt;. Im Kern also bereits eine alte Idee.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum möchte man überhaupt Java-Anwendungen zu einem Native Image kompilieren? Solche Native Images sind kleiner (im Vergleich zu einer JVM plus Anwendungscode) und starten extrem schnell, was sie spannend macht für Cloud Deployments. Sie benötigen zudem weniger RAM und CPU. Aber all das ist nicht allgemeingültig und Nachteile gibt es auch. Wie erwähnt muss man für jedes Betriebssystem ein separates Native Image kompilieren. Das Kompilieren dauert relativ lange und im Gegensatz zum Just-in-Time-Compiler finden zur Laufzeit keine Optimierungen statt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Fall der INTERLIS-Werkzeuge kann man sich schon vorstellen, dass ein Native Image ein paar Vorteile bringt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Zugang zur Anwendung vereinfachen: Für viele Anwender ist das Installieren der Java-Anwendung eine Herausforderung. Man muss die Zip-Datei herunterladen und entpacken und dann - wenn man das GUI verwenden will - müssen die Einstellungen auf dem Computer korrekt sein, damit ein Doppelklick die Anwendung startet. Zudem muss eine JVM vorhanden sein, die ebenfalls installiert werden will. Hier könnte es eventuell schon helfen, wenn alles in ein einzelnes ausführbares Binary verpackt wird.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Einbinden in andere (Nicht-Java-)Systeme: &lt;a href=&quot;https://opengisch.github.io/QgisModelBaker/&quot;&gt;&lt;em&gt;QGIS Model Baker&lt;/em&gt;&lt;/a&gt; verwendet &lt;em&gt;ili2db&lt;/em&gt; für das ganze INTERLIS-Handling. Model Baker ist ein QGIS-Plugin, geschrieben in Python. Das Python-Plugin hat somit als Abhängigkeit eine JVM und die Java-Anwendung selber. Die Kommunikation zwischen dem Plugin und &lt;em&gt;ili2db&lt;/em&gt; geschieht mittels Systemcalls. Stünde &lt;em&gt;ili2db&lt;/em&gt; als Native Image zur Verfügung, fällt die Java-Abhängigkeit weg. Champions League wäre wenn man alles direkt ins Plugin bringen könnte, was nicht ganz undenkbar ist, da man mit GraalVM auch Shared Libraries herstellen kann. Wie das funktionieren könnte, kann man &lt;a href=&quot;http://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html&quot;&gt;hier&lt;/a&gt; nachlesen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anfang 2019 habe ich &lt;a href=&quot;http://blog.sogeo.services/blog/2019/02/23/graalvm-p1-interlis-polyglot-gemacht.html&quot;&gt;erste Gehversuche mit &lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und Native Image gemacht. Damals war das noch ziemlich knorzig. Wie sieht es heute aus?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes habe ich mir &lt;a href=&quot;https://github.com/edigonzales/ili2c-native&quot;&gt;&lt;em&gt;ili2c&lt;/em&gt;&lt;/a&gt; vorgenommen. Der Swing/AWT-Support von GraalVM ist noch &lt;a href=&quot;https://github.com/oracle/graal/issues/4921&quot;&gt;verbesserungsfähig&lt;/a&gt;. Weil Liberica NIK (abgleitet von GraalVM) meint, sie hätten besseren Swing/AWT-Support, habe ich dieses zum Kompilieren verwendet. Mit dem Tracing Agent muss man aber auf jedem Betriebssystem wegen der GUI-Komponenten gewisse Konfigurationsdateien herstellen, was auch nicht spassig ist mit einem MacBook Air M1. &lt;a href=&quot;https://mac.getutm.app/&quot;&gt;&lt;em&gt;UTM&lt;/em&gt;&lt;/a&gt; sei dank. Man kann sogar Windows ARM (Insider Preview) installieren. Nur leider gibt es dafür Liberica NIK nicht. Darum musste für Windows noch ein älteres Intel MacBook herhalten, was wiederum ein käsiges Erlebnis war. Das Resultat war semi-erfolgreich. Auf Ubuntu funktioniert das GUI tadellos. Auf macOS funktioniert es auch, nur darf man das Fenster nicht verkleinern oder vergrössern. Dann schmiert es ab. Auf Windows habe ich es nicht getestet. Ansonsten ist das Kompilieren zum Native Image problemlos gegangen. Und der INTERLIS-Compiler funktioniert als Native Image auf der Konsole tadellos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes kam &lt;a href=&quot;https://github.com/edigonzales/ili2pg-native&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; dran. Da wollte ich das GUI schon a priori nicht unterstützen und wollte auch in der Github Action Pipeline direkt GraalVM verwenden, was massiv einfacher ist, als Liberica NIK: Für GraalVM gibt es eine Action, bei Liberica muss man alles händisch, selber machen. Leider kann man es nicht mit GraalVM kompilieren, da im Quellcode bereits eine Swing-Klasse verwendet wird. Interessanterweise (wenn ich es richtig verstanden habe) ist es ein GUI, das mir mitteilt, dass &lt;em&gt;ili2pg&lt;/em&gt; kein GUI unterstützt? Nun denn, back to Liberica NIK. Erlebnis und Ergebnis wie bei &lt;em&gt;ili2c&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als letztes habe ich mir &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-native&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; angeschaut. Hier wusste ich, dass es extra Native-Image-fähig gemacht wurde und das &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/f8187fe468b2ce6ea9b1be7f1d9aa7817997ae94/src/org/interlis2/validator/Main.java#L45&quot;&gt;GUI speziell behandelt wird&lt;/a&gt;. Also nur GraalVM verwendet, kompiliert und fertig. Einfach und schnell (also relativ schnell kompiliert). Funktioniert einwandfrei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Native Images für jedes der Werkzeuge gibt es im jeweiligen &amp;laquo;Native&amp;raquo;-Github-Repo unter Releases:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili2c-native/releases&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili2c-native/releases&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ili2pg-native/releases&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ili2pg-native/releases&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/edigonzales/ilivalidator-native/releases&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ilivalidator-native/releases&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von meinen ersten Experimenten mit &lt;em&gt;GraalVM&lt;/em&gt; wusste ich, dass &lt;em&gt;ilivalidator&lt;/em&gt; signifikant langsamer ist im Vergleich zur Java-Variante. Ob das nun in den fast vier Jahren anders geworden ist? &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/364&quot;&gt;Nein&lt;/a&gt;. Die Fruchtfolgeflächen des Kantons Solothurn dauern circa 8 Minuten mit der Java-Variante und 16 Minuten mit dem Native Image. Warum ist mir nicht klar. Beide Varianten verwendeten den Serial GC. Die Native Images sind nicht super geeignet für &amp;laquo;long running and high throughput&amp;raquo;-Prozesse. Vielleicht ist die Validierung von grösseren XTF ein solcher?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie weiter? Ich fände es gut, wenn es die INTERLIS-Werkzeuge als Native Images gibt. Dabei lassen sich vielleicht zwei Fliegen mit einer Klappe schlagen. Die Swing-GUI würde ich entfernen und sie mit JavaFX (bessere Native Image Unterstützung) machen. Eventuell sogar in einem anderen Repo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Performance-Einbruch von &lt;em&gt;ilivalidator&lt;/em&gt; sollte genauer untersucht werden, damit man Klarheit hat, ob das der momentane Stand der möglichen Native-Image-Performance ist oder mit Code-Änderungen im &lt;em&gt;ilivalidator&lt;/em&gt; etwas verbessert werden kann.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #10 - ÖREB on steroids</title>
      <link>http://blog.sogeo.services/blog/2022/10/18/oereb-kataster-richtig-gemacht-10.html</link>
      <pubDate>Tue, 18 Oct 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/10/18/oereb-kataster-richtig-gemacht-10.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich wollte 200 Requests/Sekunde und ich erhielt 200 Requests/Sekunde. Aber der Reihe nach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wegen des &lt;a href=&quot;http://blog.sogeo.services/blog/2022/10/16/oereb-kataster-richtig-gemacht-9.html&quot;&gt;ÖREB-Spassvogels&lt;/a&gt; habe ich mit &lt;a href=&quot;https://jmeter.apache.org/&quot;&gt;&lt;em&gt;jMeter&lt;/em&gt;&lt;/a&gt; begonnen unseren &lt;a href=&quot;https://github.com/claeis/oereb-web-service/&quot;&gt;ÖREB-Webservice&lt;/a&gt; zu benchmarken. Und nachdem ich das Zusammenspiel verschiedener Konfigurationsparameter verstanden zu haben glaubte, wollte ich ans Limit gehen. Was bringt man mit dem Teil mit handelsüblicher Hardware raus? Vor Augen hatte ich den grössten Cloudserver, den &lt;a href=&quot;https://www.hetzner.com/de/cloud&quot;&gt;Hetzner&lt;/a&gt; zu bieten hat: CCX62. 48 vCPU und 192 GB RAM. Ungefähr 500 Euro pro Monat. Diese Cloudserver sind ziemlich praktisch, da sehr einfach aufzusetzen und sehr günstig und damit super geeignet für solche Tests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um den Workflow zu testen, habe ich mit einem kleineren Server begonnen: CPX51. Dieser hat 16 vCPU und 32 GB RAM und kostet 65 Euro pro Monat. Es ist im Gegensatz zum CCX ein &amp;laquo;Standard-Server&amp;raquo;. Der &lt;a href=&quot;https://docs.hetzner.com/de/cloud/servers/overview#server-typen&quot;&gt;Unterschied Standard vs Dedicated&lt;/a&gt; ist mir aber irgendwie immer noch nicht zu 100% klar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Testsetup sieht einfachheitshalber vor, dass alles auf einem Server läuft. Also sowohl das ÖREB-Katastersystem (Datenbank und Webservice) wie auch &lt;em&gt;jMeter&lt;/em&gt;. Sowohl für die Datenbank wie auch für den Webservice verwendet ich Dockerimages. Das DB-Image ist unser ÖREB-Entwicklungsimage. Das Image für den Webservice entspricht dem Produktionsimage. Von beiden Images wird jeweils nur ein Container hochgefahren. Limits werden den Containern keine gesetzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Datengrundlage dienen mir die ÖREB-Katasterdaten des Kantons Solothurn. D.h. ich importiere auch für den ganzen Kanton die Grundstücke der amtliche Vermessung. Ebenfalls importiere ich sämtliche Bundesdaten und natürlich sämtliche Konfigurations-XML. Angefordert werden aber nur Grundstücke innerhalb der Bauzone, um zu verhindern, dass Auszüge &lt;em&gt;ohne&lt;/em&gt; Eigentumsbeschränkungen angefordert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Getestet wird nur der XML-Auszug &lt;em&gt;ohne&lt;/em&gt; eingebettete Bilder und &lt;em&gt;ohne&lt;/em&gt; Geometrien. Ganz falsch und realitätsfremd dürfte der Benchmark nicht sein, da dies einem häufigen Usecase entspricht (Anfrage via Web GIS Client aka dynamischer Auszug). Zudem bei der Variante mit den eingebetteten Bildern der Herstellungsprozess dieser Bilder der dominierende Faktor sein wird (also letzten Endes der eingesetzte WMS-Server).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Starten habe ich mit einem Thread (also einem &amp;laquo;parallelen&amp;raquo; Request) begonnen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p10/n1.png&quot; alt=&quot;n1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Durchschnitt erreiche ich so 7 Requests/Sekunde. Ich weiss nicht woher die Steigerung gegen Ende kommt. Ist das tatsächlich die JVM, die sich warm läuft?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Maximum, das ich mit dem günstigeren Hetzner-Server raushole, ist bei 16 Threads. Dabei weise ich dem Webservice 50 DB-Connections zu und erlaube ihm selber 16 Worker-Threads:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p10/n16.png&quot; alt=&quot;n16&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Durchschnitt sind es 57 Requests/Sekunde. Peakperformance liegt bei knapp unter 70.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach dem Wechsel auf den grossen Server kann man so richtig aus dem vollen Schöpfen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p10/htop.png&quot; alt=&quot;htop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei 196 parallelen Request erreiche ich das Maximum. Der Anwendung werden 180 DB-Connections und 60 Worker-Threads erlaubt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p10/n196.png&quot; alt=&quot;n196&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Durchschnittlich werden so 211 Requests/Sekunde gemacht. Die Peakperformance liegt bei etwas über 220.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich denke die 200+ Requests/Sekunde dürfte auch für die KGK genügend sein. Wahrscheinlich hätte ein CH-weiter Kataster nie diese Anforderungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p10/twitter.png&quot; alt=&quot;twitter&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;RAM war im Gegensatz zu den CPU-Kernen nie ein Thema. Sieht eventuell anders aus, wenn man mit den eingebetteten Images hantieren muss resp. das PDF herstellen muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vielleicht hilft der Benchmark auch dem einen oder anderen &amp;laquo;XML ist langsam&amp;raquo;-Vertreter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiteres Fazit: Man braucht keine Cloud und/oder kein Openshift, um performante Dienste anbieten zu können. Im Prinzip reicht ein anständiger Server. Und man kommt so gar nicht auf den Gedanken den Webservice mit 100 Millicores laufen zu lassen, dafür mit 10 Replikas&amp;#8230;&amp;#8203; Scale up before you scale out.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #31 - ili2repo</title>
      <link>http://blog.sogeo.services/blog/2022/10/17/interlis-leicht-gemacht-number-31.html</link>
      <pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/10/17/interlis-leicht-gemacht-number-31.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;INTERLIS-Modellablagen sollten ab einem gewissen Zeitpunkt nicht mehr manuell nachgeführt werden. In einem früheren &lt;a href=&quot;http://blog.sogeo.services/blog/2022/07/19/interlis-leicht-gemacht-number-28.html&quot;&gt;Beitrag&lt;/a&gt; habe ich gezeigt, wie wir das mittels eines &lt;a href=&quot;https://gradle.org&quot;&gt;Gradle&lt;/a&gt;-Tasks machen. Hier nun eine vielleicht miliztauglichere Variante:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Kommandozeilenwerkzeug &lt;a href=&quot;https://github.com/edigonzales/ili2repo&quot;&gt;&lt;em&gt;ili2repo&lt;/em&gt;&lt;/a&gt; durchsucht ein Verzeichnis und seine Unterverzeichnisse nach INTERLIS-Modelldateien und erstellt daraus die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei. Es gibt zwei Varianten des Werkzeuges, welche &lt;a href=&quot;https://github.com/edigonzales/ili2repo/releases/latest&quot;&gt;hier&lt;/a&gt; heruntergeladen werden können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die JVM-/Java-Variante (&lt;code&gt;ili2repo-&amp;lt;Version&amp;gt;.zip&lt;/code&gt;) benötigt eine installierte Java Runtime 17 oder höher, ist jedoch betriebssystemunabhängig. Die Zip-Datei muss entpackt werden. Im Verzeichnis sind zwei Unterverzeichnisse &lt;em&gt;lib&lt;/em&gt; und &lt;em&gt;bin&lt;/em&gt;. Im &lt;em&gt;lib&lt;/em&gt;-Verzeichnis sind sämtliche benötigten Java-Bibliotheken. Im &lt;em&gt;bin&lt;/em&gt;-Verzeichnis ist eine Shellskript-Datei resp. eine Batch-Datei. Diese dienen zur Auführung des Programmes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Linux/macOS:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./bin/ili2repo --help&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Windows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./bin/ili2repo.bat --help&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Native Binaries sind für das jeweilige Betriebssystem kompilierte Versionen, die keine Java Runtime benötigten. Aus diesem Grund muss für jedes Betriebssystem ein separates Binary hergestellt werden (&lt;a href=&quot;https://www.graalvm.org/&quot;&gt;GraalVM&lt;/a&gt; to the rescue). Es stehen Binaries für Windows, Linux und macOS zur Verfügung (siehe Betriebssystemabkürzung im Namen der Zip-Datei). Das macOS-Binary läuft auf Intel wie auch auf Apple Silicon Prozessoren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./ili2repo --help&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Gegensatz zu der Java-Variante erscheinen beim ersten Aufruf auf macOS und Windows Warnungen wegen fehlender Signierung des Binaries resp. wegen des unbekannten Entwicklers der Software. Man muss dem Betriebssystem das Ausführen des Programms einmalig explizit erlauben. Unter macOS erscheint direkt nach dem erstmaligen Ausführen von &lt;code&gt;./ili2repo&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p31/macos_security_01.png&quot; alt=&quot;macos_security_01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In den &amp;laquo;Einstellungen&amp;raquo; - &amp;laquo;Security &amp;amp; Privacy&amp;raquo; - &amp;laquo;General&amp;raquo; muss man mit &amp;laquo;Allow Anyway&amp;raquo; die Software entblocken:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p31/macos_security_02.png&quot; alt=&quot;macos_security_02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man den obigen Befehl nochmals ausführt, erscheint wieder eine Meldung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p31/macos_security_03.png&quot; alt=&quot;macos_security_03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese Meldung muss man mit &amp;laquo;Open&amp;raquo; bestätigen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;./ili2repo --help&lt;/code&gt; kann endlich die Hilfe angezeigt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p31/ili2repo_help_output.png&quot; alt=&quot;ili2repo_help_output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Erstellen einer &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei muss die Option &lt;code&gt;--directory&lt;/code&gt; gefolgt vom Verzeichnisnamen mit den Datenmodellen angegeben werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./ili2repo --directory=path/to/models/&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es werden ebenfalls sämtliche Unterverzeichnisse nach Datenmodellen durchsucht. Die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei wird im obersten Verzeichnis erstellt (hier &lt;em&gt;models&lt;/em&gt;). Sie wird mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; geprüft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es werden folgende Metaattribute innerhalb des Datenmodells berücksichtigt und in die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei geschrieben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;technicalContact&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;furtherInformation&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Title&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;shortDescription&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Option &lt;code&gt;--init&lt;/code&gt; wird im gleichen Verzeichnis eine &lt;em&gt;ilisite.xml&lt;/em&gt;-Datei erstellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im obersten Verzeichnis (Rootverzeichnis) sollten keine Datenmodelle platziert werden, sondern nur in Unterverzeichnissen. Da &lt;em&gt;ilimodels.xml&lt;/em&gt; selber ein INTERLIS-Datenmodell ist und zum einem bestimmten Zeitpunkt noch nicht fertig ist, entsteht ein Durcheinander.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es werden sämtliche Unterverzeichnisse berücksichtigt, so auch &lt;em&gt;replaced&lt;/em&gt;- oder &lt;em&gt;obsolete&lt;/em&gt;-Ordner. Bei einem mehr oder weniger offiziellen Konsens was wie behandelt werden soll, werde ich das ändern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Servermodus &lt;code&gt;--server&lt;/code&gt; dient zum Testen der vorgängig erzeugten &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei. Es wird dazu ein ganz simpler HTTP-Server gestartet. Die INTERLIS-Modellablage ist unter der Url &lt;a href=&quot;http://localhost:8820&quot;&gt;http://localhost:8820&lt;/a&gt; verfügbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p31/server.png&quot; alt=&quot;server&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Servermodus benötigt ebenfalls die Option &lt;code&gt;--directory&lt;/code&gt;, damit &lt;em&gt;ili2repo&lt;/em&gt; weiss welches Verzeichnis bereitgestellt werden soll. Es wird aber keine &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei erstellt.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #9 - Der ÖREB-Spassvogel: Ein Drama in Akten</title>
      <link>http://blog.sogeo.services/blog/2022/10/16/oereb-kataster-richtig-gemacht-9.html</link>
      <pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/10/16/oereb-kataster-richtig-gemacht-9.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oder vielleicht sind es auch mehrere Spassvögel. Wir wissen es nicht. Aber was ist passiert?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Plötzlich schlug unser OpenShift-Cluster Alarm. Genauer gesagt die &lt;a href=&quot;https://docs.openshift.com/container-platform/4.11/applications/application-health.html&quot;&gt;Livenessprobe&lt;/a&gt;. Als Livenessprobe haben wir den &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.7.3/actuator-api/htmlsingle/#health&quot;&gt;Standard-Health-Endpoint&lt;/a&gt; von &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; verwendet. Dieser Endpoint prüft, ob die Anwendung gesund ist. Zur Beurteilung werden dazu auch die sogenannten Backing-Services herangezogen. D.h. in unserem Fall die Datenbank. Falls bei der Prüfung nun keine Datenbankverbindung mehr hergestellt werden kann, ist die Anwendung nicht mehr gesund und die Livenessprobe schlägt fehl und der Container wird &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle&quot;&gt;neu gestartet&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber warum gingen uns die Datenbankverbindungen aus? Irgend ein &lt;a href=&quot;https://de.wikipedia.org/wiki/Havas#%C3%9Cbertragene_Bedeutung&quot;&gt;Habasch&lt;/a&gt; flutete uns mit XML-Anfragen. Wir haben bei uns zwei Instanzen/Pods mit dem Webservice am Laufen. Jeder Pod erhält 10 Datenbankverbindung resp. es wird ein DB-Connectionpool mit maximal 10 Verbindungen aufgebaut. Pro Anfrage benötigt die Anwendung 3 DB-Connections. Auch wenn die Abwicklung der Anfrage im Regelfall sehr schnell passiert, skaliert die Sache natürlich mit bloss 10 zur Verfügung stehender DB-Verbindungen nicht wirklich. Werden wir mit Anfragen geflutet und lassen diese auch auf die Anwendung los, hat diese eher früher als später ein Problem: Die DB-Verbindungen gehen aus und die Livenessprobe schlägt fehl, weil der Health-Endpoint ein &lt;code&gt;SELECT 1&lt;/code&gt; macht, um zu prüfen, ob die Datenbank noch da ist resp. die Anwendung darauf zugreifen kann. Dass in einem solchen Fall nun der Pod neu gestartet wird, löst das Problem gar nicht, da dieser sofort wieder geflutet wird. Und zu guter Letzt geht es ja weniger um die Livenessprobe, sondern dass die &amp;laquo;normalen&amp;raquo; Benutzer des ÖREB-Katasters keine Anfragen machen mehr können, weil alles verstopft ist und/oder nicht mehr läuft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was machen? Als erstes haben wir gerade den Holzhammer hervorgenommen und die IP des Spassvogels in unserem API-Gateway (&lt;a href=&quot;https://nginx.org/en/&quot;&gt;&lt;em&gt;nginx&lt;/em&gt;&lt;/a&gt;) gesperrt. Im Wissen, dass der Spassvogel sich schnell eine neue IP besorgen kann. Zusätzlich haben wir mit ungutem Gefühl die Livenessprobe ausgeschaltet, weil die ja so nix bringt. Es dauerte dann nicht lange und es ging wieder von vorne los&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also mussten wir mit mehr Hirn ran: Nach bisschen Überlegen kommt man auf die Idee eines &lt;a href=&quot;https://www.nginx.com/blog/rate-limiting-nginx/&quot;&gt;Rate Limiters&lt;/a&gt;. Dieser sorgt dafür, dass nur ein eine konfigurierte Anzahl an Requests pro Zeiteinheit vom API-Gateway an die Anwendung weitergereicht wird. Das führte zu folgender Situation:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p09/statuscake01.jpeg&quot; alt=&quot;Statuscake ÖREB-XML-Requestdauer&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kurz vor 18:00 Uhr haben wir den Rate Limiter eingeschaltet. Das führte bei &lt;a href=&quot;https://statuscake.com&quot;&gt;Statuscake&lt;/a&gt; dazu, dass es plötzlich viele Ausfallmeldungen (rote Balken) des ÖREB-Webservices gab. Das war jedoch kein Ausfall des Servers, sondern wir haben den Test so konfiguriert, dass er fehlschlägt, wenn die Antwort länger als 15 Sekunden dauert. Weil wir wussten, dass wir mit Requests geflutet werden, haben wir zuerst einfach nur den 15-Sekunden-Threshold hochgeschraubt. Und siehe da: Kurz nach 12:00 Uhr wurde alles wieder grün. Was uns aber störte und wir nicht ganz verstanden haben, war der Umstand, dass auch Requests im Web GIS Client circa 20 Sekunden dauerten. Erstens ist das für den Anwender nicht toll und zweitens haben wir es nicht verstanden, weil der Rate Limiter nach der IP limitiert. Es stellte sich heraus, dass wir den Rate Limiter noch nicht korrekt konfiguriert hatten und dieses &amp;laquo;Group-by-IP&amp;raquo; nicht funktionierte. Als wir das korrigierten, sah die Grafik wieder super aus, d.h. &lt;em&gt;fair use&lt;/em&gt; Benutzer (meistens via Web GIS Client) sind nicht mehr betroffen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p09/statuscake02.jpg&quot; alt=&quot;Statuscake ÖREB-XML-Requestdauer&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Spassvogel hat wirklich eine Woche lang XML- &lt;em&gt;und&lt;/em&gt; PDF-Auszüge à discretion gesaugt, insgesamt zwischen 1000 und 1500 Auszüge pro Stunde. Summa summarum und geschätzt, waren es sämtliche Grundstücke des Kantons&amp;#8230;&amp;#8203; Warum macht man sowas? Sinnvoll erscheint mir das nicht. Was will man mit den vielen Dateien machen? Man weiss ja nicht, ob etwas noch aktuell ist oder bereits veraltet!? Hier ein Ausschnitt aus einer Logging-Auswertung mit &lt;a href=&quot;https://logit.io/&quot;&gt;&lt;em&gt;logit.io&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p09/logitio01.jpg&quot; alt=&quot;logit.io Logging-Auswertung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir sind aber noch nicht fertig. Mit der Livenessprobe sind wir wie eingangs erwähnt nicht zufrieden, weil sie nicht das prüft, was sie soll. Und die Anwendung selber (also der ÖREB-Webservice) funktioniert zwar einwandfrei aber nur weil das API-Gateway sie vor Überlastung schützt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Livenessprobe ist der Standard-Health-Endpoint, welche die Backing-Services berücksichtigt schlichtweg das Falsche. Der Server ist ja trotzdem live aber eben nicht ready (Readinessprobe) Requests abzuarbeiten. [&lt;em&gt;Spring Boot&lt;/em&gt;] hat dafür &lt;a href=&quot;https://spring.io/blog/2020/03/25/liveness-and-readiness-probes-with-spring-boot&quot;&gt;zwei separate Endpunkte&lt;/a&gt;. Für den Liveness-Endpunkt werden die Backing-Services nicht mehr berücksichtigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bleibt noch die Anwendung selber: Mit den Standardeinstellungen lassen wir die Anwendung bis zu 200 Threads (Requests) gleichzeitig abarbeiten (sehr vereinfacht beschrieben, weil kompliziert und ich nur &lt;em&gt;denke&lt;/em&gt;, dass ich es richtig verstanden habe). Wie eingangs beschrieben, haben wir aber der Anwendung nur 10 Datenbankverbindungen zugewiesen und pro Request werden drei davon benötigt. Da muss man kein Einstein sein, um zu merken, dass das nicht aufgeht. Die maximale Anzahl sogenannter Worker-Threads muss im Einklang mit anderen Rahmenbedingungen sein: RAM, CPU aber auch DB-Connections. D.h. wir reduzieren diese Worker-Threads auf circa 3 oder 4. Das bedeutet jedoch nicht, dass weitere Requests mit einem 429 (&lt;em&gt;too many requests&lt;/em&gt;) o.ä. beantwortet werden, denn es gibt verschiedene Queues, die dann greifen und wo die Requests parkiert werden (weiter weg von der eigentlich Anwendung, aber immer noch im Applikationsserver). Tests mit &lt;em&gt;jMeter&lt;/em&gt; haben gezeigt, dass bei 32 parallelen Requests mit nur 3 Worker-Threads trotzdem alle Requests korrekt bearbeitet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Spassvogel ist und bleibt zwar nicht ganz 100. Aber wir haben immerhin viel gelernt.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #8 - ÖREB-Kataster Compliance Test Suite</title>
      <link>http://blog.sogeo.services/blog/2022/10/02/oereb-kataster-richtig-gemacht-8.html</link>
      <pubDate>Sun, 2 Oct 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/10/02/oereb-kataster-richtig-gemacht-8.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ÖREB-Kataster ist durch das &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/publication/instruction.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/Rahmenmodell-de.pdf.html&quot;&gt;Rahmenmodell&lt;/a&gt; und &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/service/webservice.html&quot;&gt;verschiedene&lt;/a&gt; &lt;a href=&quot;https://www.cadastre.ch/de/services/publication.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/Weisung-OEREB-Data-Extract-de.pdf.html&quot;&gt;Weisungen&lt;/a&gt; gut spezifiert. Das bedeutet, dass man eigentlich weiss, was man umsetzen muss. Das heisst ebenfalls, dass man das Umgesetzte gut prüfen kann. Warum sollte man das prüfen? Die sauber spezifizierte M2M-Schnittstelle bringt einfach nichts, wenn sie bei jedem Kanton anders ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So eine Prüfbibliothek wäre ausserdem schick, um End-zu-End- oder Systemtests zu machen. Gesagt, getan: Ich habe mir als Proof-of-Concept &lt;a href=&quot;https://github.com/edigonzales/oereb-cts&quot;&gt;eine Anwendung resp. eine Bibliothek&lt;/a&gt; geschrieben, die im Prinzip die beiden Aspekte - Aufruf des Services und Auszug - prüft (Stand heute: Teile davon). Das Ganze unter einen Hut zu bringen, fand ich noch herausfordernd:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt verschiedene Möglichkeiten einen Auszug (oder die GetEGRID-Antwort) anzufordern. Das kann z.B. eine Koordinate sein oder NBIdent und Grundstücksnummer. Und man kann eine Antwort mit oder ohne Geometrie resp. mit oder ohne Bilder anfordern. Das führt dazu, dass es viele Kombinationen gibt, die geprüft sein wollen und die verschiedenen Konfigurationen müssen von jemandem definiert werden. Hat man die Antwort, reicht es nicht diese gegenüber einem Schema zu prüfen, da der Inhalt ja abhängig von den Inputparametern ist. Das wiederum müsste bei der Konfiguration der Tests auch berücksichtigt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe mich daher entschieden, so wenig wie möglich von einem Menschen konfigurieren zu lassen und die vielen Kombinationen durch die Maschine selber zu machen. Das heisst, man muss nur den Service-Endpunkt und die notwendigen Parameter angeben, die für den Aufruf benötigt werden (z.B. &lt;code&gt;EGRID&lt;/code&gt; oder &lt;code&gt;EN&lt;/code&gt;). Fehlen die Parameter, wird der Test, welcher diese Parameter benötigt, nicht durchgeführt. Wenn &lt;code&gt;IDENTDN&lt;/code&gt; und &lt;code&gt;NUMBER&lt;/code&gt; fehlen, werden keine Tests durchgeführt, welche diese beiden benötigen. Momentan ist das alles über ein Ini-File zu konfigurieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;[SO]
SERVICE_ENDPOINT=https://geo.so.ch/api/oereb/
EN=2600595,1215629
IDENTDN=SO0200002457
NUMBER=168
EGRID=CH807306583219&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was als Resultat zu erwarten ist, kann oftmals anhand der Request-Parameter eruiert werden: Falls &lt;code&gt;GEOMETRY=true&lt;/code&gt; verwendet wird, muss in der Antwort ein Geometry-Element vorhanden sein. Was aber ohne menschliches Zutun nicht wirklich funktioniert, ist der Umgang mit leeren Antworten. Es kann korrekt sein, dass eine Antwort leer ist (also kein Grundstück). Woher weiss die Maschine das, ohne dass man es konfigurieren würde? Dieser Fall ist momentan nicht abgefangen. Bei der Konfiguration der Tests muss man also sicher sein, dass z.B. für den definierten &lt;code&gt;EGRID&lt;/code&gt; auch wirklich ein Auszug zurückkommt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Geprüft werden momentan der &lt;code&gt;GetEGRID&lt;/code&gt;- und &lt;code&gt;extract&lt;/code&gt;-Aufruf und die jeweiligen Antworten. Folgende Checks werden durchgeführt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Stimmt der HTTP Status Code?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stimmt der Response Content Type?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ist die Antwort schemakonform?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gibt es Geometrie-Elemente, wenn solche vorhanden sein müssen (resp. umgekehrt)?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gibt es eingebettete Bilder, wenn solche vorhanden sein müssen (resp. umgekehrt)?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sind alle Bundesthemen-Codes im XML-Inhaltsverzeichnis vorhanden? (&lt;code&gt;ConcernedTheme&lt;/code&gt;, &lt;code&gt;NotConcernedTheme&lt;/code&gt;, &lt;code&gt;ThemeWithoutData&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alle mir bekannten ÖREB-Kataster-V2-Webservice habe ich geprüft. Examplarisch ein Screenshot von unserem Dienst. Die Gesamtshow gibt es &lt;a href=&quot;http://blog.sogeo.services/data/oereb-kataster-richtig-gemacht-8/result.html&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p08/result_so_01.png&quot; alt=&quot;Prüfresultat SO Übersicht&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unser Fehler wurde auch bei der Abnahme erkannt und ist folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p08/result_so_02.png&quot; alt=&quot;Prüfresultat SO Detail&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir sind für den Fall, dass keine Geometrien angefordert werden, nicht schemakonform: Es fehlt die Boundingbox. Klammerbemerkung: Man schaue sich die Request time an. Der Request dauerte 9s. Das sind circa 8.5 Sekunden zu lang. Grund dafür sind die Spezialisten, die seit geraumer Zeit bei verschiedenen Kantonen ziemlich stupide Tausende Requests in kürzester Zeit absetzen&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Gesamtbild ist in etwa folgendes: Die Fehler wiederholen sich. Man erkennt - behaupte ich mal - anhand der Fehler, welches System ein Kanton einsetzt. Typische Fehler sind:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Grundstückstyp fehlt in der GetEGRID-Antwort&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Geometrieelement fehlt in der GetEGRID-Antwort&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Response Content Type ist &amp;laquo;falsch&amp;raquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Status Code bei der URL-Weiterleitung ist falsch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fehlende Bundesthemen-Codes&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Exoten dürfen auch nicht fehlen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Keine NBIdents in der Antwort vorhanden resp. &amp;laquo;N/A&amp;raquo;. In den Daten der amtlichen Vermessung gibt es für dieses Grundstück aber einen NBIdent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fehlender &amp;laquo;/&amp;raquo;. Anstelle von &lt;code&gt;/oereb/getegrid/xml/?EN=YYYYYYYY,XXXXXXXX&lt;/code&gt; funktioniert der Aufruf nur ohne den letzten Slash.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ignorieren von XML-Namespaces, d.h. es wird nur genau einer verwendet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo ich mir aber selber nicht ganz sicher bin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist der Response Content Type &lt;code&gt;application/xml; charset=UTF-8&lt;/code&gt; wirklich falsch? In der Weisung steht nur &lt;code&gt;application/xml&lt;/code&gt;. Eine interessante Erklärung was eine andere Spez erwartet, gibt es &lt;a href=&quot;https://stackoverflow.com/questions/3272534/what-content-type-value-should-i-send-for-my-xml-sitemap&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und stimmt mein Test bezüglich der Bundesthemen-Codes? Meines Erachtens müssen alle Bundesthemen-Codes bei den &amp;laquo;Themes&amp;raquo; vorkommen. Momentan gibt es jetzt Kantone, die formell &lt;em&gt;keine&lt;/em&gt; Nutzungsplanung (&lt;code&gt;ch.Nutzungsplanung&lt;/code&gt;) publizieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ausserdem könnte man sich eventuell darauf einigen wie Links enkodiert werden sollen. Da gibt es unterschiedlichste Varianten. Es wird alles enkodiert, z.B. &lt;code&gt;&lt;a href=&quot;https://geo.so.ch&quot; class=&quot;bare&quot;&gt;https://geo.so.ch&lt;/a&gt;&lt;/code&gt; wird zu &lt;code&gt;https%3A%2F%2Fgeo.so.ch&lt;/code&gt; oder noch spannender ist das Verpacken eines solchen Links in einem CDATA-Block&amp;#8230;&amp;#8203; Innerhalb eines Auszuges werden die Varianten auch gerne gemischt. Ich denke, dass man sich auf das Minimum beschränken sollte, was XML verlangt (z.B. &amp;laquo;&amp;amp;&amp;raquo; wird zu &amp;laquo;amp&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #30 - INTERLIS Repository Checker</title>
      <link>http://blog.sogeo.services/blog/2022/08/interlis-leicht-gemacht-number-30.html</link>
      <pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/08/interlis-leicht-gemacht-number-30.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die INTERLIS-Modellablagen (aka INTERLIS-Repos) sind ein wichtiger Baustein im INTERLIS-Ökosystem. Sie dienen zur zentralen Publikation von INTERLIS-Modellen und helfen beim Arbeiten mit kompatiblen Werkzeugen. In erster Linie heisst das, dass man sich nicht mehr gross um die Modelldateien kümmern muss. Sie sind einfach &amp;laquo;da&amp;raquo;. Die Werkzeuge suchen und finden (hoffentlich) die benötigten Modelle in einem der Repositories. Für automatisierte Prozesse sollte man sich aber in der Regel nicht auf die Modellablagen verlassen, sondern die benötigten Modelle zum Prozess kopieren oder sich ein lokales Repository anlegen, welches alle benötigten Modelle spiegelt. Man will sich ja nicht unnötig von externen Anwendungen abhängig machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viele Kantone und einige Vereine betreiben eine INTERLIS-Modellablage. Damit die Werkzeuge auch mit den Modellablagen problemlos zusammenspielen, sollten die Modellablagen auch korrekt sein. Der &lt;a href=&quot;https://github.com/claeis/ili2c&quot;&gt;&lt;em&gt;INTERLIS-Compiler&lt;/em&gt;&lt;/a&gt; bietet die Funktion &lt;code&gt;--check-repo-ilis&lt;/code&gt; an, welche eine Ablage prüft. Leider ist nirgends gross dokumentiert was genau geprüft wird. Für die Prüfung zuständig ist die Klasse &lt;a href=&quot;https://github.com/claeis/ili2c/blob/master/src/main/java/ch/interlis/ili2c/CheckReposIlis.java&quot;&gt;CheckReposIlis&lt;/a&gt;. Es werden folgende Sachverhalte geprüft:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Existiert in der Modellablage eine &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ist die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei schemakonform? D.h. die Datei wird nicht mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; geprüft, sondern sie wird &amp;laquo;nur&amp;raquo; gegen das XML-Schema geprüft.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sind die Modelle syntaktisch korrekt? (Prüfung mit dem Compiler).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stimmen die Metainformationen (wie z.B. md5-Hash) in der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wahrscheinlich noch der eine oder andere übersehene Punkt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe den Code nun in eine kleine Webanwendung gepackt, welche diese Prüfungen für INTERLIS-Modellablagen regelmässig durchführt und visualisiert. In Abweichung zu den oben erwähnten Tests, verwende ich für die Prüfung der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei &lt;em&gt;ilivalidator&lt;/em&gt;. Die Repo-Prüffunktion im Compiler gibt es bereits länger als es &lt;em&gt;ilivalidator&lt;/em&gt; gibt. Vielleicht gibt es aber noch andere Gründe warum eine XML-Schemaprüfung gemacht wird. Zusätzlich prüfe ich auch noch die &lt;em&gt;ilisite.xml&lt;/em&gt;-Datei. Ebenfalls mit &lt;em&gt;ilivalidator&lt;/em&gt;. Konkret hat das den Nachteil, dass &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/351&quot;&gt;ein Fehler&lt;/a&gt; nicht gefunden wird, der mittels Schemaprüfung entdeckt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für mich schwierig war, dass alle Fehler, welche der Compiler loggt, wenn man &lt;code&gt;--check-repo-ilis&lt;/code&gt; verwendet, bei mir ebenfalls in der Logdatei landen. Eventuell gibt es da noch die eine oder andere Meldung, die fehlt. Richtig nachhaltig ist meine Prüfmethode nicht, da ich den Code einfach kopiere und an einigen Stellen anpasse. Maintenance hell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Verpackt als Dockerimage, lässt sich die native kompilierte Spring Boot Anwendung einfach und schnell starten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;docker run -p 8080:8080 -e TZ=Europe/Zurich sogis/interlis-repo-checker:latest&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Konfigurieren lässt sie sich Anwendung mit einigen wenigen Env-Variablen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CONNECT_TIMEOUT&lt;/code&gt; und &lt;code&gt;READ_TIMEOUT&lt;/code&gt;: Connect und read Timout für Protokollhandler, die &lt;code&gt;java.net.URLConnection&lt;/code&gt; verwenden. Damit kann gesteuert werden, dass beim Lesen der Repositories nicht zu lange gewartet wird, bis eine Verbindung aufgebaut wird oder der Server die Antwort liefert.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;REPOSITORIES&lt;/code&gt;: Kommaseparierte Liste der Repositories, die geprüft werden sollen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CHECK_CRON_EXPRESSION&lt;/code&gt;: (Spring Boot) Cron Expression, die steuert wie häufig die Prüfung durchgeführt wird.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WORK_DIRECTORY&lt;/code&gt;: Verzeichnis in das die Prüfresultate gespeichert werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WORK_DIRECTORY_PREFIX&lt;/code&gt;: Präfix für &lt;code&gt;WORK_DIRECTORY&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die voreingestellten Werte sind in der &lt;a href=&quot;https://github.com/edigonzales/repo-checker/blob/main/src/main/resources/application.properties&quot;&gt;&lt;em&gt;application.properties&lt;/em&gt;-Datei&lt;/a&gt; definiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach dem Start der Webanwendung werden einmalig alle Modellablagen geprüft und es muss nicht gewartet werden bis der Cronjob läuft. Nach ein paar Minuten sollte sich unter &lt;a href=&quot;http://localhost:8080&quot; class=&quot;bare&quot;&gt;http://localhost:8080&lt;/a&gt; folgendes Bild zeigen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p30/repochecker01.png&quot; alt=&quot;repochecker01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zwei Repositories weisen momentan bereits Fehler in der &lt;em&gt;ilisite.xml&lt;/em&gt;-Datei auf. Die Fehlermeldung von &lt;em&gt;ilivalidator&lt;/em&gt; ist &lt;code&gt;Error: unknown xml file&lt;/code&gt;. Das Problem ist, dass der INTERLIS-Namespace fehlt. Anstelle von:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;steht nur:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;&amp;lt;TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei gibt es allerlei Fehler: Doppelte TID, zu lange Texte und unerlaubte Zeichen. Spannender wird es bei den Modellen selbst. Ein Kanton listet das Modell INTERLIS als Abhängigkeit auf. Diese Modell wird vom Compiler nicht gefunden. Das Modell &lt;a href=&quot;https://github.com/claeis/ili2c/issues/75&quot;&gt;darf nicht gelistet werden&lt;/a&gt;. Die Meldung sollte aber eine andere sein. Dann gibt es eigentliche Modellfehler und Unstimmigkeiten zwischen dem Modell und dem Eintrag in der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viele der Fehler lassen sich vermeiden, wenn man a) die Modellablagen nicht händisch nachführt (wer hat schon Bock einen md5-Hash abzutippen?) und b) nach dem Nachführen die Modellablagen auch prüft. Siehe dazu &lt;a href=&quot;http://blog.sogeo.services/blog/2022/07/19/interlis-leicht-gemacht-number-28.html&quot; class=&quot;bare&quot;&gt;http://blog.sogeo.services/blog/2022/07/19/interlis-leicht-gemacht-number-28.html&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;s&gt;Der Repochecker läuft unter folgender URL: https://geo.so.ch/repochecker&lt;/s&gt; Das Teil scheint mir noch ein Memoryleak zu haben und ist darum nicht online. Wer es testen will, einfach den obigen Dockerbefehl ausführen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #29 - REST-API für ilivalidator-web-service</title>
      <link>http://blog.sogeo.services/blog/2022/07/24/interlis-leicht-gemacht-number-29.html</link>
      <pubDate>Sun, 24 Jul 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/07/24/interlis-leicht-gemacht-number-29.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unser &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service&quot;&gt;ilivalidator-web-service&lt;/a&gt; hat einige Neuerungen spendiert bekommen, unter anderem:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Konfiguration&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es ist möglich eigene Konfigurationen (aka &lt;em&gt;toml&lt;/em&gt;- resp. &lt;em&gt;ini&lt;/em&gt;-Dateien und zusätzliche INTERLIS-Modelle) zu verwenden. Der Webservice berücksichtigt zwei Verzeichnisse (&lt;em&gt;config/toml/&lt;/em&gt; und &lt;em&gt;config/ili&lt;/em&gt;) während der Prüfung einer INTERLIS-Transferdatei. Es werden standardmässig einige Solothurner Konfigurationen reinkopiert, was jedoch unterbunden werden kann. Das &lt;em&gt;config&lt;/em&gt;-Verzeichnis und die beiden Unterverzeichnisse werden im Webservice via URL &lt;a href=&quot;http://localhost:8080/config&quot; class=&quot;bare&quot;&gt;http://localhost:8080/config&lt;/a&gt; exponiert. Damit wird für die Benutzer die Prüfung transparenter, da sie eine allfällige zusätzliche Konfiguration sehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p29/config01.png&quot; alt=&quot;config01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Siehe: &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service#configuration-and-running&quot; class=&quot;bare&quot;&gt;https://github.com/sogis/ilivalidator-web-service#configuration-and-running&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;REST-API&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt neben dem GUI neu eine REST-API für eine M2M-Kommunikation. Da es sich um &lt;em&gt;long running tasks&lt;/em&gt; handelt, kann kein synchroner Prozessablauf gewählt werden, d.h. man kann nicht eine Datei hochladen und die Verbindung zum Server bleibt offen, bis die Prüfung durch ist. Im GUI wird aus diesem Grund auf das Websocket-Protokoll gesetzt. Bei der REST-API verwenden wir jedoch &amp;laquo;nur&amp;raquo; HTTP.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man eine Datei hochlädt, erhält man als Antwort den Statuscode &lt;code&gt;202 ACCEPTED&lt;/code&gt; und den Header &lt;code&gt;Operation-Location&lt;/code&gt;, der auf eine Ressource zeigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -i -X POST -F file=@254900.itf http://localhost:8080/rest/jobs&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;HTTP/1.1 202
Operation-Location: http://localhost:8080/rest/jobs/4d4aa583-6575-4200-a39c-621a5190d36d
Content-Length: 0
Date: Sat, 23 Jul 2022 16:40:50 GMT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Ressource &lt;code&gt;/rest/jobs/4d4aa583-6575-4200-a39c-621a5190d36d&lt;/code&gt; liefert mir Informationen über den Stand der Validierung meiner Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl -i -X GET http://localhost:8080/rest/jobs/4d4aa583-6575-4200-a39c-621a5190d36d&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;HTTP/1.1 200
Retry-After: 30
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 23 Jul 2022 16:43:12 GMT

{&quot;createdAt&quot;:&quot;2022-07-23T16:42:15.68317767&quot;,&quot;updatedAt&quot;:&quot;2022-07-23T16:42:15.68317767&quot;,&quot;status&quot;:&quot;PROCESSING&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt einen &lt;code&gt;Retry-Header&lt;/code&gt;, der Clients anweist, 30 Sekunden zu warten bis zur nächsten Abfrage. Ist die Validierung abgeschlossen, enthält die Antwort Links zu den Logdateien:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 23 Jul 2022 16:43:29 GMT

{&quot;createdAt&quot;:&quot;2022-07-23T16:42:15.68317767&quot;,&quot;updatedAt&quot;:&quot;2022-07-23T16:43:16.011457796&quot;,&quot;status&quot;:&quot;SUCCEEDED&quot;,&quot;logFileLocation&quot;:&quot;http://localhost:8080/logs/ilivalidator_8148789347157812698/254900.itf.log&quot;,&quot;xtfLogFileLocation&quot;:&quot;http://localhost:8080/logs/ilivalidator_8148789347157812698/254900.itf.log.xtf&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Siehe: &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service/blob/master/docs/rest-api-de.md&quot; class=&quot;bare&quot;&gt;https://github.com/sogis/ilivalidator-web-service/blob/master/docs/rest-api-de.md&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Joborchestrierung ist mit &lt;a href=&quot;https://jobrunr.io&quot;&gt;&lt;em&gt;JobRunr&lt;/em&gt;&lt;/a&gt; umgesetzt. Eine Bibliothek für verteiltes Background Processing. Die Persistierung wird über eine Datenbank gemacht. &lt;em&gt;JobRunr&lt;/em&gt; hat viele Features und Einstellungsmöglichkeiten, ist aber trotzdem sehr einfach in der Handhabung. Ein interessantes Feature ist der Umgang mit nicht beendeten Jobs, z.B. bei einem Server-Absturz. Diese werden nach einem Restart nochmals ausgeführt. Es geht also nichts verloren. Verteile Prozessierung bedeutet in diesem Fall über die Grenzen der JVM und über die Grenzen von Servern hinweg, solange die &amp;laquo;Arbeitstiere&amp;raquo; Zugriff auf die gemeinsame Datenbank haben. Im absolut einfachsten Fall gibt es aber nur einen Webservice, dieser fungiert als Schnittstellenserver als auch als Validierungsserver und verwendet eine In-Memory-Datenbank.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;ilivalidator Cluster&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was kann man mit der neuen REST-API und deren Umsetzung mit &lt;em&gt;JobRunr&lt;/em&gt; machen? Zum Beispiel einen ilivalidator Cluster mit 15 Worker. Dazu reicht eine einfache &lt;em&gt;docker-compose&lt;/em&gt;-Konfiguration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;version: &apos;3&apos;
services:
  frontend:
    image: sogis/ilivalidator-web-service:2
    environment:
      TZ: Europe/Zurich
    ports:
      - 8080:8080
      - 8000:8000
    volumes:
      - type: bind
        source: /tmp/docbase
        target: /docbase
      - type: bind
        source: /tmp/work
        target: /work
  worker:
    image: sogis/ilivalidator-web-service:2
    deploy:
      replicas: 15
    environment:
      TZ: Europe/Zurich
      JOBRUNR_DASHBOARD_ENABLED: &quot;false&quot;
      REST_API_ENABLED: &quot;false&quot;
      UNPACK_CONFIG_FILES: &quot;false&quot;
      CLEANER_ENABLED: &quot;false&quot;
    volumes:
      - type: bind
        source: /tmp/docbase
        target: /docbase
      - type: bind
        source: /tmp/work
        target: /work&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wird ein (1) Frontend-Schnittstellen-Service gestartet, welcher die Uploads entgegennimmt. Es wird ein worker-Service mit 15 Replicas gestartet, die als JobRunr-Backgroundserver arbeiten. Beide Services verwenden das gleiche Dockerimage. Beim worker-Service werden einige Funktionalitäten ausgeschaltet, da diese nicht benötigt werden. Zudem ist der worker-Service von aussen nicht erreichbar. Sauberer wäre natürlich, wenn man Ressource-Limiten (RAM, CPU) setzt. Dies ist im Nicht-Swarm-Mode mit der Version 3 für die CPU nicht möglich und darum wird darauf verzichtet. Das sollte für unseren Test egal sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun benötigt man genügend Server-Ressourcen, d.h. man mietet sich bei Digitalocean einen 40vCPU-Server mit 160GB RAM. Man sollte ihn einfach nicht vergessen zu löschen, da das Teil monatlich mit $1260.00 zu Buche schlägt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Testdaten verwende ich die AV-Daten des Kantons Bern, die ich via &lt;a href=&quot;https://geodienste.ch/services/av&quot;&gt;geodienste.ch&lt;/a&gt; heruntergeladen habe. Es handelt sich um 339 Gemeinden. Ein &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-cluster-test/blob/main/sendFiles.java&quot;&gt;simples Jbang-Skript&lt;/a&gt; lädt mir die Daten via REST-API zum Server hoch. Nun geht die Post ab. Nachfolgend ein Screenshot des JobRunr-Dashboards und eine &lt;code&gt;docker stats&lt;/code&gt;-Ausgabe:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p29/jobrunr01.png&quot; alt=&quot;jobrunr01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p29/dockerstats01.png&quot; alt=&quot;dockerstats01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sämtliche 339 Gemeinden konnten innerhalb von circa 17 Minuten geprüft werden. Limitierender Faktor war zu guter Letzt die Gemeinde Bern, welche 8 Minuten benötigt, wobei 2 davon als einzige Gemeinde. Diese sollte also möglichst zu Beginn hochgeladen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend versuchte ich 29 Worker zu verwenden, was aber nicht erfolgreich funktionierte. Ein Drittel der Dockercontainer wurden wieder runtergefahren. Der Grund war mir auf die Schnelle nicht klar (Hinweis: Ressourcen setzen!). Eventuell wird unter gewissen Umständen der I/O zum Problem, wenn 30+ Replicas grossen Traffic verursachen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Grunde genommen kann man den ilivalidator-web-service so einfachst beliebig horizontal skalieren. Aus Gründen sollten die Worker nicht auf dem gleichen Server laufen und Ressourcen (&lt;code&gt;limits&lt;/code&gt; und &lt;code&gt;reservations&lt;/code&gt; oder äquivalente Einstellungen) gesetzt werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #28 - INTERLIS-Modellablagen erstellen und pflegen</title>
      <link>http://blog.sogeo.services/blog/2022/07/19/interlis-leicht-gemacht-number-28.html</link>
      <pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/07/19/interlis-leicht-gemacht-number-28.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man mehr als &lt;a href=&quot;https://models.kgk-cgc.ch/&quot;&gt;drei Modelle&lt;/a&gt; in seiner INTERLIS-Modellablage (aka INTERLIS-Repo aka Repo) gelten zwei Regeln:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Die Herstellung und das Pflegen der Modellablage muss automatisch passieren.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die für die Modellablage benötigten Informationen müssen aus den INTERLIS-Modellen kommen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Lösung wie man beide Regeln befolgt, ist hier kurz vorgestellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Auslegeordnung zeigt, dass einzig die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei häufig ändert und natürlich die Modelle selbst. Die Grundidee ist, dass man einen Ordner bestimmt, wo alle Modelle gespeichert sind (gerne auch mit Unterordner) und daraus mit &lt;a href=&quot;https://github.com/claeis/iox-ili/&quot;&gt;vorhandenen Bibliotheken&lt;/a&gt; und wenig eigenem Code die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei herstellt. Wir haben ein &lt;a href=&quot;https://plugins.gradle.org/plugin/ch.so.agi.interlis-repository-creator&quot;&gt;Gradle-Plugin&lt;/a&gt; geschrieben, damit wir das Herstellen sauber in einem &lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle-Build&lt;/a&gt; einbetten können und mit anderen atomaren Prozessschritten verknüpfen können. Der &lt;a href=&quot;https://github.com/sogis/interlis-repository-creator/blob/master/src/main/java/ch/so/agi/tasks/InterlisRepositoryCreator.java&quot;&gt;Code&lt;/a&gt; für das Herstellen ist simpel und entspricht dem Schreiben einer XTF-Datei mit einem &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/main/java/ch/interlis/iom_j/xtf/XtfWriter.java&quot;&gt;XtfWriter&lt;/a&gt;. Der minimale Gradle-Task für das Herstellen der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;plugins {
    id &quot;ch.so.agi.interlis-repository-creator&quot; version &quot;1.3.17&quot;
}

import ch.so.agi.tasks.InterlisRepositoryCreator

task createIliModels(type: InterlisRepositoryCreator) {
    modelsDir = file(&quot;models/&quot;)
    dataFile = &quot;ilimodels.xml&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese paar Zeilen speichert man in einer &lt;em&gt;build.gradle&lt;/em&gt;-Datei und mit einem &lt;code&gt;gradle&lt;/code&gt;-Aufruf in dem Verzeichnis, wo die &lt;em&gt;build.gradle&lt;/em&gt;-Datei gespeichert ist, wird die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei erstellt. Es werden alle Modelle berücksichtigt, die im Unterverzeichnis &lt;em&gt;models&lt;/em&gt; vorliegen. Es gibt zusätzliche &lt;a href=&quot;https://github.com/sogis/interlis-repository-creator/blob/master/README.md&quot;&gt;Optionen&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;repoModelName&lt;/code&gt;: Entweder &lt;code&gt;IliRepository09&lt;/code&gt; oder &lt;code&gt;IliRepository20&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;modelRepos&lt;/code&gt;: Modellablagen, die beim Kompilieren der eigenen Modelle verwendet werden sollen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;technicalContact&lt;/code&gt;: Der technische Kontakt, der in der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei bei den Modell erscheinen soll, falls es kein gleichlautendes Metaattribut im Modell gibt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ilismeta&lt;/code&gt;: Wird &lt;code&gt;ilismeta=true&lt;/code&gt; gesetzt, wird zum Modell zusätzlich die &lt;em&gt;IlisMeta07&lt;/em&gt;-Datei erstellt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei ist auch eine INTERLIS-Transferdatei. Das zugrundeliegende Modell ist sehr tolerant, d.h. es sind nur wenige Attribute nicht optional. Es muss einzig der Namen, die Version und ein relativer Pfad auf die Modell-Datei vorhanden sein. Alle übrigen Attribute sind optional. Diese drei Pflichtattribute ergeben sich aus dem Modell resp. aus der Modelldatei selber. Die MD5-Checksumme kann man ebenfalls aus der Datei berechnen lassen. Andere interessante Attribute lassen sich nicht mehr automatisch ableiten. Einige dieser Attribute kann man sinnvollerweise als Metaattribute im Modell erfassen. Seit &lt;a href=&quot;https://github.com/claeis/umleditor/commit/f6dabd413e77f6f4a5ef4dfa27b124b8b592bc4a&quot;&gt;kurzem&lt;/a&gt; kann man im &lt;a href=&quot;https://downloads.interlis.ch/umleditor/umleditor-3.9.0.zip&quot;&gt;&lt;em&gt;INTERLIS-UML-Editor&lt;/em&gt;&lt;/a&gt; für jedes Element beliebige Metaattribute erfassen, was diese Arbeit enorm erleichtert. So kann man z.B. das Metaattribut &lt;code&gt;shortDescription&lt;/code&gt; im Modell erfassen und diese Information in die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei schreiben lassen. Nach diesem Prinzip funktioniert unser Gradle-Plugin. Neben &lt;code&gt;shortDescription&lt;/code&gt; werden auch die Metaattribute &lt;code&gt;Title&lt;/code&gt;, &lt;code&gt;Issuer&lt;/code&gt;, &lt;code&gt;technicalContact&lt;/code&gt; und &lt;code&gt;furtherInformation&lt;/code&gt; - falls vorhanden - berücksichtigt. Für die letzten drei gab es im INTERLIS-UML-Editor bereits eine speziellen Eintrag zum Erfassen. Aus diesem Grund dürften diese in vielen Fällen vorhanden sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Früher hätte man die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei und die Modelle mit FTP gleich in die Produktion kopiert. Machen wir natürlich nicht mehr, sondern wir erstellen ein &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/Dockerfile&quot;&gt;Dockerimage&lt;/a&gt; auf Basis von &lt;a href=&quot;https://nginx.org/&quot;&gt;&lt;em&gt;nginx&lt;/em&gt;&lt;/a&gt;. Ist das Dockerimage erstellt, können wir einen Container starten und die Modellablage mit dem INTERLIS-Compiler prüfen (&lt;code&gt;--check-repo-ilis&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser gesamte Prozess (Herstellung, Prüfung, Image in Docker-Registry pushen) wird mit &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/build.gradle&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt; gemacht&lt;/a&gt;. Aus diesem Grund haben wir das Gradle-Plugin für die Herstellung der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei geschrieben. Wäre es z.B. &lt;em&gt;Maven&lt;/em&gt;, müsste man ein Maven-Plugin schreiben etc. Der Prozess kann lokal ausgeführt werden oder mittels &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/.github/workflows/main.yml&quot;&gt;Github-Action&lt;/a&gt;. Womit wir mehr oder weniger am Ende angelangt sind: Die Herstellung und Pflege ist automatisiert und das Wissen zum Abfüllen der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei stammt aus den Modellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die INTERLIS-Modellablage ist die einzige Anwendung, welche bei uns automatisch in die Produktionsumgebung deployed wird. Notfalls muss man ein Rollback ausführen, wenn man merkt, dass trotz der Tests etwas schief gelaufen ist. Zudem sollte man sich gut überlagen, ob man sich bei kritischen Prozessen auf externe Dienste (unnötigerweise) verlassen will und nicht einfach die benötigten Modelle zu seinem Prozess kopiert. Klammerbemerkung: Oder man macht in seiner Organisation einen INTERLIS-Modellablagen-Mirror, der die externen Ablagen regelmässig spiegelt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jedenfalls kann jeder Mitarbeiter bei uns, der in seiner Arbeit gerade etwas mit einem INTERLIS-Datenmodell macht, höchst effizient durcharbeiten und muss nicht zuerst zu einem Ops-Menschen gehen, um das Modell in der Ablage verfügbar zu machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;PS: Es gibt in &lt;a href=&quot;https://geo.so.ch/models/&quot;&gt;unserer Modellablage&lt;/a&gt; noch ein kleines Goodie. Die &lt;a href=&quot;https://geo.so.ch/models/ilimodels.xsl&quot;&gt;&lt;em&gt;ilimodels.xsl&lt;/em&gt;&lt;/a&gt;-Datei sorgt dafür, dass die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei automatisch vom Browser mittels XSLT in eine &lt;a href=&quot;https://geo.so.ch/models/ilimodels.xml&quot;&gt;HTML-Seite&lt;/a&gt; gerendert wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oh und wenn ich mir die wichtigste und bekannteste INTERLIS-Modellablage so anschaue, scheint mir das arg kompliziert zu sein. Da wird sehr viel mit Javascript gelöst. Und das Witzigste: es scheint pro Unterordner (also plusminus pro Bundestelle) eine beinahe identische Javascript-Datei zu geben: &lt;a href=&quot;https://models.geo.admin.ch/ARE/files/js/list_are.js&quot;&gt;ARE&lt;/a&gt; vs. &lt;a href=&quot;https://models.geo.admin.ch/V_D/files/js/list_vd.js&quot;&gt;V+D&lt;/a&gt;. So auf ersten Blick unterscheiden sich diese Dateien nur in einem Wort: &lt;code&gt;var S3B_ROOT_DIR = &apos;ARE&apos;;&lt;/code&gt; vs &lt;code&gt;var S3B_ROOT_DIR = &apos;V_D&apos;;&lt;/code&gt; Welcome to maintainance hell&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>State of INTERLIS model finder - Summer 2022</title>
      <link>http://blog.sogeo.services/blog/2022/07/18/state-of-modelfinder-summer-2022.html</link>
      <pubDate>Mon, 18 Jul 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/07/18/state-of-modelfinder-summer-2022.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;einleitung&quot;&gt;Einleitung&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &lt;a href=&quot;https://geo.so.ch/modelfinder&quot;&gt;&lt;em&gt;INTERLIS model finder&lt;/em&gt;&lt;/a&gt; ist aus der Not geboren. Wenn ich auf die Schnelle ein Modell anschauen wollte und nicht wusste, ob es ein Modell des &lt;a href=&quot;https://models.geo.admin.ch/BLW/&quot;&gt;BLW&lt;/a&gt; oder des &lt;a href=&quot;https://models.geo.admin.ch/BAFU/&quot;&gt;BAFU&lt;/a&gt; ist, erging es mir wie bei der Verwendung von &lt;a href=&quot;https://en.wikipedia.org/wiki/USB_hardware#Connectors&quot;&gt;USB-Typ-A-Steckern&lt;/a&gt;: Erster Versuch, geht nicht. Also umdrehen und nochmals versuchen. Geht auch nicht. Nun dann, nochmals umdrehen und plötzlich geht es. Also zuerst beim BLW schauen, dort das Modell nicht gefunden, weiter zum BAFU, dort auch nichts gefunden und wieder zurück zum BLW und plötzlich sieht man es.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;technologie&quot;&gt;Technologie&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Grundidee ist, mit im INTERLIS-Compiler &lt;a href=&quot;https://github.com/claeis/ili2c/tree/master/src/main/java/ch/interlis/ilirepository&quot;&gt;vorhandenen Methoden&lt;/a&gt;, die verschiedenen INTERLIS-Modellablagen (d.h. die dazugehörigen &lt;em&gt;ilimodels.xml&lt;/em&gt;-Dateien) zu lesen und zu interpretieren und anschliessend mit &lt;a href=&quot;https://lucene.apache.org/&quot;&gt;&lt;em&gt;Lucene&lt;/em&gt;&lt;/a&gt; zu indexieren. Weil es (noch) sehr wenige Informationen sind, die man indexiert, muss man das nicht einmal persistieren, sondern kann den Index beim Hochfahren der Anwendung erstellen und dann natürlich regelmässig updaten (z.B. einmal pro Tag). Das Frontend ist mit &lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;&lt;em&gt;GWT&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/DominoKit/domino-ui&quot;&gt;&lt;em&gt;Domino-ui&lt;/em&gt;&lt;/a&gt; als zusätzliche Bibliothek erstellt. Als Web-Framework ist &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; gesetzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit entsteht eine modulare Java-Anwendung. Der Vorteil: Man kann im Java-Ökoystem verweilen und muss nicht mehrere Technologie-Stacks mischen. Klar schreibt man am Ende des Tages eine Browser-Anwendung und muss sich auch diesbezüglich ein wenig auskennen aber ich muss z.B. nicht zwei Build-Tools und -Prozesse kennen etc. pp. Es werden auch nicht drei Docker-Images benötigt (Datenbank, Backend, Frontend), sondern maximal ein Docker-Image. &amp;laquo;notfalls&amp;raquo; reicht auch eine JVM: &lt;code&gt;java -jar modelfinder.jar&lt;/code&gt; und schon läuft die Anwendung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anwendung wird mit &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; zu einem native image kompiliert. Dann ist keine JVM mehr notwendig und die Anwendung startet viel schneller. In OpenShift weisen wir der Anwendung 100MB RAM und 100m CPU zu. Eine Anwendung, die in einer JVM läuft, würde mit diesen beschränkten Ressourcen wohl gar nicht mehr starten.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;features&quot;&gt;Features&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was kann der &lt;em&gt;INTERLIS model finder&lt;/em&gt;? Es werden alle Ablagen durchsucht, welche irgendwie mit &lt;a href=&quot;https://models.interlis.ch&quot;&gt;https://models.interlis.ch&lt;/a&gt; verknüpft sind. Es werden die Attribute aus der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei indexiert (Details siehe &lt;a href=&quot;https://github.com/sogis/modelfinder/blob/main/modelfinder-server/src/main/java/ch/so/agi/search/LuceneSearcher.java#L139&quot;&gt;LuceneSearcher.java&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der Eingabe im Suchfeld werden die Attribute &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;issuer&lt;/code&gt;, &lt;code&gt;technicalcontact&lt;/code&gt;, &lt;code&gt;furtherinformation&lt;/code&gt; und &lt;code&gt;idgeoiv&lt;/code&gt; &lt;a href=&quot;https://github.com/sogis/modelfinder/blob/main/modelfinder-server/src/main/java/ch/so/agi/search/LuceneSearcher.java#L139&quot;&gt;durchsucht&lt;/a&gt;. Das Resultat wird gruppiert nach der Modellablage. Bis &lt;a href=&quot;https://github.com/claeis/ili2c/issues/70&quot;&gt;ili2c Version 5.2.7&lt;/a&gt; ist es nicht möglich exakt die URL der Modellablage zu bestimmen. Aus diesem Grund stimmt der Namen von Ablagen nicht, die einen Pfad aufweisen, da in diesem Fall nur die Domain verwendet wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Steuern lässt sich das Verhalten der Anwendung mittels Query-Parameter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;query=[searchTerms]&lt;/code&gt;:&lt;/strong&gt; Damit kann via URL eine Suche gestartet werden. Z.B. &lt;code&gt;query=Wald&lt;/code&gt; sucht in allen Ablagen nach Modellen, bei denen das Wort &amp;laquo;Wald&amp;raquo; indexiert wurde.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;expanded=[true|false]&lt;/code&gt;:&lt;/strong&gt; Wird &lt;code&gt;expanded=true&lt;/code&gt; verwendet, sind die Modellablagen aufgeklappt und die Treffer sind sichtbar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ilisite=[repository name]&lt;/code&gt;:&lt;/strong&gt; Damit lässt sich die Suche auf eine Modellablage einschränken. Achtung: Wegen der Domain-vs-Modellablagen-URL-Problematik (siehe oben) darf unter Umständen (noch) nicht die genaue URL der Modellablage verwendet werden, sondern nur der Domainname.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;nologo=[true|false]&lt;/code&gt;:&lt;/strong&gt; Falls &lt;code&gt;nologo=true&lt;/code&gt; wird das Logo nicht dargestellt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man Modelle, die irgendwas mit &amp;laquo;Wald&amp;raquo; zu tun haben, in der Modellablage des Bundes suchen will, die Resultate gleich sichtbar aufgeklappt haben und nicht immer das Solothurner Logo anschauen will, geht das so: &lt;a href=&quot;https://geo.so.ch/modelfinder/?expanded=true&amp;amp;ilisite=models.geo.admin.ch&amp;amp;nologo=true&amp;amp;query=wald&quot;&gt;https://geo.so.ch/modelfinder/?expanded=true&amp;amp;ilisite=models.geo.admin.ch&amp;amp;nologo=true&amp;amp;query=wald&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Profitipp #1: Kann man sich natürlich (ohne den &lt;code&gt;query&lt;/code&gt;-Parameter) z.B. als Bookmark abspeichern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein spannendes Feature ist der partielle &lt;a href=&quot;https://en.wikipedia.org/wiki/OpenSearch&quot;&gt;OpenSearch&lt;/a&gt;-Support. Besucht man die &lt;a href=&quot;https://geo.so.ch/modelfinder/&quot;&gt;Webseite&lt;/a&gt; mit Firefox zum ersten Mal, erscheint im Suchfenster die Lupe mit einem Pluszeichen auf grünem Grund:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/state_of_modelfinder_summer_2022/opensearch01.png&quot; alt=&quot;opensearch firefox 01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man auf die Lupe klickt, erscheint ein kleines Fenster, wo man &quot;Add search engine &apos;INTERLIS model finder&apos;&quot; auswählen muss. Neben Wikipedia und anderen Suchen, sollte nun ein INTERLIS-Icon vorhanden sein. Tippt man &amp;laquo;Wald&amp;raquo; in das Suchfeld werden Resultate aus der Standardsuche vorgeschlagen und vorangegangene Suchbegriffe, es reicht ein Klick auf das INTERLIS-Icon und es wird nach INTERLIS-Modellen gesucht. Das funktioniert zu jedem Zeitpunkt, d.h. man muss sich nicht auf der INTERLIS model finder Webseite befinden. Wird INTERLIS model finder in Firefox zur Standardsuche gemacht, werden Treffer vorgeschlagen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/state_of_modelfinder_summer_2022/opensearch02.png&quot; alt=&quot;opensearch firefox 02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Profitipp #2: Man kann trotz Freitextsuche relativ streng den Kanton (oder Bund) eingrenzen indem man zusätzlich zum eigentlichen Suchterm z.B. noch &amp;laquo;so.ch&amp;raquo; miteintippt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Natürlich kann man es niemandem übel nehmen, der nicht die Modellsuche als Standardsuche definiert. Trotzdem lässt sich die Suchfunktionalität in Wert setzen, indem man einen Shortcut setzt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/state_of_modelfinder_summer_2022/opensearch03.png&quot; alt=&quot;opensearch firefox 03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetzt ist es möglich mit &lt;code&gt;@models&lt;/code&gt; und Tab-Taste klicken in der Address Bar nach Modell zu suchen und gleich auch Vorschläge zu bekommen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/state_of_modelfinder_summer_2022/opensearch04.png&quot; alt=&quot;opensearch firefox 04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In ähnlicher Form funktioniert das auch mit Chrome (Chromium). Dort ist die OpenSearch-Unterstützung zusätzlich mittels &lt;a href=&quot;https://www.chromium.org/tab-to-search/&quot;&gt;Tab-to-search&lt;/a&gt; noch besser umgesetzt. Aber ich weiss nicht, ob das funktioniert, wenn sich die OpenSearch-Funktionalität hinter einem Pfad versteckt (wie hier) und nicht direkt hinter einem Domainnamen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie und ob es bei Safari funktioniert? Keine Ahnung.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ausblick&quot;&gt;Ausblick&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schnell kommt der Vorschlag, dass man auch nach Klassen etc. suchen könnte. Ja, aber ich denke, das wird bezüglich UX/UI herausfordernd. Reicht hier eine Freitextsuche oder muss geführter gesucht werden, um halbwegs sinnvolle Treffer zu bekommen. Hat überhaupt jemand schon einmal dieses Bedürfnis wirklich gehabt?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der sinnvolle Umgang mit alten Modellversionen ist mir auch nicht so richtig klar. Sollen die überhaupt gefunden werden können? Sollen die bewusst gefiltert werden können?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Am wichtigsten scheint mir, dass die &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei mit sinnvollen Inhalten gefüllt ist. Stand heute finde ich das &lt;a href=&quot;https://models.geo.admin.ch/BAFU/Hazard_Mapping_V1_3.ili&quot;&gt;aktuelle Naturgefahren-MGDM&lt;/a&gt; nicht, weil nirgends das Wort &amp;laquo;Naturgefahren&amp;raquo; vorkommt. Weder in der &lt;em&gt;ilimodels.xml&lt;/em&gt;-Datei noch im Modell selber. In diesem konkreten Fall könnten die &lt;code&gt;Tags&lt;/code&gt;-Attribute helfen oder die &lt;code&gt;shortDescription&lt;/code&gt;, die dann indexiert werden können. Das führt zur Fragestellung: &amp;laquo;Wie erstelle und pflege ich eine INTERLIS-Modellablage&amp;raquo;. Dazu später mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #7 - Multi-Tenancy</title>
      <link>http://blog.sogeo.services/blog/2022/06/12/oereb-kataster-richtig-gemacht-7.html</link>
      <pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/06/12/oereb-kataster-richtig-gemacht-7.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;http://blog.sogeo.services/blog/2022/06/02/oereb-kataster-richtig-gemacht-6.html&quot;&gt;letzten Teil&lt;/a&gt; wollte ich zeigen, dass das Solothurner System auch mit Daten anderer Kantone reibungsfrei, einfach und transparent funktioniert. Das tut es. Ein wichtiger Aspekt hat aber gefehlt: Der Testaufbau beinhaltete nur Daten eines Kantons. Herausforderung akzeptiert. Zwei Kantone sind langweilig, nehmen wir noch einen dritten hinzu. Das Schwierigste ist in der Regel in den Besitz von Daten im Rahmenmodell zu kommen. Weil diese nun nicht einfach vom jeweiligen Kanton herunterladbar sind, erzeuge ich aus dem MGDM der belasteten Standorte des Kantons Schwyz selber die Rahmenmodell-Transferstruktur. Die Daten im MGDM kann man auf &lt;a href=&quot;https://geodienste.ch&quot;&gt;geodienste.ch&lt;/a&gt; herunterladen. Den Datenumbau machen wir nicht mit dem im letzten Teil erwähnten &lt;code&gt;mgdm2oereb&lt;/code&gt;-Tool, sondern mit unserem Sql-Ansatz. D.h. ich importiere die Daten im MGDM mit &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; in die Datenbank, erzeuge ein Schema mit leeren Rahmenmodell-Tabellen und mit der &amp;laquo;Power of Sql&amp;raquo; baue ich die Daten von einer Struktur in die andere. Für den Datenexport kommt wieder &lt;em&gt;ili2pg&lt;/em&gt; zum Zug. Diesen Ansatz verwenden wir bei uns konsequent für das Herstellen des Rahmenmodells und auch sonst für praktisch alles (z.B. KGDM &amp;#8594; MGDM). Die einzelnen Befehle und die SQL-Queries kann man sich im &lt;a href=&quot;https://github.com/oereb/oereb-sz&quot;&gt;Github-Repo&lt;/a&gt; zu Gemüte führen. Das &lt;code&gt;mgdm2oereb&lt;/code&gt;-Tool kann nicht verwendet werden, weil es mit der Version 1.5 des MGDM funktioniert. Die Daten des Kantons Schwyz liegen jedoch in der Version 1.4 vor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Haben wir die KbS des Kantons Schwyz nun im Rahmenmodell &lt;a href=&quot;https://raw.githubusercontent.com/oereb/oereb-sz/main/mgdm2oereb/ch.sz.afu.oereb_kataster_belasteter_standorte_V2_0.xtf&quot;&gt;vorliegend&lt;/a&gt;, können wir die Konfigurationsdateien erstellen. Klammerbemerkung: Dabei sogar eine Premiere. Einmal im Leben &lt;a href=&quot;https://geobasisdaten.ch/&quot;&gt;geobasisdaten.ch&lt;/a&gt; verwendet und zwar um die gesetzlichen Grundlagen des KbS im Kanton Schwyz herauszufinden. Wie beim Kanton Schaffhausen mache ich bloss das Minimum. D.h. ich schalte eine Gemeinde frei (Schwyz) damit sich die Fleissarbeit für mich in Grenzen hält. Die Konfigurationsdateien sind im &lt;a href=&quot;https://github.com/oereb/poc_oereb_kataster/tree/main/oereb_config/sz&quot;&gt;PoC-Repo&lt;/a&gt; zu finden. Der Datenimport, den wir mit &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; (unser ETL-Werkzeug mit integriertem &lt;em&gt;ili2pg&lt;/em&gt; etc.) machen, ist um &lt;a href=&quot;https://github.com/oereb/poc_oereb_kataster/blob/main/README.md&quot;&gt;die SZ-Jobs&lt;/a&gt; angewachsen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das ist es eigentlich auch bereits. Sind sämtliche Daten importiert, kann man Requests für drei Kantone machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SO:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH955832730623&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH955832730623&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH955832730623&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH955832730623&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SH:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH167308546127&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH167308546127&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/pdf?EGRID=CH167308546127&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/pdf?EGRID=CH167308546127&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SZ:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH667722407539&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH667722407539&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/pdf?EGRID=CH667722407539&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/pdf?EGRID=CH667722407539&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Antwort des SZ-Beispieles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p07/kbs_sz.png&quot; alt=&quot;pdf kbs&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mission accomplished. Aber leider auch nicht ganz so erfolgreich. Das PDF sieht zwar tiptop aus und auf den ersten Blick auch korrekt (gut, bis auf die hässliche fette Umrandung des geodienste.ch-WMS), aber auf den zweiten Blick sieht man die überflüssigen gesetzlichen Grundlagen. Es werden auch Gesetze und Verordnungen der Kantons Schaffhausen und Solothurn aufgelistet. Warum? Des Rätsels Lösung ist einfach. In den &lt;code&gt;ch.KANTON.agi.oereb_themen_V2_0.xtf&lt;/code&gt;-Dateien weisen wir einem ÖREB-Thema weitere (kantonale oder kommunale) gesetzliche Grundlagen zu. D.h. in unserem gemeinsamen ÖREB-Katastersystem sind dem Thema &lt;code&gt;ch.BelasteteStandorte&lt;/code&gt; gesetzliche Grundlagen von drei Kantonen zugewiesen. Das ist falsch. Entweder müsste man das Rahmenmodell anpassen (wie auch wegen der fehlenden Beziehung einer Gemeinde zu einer KVS) oder man löst es applikatorisch indem man Konventionen einführt (mit Baskets und/oder Datasets). Aber für mich noch kein Abbruchkriterium für eine gemeinsame ÖREB-Katasterinfrastruktur.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Architektur bleibt für mich faszinierend einfach. Sie hat klare Schnittstellen, ist effizient und transparent und basiert auf robusten, vorhandenen Werkzeugen (&lt;em&gt;PostGIS&lt;/em&gt;, &lt;em&gt;ili2db&lt;/em&gt;, &lt;em&gt;ilivalidator&lt;/em&gt;, &amp;#8230;&amp;#8203;). &amp;laquo;Standing on the shoulders of giants&amp;raquo; quasi. Vielleicht sollten wir wieder vermehrt einfache Dinge einfach lösen. Technisch wie auch organisatorisch.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #6 - Quod erat demonstrandum</title>
      <link>http://blog.sogeo.services/blog/2022/06/02/oereb-kataster-richtig-gemacht-6.html</link>
      <pubDate>Thu, 2 Jun 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/06/02/oereb-kataster-richtig-gemacht-6.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Teil 1 - 5 zeigen wie man sich einen ÖREB-Kataster zusammenstöpselt. Dass das funktioniert, ist nicht sonderlich erstaunlich, da es ziemlich 1:1 dem entspricht, was &lt;a href=&quot;https://agi.so.ch&quot;&gt;wir&lt;/a&gt; und wie es wir im Einsatz haben. Funktioniert es auch mit in anderen Kantonen?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ja, klar. Man nehme z.B. die KbS des Kantons Schaffhausen im Rahmenmodell. Diese werden direkt aus dem MGDM mittels XSLT hergestellt. Die Idee finde ich interessant, freue mich aber auf die  Transformation für Fälle von MGDM, wo man Geodaten filtern muss. Welcome to XPath hell&amp;#8230;&amp;#8203; Zudem natürlich alle Bundesdaten. Benötigt werden insbesondere noch die Konfiguration-INTERLIS-Transferdateien. Das sind:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zuständige Stellen:&lt;/strong&gt; Für unsere Gesamtsystem wird zwingend die katasterverantwortliche Stelle benötigt. Sie landet auf dem PDF-Ausdruck und natürlich auch in der XML-Datei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Optional werden sämtliche zuständigen Stellen des Kantons und der Gemeinden benötigt. Werden diese zentral nachgeführt und verwaltet, kann in den Geodaten nur noch darauf verwiesen werden und sie müssen nicht mitgeliefert werden. Das hat den Vorteil, dass bei Mutationen einfach und schnell reagiert werden kann und dies nur an einer Stelle gemacht werden muss. Zudem ist es einfach &amp;laquo;sauberer&amp;raquo;, wenn im ÖREB-Kataster z.B. die Staatskanzlei nur einmal vorkommt und nicht x-fach. Die Staatskanzlei eines Kantons gibt es in der Realwelt ebenfalls nur einmal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_zustaendigestellen_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_zustaendigestellen_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Gesetze:&lt;/strong&gt; Eine Transferdatei mit den Gesetzen wird zwingend benötigt, um die kantonalen gesetzlichen Grundlagen verwalten zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.sk.oereb_gesetze_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.sk.oereb_gesetze_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Themen:&lt;/strong&gt; Die Themen-Konfiguration ist ebenfalls zwingend notwendig. Sie wird benötigt, um die kantonalen gesetzlichen Grundlagen den Themen zuzuweisen. Zusätzliche kantonale Themen und Subthemen werden auch in dieser Datei verwaltet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_themen_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_themen_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Verfügbarkeit:&lt;/strong&gt; Die Verfügbarkeits-Konfiguration wird zwingend für die Steuerung der verfügbaren Themen und Gemeinden benötigt. Ebenfalls wird das Datum des Standes der Daten der amtlichen Vermessung in dieser Konfiguration verwaltet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_verfuegbarkeit_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_verfuegbarkeit_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Grundbuch:&lt;/strong&gt; Wird zwingend benötigt, falls es Grundbuchkreise (o.ä.) gibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_grundbuchkreis_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_grundbuchkreis_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;MapLayering:&lt;/strong&gt; Falls man die Opazität und die Reihenfolge der WMS-Bilder (resp. des Darstellungsdienstes) steuern will, ist diese Konfigruation zwingend.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_maplayering_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_maplayering_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Logo:&lt;/strong&gt; Wird zwingend für die Verwaltung der Logos der Gemeinden und des Kantons benötigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_logo_V2_0.xtf&quot;&gt;https://github.com/oereb/oereb-sh/blob/main/oereb_config/ch.sh.agi.oereb_logo_V2_0.xtf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Texte:&lt;/strong&gt; Wird zwingend benötigt, falls es neben den Bundestexten noch weitere kantonale Texte für den Glossar gibt. In meinen Beispiel für den Kanton Schaffhausen habe ich keine zusätzlichen Texte eingeführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Aufsplittung der Konfiguration kann man teilweise (es muss ja immer noch modellkonform sein) selber wählen. Wahrscheinlich macht man das eher themen-/kontextbezogen und abhängig vom Nachführungszyklus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Dockerfile resp. in der &lt;a href=&quot;https://github.com/oereb/oereb-sh/blob/main/oereb_stack/docker-compose.yml&quot;&gt;docker-compose.yml-Datei&lt;/a&gt; sieht man die übriggebliebene Konfiguration, die man &lt;em&gt;nicht&lt;/em&gt; mittels INTERLIS-Dateien verwalten kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Timezone&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Datenbankverbindung&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Datenbankschema&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Minimale Grösse der Geometrien, die berücksichtigt werden beim Verschnitt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;URL der katasterverantwortlichen Stelle&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WMS für die Hintergrundkarte(n)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pro ÖREB-Webservice-Instanz kann somit nur eine KVS vorhanden sein kann. D.h. es bräuchte im Rahmenmodell noch etwas wie &amp;laquo;KVS pro Gemeinde&amp;raquo;, damit es nicht für jeden Kanton eine eigene Webservice-Instanz braucht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf Stufe Datenbank könnte man bereits heute mehrere Kantone in das gleiche Schema schmeissen und es würde funktionieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Konfiguration mittels Rahmenmodell ist sehr transparent und man muss für verschiedene Schritte das Rad nicht neu erfinden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Nachführung: Entweder schnell mal von Hand oder mit &lt;a href=&quot;https://qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validierung und Import in das Katastersystem: &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schreiten wir zur Tat und implementieren wir ein ÖREB-Katastersystem für den Kanton Schaffhausen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;docker-compose up&lt;/code&gt; (im &lt;a href=&quot;https://github.com/oereb/oereb-sh/tree/main/oereb_stack&quot;&gt;&lt;code&gt;oereb_stack&lt;/code&gt;-Ordner&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das startet eine leere ÖREB-Datenbank und den ÖREB-Webservice. Die &lt;a href=&quot;https://github.com/oereb/oereb-sh/tree/main/oereb_gretljobs&quot;&gt;GRETL-Jobs&lt;/a&gt; konnte ich mit minimalen Anpassungen aus &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html&quot;&gt;Teil 3&lt;/a&gt; übernehmen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die magischen Env-Variablen müssen wir setzen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;export ORG_GRADLE_PROJECT_dbUriOerebV2=&quot;jdbc:postgresql://oereb-db/oereb&quot;
export ORG_GRADLE_PROJECT_dbUserOerebV2=&quot;gretl&quot;
export ORG_GRADLE_PROJECT_dbPwdOerebV2=&quot;gretl&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sämtliche Datenimports werden mit einem Befehl im &lt;a href=&quot;https://github.com/oereb/oereb-sh/tree/main/oereb_gretljobs&quot;&gt;Ordner &lt;code&gt;oereb_gretljobs&lt;/code&gt;&lt;/a&gt; angestossen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network oereb_stack_default --job-directory $PWD :oereb_av:replaceCadastralSurveyingData :oereb_plzo:importPLZO :oereb_bundesgesetze:importData :oereb_bundeskonfiguration:importBundeskonfiguration :oereb_kantonskonfiguration:importKantonskonfiguration :oereb_bundesdaten:importData :oereb_kbs:importData&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe nur die Gemeinde Schaffhausen freigeschaltet. Aus diesem Grund wird auch nur die amtliche Vermessung dieser Gemeinde heruntergeladen und importiert. Der gesamte Importprozess dauert bei mir circa fünf Minuten, wobei das meiste für die amtliche Vermessung und die PLZ/Ortschaften draufgeht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Testrequest &amp;laquo;KbS ÖV und Baulinien Nationalstrassen&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/getegrid/xml/?EN=2689879,1284094&quot; class=&quot;bare&quot;&gt;http://localhost:8080/getegrid/xml/?EN=2689879,1284094&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH615475087406&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH615475087406&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/pdf?EGRID=CH615475087406&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/pdf?EGRID=CH615475087406&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Testrequest &amp;laquo;KbS (mgdm2oereb)&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/getegrid/xml/?EN=2689819,1283962&quot; class=&quot;bare&quot;&gt;http://localhost:8080/getegrid/xml/?EN=2689819,1283962&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH167308546127&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH167308546127&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/pdf?EGRID=CH167308546127&quot; class=&quot;bare&quot;&gt;http://localhost:8080/extract/pdf?EGRID=CH167308546127&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p06/kbs.png&quot; alt=&quot;pdf kbs&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Unschönheit weist der Auszug bei den gesetzlichen Grundlagen auf: Das USG und die USGV erscheinen doppelt. Das rührt daher, weil die gesetzlichen Grundlagen ebenfalls in den Geodaten bei den Dokumenten mitgeliefert werden und dann ebenfalls nochmals in meiner Konfiguration dem Thema zugeordnet werden. Die gesetzlichen Grundlagen sollten nicht mit den Geodaten mitgeliefert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Frage, die mich aber nachts fast nicht mehr schlafen lässt: Warum um Herrgottswillen steht da auf dem &lt;a href=&quot;https://geodienste.ch/services/av&quot;&gt;geodienste.ch-WMS&lt;/a&gt; &amp;laquo;frei erhältlich&amp;raquo;? Habe ich irgendwie falsche WMS-Endpunkte angezapft oder ist das ein spezieller WMS-Layer? Ich habe das Root-Element als Layer-Parameter gewählt.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #5 - ÖREB-Webservice</title>
      <link>http://blog.sogeo.services/blog/2022/05/14/oereb-kataster-richtig-gemacht-5.html</link>
      <pubDate>Sat, 14 May 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/05/14/oereb-kataster-richtig-gemacht-5.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ÖREB-Webservice ist das eigentliche Herz des ÖREB-Katasters und das einzige Originäre. Trotzdem ist er im Prinzip sehr simpel. Er macht pro Aufruf ein paar Datenbankabfragen und wandelt das Resultat nach XML um. Wobei man sich um das eigentliche Formatieren nicht einmal kümmern muss. Doch dazu später mehr. Der PDF-Auszug wird sinnvollerweise direkt aus dem XML abgeleitet und wird nicht mit zusätzlichen Datenbankabfragen gesondert hergestellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Basis des Webservices ist &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt;. Ein Java-Framework, das wir für sehr vieles einsetzen. Sämtliche ÖREB-Magie geschieht praktisch in einem &lt;a href=&quot;https://github.com/claeis/oereb-web-service/blob/master/src/main/java/ch/ehi/oereb/webservice/OerebController.java&quot;&gt;Controller&lt;/a&gt;. Der Controller nimmt die Anfragen (GetEgrid, GetExtractById, &amp;#8230;&amp;#8203;) entgegen und sammelt anschliessend die Daten in der Datenbank zusammen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die XML-Datei als solches müssen wir nicht selber formatieren, sondern wir verwenden &lt;a href=&quot;https://javaee.github.io/jaxb-v2/&quot;&gt;&lt;em&gt;JAXB&lt;/em&gt;&lt;/a&gt;, das uns diese Arbeit abnimmt. Wir müssen bloss die Resultate aus der Datenbank in Java-Objekte umwandeln. Aus den Java-Objekten erzeugt &lt;em&gt;JAXB&lt;/em&gt; die XML-Datei selbständig. Die Java-Klassen müssen vorgängig einmalig - ebenfalls mit &lt;em&gt;JAXB&lt;/em&gt; - aus dem XML-Schema erzeugt werden. Manchmal kann das auch mühsam werden, wenn - wie in in der Version 1 des Rahmenmodelles - die Geometriekodierung auf GML basiert. Dann enstehen, aufgrund der Komplexität des GML-Standards, hunderte von Java-Klassen. Die meisten natürlich für unseren Anwendungsfall völlig unnötigt. Weil in der Version 2 des Rahmenmodelles die Geometriekodierung auf INTERLIS basiert, schrumpft dies auf ein paar Klassen zusammen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Umwandlung der XML-Datei in eine PDF-Datei machen wir mit &lt;a href=&quot;https://www.w3.org/TR/xslt/&quot;&gt;XSLT&lt;/a&gt; und &lt;a href=&quot;https://www.w3.org/wiki/Xsl-fo&quot;&gt;XSL-FO&lt;/a&gt;. Die XSL-Transformation wandelt das XML in eine &amp;laquo;ASCII-PDF&amp;raquo;-Datei um. Die Transformation mit XSL-FO formatiert diese &amp;laquo;ASCII-PDF&amp;raquo;-Datei in eine richtige PDF-Datei. XSLT ist ein lebendiger Standard, der in der Version 3 sehr mächtig ist. Sogar Browser unterstützen XSLT. Leider nur die Version 1. Trotzdem lassen sich einige sinnvolle Anwendungen für XSL-Transformationen im Browser finden, so z.B. die ilimodels.xml-Datei einer INTERLIS-Modellablage lesbarer zu gestalten: &lt;a href=&quot;https://geo.so.ch/models/ilimodels.xml&quot;&gt;https://geo.so.ch/models/ilimodels.xml&lt;/a&gt;. XSL-FO wird leider als Standard nicht mehr weiterentwickelt. Zukünftig könnte vielleicht &lt;a href=&quot;https://www.w3.org/TR/css-page-3/&quot;&gt;CSS Paged Media&lt;/a&gt; diese Rolle übernehmen. Jedoch gibt es immer noch lebendige XSL-FO-Softwareprojekte, die stetig weiterentwickelt werden, z.B. &lt;a href=&quot;https://xmlgraphics.apache.org/fop/&quot;&gt;Apache FOP&lt;/a&gt;, das auch wir einsetzen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Umwandlung der XML-Datei in eine PDF-Datei haben wir in eine separate &lt;a href=&quot;https://github.com/sogis/pdf4oereb&quot;&gt;Bibliothek&lt;/a&gt; ausgelagert, die vom Webservice referenziert wird. Sie unterstützt alle Landessprachen und kann mit eingebetteten Bildern oder mit WMS-GetMap-Requests umgehen. Veröffentlicht wird die Bibliothek auf &lt;a href=&quot;https://mvnrepository.com/artifact/io.github.sogis/pdf4oereb&quot;&gt;Maven-Central&lt;/a&gt; oder als Standalone-CLI-Tool auf &lt;a href=&quot;https://github.com/sogis/pdf4oereb/releases&quot;&gt;Github&lt;/a&gt;. Die Verwendung des CLI-Tools ist im &lt;a href=&quot;https://github.com/sogis/pdf4oere&quot;&gt;README.md&lt;/a&gt; beschrieben. Die XML-Datei wird vor der Transformation nicht auf Schemakonformität geprüft. Dieser Schritt könnte/sollte man eventuell noch einbauen. Für einige sehr spezifische Teilprozesse mussten &lt;a href=&quot;https://github.com/sogis/pdf4oereb/tree/master/app/src/main/java/ch/so/agi/oereb/pdf4oereb/saxon/ext&quot;&gt;eigene XSLT-Funktionen mit Java implementiert&lt;/a&gt; werden. Dabei handelt es sich vor allem um grafische Transformationen, wie z.B. das Herstellen des Overlay-Images bestehend aus Rubberband, Massstabsbalken und Nordpfeil. Es gibt ausserdem einen (quick &apos;n&apos; dirty) &lt;a href=&quot;https://github.com/edigonzales/pdf4oereb-web-service/&quot;&gt;Webservice&lt;/a&gt; mit einem hässlichen GUI und einer M2M-Schnittstelle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben der &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html&quot;&gt;ÖREB-Datenbank&lt;/a&gt; und dem &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/24/oereb-kataster-richtig-gemacht-4.html&quot;&gt;ÖREB-WMS&lt;/a&gt; ist der ÖREB-Webservice die dritte Software-Komponente des ÖREB-Katasters. Die &lt;code&gt;docker-compose&lt;/code&gt; &lt;a href=&quot;https://github.com/oereb/oereb-stack/blob/main/docker-compose.yml&quot;&gt;Datei&lt;/a&gt; muss entsprechend erweitert werden. Im Rahmen dieser Blogreihe erstellen wir kein separates Dockerimage für den Webservice, sondern verwenden das Image, welches auch wir in Betrieb haben. Das &lt;a href=&quot;https://github.com/sogis/oereb-web-service-docker/blob/master/Dockerfile.alpine&quot;&gt;Dockerfile&lt;/a&gt; zeigt die wenigen notwendigen Einstellungsmöglichkeiten. Einige der Optionen (z.B. Hintergrundkarte, &amp;#8230;&amp;#8203;) müssten noch als Umgebungsvariable exponiert werden, damit nicht nur der Kanton Solothurn dieses Image verwenden kann. Ergänzt werden muss die Compose-Datei um folgenden Eintrag:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;  webservice:
    image: sogis/oereb-web-service:2
    environment:
      TZ: Europe/Zurich
      DBURL: jdbc:postgresql://db:5432/oereb
      DBUSR: dmluser
      DBPWD: dmluser
      TMPDIR: /tmp/
      DBSCHEMA: live
      MININTERSECTION: &quot;0.1&quot;
    ports:
      - 8080:8080
    depends_on:
      - qgis-server
      - db&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;docker-compose up&lt;/code&gt; kann man die drei Container starten und falls der Webservice erfolgreich gestartet ist, erscheint unter &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt; die Meldung &amp;laquo;oereb web service&amp;raquo;. Der Webservice wird trotzdem gestartet, wenn z.B. die Datenbank-Url oder die Login-Credentials falsch sind. In diesem Fall meldet sich der Webservice jedoch &amp;laquo;krank&amp;raquo;. Unter dem &lt;a href=&quot;http://localhost:8080/actuator/health&quot;&gt;Health-Endpoint&lt;/a&gt; erscheint die Meldung &lt;code&gt;&quot;status&quot;: &quot;DOWN&quot;&lt;/code&gt; anstelle von &lt;code&gt;&quot;UP&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls der DB-Container seit Teil 4 gestoppt wurde, müssen wir die Daten nochmals importieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network \
oereb-stack_default --job-directory $PWD motherOfAllTasks&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wurden die Daten wieder importiert, können wir einen Testrequest machen und einen E-GRID an einer bestimmen Koordinate suchen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/getegrid/xml/?EN=2600573,1215488&amp;amp;GEOMETRY=true&quot; class=&quot;bare&quot;&gt;http://localhost:8080/getegrid/xml/?EN=2600573,1215488&amp;amp;GEOMETRY=true&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sieht der Output plausibel aus, können wir mit dem eruierten E-GRID einen XML-Auszug anfordern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH955832730623&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH955832730623&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch hier gibt es nicht viel zu sagen. Im Browser sollte eine XML-Datei angezeigt werden. Der Webservice unterstützt keinen JSON-Output. Ich finde die Weisung diesbezüglich unglücklich: XML-Output ist Pflichtformat, JSON ist optional. Ich bin der Meinung man sollte sich auf ein Format beschränken. Stand heute würde ich klar immer noch XML vorziehen. Die Schemaunterstützung von XML ist viel erwachsener, entsprechend auch die Werkzeuge. Oftmals hört man in diesem Kontext das Argument &amp;laquo;aber JSON ist schneller&amp;raquo;. Gemeint ist (wohl?), dass aufgrund der absoluten Dateigrösse weniger Daten vom Server an den Clienten geschickt werden müssen, was natürlich stimmt. Jetzt kommt ein grosses ABER: Wenn man seinen Webserver nicht komplett falsch konfiguriert hat, werden die zu sendenden Daten komprimiert. Der Grössenvorteil von JSON gegenüber XML schmilzt so natürlich ziemlich dahin. Und man sollte für eine gute User Experience nicht bloss die Zeit für das Übertragen des Auszugs zählen, sondern die gesamte Zeitdauer, beginnend vom Klicken in die Karte bis zum fertigen Auslieferen des PDF oder XML. Somit dürfte matchentscheidender sein wie schnell das eigentliche Herstellen des Auszuges auf dem Server dauert. Insbesondere die Datenbankabfragen inkl. Verschnitte etc. werden viel mehr ins Gewicht fallen als ein paar Kilobyte mehr, die übertragen werden müssen. Ausserdem sollten die dynamischen Clienten so konfiguriert sein, dass sie nicht zwingend immer auch eingebettete Bilder anfordern, sondern bloss die WMS-Requests. Denn die Bilder braucht man nicht für die Darstellung im Web GIS Client. Und das Herstellen dieser Bilder ist sehr teuer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zurück zu unserem Webservice: Ein Request mit eingebetteten Bildern funktioniert nicht (richtig):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/xml?EGRID=CH955832730623&amp;amp;WITHIMAGES=true&quot;&gt;http://localhost:8080/extract/xml?EGRID=CH955832730623&amp;amp;WITHIMAGES=true&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es erscheint zwar eine XML-Datei im Browser aber das &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt;-Element enthält bloss einen minimalen Platzhalter-Blob. Und in der Konsole, in der wir den Service mit docker-compose gestartet haben, erscheinen Fehlermeldungen: &lt;code&gt;failed to get wms image&lt;/code&gt;. Was ist passiert?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;http://localhost:8820/blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html&quot;&gt;dritten Teil&lt;/a&gt; haben wir vor dem Import der Daten eine Umgebungsvariable (&lt;code&gt;ORG_GRADLE_PROJECT_geoservicesUrl&lt;/code&gt;) gesetzt, welche die URL des Darstellungsdienstes in den kantonalen Daten steuert. Unter dieser URL (&lt;em&gt;&lt;a href=&quot;http://localhost/wms/oereb&quot; class=&quot;bare&quot;&gt;http://localhost/wms/oereb&lt;/a&gt;&lt;/em&gt;) ist der WMS-Server aber nicht erreichbar, sondern es muss der korrekte Port (8083) mitangegeben werden (siehe dazu die docker-compose-Datei). Wenn wir die Umgebungsvariable nochmals korrekt setzen und anschliessend die kantonalen Daten importieren, funktioniert es aber immer noch nicht. Warum?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun hier wird es leider ein wenig kompliziert. Erstens muss erwähnt werden, dass es nur unter macOS und Windows nicht funktioniert. Das hat damit zu tun, dass Docker auf diesen beiden Betriebssystemen nicht nativ läuft, sondern immer noch eine sehr kleine VM dazwischen ist. Zweitens gibt es die Probleme nur bei der Verwendung von &lt;a href=&quot;https://docs.docker.com/desktop/mac/networking/&quot;&gt;&lt;em&gt;localhost&lt;/em&gt;&lt;/a&gt;. D.h. der Webservice findet den WMS-Server (beiden laufen in einem Docker-Container) immer noch nicht. Abhilfe schafft die Verwendung von &lt;em&gt;&lt;a href=&quot;http://host.docker.internal:8083/wms/oereb&quot; class=&quot;bare&quot;&gt;http://host.docker.internal:8083/wms/oereb&lt;/a&gt;&lt;/em&gt; anstelle von &lt;em&gt;localhost&lt;/em&gt;. Das aber führt leider dazu, dass im XML-Auszug nun die URL der Darstellungsdienste (&lt;code&gt;&amp;lt;ReferenceWMS&amp;gt;&lt;/code&gt;) von ausserhalb der Container, also wenn man z.B. die URL in die Browserleiste kopiert, nicht funktioniert. Diese Kröte muss man leider unter macOS und Windows während des lokalen Testens oder Entwickelns schlucken.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu Testen fordern wir für das Grundstück eine PDF-Datei an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8080/extract/pdf?EGRID=CH955832730623&quot;&gt;http://localhost:8080/extract/pdf?EGRID=CH955832730623&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sollte ein, den Weisungen entsprechender, PDF-Auszug sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p05/pdfextract.png&quot; alt=&quot;pdf extract&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem ÖREB-Webservice und den anderen bereits vorgestellten Komponenten steht eine einfache, transparente und performante ÖREB-Katasterinfrastruktur zur Verfügung. Wir sind damit sehr zufrieden, sei es während der Weiterentwicklung oder während des Betriebes. Es wurde nichts Unnötiges erfunden. Insbesondere der Fokus auf das Rahmenmodell und damit auf das INTERLIS-Ökosystem hilft ungemein.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #4 - ÖREB-WMS</title>
      <link>http://blog.sogeo.services/blog/2022/04/24/oereb-kataster-richtig-gemacht-4.html</link>
      <pubDate>Sat, 23 Apr 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/04/24/oereb-kataster-richtig-gemacht-4.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als WMS-Server verwenden wir QGIS-Server. Für die (Nicht-)Anforderungen des ÖREB-Katasters ginge aber wohl jeder halbwegs konforme WMS-Server. Als erstes müssen wir die Layer unserer Daten konfigurieren. Die Datenquelle ist die Datenbank aus &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html&quot;&gt;Teil 2&lt;/a&gt;, die wir im &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html&quot;&gt;Teil 3&lt;/a&gt; mit Daten befüllt haben. Es gibt in der Datenbank die Schemen &lt;code&gt;stage_wms&lt;/code&gt; und &lt;code&gt;live_wms&lt;/code&gt;. Ersteres dient zur Verifikation, der in die Katasterstruktur importieren Daten. Das zweite Schema ist das eigentliche Produktionsschema. Damit die QGIS-Projektdatei aber unabhängig vom Schemennamen aufgebaut werden kann, verwenden wir nicht direkt Datenbankverbindungsparameter (Host, DB-Name, User, Passwort), sondern das sogenannte &lt;a href=&quot;https://www.postgresql.org/docs/current/libpq-pgservice.html&quot;&gt;Connection Service File&lt;/a&gt;. Mit grosser Wahrscheinlichkeit werden mindestens zwei benötigt. Eines für das lokale Arbeiten und eines für den Betrieb, da sich die Verbindungsparameter unterscheiden werden. Das Service-File für das lokale Arbeiten sieht bei mir so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;[oereb]
host=localhost
port=54323
dbname=oereb
user=gretl
password=gretl
sslmode=disable
options=-c search_path=public,live_wms&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;QGIS-Desktop muss man das Service-File bekannt machen, indem man unter &lt;code&gt;Preferences&lt;/code&gt; - &lt;code&gt;System&lt;/code&gt; - &lt;code&gt;Environment&lt;/code&gt; eine &lt;code&gt;PGSERVICEFILE&lt;/code&gt;-Variable mit dem Pfad zur Datei setzt. Die erste Zeile des Service-Files ist der Name der Verbindung. In QGIS-Desktop muss man im Datenbank-Connection-Fenster einzig dieser Name eintippen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p04/qgis_pg_connection.png&quot; alt=&quot;qgis pg connection&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die letzte Zeile (&lt;code&gt;options=&lt;/code&gt;) bestimmt den Suchpfad von Datenbanktabellen. Weil wir wollen, dass in der QGIS-Projektdatei nur die Tabellennamen stehen aber keine Schemennamen, definieren wir einen sogenannten Suchpfad. Dieser bestimmt in welchen Schemen die Tabelle gesucht wird, wenn das QGIS-Projekt geladen wird. Wenn ich mich aber richtig erinnere, musste ich beim Zusammenstöpseln des QGIS-Projektes nachträglich im Texteditor die Schemennamen rauslöschen, weil der &lt;code&gt;search_path&lt;/code&gt; nur beim Lesen des Projektes greift, wenn die Tabelle kein Schemennamen im Projekt definiert hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man  keinen Validierungsschritt verwenden will, ist das alles unnötig und die Schemennamen dürfen im Projektfile stehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach bisschen Fleissarbeit sieht es bei mir so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p04/qgis_layers.png&quot; alt=&quot;qgis layers&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mindestens die vorgebenen Themen hat jeder Kanton. Dazu kommen kantonale ÖREB-Themen. Bei uns z.B. das Thema Einzelschutz.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun geht es um das Verpacken des ÖREB-WMS in ein Docker-Image. Als Basis-Image verwende ich &lt;a href=&quot;https://github.com/sogis-oereb/docker-qgis-server&quot;&gt;unser&lt;/a&gt; &lt;a href=&quot;https://hub.docker.com/repository/docker/sogis/qgis-server-base&quot;&gt;QGIS-Server-3.16-Image&lt;/a&gt;. Wir betreiben QGIS-Server in Kombination mit &lt;em&gt;Apache Webserver&lt;/em&gt; und nicht mit &lt;em&gt;nginx&lt;/em&gt;. Super glücklich sind wir mit dem Image nicht. Ob aber die Kombination mit &lt;em&gt;nginx&lt;/em&gt; besser ist, ist uns auch nicht klar. Es scheint als scheiden sich hier die Geister. Uns stört aber vor allem, dass unser Image als &lt;code&gt;root&lt;/code&gt; laufen muss, eine stattliche Grösse aufweist und dass niemand wirklich weiss in welcher Konfiguration man das Teil unter grosser Last in einem Kubernetes-Cluster laufen lassen soll. Aber andere Baustelle&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf dem Basis-Image aufbauend, müssen wir für den ÖREB-WMS nicht mehr grosse Handstände machen. Es müssen lediglich die QGIS-Projektdatei und das Servicefile reinkopiert werden. Das Servicefile in dieser Form würde man in einer Produktionsumgebung nicht reinbrennen oder dann das Image nicht öffentlich publizieren oder die Credentials mit Umgebungsvariablen injizieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das &lt;a href=&quot;https://github.com/oereb/oereb-wms/blob/main/Dockerfile.qgisserver&quot;&gt;Dockerfile&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;FROM sogis/qgis-server-base:3.16

LABEL maintainer=&quot;Amt fuer Geoinformation Kanton Solothurn &amp;lt;agi@bd.so.ch&amp;gt;&quot;

# copy .qgs
COPY qgis /data

RUN chown -R www-data:www-data /data

#pg_service.conf File
COPY conf/pg_service.conf /etc/postgresql-common/pg_service.conf
ENV PGSERVICEFILE=&quot;/etc/postgresql-common/pg_service.conf&quot;

#sed command to change URL rewrite
RUN sed -i &apos;s/\^\/qgis\//\^\/wms\//g&apos; /etc/apache2/sites-enabled/qgis-server.conf

#tell apache/qgis-server where to find the pg_service.conf file
RUN echo &apos;SetEnv PGSERVICEFILE &quot;/etc/postgresql-common/pg_service.conf&quot;&apos; &amp;gt; /etc/apache2/mods-enabled/env.conf

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s CMD curl http://localhost&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erstellt wird das Image in einer Github Action und wird via &lt;a href=&quot;https://github.com/oereb/oereb-wms/pkgs/container/oereb-wms&quot;&gt;Github Container Registry publiziert&lt;/a&gt;. Wie bei der &lt;a href=&quot;https://github.com/oereb/oereb-db&quot;&gt;ÖREB-Datenbank&lt;/a&gt; wird sowohl ein Image für &lt;code&gt;linux/amd64&lt;/code&gt; wie auch für &lt;code&gt;linux/arm64&lt;/code&gt; erstellt. Weil es jedoch das Ubuntugis-Repo nur für &lt;code&gt;linux/amd64&lt;/code&gt; gibt (?), wird im ARM-Image die QGIS-Version aus dem normalen Ubuntu-Repository (3.10) installiert. Für das lokale Entwickeln soweit kein Problem. Möchte man QGIS 3.16 auf auch ARM-Rechner verwenden, muss man es wohl selber kompilieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusammen mit der ÖREB-Datenbank haben wir nun schon zwei Komponenten des ÖREB-Katasters. In einem weiteren &lt;a href=&quot;https://github.com/oereb/oereb-stack&quot;&gt;Github-Repository&lt;/a&gt; beginne ich mit einer &lt;a href=&quot;https://github.com/oereb/oereb-stack/blob/main/docker-compose.yml&quot;&gt;docker-compose-Datei&lt;/a&gt; mit der man die Komponenten zusammen starten kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wichtig ist, dass der Name des Datenbank-Services im &lt;a href=&quot;https://github.com/oereb/oereb-stack/blob/main/docker-compose.yml&quot;&gt;docker-compose-File&lt;/a&gt; gleich heisst, wie der host-Name im Service-File (&lt;code&gt;db&lt;/code&gt;), das wir in das WMS-Image reingebrannt haben. Sonst kann sich der WMS-Server nicht mit der Datenbank verbinden. Funktioniert alles, liefert der Server ein &lt;a href=&quot;http://localhost:8083/wms/oereb?SERVICE=WMS&amp;amp;REQUEST=GetCapabilities&quot;&gt;GetCapabilities-Dokument&lt;/a&gt; zurück. Schlägt z.B. die Verbindung zur Datenbank fehl, erscheint im Browser die Fehlermeldung &lt;code&gt;Layer(s) not valid&lt;/code&gt;. Daten sind aber noch keine in der Datenbank. Mit den Gretl-Jobs aus &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html&quot;&gt;Teil 3&lt;/a&gt; können wir Daten einfach importieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;export ORG_GRADLE_PROJECT_dbUriOerebV2=&quot;jdbc:postgresql://db/oereb&quot;
export ORG_GRADLE_PROJECT_dbUserOerebV2=&quot;gretl&quot;
export ORG_GRADLE_PROJECT_dbPwdOerebV2=&quot;gretl&quot;
export ORG_GRADLE_PROJECT_geoservicesUrl=&quot;http://localhost/wms/oereb&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network oereb-stack_default --job-directory $PWD motherOfAllTasks&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man beachte den anderen Docker-Netzwerk-Namen und bei den &amp;laquo;magischen&amp;raquo; Umgebungsvariablen für &lt;em&gt;Gretl&lt;/em&gt; die leicht andere Datenbank-Url. Auch hier musste der Hostname der Datenbank angepasst werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein paar Minuten später kann man das Werk in QGIS-Desktop anschauen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p04/qgis_wms.png&quot; alt=&quot;qgis wms&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der WMS ist momentan unter der URL &lt;a href=&quot;http://localhost:8083/wms/oereb&quot;&gt;http://localhost:8083/wms/oereb&lt;/a&gt; erreichbar. Die URL des Darstellungsdienstes ist Bestandteil der Geobasisdaten und steht auch im XML-Auszug. Sie kann verwendet werden, um z.B. die Bilder für das PDF herzustellen. Wenn der Darstellungsdienst nicht von einer anderen Stelle bereitgestellt wird, sondern direkt aus der ÖREB-Datenbank publiziert wird, führt das zum Umstand, dass ich je nach Umgebung (lokal, Test, Integration, Produktion) die URL des Darstellungsdienst anpassen muss. Die URL anpassen, heisst die Daten verändern. So gesehen müsste der URL eher eine Konfiguration des Katastersystems sein und nicht Inhalt der Geobasisdaten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im nächsten Teil geht es um den ÖREB-Webservice (DATA-Extract und statischer Auszug).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht (und einfacher) #3 - ÖREB-Gretljobs</title>
      <link>http://blog.sogeo.services/blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html</link>
      <pubDate>Tue, 19 Apr 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/04/19/oereb-kataster-richtig-gemacht-3.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im dritten Teil schlägt die Stunde der ÖREB-Gretljobs. Gretljobs? Gretl?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als wir uns vor Jahren daran machten unser Cronjob- und Datenintegrationschaos (oder allgemeiner ETL-Prozesse) zu beseitigen, kamen wir auf die Idee ein Build-Tool zu verwenden. Der Grund für diese Entscheidung war, dass ein solcher Prozess (= Job) immer in Teilschritte (= Tasks) runtergebrochen werden kann. Beispiel: Daten werden heruntergeladen (Task 1), Daten werden entzippt (Task 2), Daten werden geprüft (Task 3), Daten werden importiert (Task 4) und Daten werden in eine andere Datenbankstruktur umgebaut (Task 5). Als Build-Tool verwenden wir &lt;a href=&quot;https://gradle.org&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt;. Einerseits gibt es out-of-the-box bereits viele Funktionen und Plugins und andererseits ist es relativ einfach erweiterbar. Erweiterbar muss es sein, weil wir ein paar &amp;laquo;Geo-Tasks&amp;raquo; benötigen (z.B. INTERLIS-Import etc.) oder andere Funktionen, die es weder im Core noch in einem Plugin gibt. Unsere Erweiterungen packten wir ebenfalls in ein &lt;a href=&quot;https://plugins.gradle.org/plugin/ch.so.agi.gretl&quot;&gt;Plugin&lt;/a&gt; und somit war &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;Gretl (Gradle ETL)&lt;/a&gt; geboren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein minimales Beipiel, das eine INTERLIS-Datei prüft, sieht wie folgt aus (der Code muss als &lt;code&gt;build.gradle&lt;/code&gt;-Datei gespeichert werden):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import ch.so.agi.gretl.tasks.*
import ch.so.agi.gretl.api.*

apply plugin: &apos;ch.so.agi.gretl&apos;

buildscript {
    repositories {
        maven { url &quot;http://jars.interlis.ch&quot; }
        maven { url &quot;http://jars.umleditor.org&quot; }
        maven { url &quot;https://repo.osgeo.org/repository/release/&quot; }
        maven { url &quot;https://plugins.gradle.org/m2/&quot; }
        mavenCentral()
    }
    dependencies {
        classpath group: &apos;ch.so.agi&apos;, name: &apos;gretl&apos;,  version: &apos;2.1.+&apos;
    }
}

defaultTasks &apos;validate&apos;

task validate(type: IliValidator){
    dataFiles = [&quot;fubar.xtf&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Verzeichnis, wo die &lt;code&gt;build.gradle&lt;/code&gt;-Datei liegt, kann man den Job mit &lt;code&gt;gradle&lt;/code&gt; aufgerufen werden. Die zu prüfende Datei &lt;code&gt;fubar.xtf&lt;/code&gt; muss natürlich vorhanden sein und &lt;em&gt;Gradle&lt;/em&gt; (und Java) installiert sein. Das Interessante sind die sogenannten Tasks. Von diesen Tasks kann es beliebig viele geben und sie können auch voneinander abhängig (&lt;code&gt;dependsOn&lt;/code&gt;) sein. Einen Überblick über alle unsere Jobs (und Inspiration) erlangt man in unserem &lt;a href=&quot;https://github.com/sogis/gretljobs/&quot;&gt;gretljobs-Repo&lt;/a&gt;. Eine möglichst vollständige Dokumentation unserer selbst programmierten Tasks findet man ebenfalls im &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md&quot;&gt;Repo&lt;/a&gt; oder man kann sich die &lt;a href=&quot;https://pretalx.com/fossgis2019/talk/ESDMQB/&quot;&gt;Gretl-Präsentation&lt;/a&gt; an der FOSSGIS 2019 zu Gemüte führen. Die ganze Sache läuft nun seit circa fünf Jahren und wir könnten zufriedener nicht sein. Zur Orchestrierung sämtliche Jobs verwenden wir übrigens &lt;a href=&quot;https://www.jenkins.io/&quot;&gt;&lt;em&gt;Jenkins&lt;/em&gt;&lt;/a&gt;, was sich ebenfalls sehr bewährt hat: Perfekte Übersicht über sämtliche Jobs und Logfiles immer am gleichen Ort. Viel mehr braucht es nicht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p03/jenkins.png&quot; alt=&quot;gretl jenkins&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Jenkins&lt;/em&gt; wird hier der Einfachheithalber keine Rolle spielen, da ein Gretl-Job eben auch ganz ohne Schnick-Schnack nur in der Konsole ausgeführt werden kann. Wir haben das Gretl-Plugin mit sämtlichen Abhängigkeiten in ein &lt;a href=&quot;https://hub.docker.com/repository/docker/sogis/gretl&quot;&gt;Docker-Image&lt;/a&gt; gepackt. So sind wir sicher, dass wir immer die gleichen Versionen der Abhängigkeiten verwenden und sind zugleich unabhängig von einer Java-Installation. Das Image ist seit kurzem auch auf einem Apple Silicon Rechner lauffähig. Man sieht: eine gewisse Leidensfähigkeit und Durchhaltewillen als Macbook-Anwender der neueren Generation muss man mitbringen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zurück zum eigentlichen Ziel: Dem Import aller benötigten Daten in die ÖREB-Datenbank (aus &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html&quot;&gt;Teil 2&lt;/a&gt;) für den Betrieb des ÖREB-Katasters. Und mit allen Daten sind eben nicht nur Geodaten gemeint, sondern auch Konfiguration im weiteren Sinne, d.h. Gesetzliche Grundlagen, Themen, Logos, Texte. Sowohl für Bund und Kanton (Gemeinden). Ein wichtige Konfiguration im engeren Sinne sind z.B. die freigeschalteten Gemeinden (wo ist der ÖREB-Kataster verfügbar?). Dafür gibt es im &lt;a href=&quot;https://models.geo.admin.ch/V_D/OeREB/OeREBKRMkvs_V2_0.ili&quot;&gt;Modell &lt;code&gt;OeREBKRMkvs_V2_0&lt;/code&gt;&lt;/a&gt; die Klasse &lt;code&gt;GemeindeMitOeREBK&lt;/code&gt;. In dieser Klasse kann man feingranular verwalten, welche Themen in welcher Gemeinde vorhanden sind. Alle diese Daten(sätze) sind bei uns in einer INTERLIS-Transferdatei vorhanden resp. für die Bundesthemen stellt sie bereits Swisstopo &lt;a href=&quot;https://models.geo.admin.ch/V_D/OeREB/&quot;&gt;zur Verfügung&lt;/a&gt;. Es gibt absolut keine Notwendigkeit diese Konfigurationen mit etwas selber Gestricktem zu definieren und zu verwalten. Mit der ersten Version des ÖREB-Katasters konnte man aufgrund des fehlenden Konfigurations-Teilmodelles noch nicht vollständig und konsequent mit dem Rahmenmodell (resp. den Daten) einen XML-Auszug und das PDF herstellen. Damals entwickelten wird unser sogenanntes &amp;laquo;Annex-Datenmodell&amp;raquo;, der Vorläufer des heutigen Konfigurationsmodells, damit wir trotzdem in der INTERLIS-Bubble bleiben konnten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stehen die Datensätze online zur Verfügung, müssen die Gretl-Jobs diese nur noch herunterladen, validieren und importieren (siehe &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/17/oereb-kataster-richtig-gemacht-1.html&quot;&gt;Teil 1&lt;/a&gt; &amp;laquo;sauberer Schnitt&amp;raquo; und &amp;laquo;Zuständigkeiten&amp;raquo;). Matchentscheidend ist die Reihenfolge wie die Gretl-Jobs ausgeführt werden: Die Daten werden in eine Datenbank importiert. Die Beziehungen zwischen Klassen werden mittels Fremdschlüsseln abgebildet. D.h. man kann keine Daten importieren, wenn die Daten auf ein Objekt zeigen, dass noch nicht in der Datenbank vorhanden ist. In unserem Fall müssen zwingend die zuständigen Stellen vorhanden sein, wenn man Geobasisdaten importieren will und die zuständigen Stellen nicht mit den Daten mitgeliefert werden. Man kann (mit einer ili2pg-Option) das Erstellen der Fremdschlüsseln zwar ausschalten, was aber meines Erachtens nicht sinnvoll ist. Diese gibt es ja aus guten Gründen und wenn man immer und immer wieder Daten in den DB-Tabellen austauscht, passiert garantiert irgendwann mal ein Fehler und die Daten passen nicht mehr zusammen (Stichwort referentielle Integrität). Herausfordernd wird es, wenn &amp;ndash; bleiben wir beim Beispiel der zuständigen Stellen &amp;ndash; der Name oder die Adresse der zuständigen Stelle ändert. Dieser Record kann in der Datenbank nicht einfach so ausgetauscht werden, weil ein anderer Record einen Fremdschlüssel auf die zuständige Stelle hat. Die meisten solcher Fälle können mit ili2db-Magie gelöst werden indem man die &lt;code&gt;--update&lt;/code&gt;-Option verwendet. Das Objekt in der Datenbank wird nicht gelöscht, sondern &amp;ndash; nomen est omen &amp;ndash; upgedatet. Das funktioniert natürlich nicht, wenn die zuständige Stelle gelöscht werden soll und es immer noch Objekte gibt, die auf diese zuständige Stelle verweisen. In diesem Fall bleibt einem nichts anderes übrig, als zuerst diese Objekte zu löschen und anschliessend auch die zuständige Stelle. Aus diesem Grund ist es sehr wichtig, dass &lt;em&gt;Gradle&lt;/em&gt; (das Build-Tool, die Basis von Gretl) verschiedene Hilfsmittel anbietet, welche die Ausführungs-Reihenfolge der Tasks garantiert (&lt;code&gt;dependsOn&lt;/code&gt;, &lt;code&gt;mustRunAfter&lt;/code&gt;, &lt;code&gt;finalizedBy&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der Organisation der Gretl-Jobs sind der Fantasie keine Grenzen gesetzt. Es gibt viele richtige Varianten. Im vorliegenden Fall habe ich mich für einen Haupt-Gretl-Job entschieden, der aus verschiedenen Sub-Jobs besteht. So kann man mit einem Befehl alles Notwendige importieren aber trotzdem noch einzelne Schritte selbständig ausführen, z.B. das Ersetzen sämtlicher Bundesgeobasisdaten. Ein Blick hinter die Kulissen erlaubt das &lt;a href=&quot;https://github.com/oereb/oereb-gretljobs&quot;&gt;Gretljobs-Repo&lt;/a&gt;. Es gibt eine &lt;code&gt;build.gradle&lt;/code&gt;-Datei im Root-Verzeichnis, welche den Haupt-Gretl-Job definiert. Zusätzlich gibt es eine &lt;code&gt;settings.gradle&lt;/code&gt;-Datei, die dem Haupt-Gretl-Job die Sub-Jobs bekannt macht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Meine gewünschte Import-Reihenfolge soll folgende sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Grundlagedaten, da diese von nichts abhängig sind: Amtliche Vermessung und PLZ/Ortschaften.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bundesgesetze&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bundeskonfigurationen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kantonale Konfigurationen (beinhalten auch kantonale Gesetze)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bundesdaten (alle)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kantonsdaten (Auswahl): kommunale und kantonale Nutzungsplanung (einige Gemeinden), KbS, Grundwasserschutz&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erreicht wird das durch eine Kombination von &lt;code&gt;mustRunAfter&lt;/code&gt;-Optionen in Tasks der Sub-Jobs und einem einzelnen Task im Haup-Gretl-Job, welcher von allen Import-Tasks der einzelnen Sub-Jobs abhängig ist. Die Reihenfolge wie die Tasks resp. Jobs ausgeführt werden, entspricht nicht zwingend der Reihenfolge wie sie in der &lt;code&gt;dependsOn&lt;/code&gt;-Option definiert sind. Deshalb sind die &lt;code&gt;mustRunAfter&lt;/code&gt;-Optionen (siehe &lt;a href=&quot;https://github.com/oereb/oereb-gretljobs/blob/main/oereb_plzo/build.gradle#L63&quot;&gt;Beispiel&lt;/a&gt;) notwendig. Mit dieser Definition der Tasks und Jobs und Abhängigkeiten zwischeneinander kann man mit einem Befehl alle Daten importieren aber zu einem späteren Zeitpunkt z.B. nur noch die Geobasisdaten in der Datenbank austauschen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schaut man sich die verschiedenen &lt;code&gt;build.gradle&lt;/code&gt;-Dateien im Repo an, erkennt man, dass sie alle sehr ähnlich sind. Ich denke, man sieht sehr gut, dass die zu erledigende Arbeit eines Sub-Jobs in einzelne kleine Schritte (= Tasks) aufgesplittet wurde, was der Transparenz sehr zuträglich ist (Fehlersuche etc.). Die vorliegende Aufteilung in die Sub-Jobs ist vielleicht nicht ultra-logisch, was darauf zurück zu führen ist, dass ich vieles von unseren produktiven Gretl-Jobs für den ÖREB-Kataster übernommen habe, diese aber für Demo-Zwecke nicht zwingend sinnvoll gruppiert sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wer es nun durchspielen will, hier die notwendigen wenigen Schritte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Datenbank aus &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html&quot;&gt;Teil 2&lt;/a&gt; mittels &lt;code&gt;docker-compose up&lt;/code&gt; (damit ein Netzwerk angelegt wird) starten. Die &lt;a href=&quot;https://github.com/oereb/oereb-gretljobs/blob/main/docker-compose.yml&quot;&gt;docker-compose-Datei&lt;/a&gt; ist nichts anderes als der &lt;code&gt;docker run&lt;/code&gt;-Befehl. Anschliessend setzen wir für &lt;em&gt;Gretl&lt;/em&gt; einige Umgebungsvariablen, damit man z.B. Datenbank-Credentials nicht in der &lt;code&gt;build.gradle&lt;/code&gt;-Datei hardcodieren muss:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;export ORG_GRADLE_PROJECT_dbUriOerebV2=&quot;jdbc:postgresql://oereb-db/oereb&quot;
export ORG_GRADLE_PROJECT_dbUserOerebV2=&quot;gretl&quot;
export ORG_GRADLE_PROJECT_dbPwdOerebV2=&quot;gretl&quot;
export ORG_GRADLE_PROJECT_geoservicesUrl=&quot;http://localhost/wms&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Umgebungsvariablen, die mit &lt;code&gt;ORG_GRADLE_PROJECT_&lt;/code&gt; starten, müssen nicht manuell im Build-Skript ausgelesen werden, sondern stehen automatisch zur Verfügung. Für das Auführen der Gretl-Jobs verwenden wir unser &lt;a href=&quot;https://hub.docker.com/repository/docker/sogis/gretl&quot;&gt;Docker-Image&lt;/a&gt;, welches wir mit einem &lt;a href=&quot;https://github.com/oereb/oereb-gretljobs/blob/main/start-gretl.sh&quot;&gt;Shell-Skript&lt;/a&gt; bedienen. Das Shell-Skript ist nicht viel mehr als ein &lt;code&gt;docker run&lt;/code&gt;-Befehl. Früher hatte das Skript noch weitere Funktionen, die aber nicht mehr benötigt werden. Zum Auflisten sämtlicher Task aller (Sub-)Jobs muss folgender Befehl verwendet werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network oereb-gretljobs_default --job-directory $PWD tasks --all&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man &amp;laquo;Gretl pur&amp;raquo; verwenden würde, also ohne Docker, entspräche das dem Befehl: &lt;code&gt;gradle tasks --all&lt;/code&gt;. Mit dem Shell-Skript können wir das Docker-Image, das Docker-Netzwerk und das Job-Directory auswählen. Damit der Gretl-Docker-Container mit dem Datenbank-Container kommunizieren kann, müssen sie im gleichen Netzwerk sein. Falls das Datenbank-Container-Netzwerk anders heisst (&lt;code&gt;docker network ls&lt;/code&gt;), muss selbstverständlich dieser Namen verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Importieren sämtlicher Daten reicht der Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network oereb-gretljobs_default --job-directory $PWD motherOfAllTasks&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Funktioniert die Kommunikation zwischen den Images, sollte man in der Konsole sehen, welche Tasks &lt;em&gt;Gretl&lt;/em&gt; gerade abarbeitet. Bei mir dauert der Import (Download, i.d.R. Validierung, Import) meiner Datenauswahl in das &lt;code&gt;live&lt;/code&gt;-Schema auf einem Macbook Air (und somit sehr schlechter I/O-Performance mit Docker) circa 6 Minuten. Am längsten dauerte der Import der PLZ/Ortschaften und die amtliche Vermessung. Bei den PLZ/Ortschaften sind halt viele Daten dabei, die mich geografisch nicht interessieren. Hat alles ohne Fehler funktioniert, quittiert &lt;em&gt;Gradle&lt;/em&gt; die Arbeit mit &lt;code&gt;BUILD SUCCESSFUL&lt;/code&gt;. Ansonsten sollte eine mehr oder weniger sinnvolle Fehlermeldung in der Konsole erscheinen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Beweis laden wir einige Tabellen in QGIS:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p03/qgis.png&quot; alt=&quot;qgis daten&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man zukünftig nur die Bundesdaten ersetzen, reicht folgender Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./start-gretl.sh --docker-image sogis/gretl:latest --docker-network oereb-gretljobs_default --job-directory $PWD oereb_bundesdaten:importData&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und 35 Sekunden später sind alle Bundesdaten ersetzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wer jetzt Interesse an &lt;em&gt;Gretl&lt;/em&gt;, seinen Fähigkeiten und Einsatzmöglichkeiten in einer GDI hat, darf sich gerne melden. Im nächsten Teil geht es dann ruhiger zu und her: Es entsteht der ÖREB-WMS.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #2 - ÖREB-Datenbank</title>
      <link>http://blog.sogeo.services/blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html</link>
      <pubDate>Mon, 18 Apr 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/04/18/oereb-kataster-richtig-gemacht-2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie im &lt;a href=&quot;http://blog.sogeo.services/blog/2022/04/17/oereb-kataster-richtig-gemacht-1.html&quot;&gt;ersten Teil&lt;/a&gt; der &amp;laquo;ÖREB-Kataster richtig gemacht&amp;raquo;-Serie angekündigt, geht es im zweiten Teil um die ÖREB-Datenbank.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Grundidee ist, dass sämtliche Daten, die für den Betrieb des Katasters notwendig sind, in einem Schema vorliegen. Wobei das weniger wichtig ist (und schlussendlich aus zwei Gründen nicht ganz eingehalten wird), als dass sämtliche Geobasisdaten inkl. der Rechtsvorschriften in den gleichen Tabellen vorhanden sind. In &amp;laquo;ili2pg-Sprech&amp;raquo; bedeutet das, dass mit &lt;em&gt;ili2pg&lt;/em&gt; das Transferstruktur-Teilmodell des ÖREB-Rahmenmodells in der Datenbank abgebildet wird und anschliessend können sämtliche ÖREB-Themen in dieses eine Schema importiert werden. Ein ÖREB-Webservice muss sich so nur um eine einzige Datenquelle kümmern und kann mit einer einzigen SQL-Abfrage sämtliche betroffenen Eigentumsbeschränkungen sämtlicher ÖREB-Themen eruieren (&amp;laquo;Cookie-Cutter&amp;raquo;). In das gleiche Schema importieren wir ebenfalls die Konfigurationen (Logos, Texte, Themen, gesetzliche Grundlagen, &amp;#8230;&amp;#8203;), die amtliche Vermessung und die PLZ/Ortschaften.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus betrieblichen Gründen erstellen wir bei uns zwei identische solche Schemen: &lt;code&gt;live&lt;/code&gt; und &lt;code&gt;stage&lt;/code&gt;. Das &lt;code&gt;live&lt;/code&gt;-Schema dient als Produktions-Umgebung und das &lt;code&gt;stage&lt;/code&gt;-Schema dient den Fachstellen für die Validierung der importierten Daten in den ÖREB-Kataster vor dem definitiven Freischalten. Ein ÖREB-Webservice muss einzig mit einem Schemanamen parametrisiert werden, um eine Validierungsumgebung zu erhalten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Erstellen des Schemas mit &lt;em&gt;ili2pg&lt;/em&gt; ist leider nicht ganz so super-trivial wie man sich das wünscht. Der Hauptgrund ist, dass man im gleichen Schema INTERLIS 1 und INTERLIS 2 Modelle abbilden will. Das funktioniert mit einem einzelnen ili2pg-Befehl nicht. Wenn man aber zwei Befehle ausführen muss und man mit der &lt;code&gt;--createScript&lt;/code&gt;-Option das SQL in eine Datei schreiben will, muss man das erzeugte SQL noch leicht anpassen, weil sonst z.B. versucht wird Sequenzen doppelt zu erstellen. Ein SQL-Skript wird benötigt, weil wir so beim erstmaligen Hochfahren der Datenbank (als Dockercontainer) das Schema und die Tabellen anlegen wollen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Langer Rede, kurzer Sinn: Man muss irgendwie mit &lt;em&gt;ili2pg&lt;/em&gt; die SQL-Skripte erstellen, diese noch leicht anpassen und dann zusammensetzen. Soweit keine Hexerei. Man kann dazu z.B. ein Shellskript schreiben oder, weil man sowieso Java benötigt, mit &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;&lt;em&gt;jbang&lt;/em&gt;&lt;/a&gt; ein Java-Skript (also sowas wie ein Skript in Java geschrieben) schreiben. Das &lt;a href=&quot;https://github.com/oereb/oereb-db/blob/main/create_schema_sql.java&quot;&gt;Skript&lt;/a&gt; befindet sich im &lt;a href=&quot;https://github.com/oereb/oereb-db&quot;&gt;Github-Repository&lt;/a&gt;. Technisch ist es nicht sonderlich interessant. Es wird mit der ili2db-Bibliothek direkt gearbeitet und anschliessend mit Regex-Magie und Suchen und Ersetzen die notwendigen Anpassungen gemacht. Schaut man sich das Skript ein wenig genauer an, fällt auf, dass es neben den beiden erwähnten Schemen auch noch die beiden Schemen &lt;code&gt;live_wms&lt;/code&gt; und &lt;code&gt;stage_wms&lt;/code&gt; erstellt. Dabei handelt es sich um Schemen mit Tabellen, die dem ÖREB-Darstellungsdienst (WMS) als Quelle dienen. Die Originaltabellen des Rahmenmodells sind normalisiert, was für die Verwendung in WMS-Servern nicht hilfreich ist. Entweder erstellt man Datenbankviews oder -tabellen und denormalisiert. Wir haben uns für Tabellen entschieden, weil wir die Dokumente als JSON-codierten Text in einer Spalte speichern und so via WMS-GetFeatureInfo verfügbar machen wollen. Das ist aus Sicht ÖREB-Kataster nicht zwingend notwendig. Uns dient die Spalte aber im Validierungsprozess. Da somit der Datenumbau vom Rahmenmodell in unsere denormalisierte Struktur aufwändiger ist, reicht die Performance mit Views nicht mehr. Damit das Anlegen der WMS-Tabellen analog funktioniert, haben wir ein &lt;a href=&quot;https://geo.so.ch/models/AGI/SO_AGI_OeREB_WMS_20220222.ili&quot;&gt;INTERLIS-Datenmodell&lt;/a&gt; erstellt. Das Modell muss in separaten Schemen abgebildet werden, weil andere ili2db-Optionen verwendet werden sollen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Skript kann mit folgenden Befehl ausgeführt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jbang create_schema_sql.java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist &lt;em&gt;jbang&lt;/em&gt; nicht installiert, kann man das Skript (unter Linux und macOS) wie folgt ausführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;curl -Ls https://sh.jbang.dev | bash -s - create_schema_sql.java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat ist eine &lt;code&gt;setup.sql&lt;/code&gt;-Datei. Mit dieser lassen sich nun sämtliche Schemen und sämtlichen Tabellen in einer &lt;em&gt;PostgreSQL&lt;/em&gt;/&lt;em&gt;PostGIS&lt;/em&gt;-Datenbank erstellen. Die Datei interessiert mich direkt aber nicht, da ich sie selber nicht benötige, sondern der Docker-Container beim erstmaligen Hochfahren die SQL-Befehle ausführt. Für das Docker-Image verwende ich unser PostgreSQL/PostGIS-Basisimage (&lt;code&gt;sogis/postgis:14-3.2&lt;/code&gt;). Bis es offizielle PostGIS-Images gibt, welche auf ARM-Prozessoren (Apple Silicon) laufen, muss ich mich selber um ein solches Image kümmern, was wiederum nicht sonderlich aufwändig ist, weil das offizielle PostgreSQL-Image glückerlicherweise für ARM-Prozesseren verfügbar ist. Richtig viel passiert im &lt;a href=&quot;https://github.com/oereb/oereb-db/blob/main/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; nicht mehr. Es werden einzig zwei Dateien in den Ordner &lt;code&gt;/docker-entrypoint-initdb.d&lt;/code&gt; kopiert: Die soeben erstellt Datei &lt;code&gt;setup.sql&lt;/code&gt; dient dazu die Tabellen und Scheman in der Datenbank zu erstellen. Die Datei &lt;code&gt;initdb-user.sh&lt;/code&gt; wird benötigt um einige Benutzer in der Datenbank zu erstellen und diesen die benötigten Rechte zuzuweisen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Dockerimage wird mit folgendem Befehl erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;docker build -t ghcr.io/oereb/oereb-db:latest .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Starten des Containers müssen Umgebungsvariablen mitgeliefert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;docker run --rm --name oerebdb -p 54323:5432 -e POSTGRES_PASSWORD=mysecretpassword -e POSTGRES_DB=oereb -e POSTGRES_HOST_AUTH_METHOD=md5 -e PG_READ_PWD=dmluser -e PG_WRITE_PWD=ddluser -e PG_GRETL_PWD=gretl ghcr.io/oereb/oereb-db:latest&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt;: Passwort des &lt;code&gt;postgres&lt;/code&gt;-Benutzers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;POSTGRES_DB&lt;/code&gt;: Name der Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;POSTGRES_HOST_AUTH_METHOD&lt;/code&gt;: Siehe &lt;a href=&quot;https://github.com/claeis/ili2db/issues/448&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ili2db/issues/448&lt;/a&gt;. Oder mit den Worten von &lt;a href=&quot;https://www.youtube.com/watch?v=Ze5Ul7Z9kE8&quot;&gt;Tom Petty: &amp;laquo;Time to move on&amp;raquo;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PG_READ_PWD&lt;/code&gt;: Passwort eines read-only Benutzers (&lt;code&gt;dmluser/dmluser&lt;/code&gt;). Siehe auch &lt;code&gt;initdb-user.sh&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PG_WRITE_PWD&lt;/code&gt;: Passwort eines read/write Benutzers (&lt;code&gt;ddluser/ddluser&lt;/code&gt;). Siehe auch &lt;code&gt;initdb-user.sh&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PG_GRETL_PWD&lt;/code&gt;: Passwort eines read/write Benutzers (&lt;code&gt;gretl/gretl&lt;/code&gt;). Siehe auch &lt;code&gt;initdb-user.sh&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann natürlich in &lt;code&gt;initdb_user.sh&lt;/code&gt; komplett andere Benutzer und ein eigenes Dockerimage erstellen. Diese drei Benutzer sind bei uns intern häufig die Regel für Entwicklungsumgebungen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Datenbank &lt;code&gt;oereb&lt;/code&gt; mit unseren vier erstellten Schemen ist nach kurzer Zeit verfügbar (aber leer):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p02/dbeaver_oerebdb_empty.png&quot; alt=&quot;dbeaver oerebdb empty&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wird der Docker-Container gestoppt, verliert man sämtliche Daten. In unserem Fall ist das noch nicht so tragisch, da bis jetzt noch keine Daten importiert wurden. Jedoch gehen auch die vier Schemen verloren. D.h. bei jedem Container-Neustart, werden diese Schemen wieder erzeugt. Will man dieses Verhalten verhindern, muss man das Datenbankverzeichnis auf dem lokalen Dateisystem persistieren. Das lokale Verzeichnis muss mit korrekten Permissions erzeugt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;mkdir -m 0777 ~/pgdata
docker run --rm --name oerebdb -p 54323:5432 -v ~/pgdata:/var/lib/postgresql/data:delegated .... (siehe oben)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Herstellen und Testen des Images übernimmt eine &lt;a href=&quot;https://github.com/oereb/oereb-db/blob/main/.github/workflows/main.yml&quot;&gt;Github&lt;/a&gt; &lt;a href=&quot;https://github.com/oereb/oereb-db/actions&quot;&gt;Action&lt;/a&gt;. Die Images gibt es für &lt;code&gt;amd64&lt;/code&gt;- (Intel, AMD) wie auch für &lt;code&gt;arm64&lt;/code&gt;-Systeme (Apple Silicon) in der &lt;a href=&quot;https://github.com/oereb/oereb-db/pkgs/container/oereb-db&quot;&gt;Github Container Registry&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil eine leere Datenbank nichts bringt, zeige ich im dritten Teil, wie man alle notwendigen Daten einfach importiert.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster richtig gemacht #1 - Übersicht</title>
      <link>http://blog.sogeo.services/blog/2022/04/17/oereb-kataster-richtig-gemacht-1.html</link>
      <pubDate>Sun, 17 Apr 2022 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2022/04/17/oereb-kataster-richtig-gemacht-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe bereits während der Realisierung der ersten Version des ÖREB-Katasters über unsere &lt;a href=&quot;http://blog.sogeo.services/blog/2018/10/21/oereb-kataster-1-as-a-gradle-script.html&quot;&gt;Architektur&lt;/a&gt; &lt;a href=&quot;http://blog.sogeo.services/blog/2018/12/31/xslt-xslfo-2-pdf4oereb.html&quot;&gt;geschrieben&lt;/a&gt;. Diese Lösung hat sich sehr bewährt. Wir hatten damals zwei bestehende Softwarelösungen angeschaut und uns trotzdem für etwas Eigenes entschieden. Eine der  bestehenden Lösung kann meines Erachtens zu viel, die andere habe ich irgendwie nie ganz verstanden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Rahmen unserer Realisierung der zweiten Version des ÖREB-Katasters möchte ich detailliertere Einblicke in unser System geben und auch ein paar Skripte und Docker-Images bereitstellen, damit man das ausprobieren kann. Und ich gebe die Hoffnung nicht auf, dass auch Swisstopo bei passender Gelegenheit die ganze Sache durchspielt und sieht, was es zu tun gibt und damit ein vertiefteres technisches Verständnis für die Materie bekommt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/publication/instruction.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/Rahmenmodell-de.pdf.html&quot;&gt;Erläuterungen&lt;/a&gt; zum Rahmenmodell des ÖREB-Katasters zeigen in Kapitel 4 &amp;laquo;Organisatorischer Rahmen&amp;raquo; folgende Abbildung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p01/rahmenmodell_organisation.png&quot; alt=&quot;rahmenmodell_organisation&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Mitte steht die Katasterverantwortliche Stelle (KVS), welche in irgendeiner Ausprägung den ÖREB-Kataster betreibt. Die Pfeile deuten die Datenflüsse resp. die Schnittstellen an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Gemeinden, Staatskanzlei und die Bundeskanzlei liefern die Gesetzlichen Grundlagen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die zuständigen Stellen liefern die Geobasisdaten inkl. der Rechtsvorschriften.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Aus der amtlichen Vermessung kommen die Liegenschaften und die PLZ/Ortschaften.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus dieser Organisation ergeben sich die Anforderungen an das &lt;a href=&quot;https://models.geo.admin.ch/V_D/OeREB/&quot;&gt;ÖREB-Rahmenmodell&lt;/a&gt;. Es gibt somit INTERLIS-Modelle, welche den ganzen Kataster und die Organisation dazu beschreibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Legen wir den Augenmerk vom eher Organisatorischen auf die technischen Rahmenbedingungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Die Bundesthemen inkl. der gesetzlichen Grundlagen, der Logos und der Texte des Bundes liegen als INTERLIS-Transferdatei im Rahmenmodell vor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ein kantonales Thema wird in einer Fachanwendung bewirtschaftet, welche die Daten im INTERLIS-Rahmenmodell bereitstellt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Daten der amtlichen Vermessung liegen als INTERLIS-Transferdatei vor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Seit Rahmenmodell V2.0 gibt es ein &lt;a href=&quot;https://models.geo.admin.ch/V_D/OeREB/OeREBKRMkvs_V2_0.ili&quot;&gt;Konfigurations-Teilmodell&lt;/a&gt;, welches z.B. die Logos und die statischen Texte beinhaltet oder die Verfügbarkeit der Gemeinden steuern kann.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es gibt ein elaboriertes Toolset für das Arbeiten mit INTERLIS-Datenmodellen und -Transferfiles: &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für uns war bei der Lösungsfindung wichtig, dass sowohl die Realisierung als auch der Betrieb des ÖREB-Katasters möglichst einfach, transparent und effizient verläuft. Zusammen mit den vorhandenen Rahmenbedingungen ergab sich (vereinfacht) folgende Architektur:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk_richtig_gemacht_p01/oerebkataster_overview.png&quot; alt=&quot;oerebkataster_overview&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erläuterung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Es gibt eine ÖREB-Datenbank mit einem Schema, welches sämtliche Daten (Geobasisdaten, amtliche Vermessung, Konfiguration, &amp;#8230;&amp;#8203;) beinhaltet. Der Import resp. das Ersetzen der Daten wird mit &lt;em&gt;ili2pg&lt;/em&gt; durchgeführt. Die Daten werden vor dem Import mit &lt;em&gt;ilivalidator&lt;/em&gt; geprüft.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es braucht einen Webdienst, der den DATA-Extract (aka XML) und den statischen Auszug (das PDF) herstellt. Dieser Dienst muss bloss mit einer Datenbank (der ÖREB-Datenbank) im Sinne einer &amp;laquo;Cookie-Cutter&amp;raquo;-Abfrage kommunizieren. Das Resultat dieser Abfrage muss nach XML umformatiert werden und das XML muss zu einem PDF gemacht werden. Mehr muss diese Anwendung nicht können.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sämtliche Geobasisdaten müssen im ÖREB-Rahmenmodell vorliegen, damit sie mit den vorhandenen Werkzeugen in die ÖREB-Datenbank importiert und bei einer Nachführung ersetzt werden können. Konsequenz: Einige kantonale Themen müssen vom kantonalen Erfassungsmodell in das ÖREB-Rahmenmodell umgebaut werden. Auch für diesen Schritt können wir auf bewährte Methoden und Werkzeuge zurückgreifen (Stichwort SQL und &lt;em&gt;ili2pg&lt;/em&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Konfigurationen (Logos, Themen, Texte, &amp;#8230;&amp;#8203;) und gesetzliche Grundlagen des Bundes und des Kantons müssen ebenfalls im dafür vorgesehenen Teilmodell vorliegen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In der Übersicht fehlt bewusst der dynamische Auszug.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee war/ist alles was mit Daten und Datenimport/-export zu tun hat mit INTERLIS und den bekannten INTERLIS-Werkzeugen zu machen, also die Reduzierung auf eine einzelne Schnittstelle. Auch weil hier bereits alles vorhanden ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese Architektur liefert uns die Basis für eine grösstmögliche Transparenz (z.B. beim Protokollieren von neuen Publikationen in den Kataster) und eine sehr gute Qualitätskontrolle inkl. sauberer Schnitte zwecks Zuweisung der Verantwortlichkeiten. So ist das System auch praktisch unabhängig vom Ort, wo es installiert ist, da es keine Laufzeitabhängigkeiten zu bestehenden GDI-Komponenten hat (z.B. Erfassungsdatenbank).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im zweiten Teil gehe ich vertieft auf die Herstellung und Organisation der ÖREB-Datenbank ein und stelle ein Docker-Image inkl. Import-Skripte zur Verfügung.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #27 - ilivalidator/ili2db JVM-Benchmarking</title>
      <link>http://blog.sogeo.services/blog/2021/11/28/interlis-leicht-gemacht-number-27.html</link>
      <pubDate>Sun, 28 Nov 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/11/28/interlis-leicht-gemacht-number-27.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java und die JVM entwickeln sich. Momentan sind wir bei Version 17 angelangt. Zeit zu schauen, ob sich die Entwicklung auch auf die Geschwindigkeit von &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt; niederschlägt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Testumgebung&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil ich neben unterschiedlichen Versionen der &amp;laquo;normalen&amp;raquo; JVM auch die &lt;a href=&quot;https://www.graalvm.org/java/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; testen will, kann ich nicht die Benchmarks nicht direkt auf macOS mit einem Apple Silicon Prozessor ausführen, sondern ich verwende &lt;a href=&quot;https://multipass.run/&quot;&gt;&lt;em&gt;Multipass&lt;/em&gt;&lt;/a&gt;, um die Benchmarks in einer Ubuntu-ARM-VM laufen zu lassen. Das Gute an &lt;em&gt;Multipass&lt;/em&gt; ist, dass es einfach ist und im Gegensatz zu z.B. VirtualBox auch auf einem Apple Silicon Rechner läuft. Zudem ist für den ili2db-Benchmark eine PostgreSQL-Datenbank notwendig. Weil unter macOS der I/O mit Docker sehr schlecht ist und damit garantiert der limitierende Faktor des Benchmarks sein würde, kommt mir das Ausweichen auf Linux gerade recht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Benchmarks selber sind sehr einfach gehalten. Es sind sowas wie &amp;laquo;Java-Skripte&amp;raquo;. Nämlich eine Java-Klasse, die mit &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;&lt;em&gt;jbang&lt;/em&gt;&lt;/a&gt; ausgeführt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den Code gibt es hier: &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-java-perf-test&quot; class=&quot;bare&quot;&gt;https://github.com/edigonzales/ilivalidator-java-perf-test&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Benchmark&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu 20.04&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4 CPUS / 8 GB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JVM mit -Xmx2048m&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ilivalidator 1.11.11&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ili2pg 4.6.1&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wurde die amtliche Vermessung (ITF) sämtlicher 107 Solothurner Gemeinden, alle vorhandenen Nutzungsplanungsdaten (53 Gemeinden, XTF) und das MOpublic des gesamten Kantons als einzelne XTF-Datei geprüft. Für das Benchmarking von &lt;em&gt;ili2pg&lt;/em&gt; wurde nur die amtliche Vermessung sämtlicher Gemeinden und das MOpublic verwendet. Die 53 Gemeinden der Nutzungsplanung waren mengenmässig schlichtweg zu wenig representativ, d.h. der Import (jeweils ohne Validierung) ging zu schnell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Insbesondere wurden mit Java 17 jeweils zwei unterschiedliche Garbage Collectors getestet. Der seit Java 9 standardmässig verwendete &lt;em&gt;Garbage First Garbage Collector (G1GC)&lt;/em&gt; und der &lt;em&gt;Throughput Collector (ParallelGC)&lt;/em&gt;. Letzterer war in Java 8 der Standard-GC und noch immer verfügbar in neueren Java-Versionen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es wurden jeweils drei Durchläufe gemacht und die Zeit gemittelt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Resultate&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;em&gt;ilivalidator&lt;/em&gt; ergeben sich folgende Resultate:&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 1. Amtliche Vermessung (107 Gemeinden) - INTERLIS 1&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Java Version&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Avg. Time (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 8 (temurin)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;10:21&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;10:06&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;9:39&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;11:25&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;11:07&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 2. Nutzungsplanung Kanton SO (53 files) - INTERLIS 2&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Java Version&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Avg. Time (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 8 (temurin)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;8:02&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:44&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:12&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:39&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:08&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 3. MOpublic Kanton Solothurn (1 file, 2.4 GB) - INTERLIS 2&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Java Version&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Avg. Time (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 8 (temurin)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:47&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:59&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:22&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;8:03&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;7:21&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;em&gt;ili2pg&lt;/em&gt; ergeben sich folgende Resultate:&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 4. Amtliche Vermessung (107 Gemeinden) - INTERLIS 1&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Java Version&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Avg. Time (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 8 (temurin)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;25:45&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;25:12&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;24:53&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;25:37&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;25:00&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 5. MOpublic Kanton Solothurn (1 file, 2.4 GB) - INTERLIS 2&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Java Version&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Avg. Time (mins:secs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 8 (temurin)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;12:26&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;12:36&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (temurin + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;11:48&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + G1GC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;12:37&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java 17 (graalvm + ParallelGC)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;11:47&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java 17 mit ParallelGC ist die schnellste Variante.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GraalVM bringt für diesen Anwendungsfalls nichts resp. ist eventuell sogar kontraproduktiv.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ili2db-Benchmarks sind nicht sehr aussagekräftig, da wohl sehr viel vom Lesen/Schreiben von/in die Datenbank abhängig ist.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant ist ein Vergleich mit meinem 2016er-MacBookPro. Die Validierung der amtlichen Vermessung aller 107 Gemeinden dauerte circa 25 Minuten. Also 2.5 Mal so lange wie mit dem MacBook Air mit Apple Silicon, das lüfterlos knapp handwarm wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Praxis fast noch interessanter ist der Vergleich mit &lt;a href=&quot;https://geodienste.ch&quot;&gt;geodienste.ch&lt;/a&gt;. Dort dauert die Validierung der amtlichen Vermessung circa eine Stunde. Ich muss noch abklären, ob das gleiche gemacht wird. Aber falls ja, müssten sie sich besser ein paar Mac minis ins Rechenzentrum schieben&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #26 - Runtime Parameter</title>
      <link>http://blog.sogeo.services/blog/2021/11/01/interlis-leicht-gemacht-number-26.html</link>
      <pubDate>Mon, 1 Nov 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/11/01/interlis-leicht-gemacht-number-26.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wer kennt sie nicht? Die INTERLIS-Laufzeitparameter (siehe &lt;a href=&quot;https://www.interlis.ch/download/interlis2/ili2-refman_2006-04-13_d.pdf&quot;&gt;Referenzhandbuch Kapitel 2.11&lt;/a&gt;). Also zum Beispiel ich. Jedenfalls waren sie mir lange nicht bekannt. Sie sind jedoch die Lösung für eine bekannte Herausforderung: Die Nachführungsgeometer schicken die Geschäfte via AVGBS ans Grundbuch. Der Name der Transferdatei muss dabei gewisse Konventionen erfüllen. Ganz einfach formuliert, muss der Name der Datei einem Attributwert innerhalb der Datei entsprechen. In Tat und Wahrheit ist es leicht komplexer aber es geht ums grundlegende Prinzip. Wie kann &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; nun prüfen, ob der Namen der zu prüfenden Datei einem Werte eines Attributes der zu prüfenden Daten selbst entspricht? Runtime Parameter to the rescue!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So wie ich das Referenzhandbuch verstehe, ist die Verfügbarkeit von Laufzeitparametern vom System abhängig:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Nebst den eigentlichen Daten und den Metadaten können auch einzelne Datenelemente definiert werden, bei denen erwartet wird, dass sie von einem Bearbeitungs-, Auswerte- oder Darstellungssystem zur Laufzeit bereitgestellt werden. Sie heissen Laufzeitparameter.&amp;raquo;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unser Auswertesystem &lt;em&gt;ilivalidator&lt;/em&gt; kennt zum jetzigen Zeitpunkt folgende Laufzeitparameter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RuntimeSystemName&lt;/code&gt;: Name der Software, z.B. &amp;laquo;ilivalidator&amp;raquo; (ilivalidator)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RuntimeSystemVersion&lt;/code&gt;: Version der Software, z.B. &amp;laquo;1.11.11&amp;raquo; (ilivalidator)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;OperatingSystemName&lt;/code&gt;: Betriebssystem, z.B. &amp;laquo;Mac OS X&amp;raquo; (ili2c)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;HostName&lt;/code&gt;: Name des Hosts, z.B. &amp;laquo;localhost&amp;raquo; (ili2c)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CurrentUserName&lt;/code&gt;: Name des Users, der den Prozess startet, z.B. &amp;laquo;stefan&amp;raquo; (ili2c)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CurrentDateTime&lt;/code&gt;: Zeitpunkt des Prozesstarts, z.B. &amp;laquo;2017-08-22T15:00:00.000&amp;raquo; (ili2c)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CurrentTransferfile&lt;/code&gt;: Name der zu prüfenden Datei, z.B. &amp;laquo;SO0200002403_1180.xml&amp;raquo; (ilivalidator)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Machen wir ein Beispiel anhand eines sehr einfachen Modelles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;
MODEL Testmodel
  AT &quot;mailto:stefan.ziegler@bd.so.ch&quot; VERSION &quot;2019-01-27&quot; =

  IMPORTS MinimalRuntimeSystem01;

    TOPIC Topic =

        CLASS ClassA =
            attr2 : TEXT;
            MANDATORY CONSTRAINT attr2==PARAMETER MinimalRuntimeSystem01.CurrentTransferfile;
        END ClassA;

    END Topic;

END Testmodel.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Modell muss das MinimalRuntimeSystem01-Datenmodell importieren. Dieses ist Stand heute noch in keiner Modellablage vorhanden, muss also lokal vorliegen. Die Klasse hat genau ein Attribut &lt;code&gt;attr2&lt;/code&gt; und einen &lt;code&gt;MANDATORY CONSTRAINT&lt;/code&gt;, der die gewünschte Prüfung übernimmt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Inhalt der Datei SO0200002403_1180.xtf sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&amp;gt;
  &amp;lt;HEADERSECTION SENDER=&quot;sogis&quot; VERSION=&quot;2.3&quot;&amp;gt;
  &amp;lt;/HEADERSECTION&amp;gt;
  &amp;lt;DATASECTION&amp;gt;
    &amp;lt;Testmodel.Topic BID=&quot;b1&quot;&amp;gt;
      &amp;lt;Testmodel.Topic.ClassA TID=&quot;o1&quot;&amp;gt;
        &amp;lt;attr2&amp;gt;SO0200002403_1180.xtf&amp;lt;/attr2&amp;gt;
      &amp;lt;/Testmodel.Topic.ClassA&amp;gt;
    &amp;lt;/Testmodel.Topic&amp;gt;
  &amp;lt;/DATASECTION&amp;gt;
&amp;lt;/TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich kann &lt;em&gt;ilivalidator&lt;/em&gt; wie gewohnt starten und erhalte das Resultat der Prüfung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar ilivalidator-1.11.11.jar SO0200002403_1180.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ändere ich entweder den Namen der Datei oder den Wert des Attributes &lt;code&gt;attr2&lt;/code&gt;, erscheint eine Fehlermeldung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Error: line 7: Testmodel.Topic.ClassA: tid o1: Mandatory Constraint Testmodel.Topic.ClassA.Constraint1 is not true.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Fehlermeldung kann man mit einem Metaattribut gehaltvoller machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;!!@ ilivalid.msg = &quot;attr2 = {attr2} entspricht Dateinamen.&quot;
MANDATORY CONSTRAINT attr2==PARAMETER MinimalRuntimeSystem01.CurrentTransferfile;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Ilivalidator&lt;/em&gt; kennt noch eine weitere Syntax für die Verwendung der Laufzeitparameter in Constraints:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;MANDATORY CONSTRAINT attr2==MinimalRuntimeSystem01.getParameterValue(&quot;MinimalRuntimeSystem01.CurrentTransferfile&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Muss man für die Validierung eine eigene &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/demoplugin/src/org/interlis2/validator/demo/CheckGebaeudeVersicherungsSystemIoxPlugin.java&quot;&gt;&lt;code&gt;InterlisFunction&lt;/code&gt;&lt;/a&gt; implementieren und benötigt Laufzeitparameter, können diese via &lt;code&gt;TransferDescription&lt;/code&gt; ausgelesen werden. Die benötigen Methoden sind &lt;code&gt;getActualRuntimeParameters()&lt;/code&gt; resp. &lt;code&gt;getActualRuntimeParameter(&amp;#8230;&amp;#8203;)&lt;/code&gt;. In Unit-Tests muss z.B. der &lt;code&gt;CurrentTransferfile&lt;/code&gt;-Parameter explizit selber gesetzt werden, weil es ja keine Datei gibt, die geprüft wird, sondern nur Objekte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Iom_jObject iomObjA = new Iom_jObject(ILI_CLASSA, OBJ_OID1);
iomObjA.setattrvalue(&quot;attr2&quot;, &quot;SO0200002601_3396.xml&quot;);
ValidationConfig modelConfig = new ValidationConfig();
modelConfig.mergeIliMetaAttrs(td);
LogCollector logger = new LogCollector();
LogEventFactory errFactory = new LogEventFactory();
Settings settings = new Settings();
Map&amp;lt;String,Class&amp;gt; newFunctions = new HashMap&amp;lt;String,Class&amp;gt;();
newFunctions.put(&quot;SO_FunctionsExt.RuntimeDummy&quot;, RuntimeDummyIoxPlugin.class);
settings.setTransientObject(Validator.CONFIG_CUSTOM_FUNCTIONS, newFunctions);
td.setActualRuntimeParameter(ch.interlis.ili2c.metamodel.RuntimeParameters.MINIMAL_RUNTIME_SYSTEM01_CURRENT_TRANSFERFILE, &quot;SO0200002601_3396.xml&quot;);
Validator validator=new Validator(td, modelConfig, logger, errFactory, new PipelinePool(), settings);
validator.validate(new StartTransferEvent());
validator.validate(new StartBasketEvent(ILI_TOPIC,BID1));
validator.validate(new ObjectEvent(iomObjA));
validator.validate(new EndBasketEvent());
validator.validate(new EndTransferEvent());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #25 - Geschützte Attribute</title>
      <link>http://blog.sogeo.services/blog/2021/10/25/interlis-leicht-gemacht-number-25.html</link>
      <pubDate>Mon, 25 Oct 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/10/25/interlis-leicht-gemacht-number-25.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Solothurn können Geodaten seit circa 15 Jahren frei bezogen und genutzt werden. Es gibt jedoch einige sensible Informationen, die man nicht jedermann zugänglich machen kann. Oftmals sind es personenbezogene Daten wie z.B. eine Telefonnummer des Imkers. Wie gehen wir damit um?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Publikation der Daten als WMS-Layer (resp. als Kartenlayer im Web GIS Client) lösen wir es applikatorisch. D.h. der Layer wird zweimal als WMS-Layer publiziert. Einmal als öffentlicher Layer ohne die nicht-öffentlichen Attribute und einmal als passwortgeschützter Layer mit den nicht-öffentlichen Attributen. Eine weitere Variante ist das Erstellen einer View in der Datenbank, die nur die öffentlichen Attribute enthält. Diese View wird anschliessend als WMS-Layer publiziert. Für diese Variante wäre eine bessere Unterstützung von Views in &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt; wünschenswert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie gehen wir aber mit der Abgabe von Rohdaten vor? Wie können wir verhindern, dass die nicht-öffentlichen Attribute ebenfalls ohne Authentifizierung und Authorisierung bezogen werden können? Um die Lösungsvarianten besser zu verstehen, muss man sich unsere Rahmenbedingungen vor Augen führen: Daten werden in einem normalisierten INTERLIS-Datenmodell in der Edit-Datenbank erfasst und nachgeführt. Mit SQL bauen wir die Daten in ein sehr einfaches, d.h. denormalisiertes INTERLIS-Datenmodell ohne Assoziationen um und speichern die Daten in der Publikationsdatenbank. Aus dieser Publikationsdatenbank bedient sich auch der WMS- und WFS-Dienst. Die Erfahrung zeigt, dass die meisten Datenbezüger die Daten nicht im Ursprungsmodell (d.h. in der normalisierten Form) haben wollen, sondern im Publikationsmodell. Die Daten werden also mit &lt;em&gt;ili2pg&lt;/em&gt; in eine INTERLIS-Transferdatei exportiert und anschliessend mit &lt;em&gt;ili2gpkg&lt;/em&gt; in eine GeoPackage-Datei umgewandelt. Daraus erstellen wir Shapefiles und DXF-Dateien.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine erste Idee war die nicht-öffentlichen Attribute im Publikationsdatenmodell mit einem Metaattribut zu versehen. &lt;em&gt;Ili2pg&lt;/em&gt; hätte dann dahingehend erweitert werden müssen, dass Attribute mit diesem Metaattribut nicht exportiert werden. Das führt jedoch zum Problem, dass dieses Attribut nie ein Pflichtattribut sein darf. Da sonst die Daten nicht mehr zwingend modellkonform sind. Ein weiterer Nachteil ist, dass man als Empfänger anhand der Daten (resp. des Datenmodelles) nicht mehr erkennt, ob man nun den öffentlichen oder den nicht-öffentlichen Datensatz vor sich liegen hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die zweite Idee war das Maskieren von Attributwerten: Ein Text-Attribut wird z.B. mit &amp;laquo;XXXXXXXXX&amp;raquo; maskiert. Was macht man aber mit anderen Datentypen? Für Zahlenwerte schreibt man &amp;laquo;99999&amp;raquo;? Oder für ein Datum &amp;laquo;9999-12-31&amp;raquo;? Da wird es ziemlich gruselig. Auch für diese Variante müsste man &lt;em&gt;ili2pg&lt;/em&gt; anpassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die dritte und bisher beste Variante ist ohne Anpassung von &lt;em&gt;ili2pg&lt;/em&gt; bereits heute möglich: zwei Datenmodelle. Das Basismodell beinhaltet sämtliche öffentlichen Attribute. Das spezialisierte Modell erweitert die Klassen des Basismodelles um die nicht öffentlichen Attribute. Als Beispiel verwenden wir die &lt;a href=&quot;https://geo.so.ch/map/?k=806e44957&quot;&gt;Bienenstandorte&lt;/a&gt;. Neben einigen öffentlichen Attributen gibt es zwei nicht-öffentliche Atribute: den Namen und die Telefonnummer des Imkers. Das Modell sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

MODEL SO_ALW_Bienenstandorte_20211020 (de)
AT &quot;mailto:stefan@localhost&quot;
VERSION &quot;2021-10-20&quot;  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC Bienenstandorte =
    OID AS INTERLIS.UUIDOID;

    CLASS Bienenstandort =
      Nummer : MANDATORY TEXT*16;
      Standort : MANDATORY GeometryCHLV95_V1.Coord2;
      Bemerkung : TEXT*200;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_20211020.

MODEL SO_ALW_Bienenstandorte_restricted_20211020 (de)
AT &quot;mailto:stefan@localhost&quot;
VERSION &quot;2021-10-20&quot;  =
  IMPORTS SO_ALW_Bienenstandorte_20211020;

  TOPIC Bienenstandorte
  EXTENDS SO_ALW_Bienenstandorte_20211020.Bienenstandorte =

    CLASS Bienenstandort (EXTENDED) =
      Name : MANDATORY TEXT*255;
      Telefonnummer : TEXT*20;
    END Bienenstandort;

  END Bienenstandorte;

END SO_ALW_Bienenstandorte_restricted_20211020.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Klasse &lt;code&gt;Bienenstandort&lt;/code&gt; wird mit zwei Attributen erweitert. Das zusätzliche Attribut &lt;code&gt;Name&lt;/code&gt; ist ein Pflichtattribut.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit folgendem ili2pg-Befehl werden die leeren Tabellen angelegt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar /Users/stefan/apps/ili2pg-4.6.0/ili2pg-4.&amp;amp;.0.jar \
--dbhost localhost --dbport 54322 --dbdatabase pub --dbusr ddluser --dbpwd ddluser \
--dbschema alw_bienenstandorte_pub --models SO_ALW_Bienenstandorte_restricted_20211020 \
--defaultSrsCode 2056 --createGeomIdx --createFk --createFkIdx --createUnique \
--createMetaInfo --createNumChecks --nameByTopic --strokeArcs \
--createBasketCol \
--modeldir &quot;.;http://models.geo.admin.ch&quot; \
--schemaimport&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es muss das &quot;restricted&quot;-Modell verwendet werden und man muss zwingend &lt;code&gt;--createBasketCol&lt;/code&gt; verwenden. Aufgrund der bewussten Einfachheit des Modelles und der Abbildungsregeln von &lt;em&gt;ili2pg&lt;/em&gt; wird nur eine einzige (Daten-)Tabelle in der Datenbank erzeugt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p25/dbeaver01.png&quot; alt=&quot;dbeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich zu den erwartenden Attributen wird ein &lt;code&gt;t_type&lt;/code&gt;-Attribut angelegt. Dieses Attribut speichert die Information zu welcher Klasse ein Record gehört. Wir gehen davon aus, dass immer alle Records Bestandteil beider Datenmodelle sind und wir nur Attribute filtern wollen (und keine Zeilen). In diesem Fall entspricht der Wert von &lt;code&gt;t_type&lt;/code&gt; immer dem SQL-Namen der &quot;restricted&quot;-Klasse: &lt;code&gt;so_lw_b0211020bienenstandorte_bienenstandort&lt;/code&gt; (siehe Tabelle &lt;code&gt;t_ili2db_classename&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bevor jedoch Daten in der Tabelle gespeichert werden können, muss ein Basket in der Tabelle &lt;code&gt;t_ili2db_basket&lt;/code&gt; erstellt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH topics AS
(
    SELECT DISTINCT
        split_part(iliname, &apos;.&apos;, 1) || &apos;.&apos; || split_part(iliname, &apos;.&apos;, 2) AS topicname
    FROM
        alw_bienenstandorte_pub.t_ili2db_classname
    WHERE
        iliname ILIKE &apos;%restricted%&apos;
)
INSERT INTO
    alw_bienenstandorte_pub.t_ili2db_basket
    (
        t_id,
        topic,
        attachmentkey
    )
SELECT
    nextval(&apos;alw_bienenstandorte_pub.t_ili2db_seq&apos;),
    topics.topicname,
    &apos;foo&apos;
FROM
    topics
;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Topic muss das &quot;restricted&quot;-Topic sein. Ich erstelle zwei Records, je einer mit nur öffentlichen Attributen und einer mit zusätzlichen, nicht-öffentlichen Attributen. Spannend wird der Export der Daten. Als erstes will ich sämtliche Daten exportieren (also auch die nicht-öffentlichen Attribute);&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar /Users/stefan/apps/ili2pg-4.5.0/ili2pg-4.5.0.jar \
--dbhost localhost --dbport 54322 --dbdatabase pub --dbusr ddluser --dbpwd ddluser \
--dbschema alw_bienenstandorte_pub --models SO_ALW_Bienenstandorte_restricted_20211020 \
--modeldir &quot;.;http://models.geo.admin.ch&quot; \
--disableValidation \
--export restricted.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das erzeugt mir eine XTF-Datei mit meinen zwei Objekten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot;&amp;gt;
  &amp;lt;HEADERSECTION SENDER=&quot;ili2pg-4.5.0-fc023c8d2d8cd44d792927e45dc80c1ad973f095&quot; VERSION=&quot;2.3&quot;&amp;gt;
    &amp;lt;MODELS&amp;gt;
      &amp;lt;MODEL NAME=&quot;Units&quot; VERSION=&quot;2012-02-20&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;CoordSys&quot; VERSION=&quot;2015-11-24&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV03_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV95_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;SO_ALW_Bienenstandorte_20211020&quot; VERSION=&quot;2021-10-20&quot; URI=&quot;mailto:stefan@localhost&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;SO_ALW_Bienenstandorte_restricted_20211020&quot; VERSION=&quot;2021-10-20&quot; URI=&quot;mailto:stefan@localhost&quot;/&amp;gt;
    &amp;lt;/MODELS&amp;gt;
  &amp;lt;/HEADERSECTION&amp;gt;
  &amp;lt;DATASECTION&amp;gt;
    &amp;lt;SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte BID=&quot;1&quot;&amp;gt;
      &amp;lt;SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte.Bienenstandort TID=&quot;ce04e93c-bcaa-45ca-871c-1cc1a8f2c683&quot;&amp;gt;
        &amp;lt;Nummer&amp;gt;1234&amp;lt;/Nummer&amp;gt;
        &amp;lt;Standort&amp;gt;
          &amp;lt;COORD&amp;gt;
            &amp;lt;C1&amp;gt;2600000.000&amp;lt;/C1&amp;gt;
            &amp;lt;C2&amp;gt;1200000.000&amp;lt;/C2&amp;gt;
          &amp;lt;/COORD&amp;gt;
        &amp;lt;/Standort&amp;gt;
        &amp;lt;Bemerkung&amp;gt;foo&amp;lt;/Bemerkung&amp;gt;
        &amp;lt;Name&amp;gt;Lisa Liegenschaft&amp;lt;/Name&amp;gt;
        &amp;lt;Telefonnummer&amp;gt;555-1234&amp;lt;/Telefonnummer&amp;gt;
      &amp;lt;/SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte.Bienenstandort&amp;gt;
      &amp;lt;SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte.Bienenstandort TID=&quot;e10f19fa-b60d-4c78-833f-4b3b3bd4890c&quot;&amp;gt;
        &amp;lt;Nummer&amp;gt;4321&amp;lt;/Nummer&amp;gt;
        &amp;lt;Standort&amp;gt;
          &amp;lt;COORD&amp;gt;
            &amp;lt;C1&amp;gt;2600010.000&amp;lt;/C1&amp;gt;
            &amp;lt;C2&amp;gt;1200010.000&amp;lt;/C2&amp;gt;
          &amp;lt;/COORD&amp;gt;
        &amp;lt;/Standort&amp;gt;
        &amp;lt;Bemerkung&amp;gt;bar&amp;lt;/Bemerkung&amp;gt;
      &amp;lt;/SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte.Bienenstandort&amp;gt;
    &amp;lt;/SO_ALW_Bienenstandorte_restricted_20211020.Bienenstandorte&amp;gt;
  &amp;lt;/DATASECTION&amp;gt;
&amp;lt;/TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich die Daten mit &lt;em&gt;ilivalidator&lt;/em&gt; prüfe, erhalte ich einen Fehler: &lt;code&gt;Attribute Name requires a value&lt;/code&gt;. Was absolut korrekt ist. Obwohl &lt;code&gt;Name&lt;/code&gt; ein zwingendes Attribut ist, konnte ich einen Record in der Datenbank speichern, der diese Information nicht enthält. Das muss so sein, weil es für beide Klassen nur eine Tabelle gibt. Die Tabelle muss also auch fähig sein Records vom Basis-Klassen-Typ zu speichern. Aus diesem Grund muss das Attribut &lt;code&gt;Name&lt;/code&gt; nullable sein. Für unseren Usecase ist das kein Problem, da es sich &amp;laquo;nur&amp;raquo; um die Publikationsmodelle und -daten handelt. Die originäre Nachführung der Daten geschieht in den Erfassungsmodellen. Notfalls kann man die Daten bereits in der Datenbank mit &lt;em&gt;ili2pg&lt;/em&gt; und dem Modus &lt;code&gt;--validate&lt;/code&gt; prüfen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spannender ist der Befehl zum Exportieren der Daten im öffentlichen Datenmodell:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar /Users/stefan/apps/ili2pg-4.5.0/ili2pg-4.5.0.jar \
--dbhost localhost --dbport 54322 --dbdatabase pub --dbusr ddluser --dbpwd ddluser \
--dbschema alw_bienenstandorte_pub --models SO_ALW_Bienenstandorte_restricted_20211020 \
--exportModels SO_ALW_Bienenstandorte_20211020 \
--modeldir &quot;.;http://models.geo.admin.ch&quot; \
--disableValidation \
--export public.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Befehl ist bis auf die Option &lt;code&gt;--exportModels&lt;/code&gt; identisch. Die Option bestimmt gemäss welchem Modell die Daten exportiert werden. Das Resultat sieht wie gewünscht aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot;&amp;gt;
  &amp;lt;HEADERSECTION SENDER=&quot;ili2pg-4.5.0-fc023c8d2d8cd44d792927e45dc80c1ad973f095&quot; VERSION=&quot;2.3&quot;&amp;gt;
    &amp;lt;MODELS&amp;gt;
      &amp;lt;MODEL NAME=&quot;SO_ALW_Bienenstandorte_20211020&quot; VERSION=&quot;2021-10-20&quot; URI=&quot;mailto:stefan@localhost&quot;/&amp;gt;
    &amp;lt;/MODELS&amp;gt;
  &amp;lt;/HEADERSECTION&amp;gt;
  &amp;lt;DATASECTION&amp;gt;
    &amp;lt;SO_ALW_Bienenstandorte_20211020.Bienenstandorte BID=&quot;1&quot;&amp;gt;
      &amp;lt;SO_ALW_Bienenstandorte_20211020.Bienenstandorte.Bienenstandort TID=&quot;ce04e93c-bcaa-45ca-871c-1cc1a8f2c683&quot;&amp;gt;
        &amp;lt;Nummer&amp;gt;1234&amp;lt;/Nummer&amp;gt;
        &amp;lt;Standort&amp;gt;
          &amp;lt;COORD&amp;gt;
            &amp;lt;C1&amp;gt;2600000.000&amp;lt;/C1&amp;gt;
            &amp;lt;C2&amp;gt;1200000.000&amp;lt;/C2&amp;gt;
          &amp;lt;/COORD&amp;gt;
        &amp;lt;/Standort&amp;gt;
        &amp;lt;Bemerkung&amp;gt;foo&amp;lt;/Bemerkung&amp;gt;
      &amp;lt;/SO_ALW_Bienenstandorte_20211020.Bienenstandorte.Bienenstandort&amp;gt;
      &amp;lt;SO_ALW_Bienenstandorte_20211020.Bienenstandorte.Bienenstandort TID=&quot;e10f19fa-b60d-4c78-833f-4b3b3bd4890c&quot;&amp;gt;
        &amp;lt;Nummer&amp;gt;4321&amp;lt;/Nummer&amp;gt;
        &amp;lt;Standort&amp;gt;
          &amp;lt;COORD&amp;gt;
            &amp;lt;C1&amp;gt;2600010.000&amp;lt;/C1&amp;gt;
            &amp;lt;C2&amp;gt;1200010.000&amp;lt;/C2&amp;gt;
          &amp;lt;/COORD&amp;gt;
        &amp;lt;/Standort&amp;gt;
        &amp;lt;Bemerkung&amp;gt;bar&amp;lt;/Bemerkung&amp;gt;
      &amp;lt;/SO_ALW_Bienenstandorte_20211020.Bienenstandorte.Bienenstandort&amp;gt;
    &amp;lt;/SO_ALW_Bienenstandorte_20211020.Bienenstandorte&amp;gt;
  &amp;lt;/DATASECTION&amp;gt;
&amp;lt;/TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #24 - Ilivalidator in/mit QGIS</title>
      <link>http://blog.sogeo.services/blog/2021/10/12/interlis-leicht-gemacht-number-24.html</link>
      <pubDate>Tue, 12 Oct 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/10/12/interlis-leicht-gemacht-number-24.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wer kennt es nicht? Daten in QGIS nachgeführt, mit &lt;a href=&quot;https://github.com/claeis/ili2pg&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; exportiert und &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; meldet Fehler:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;Error: SO_AWJF_Foerderprogramm_Biodiversitaet_Publikation_20200519.Biotopflaechen.Biotopflaeche.Geometrie: Intersection coord1 (2610894.968, 1249766.404), tids 4b76926f-cef2-4b9e-8750-f3aef21385eb, 4b76926f-cef2-4b9e-8750-f3aef21385eb
Error: line 22: SO_AWJF_Foerderprogramm_Biodiversitaet_Publikation_20200519.Biotopflaechen.Biotopflaeche: tid dc58062e-4251-433b-b124-835356dc873e: duplicate coord at (2621389.108, 1244991.863, NaN)
Error: SO_AWJF_Foerderprogramm_Biodiversitaet_Publikation_20200519.Biotopflaechen.Biotopflaeche.Geometrie: Overlay coord1 (2617574.166, 1240369.683), coord2 (2617621.209, 1240261.671), tids 8ed21983-6692-4f99-b306-f084a364440f, 8ed21983-6692-4f99-b306-f084a364440f
Error: line 36: SO_AWJF_Foerderprogramm_Biodiversitaet_Publikation_20200519.Biotopflaechen.Biotopflaeche: tid 01857f02-9fca-4e18-83af-f97de8744ecd: duplicate coord at (2635087.966, 1247870.588, NaN)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Entweder wieder doppelte Vertexpunkte (&lt;code&gt;duplicate coords&lt;/code&gt;) oder minimalste Überlappungen (&lt;code&gt;Intersection&lt;/code&gt;, &lt;code&gt;Overlay&lt;/code&gt;) werden schonungslos aufgedeckt. Es gibt in &lt;a href=&quot;https://qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; verschiedene Mittel, damit diese Fehler schon gar nicht gemacht werden können und auch Werkzeuge, die solche Fehler nachträglich aufdecken. Warum aber nicht den Prüfalgorithmus von &lt;em&gt;ilivalidator&lt;/em&gt; näher zu &lt;em&gt;QGIS&lt;/em&gt; bringen? Damit ist sichergestellt, dass die Geometrien nach einem Export auch tatsächlich valide INTERLIS-Geometrien sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber wie soll das gehen? &lt;em&gt;Ilivalidator&lt;/em&gt; ist in Java geschrieben und QGIS will entweder C++ oder Python. Des Rätsels Lösung habe ich &lt;a href=&quot;http://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html&quot;&gt;hier&lt;/a&gt; (im unteren Teil) beschrieben. Ich will für diese zusätzliche Geometrieprüfung in &lt;em&gt;QGIS&lt;/em&gt; keine Java-Runtime installiert haben, sondern im Idealfall maximal nur ein Python-Plugin herunterladen und gut ist. Mit &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;&lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; können einzelne &lt;a href=&quot;https://www.graalvm.org/reference-manual/native-image/ImplementingNativeMethodsInJavaWithSVM/&quot;&gt;Java-Methoden zu shared libraries&lt;/a&gt; (*.so, *.dylib, *.dll) kompiliert werden. Diese wiederum können relativ einfach in Python verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Java-Methode, welche Geometrien nach INTERLIS-Spezifikation validiert, gibt es hier in einem &lt;a href=&quot;https://github.com/edigonzales/geomvalidator-libnative&quot;&gt;Github-Repo&lt;/a&gt;. Es ist im Prinzip sehr viel Copy/Paste aus der &lt;a href=&quot;https://github.com/claeis/iox-ili&quot;&gt;iox-ili-Bibliothek&lt;/a&gt;, welche die eigentliche INTERLIS-Validierung für &lt;em&gt;ilivalidator&lt;/em&gt; durchführt. Copy/Paste darum, weil die Geometrievalidierungs-Methoden nicht zum öffentlichen API gehören und auch weil für den vorliegenden Anwendungsfall Informationen fehlen, welche die Originalmethoden benötigen, z.B. den Wert für erlaubte Overlaps. Dieser steckt im Datenmodell, welches mir beim Datenerfassen in QGIS nicht zur Verfügung steht. Auch fehlt mir die Auflösung der Geometrien. In der Regel ist das bei uns ein Millimeter. Darum werden diese Werte resp. daraus abgeleitet Werte in meiner Copy/Paste-Methode hardcodiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein grosser Teil der Magie der Geometrievalidierung von z.B. Flächengeometrien (im Sinne der Surfacetopologie) steckt in der Methode &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/main/java/ch/interlis/iom_j/itf/impl/ItfSurfaceLinetable2Polygon.java#L263&quot;&gt;&lt;code&gt;validatePolygon&lt;/code&gt;&lt;/a&gt; (ff.) der Klasse &lt;code&gt;ItfSurfaceLinetable2Polygon&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unter Linux lässt sich die Copy/Paste-Java-Methode dann wie folgt zu einer shared library kompilieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./gradlew clean lib:build shadowJar &amp;amp;&amp;amp; \
native-image --no-fallback --no-server -cp lib/build/libs/lib-all.jar --shared -H:Name=libgeomvalidator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unter Ubuntu 20.04 musste ich noch ein paar Pakete installieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo apt-get install build-essential
sudo apt install libstdc++-8-dev
sudo apt-get install zlib1g-dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wobei das letzte Paket nur unter Ubuntu 20.04 auf einem Apple Silicon Rechner (also einem ARM-Prozessor) notwendig war. Ubuntu (die ARM-Variante) läuft hier als virtuelle Maschine in &lt;a href=&quot;https://mac.getutm.app/&quot;&gt;UTM&lt;/a&gt; inkl. QGIS ganz passabel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &lt;code&gt;native-image&lt;/code&gt;-Befehl erzeugt aus der Jar-Datei die verschiedenen Header-Dateien und die shared library (*.so-Datei). Mit einem Dummy-C-Programm prüfe ich, ob das ganze auch funktioniert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

#include &amp;lt;libgeomvalidator.h&amp;gt;

int main(int argc, char **argv) {
    graal_isolate_t *isolate = NULL;
    graal_isolatethread_t *thread = NULL;

    if (graal_create_isolate(NULL, &amp;amp;isolate, &amp;amp;thread) != 0) {
        fprintf(stderr, &quot;graal_create_isolate error\n&quot;);
        return 1;
    }

    char * layername = &quot;my_layer_name&quot;;
    char * fid = &quot;afid&quot;;
    char * wktGeom = &quot;POLYGON ((2609000 1236700, 2609200 1236700, 2609200 1236700, 2609200 1236600, 2609000 1236600, 2609000 1236700))&quot;;
    printf(&quot;%d\n&quot;, geomvalidator(thread, layername, fid, wktGeom));

    if (graal_detach_thread(thread) != 0) {
        fprintf(stderr, &quot;graal_detach_thread error\n&quot;);
        return 1;
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Parameter werden neben der Geometrie (im WKT-Format), der Layername und eine Feature-Id der Methode übergeben. Dies entspricht beinahe der Signatur der Originalmethode von &lt;em&gt;ilivalidator&lt;/em&gt;: Die Feature-Id entspricht der TID, der Layername dem qualifizierten Attributnamen. Was in diesem Kontext nicht unerwähnt bleiben darf, ist die Tatsache, dass als Übergabe- und Rückgabeparameter nur Nicht-Objekttypen verwendet werden können. Abhilfe schafft oftmals die Serialisierung in ein geeignetes Format (z.B. JSON).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das C-Programm lässt sich kompilieren und ausführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;cc  geomvalidator.c -I. -L. -lgeomvalidator -o geomvalidator
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH &amp;amp;&amp;amp; ./geomvalidator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Output in der Konsole entspricht dem zu erwarteten Ergebnis (siehe WKT-String im C-Programm):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;duplicate coord at (2609200.0, 1236700.0, NaN)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun können wir einen Schritt weitergehen und versuchen sämtliche Geometrien eines Layers in QGIS mit dem ilivalidator-Algorithmus zu prüfen. Dazu schreiben wir kein Plugin, sondern als Proof-of-Concept reicht die Python-Konsole:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p24/qgis_python_console01.png&quot; alt=&quot;QGIS-Python-Konsole&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Skript &lt;code&gt;validate_data.py&lt;/code&gt;, welches die Validierung macht, ist relativ simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from ctypes import *
dll = CDLL(&quot;/home/stefan/sources/geomvalidator-libnative/libgeomvalidator.so&quot;)
isolate = c_void_p()
isolatethread = c_void_p()
dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))
dll.geomvalidator.restype = int

layer = QgsVectorLayer(&quot;/home/stefan/sources/geomvalidator-libnative/data/ch.so.awjf.foerderprogramm_biodiversitaet.gpkg|layername=biotopflaechen_biotopflaeche&quot;, &quot;biotopflaechen_biotopflaeche&quot;, &quot;ogr&quot;)
if not layer.isValid():
    print (&quot;failed to load layer&quot;)

for f in layer.getFeatures():
    result = dll.geomvalidator(isolatethread, c_char_p(bytes(&quot;biotopflaechen_biotopflaeche&quot;, &quot;utf8&quot;)), c_char_p(bytes(str(f.id()), &quot;utf8&quot;)), c_char_p(bytes(f.geometry().asWkt(), &quot;utf8&quot;)))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Zeilen 1 bis 6 dienen dazu die shared library als Python-Binding verfügbar zu machen. Es gibt dazu verschiedene &lt;a href=&quot;https://realpython.com/python-bindings-overview/&quot;&gt;Varianten&lt;/a&gt;. Ich habe mich für die &lt;a href=&quot;https://realpython.com/python-bindings-overview/#ctypes&quot;&gt;ctypes-Variante&lt;/a&gt; entschieden. Ist zwar ziemlich low-level, aber von Vorteil ist für mich vor allem, dass bereits alles in einer Python-Standard-Installation dabei ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zeile 8 importiert die Tabelle einer Geopackage-Datei als QGIS-Vektorlayer. Die Validierung der Geometrien geschieht in der Schleife über sämtliche Geometrien des Layers in den Zeilen 12 - 13. Das erstmalige Ausführen war ernüchternd: Nichts passierte. Aber die Post ging nicht in der Python-Konsole in QGIS ab, sondern im Terminal aus dem QGIS gestartet wurde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p24/output01.png&quot; alt=&quot;QGIS-Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die shared library loggt nach stderr. Trotzdem hätte ich eigentlich erwartet, dass man den Output in der Python-Konsole sieht. Aber wahrscheinlich ist das Verhalten logisch und ich verstehe es nur nicht. Vergleicht man den Output der Prüfung des QGIS-Layers mittels shared library mit dem ilivalidator-Logfile, kann man mit sich und der Welt zufrieden sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p24/output02.png&quot; alt=&quot;Ilivalidator-Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ganze ist natürlich bloss eine Spielerei aber zeigt es doch die Fähigkeiten und Möglichkeiten von &lt;em&gt;GraalVM&lt;/em&gt; und dass Java sehr flexibel eingesetzt werden kann.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #23 - Paketierte ilitools</title>
      <link>http://blog.sogeo.services/blog/2021/07/21/interlis-leicht-gemacht-number-23.html</link>
      <pubDate>Wed, 21 Jul 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/07/21/interlis-leicht-gemacht-number-23.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Etwas nervte mich seit langem an &lt;a href=&quot;http://www.umleditor.org/&quot;&gt;&lt;em&gt;INTERLIS-UML-Editor&lt;/em&gt;&lt;/a&gt; auf macOS: Eine Java-Anwendung kann man nicht einfach ins Dock ziehen und gut ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Früher gab es für JavaFX das Tool &lt;em&gt;javapackager&lt;/em&gt;, das JavaFX-Anwendungen und alle anderen Java-Anwendungen betriebssystemabhängig zu einem Paket inkl. Java-Runtime schnürt. Dieses konnte dann wie jede andere Anwendung auf Linux, Windows oder macOS installiert werden. Praktisch war auch, dass man sich nicht um eine Java-Installation zu kümmern brauchte, weil die Runtime eben mitgeliefert wurde. Leider flog das Tool mit JavaFX aus dem JDK.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein paar Jahre (Java 16) später taucht es unter dem Namen &lt;a href=&quot;https://openjdk.java.net/jeps/392&quot;&gt;&lt;em&gt;jpackage&lt;/em&gt;&lt;/a&gt; in einer sehr ähnlichen Form wieder auf. Einige Features sind nicht mehr vorhanden, so z.B. JavaFX-spezifische Features. Das Wichtigste ist aber immer noch möglich: betriebssystemabhängige Installationspakete für Java-Anwendungen inkl. Runtime erstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im einfachsten Fall sieht ein Befehl so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;jpackage --icon icon-umleditor-128x128.icns --name umleditor --type dmg --input libs --main-jar umleditor-3.7.6.jar --app-version 3.7.6 -d output&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;--icon: Das Icon für die Anwendung. Dateiformat ist leider je nach Betriebssystem unterschiedlich.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;--name: Name der Anwendung&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;--type: &lt;code&gt;dmg&lt;/code&gt; und &lt;code&gt;pkg&lt;/code&gt; für macOS, &lt;code&gt;deb&lt;/code&gt; und &lt;code&gt;rpm&lt;/code&gt; für Linux, &lt;code&gt;exe&lt;/code&gt; und &lt;code&gt;msi&lt;/code&gt; für Windows. &amp;laquo;Cross-Herstellung&amp;raquo; - also rpm-Pakete auf Windows herstellen, geht nicht.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;--input: Das Verzeichnis mit den Jar-Dateien.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;--main-jar: Die Jar-Datei mit der Main-Klasse&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;--app-version: Versionsnummer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;-d: Das Verzeichnis, wo das Paket gespeichert wird.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Befehl liefert mir im Verzeichnis &lt;code&gt;output&lt;/code&gt; die Datei &lt;code&gt;ilivalidator-1.11.10.dmg&lt;/code&gt;. Diese .dmg-Datei kann ich jetzt wie gewohnt ausführen und die Anwendung installieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p23/interlis-uml-editor.png&quot; alt=&quot;INTERLIS-UML-Editor dmg&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit ein wenig Zusatzeffort kann man das Ganze hinsichtlich der Grösse optimieren. Standardmässig wird einfach die gesamte Java-Runtime reinkopiert. Bei mir ergibt das eine 56MB grosse DMG-Datei. Man kann mit &lt;em&gt;jlink&lt;/em&gt; selber eine Java-Runtime herstellen. Damit diese kleiner wird, muss man die von der Anwendung (also vom INTERLIS-UML-Editor) benötigten Module als Parameter angeben. Dieses Information liefert mir &lt;em&gt;jdeps&lt;/em&gt;. Und hier wird es ein wenig hakelig, vor allem bei nicht-modularen Java-Anwendungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;jdeps --class-path &apos;libs/*&apos; --multi-release base -recursive --ignore-missing-deps --print-module-deps umleditor-3.7.6.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert mir: &lt;code&gt;java.base,java.desktop,java.management,java.sql&lt;/code&gt;. Mit dieser Information erzeuge ich mit &lt;em&gt;jlink&lt;/em&gt; meine spezifische Java-Runtime:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt; jlink --add-modules java.base,java.desktop,java.management,java.sql --output umleditor-jre&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der jpackage-Befehl wird um den Parameter &lt;code&gt;--runtime-image umleditor-jre&lt;/code&gt; erweitert. Das Paket ist nun nur noch 40MB gross.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil dank &lt;a href=&quot;https://www.geowerkstatt.ch/&quot;&gt;Geowerkstatt&lt;/a&gt; &lt;a href=&quot;https://github.com/claeis/ilivalidator/pull/315&quot;&gt;momentan&lt;/a&gt; &lt;a href=&quot;https://github.com/claeis/ilivalidator/pull/313&quot;&gt;richtig&lt;/a&gt; &lt;a href=&quot;https://github.com/claeis/ilivalidator/pull/312&quot;&gt;Schwung&lt;/a&gt; in der GUI-Programmierung von &lt;em&gt;ilivalidator&lt;/em&gt; ist, habe ich für die Anwendungen &lt;em&gt;ilivalidator&lt;/em&gt;, &lt;em&gt;ili2gpkg&lt;/em&gt;, &lt;em&gt;ili2c&lt;/em&gt; und &lt;em&gt;INTERLIS-UML-Editor&lt;/em&gt; solche Pakete für Ubuntu, Windows und macOS erstellt: &lt;a href=&quot;https://ilitools.sogeo.services&quot;&gt;ilitools.sogeo.services&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Pakete werden mit einer &lt;a href=&quot;https://github.com/edigonzales/ilitools-packager&quot;&gt;Github Action&lt;/a&gt; erstellt. Dabei musste ich feststellen, dass die DMG-Dateien bei mir nicht funktionieren. Aus diesem Grund bin ich auf PKG umgeschwenkt. MacOS bemängelt natürlich immer noch, dass die Pakete nicht signiert sind. Dies wäre technich aber möglich. Die anderen beiden Varianten konnte ich mangels fehlendem Betriebssystem nicht testen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Den beiden Anwendungen &lt;em&gt;ilivalidator&lt;/em&gt; und &lt;em&gt;ili2gpkg&lt;/em&gt; habe ich mit &lt;code&gt;--java-option -Xmx2G&lt;/code&gt; zwei Gigabyte Heap zugewiesen. Es gäbe noch einige weitere spannende Optionen, die man ausprobieren könnte. So sind verschiedene Launcher möglich, was aber bei mir nicht wirklich funktioniert hat. Eventuell können diese Launcher dazu verwendet werden, die Anwendung mit einem GUI zu starten oder in der Konsole. Eine gute Übersicht mit Erläuterung der Möglichkeiten gibt es &lt;a href=&quot;https://docs.oracle.com/en/java/javase/14/jpackage/image-and-runtime-modifications.html&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #22 - Python goes INTERLIS / INTERLIS goes Python</title>
      <link>http://blog.sogeo.services/blog/2021/02/02/interlis-leicht-gemacht-number-22.html</link>
      <pubDate>Tue, 2 Feb 2021 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2021/02/02/interlis-leicht-gemacht-number-22.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf einem jungfräulichen Betriebssystem installiere ich als erstes Java. Eigentlich als zweites, denn um verschiedene Java-Versionen einfach installieren zu können, verwende ich &lt;a href=&quot;https://sdkman.io/&quot;&gt;&lt;em&gt;SDKMAN!&lt;/em&gt;&lt;/a&gt;. Aber es gibt auch genauso die Liebhaber von &lt;em&gt;Python&lt;/em&gt;. Insbesondere in der Geo-Welt scheint &lt;em&gt;Python&lt;/em&gt; viele Anhänger zu haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es geschehen ja noch Zeichen und Wunder und INTERLIS wird immer wie mehr verwendet. Eine INTERLIS-&amp;laquo;Produktefamilie&amp;raquo; -  namentlich &lt;em&gt;ili2db&lt;/em&gt; und &lt;em&gt;ilivalidator&lt;/em&gt; ist mit Java geschrieben. Das wiederum passt nun nicht so ganz in die Geo-Welt, die lieber Python verwendet. Ebenso wenig matcht das mit QGIS-Plugins (insb. Model Baker), die mit INTERLIS umgehen wollen resp. müssen. Was tun? Es gibt verschiedene Möglichkeiten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jython.org/&quot;&gt;&lt;em&gt;Jython&lt;/em&gt;&lt;/a&gt; ist eine Java-Implementierung von Python. Das bedeutet in erster Linie, dass eine JVM benötigt wird, um Python-Skripte auszuführen. Ansonsten kann aber &amp;laquo;ganz normal&amp;raquo; Python programmiert werden. Leider gibt es keine 3er-Version, sondern es wird nur Python 2.7 unterstützt. Die Unterstützung von bekannten Python-Bibliotheken und -Frameworks ist relativ gut. Das Interessante ist aus Sicht INTERLIS, dass auch beliebige Java-Klassen im Python-Code verwendet werden können, ohne dass System-Calls abgesetzt werden müssen. Wenn ich mit &lt;em&gt;ili2gpkg&lt;/em&gt; Daten in eine GeoPackage-Datei importieren will, sieht das wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env jython
import sys

from ch.ehi.ili2db.base import Ili2db
from ch.ehi.ili2db.base import Ili2dbException
from ch.ehi.ili2db.gui import Config
from ch.ehi.ili2gpkg import GpkgMain

print sys.path

settings = Config()
GpkgMain().initConfig(settings)
settings.setFunction(Config.FC_IMPORT)
settings.setDoImplicitSchemaImport(True)
settings.setModels(&quot;DM01AVCH24LV95D&quot;)
settings.setDefaultSrsCode(&quot;2056&quot;)
settings.setNameOptimization(Config.NAME_OPTIMIZATION_TOPIC)
settings.setCreateEnumDefs(Config.CREATE_ENUM_DEFS_MULTI)
settings.setDbfile(&quot;254900.gpkg&quot;)
Config.setStrokeArcs(settings, Config.STROKE_ARCS_ENABLE)
settings.setValidation(False)
settings.setItfTransferfile(True)
settings.setDburl(&quot;jdbc:sqlite:&quot; + settings.getDbfile())
settings.setXtffile(&quot;254900.itf&quot;)
try:
    Ili2db.run(settings, None)
except Ili2dbException, value:
    print value&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dem Skript muss man via &lt;code&gt;JYTHONPATH&lt;/code&gt; die Java-Klassen bekannt machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;export JYTHONPATH=~/apps/ili2gpkg-4.4.5/ili2gpkg-4.4.5.jar:~/apps/ili2gpkg-4.4.5/libs/antlr-2.7.7.jar:~/apps/ili2gpkg-4.4.5/libs/base64-2.3.9.jar:~/apps/ili2gpkg-4.4.5/libs/ehibasics-1.4.0.jar:~/apps/ili2gpkg-4.4.5/libs/ehisqlgen-1.13.8.jar:~/apps/ili2gpkg-4.4.5/libs/ili2c-core-5.1.5.jar:~/apps/ili2gpkg-4.4.5/libs/ili2c-tool-5.1.5.jar:~/apps/ili2gpkg-4.4.5/libs/ili2db-4.4.5.jar:~/apps/ili2gpkg-4.4.5/libs/iox-api-1.0.3.jar:~/apps/ili2gpkg-4.4.5/libs/iox-ili-1.21.4.jar:~/apps/ili2gpkg-4.4.5/libs/jackson-core-2.9.7.jar:~/apps/ili2gpkg-4.4.5/libs/jts-core-1.14.0.jar:~/apps/ili2gpkg-4.4.5/libs/sqlite-jdbc-3.8.11.2.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zukunftsträchtiger (?) und sicher hipper ist eine andere Python-Implementierung. Nämlich die Python-Implementierung für die &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;GraalVM&lt;/a&gt;. GraalVM bietet zudem Implmentierungen für Java, Ruby, R und Node.js an und ermöglicht wirklich polyglote Anwendungen. Die Python-3-Implementierung steckt leider noch in den Kinderschuhen. Das merkt man insbesondere, wenn man beliebte Frameworks und Bibliotheken verwenden will. Viele von diesen werden nicht unterstützt. Der Fokus der Entwickler liegt momentan bei Numpy und SciPy. Im Prinzip funktioniert es wie mit &lt;em&gt;Jython&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import java

Config = java.type(&apos;ch.ehi.ili2db.gui.Config&apos;)
Ili2db = java.type(&apos;ch.ehi.ili2db.base.Ili2db&apos;)
Ili2dbException = java.type(&apos;ch.ehi.ili2db.base.Ili2dbException&apos;)
GpkgMain = java.type(&apos;ch.ehi.ili2gpkg.GpkgMain&apos;)

settings = Config()
GpkgMain().initConfig(settings)
settings.setFunction(Config.FC_IMPORT)
settings.setDoImplicitSchemaImport(True)
settings.setModels(&quot;DM01AVCH24LV95D&quot;)
settings.setDefaultSrsCode(&quot;2056&quot;)
settings.setNameOptimization(Config.NAME_OPTIMIZATION_TOPIC)
settings.setCreateEnumDefs(Config.CREATE_ENUM_DEFS_MULTI)
settings.setDbfile(&quot;254900.gpkg&quot;)
Config.setStrokeArcs(settings, Config.STROKE_ARCS_ENABLE)
settings.setValidation(False)
settings.setItfTransferfile(True)
settings.setDburl(&quot;jdbc:sqlite:&quot; + settings.getDbfile())
settings.setXtffile(&quot;254900.itf&quot;)
try:
    Ili2db.run(settings, None)
except Ili2dbException as value:
    print(value)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In dieser Variante muss der &lt;code&gt;CLASSPATH&lt;/code&gt; korrekt gesetzt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;export CLASSPATH=~/apps/ili2gpkg-4.4.5/ili2gpkg-4.4.5.jar:~/apps/ili2gpkg-4.4.5/libs/antlr-2.7.7.jar:~/apps/ili2gpkg-4.4.5/libs/base64-2.3.9.jar:~/apps/ili2gpkg-4.4.5/libs/ehibasics-1.4.0.jar:~/apps/ili2gpkg-4.4.5/libs/ehisqlgen-1.13.8.jar:~/apps/ili2gpkg-4.4.5/libs/ili2c-core-5.1.5.jar:~/apps/ili2gpkg-4.4.5/libs/ili2c-tool-5.1.5.jar:~/apps/ili2gpkg-4.4.5/libs/ili2db-4.4.5.jar:~/apps/ili2gpkg-4.4.5/libs/iox-api-1.0.3.jar:~/apps/ili2gpkg-4.4.5/libs/iox-ili-1.21.4.jar:~/apps/ili2gpkg-4.4.5/libs/jackson-core-2.9.7.jar:~/apps/ili2gpkg-4.4.5/libs/jts-core-1.14.0.jar:~/apps/ili2gpkg-4.4.5/libs/sqlite-jdbc-3.8.11.2.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Werden, wie in diesem Fall, Java-Bibliotheken verwendet, muss Python im JVM-Modus gestartet werden (was wiederum die Startzeit fast quälend langsam macht):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;graalpython --jvm --vm.cp=$CLASSPATH ili2db.py&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Je nach Anwendungsfall sind das valable Lösungen. Insbesondere auch weil die Hürde einer Java-Runtime in Zeiten von Docker je nachdem sehr tief ist. Wo dieser Ansatz nicht funktioniert, ist bei QGIS-Plugins. Im Model-Baker-Plugin sind die System-Calls relativ elaboriert und sicher ziemlich robust. Andererseits braucht man immer noch eine Java-Runtime, was mich in diesem konkreten Fall stört. Mit der GraalVM können Anwendungen zu &lt;a href=&quot;http://blog.sogeo.services/blog/2019/02/23/graalvm-p1-interlis-polyglot-gemacht.html&quot;&gt;nativem Code kompiliert&lt;/a&gt; werden und brauchen zur Laufzeit keine Java-Runtime mehr. Tönt cool, ist hip, hat aber auch Nachteile. Es können jedoch nicht nur komplette Java-Awendungen zu nativem Code kompiliert werden, sondern auch einzelne (statische) Java-Methoden zu shared libraries (*.so, *.dylib, *.dll). Diese wiederum - so die Idee - könnte man mittels Python-Bindings in QGIS-Plugins ansprechen. Somit wäre man die Laufzeitanforderung Java los. Gesagt getan:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;package ch.so.agi.ili2db.libnative;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;

import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;

import com.fasterxml.jackson.databind.ObjectMapper;

import ch.ehi.ili2db.base.Ili2db;
import ch.ehi.ili2db.base.Ili2dbException;
import ch.ehi.ili2db.gui.Config;
import ch.ehi.ili2pg.PgMain;

public class Ili2dbLib {

    @CEntryPoint(name = &quot;ili2pg&quot;)
    public static int ili2pg(IsolateThread thread, CCharPointer settings) {
        try {
            Config config = json2config(CTypeConversion.toJavaString(settings));
            Ili2db.run(config, null);
        } catch (Ili2dbException e) {
            e.printStackTrace();
            System.err.println(e.getMessage());
            return 1;
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println(e.getMessage());
            return 1;
        }
        return 0;
    }

    public static Config json2config(String jsonString) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Map&amp;lt;String, Object&amp;gt; map = mapper.readValue(jsonString, Map.class);

        Config config = new Config();
        new PgMain().initConfig(config);

        if (!map.containsKey(&quot;function&quot;)) {
            throw new IllegalArgumentException(&quot;missing function parameter&quot;);
        } else {
            String function = (String) map.get(&quot;function&quot;);

            if (function.equalsIgnoreCase(&quot;import&quot;)) {
                config.setFunction(Config.FC_IMPORT);
            }
        }

        // TODO if/else/exceptions etc.
        config.setDoImplicitSchemaImport(true);
        config.setConfigReadFromDb(true);
        config.setModels((String) map.get(&quot;models&quot;));

        config.setDbhost((String) map.get(&quot;dbhost&quot;));
        config.setDbport((String) map.get(&quot;dbport&quot;));
        config.setDbusr((String) map.get(&quot;dbusr&quot;));
        config.setDbusr((String) map.get(&quot;dbusr&quot;));
        config.setDbpwd((String) map.get(&quot;dbpwd&quot;));
        config.setDburl((String) map.get(&quot;dburl&quot;));
        config.setDbschema((String) map.get(&quot;dbschema&quot;));

        config.setDefaultSrsCode((String) map.get(&quot;defaultSrsCode&quot;));

        if (map.containsKey(&quot;strokeArcs&quot;)) {
            Config.setStrokeArcs(config, Config.STROKE_ARCS_ENABLE);
        }

        if ((Boolean) map.get(&quot;disableValidation&quot;)) {
            config.setValidation(false);
        }

        if ((Boolean) map.get(&quot;doSchemaImport&quot;)) {
            config.setDoImplicitSchemaImport(true);
        }

        String fileName = (String) map.get(&quot;file&quot;);
        if (fileName.toLowerCase().endsWith(&quot;itf&quot;)) {
            config.setItfTransferfile(false);
        } else {
            config.setItfTransferfile(true);
        }
        config.setXtffile(new File(fileName).getAbsolutePath());

        return config;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt bei der Implementierung einer solchen statischen Methode einige Einschränkungen. Zum einen, dass sie eben statisch sein muss und zum anderen, dass die Übergabe von Parametern recht eingeschränkt ist. Viel mehr als Integer, Double und String geht nicht. Beliebige Objekte können nicht ausgetauscht werden, was das Vorhaben nicht einfacher macht. Eine Möglichkeit ist, dass die Objekte (die übergeben werden sollen) nach JSON serialisiert und als String übergeben werden. Hat man den Java-Code, muss man die Methode zu einer shared library kompilieren (konkret hier für Linux):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;./gradlew clean lib:build shadowJar &amp;amp;&amp;amp; \
native-image --no-fallback --no-server -cp lib/build/libs/lib-all.jar --shared -H:Name=libili2db&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Produkt sind verschiedene Headerfiles und die Bibliothek selbst. Die simpelste Form von Python-Bindinds ist der Weg über &lt;code&gt;ctypes&lt;/code&gt;. Das ergibt circa folgenden Code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;settings = &quot;{ \&quot;dbhost\&quot; : \&quot;192.168.56.1\&quot;, \&quot;dbport\&quot; : \&quot;54321\&quot;, \&quot;dbdatabase\&quot; : \&quot;edit\&quot;, \&quot;dbusr\&quot; : \&quot;admin\&quot;, \&quot;dbpwd\&quot; : \&quot;admin\&quot;, \&quot;dburl\&quot; : \&quot;jdbc:postgresql://192.168.56.1:54321/edit\&quot;, \&quot;dbschema\&quot; : \&quot;npl_2551\&quot;, \&quot;defaultSrsCode\&quot; : \&quot;2056\&quot;, \&quot;strokeArcs\&quot; : \&quot;enable\&quot;, \&quot;disableValidation\&quot; : true, \&quot;models\&quot; : \&quot;SO_Nutzungsplanung_20171118\&quot;, \&quot;doSchemaImport\&quot; : true, \&quot;function\&quot; : \&quot;import\&quot;, \&quot;file\&quot; : \&quot;./lib/src/test/data/2551.xtf\&quot; }&quot;
print(settings)

from ctypes import *
dll = CDLL(&quot;./libili2db.so&quot;)
isolate = c_void_p()
isolatethread = c_void_p()
dll.graal_create_isolate(None, byref(isolate), byref(isolatethread))
dll.ili2pg.restype = int
result = dll.ili2pg(isolatethread, c_char_p(bytes(settings, &quot;utf8&quot;)))
result&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein wenig syntactic sugar drum herum und es sieht gar nicht mehr so schlimm aus. Ein Nachteil ist, dass zusätzlicher &lt;em&gt;ili2db&lt;/em&gt;-Code entstehen würde, der ebenfalls von irgend jemandem gepflegt werden will.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #21 - INTERLIS ohne INTERLIS</title>
      <link>http://blog.sogeo.services/blog/2020/09/23/interlis-leicht-gemacht-number-21.html</link>
      <pubDate>Wed, 23 Sep 2020 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2020/09/23/interlis-leicht-gemacht-number-21.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Dienststelle muss einige seiner in die Jahre gekommenen Fachanwendungen ablösen und wählt dafür eine &amp;laquo;Plattform&amp;raquo; eines Anbieters aus. Die Idee dahinter ist, dass dank des Plattformgedankens Synergien genützt werden können: technisch aber auch v.a. finanziell. Klingt gut, hat aber auch ein paar gravierende Nachteile. Im konkreten Fall fiel die Wahl auf eine Plattform, die überhaupt nichts mit GIS am Hut hat, aber trotzdem &amp;laquo;GIS machen&amp;raquo; können soll. Das Tragische ist, dass die GIS-Anforderungen eigentlich überschaubar sind: Häufig muss man nur einen Punkt in einer Karte absetzen und ein paar Attribute dazu erfassen. Soweit keine Raketenwissenschaft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Verheiraten von GIS-freien Webanwendungen und unserem Web GIS Client haben wir eine &lt;a href=&quot;https://so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-geoinformation/geoportal/geodienste/ccc-schnittstelle/&quot;&gt;Schnittstelle&lt;/a&gt; auf Basis Websocket eingeführt. Damit können sich die beiden Awendungen koppeln und Daten austauschen. Die Fachanwendung muss also nicht &amp;laquo;GIS können&amp;raquo;, sondern nur Websocket und bisschen JSON rumschicken. Im aktuellsten Fall gibt es nun zusätzlich die Anforderung, dass die Geometrien nicht nur Punkte sind, sondern Polygone. Die Polygone müssen zudem sauber, d.h. überlappungsfrei erfasst werden können. Natürlich kann man sowas auch in einem Web GIS Client umsetzen. Das Kosten/Nutzen-Verhältnis muss aber berücksichtigt werden. Vor allem wenn es Werkzeuge wie &lt;a href=&quot;https://qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; gibt, die sehr gute Digitalisierungswerkzeuge mitbringen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie verheiraten wir aber jetzt &lt;em&gt;QGIS&lt;/em&gt; mit der Fachanwendung, die im Browser läuft? Einerseits sollen zu einem Geschäftsfall in der Fachanwendung Geometrien erfasst werden und andererseits sollen die gemeinsamen Daten (also Geometrie und Sachattribute aus der Fachanwendung) regelmässig für Interessierte im Web GIS Client aktualisiert und dargestellt werden. Was wir nicht machen wollen, ist für &lt;em&gt;QGIS&lt;/em&gt; einen Websocket-Provider entwickeln lassen. Was machen? Back to the roots: Datenaustausch mit Dateien und bisschen Copy/Paste. Grundidee ist etwa folgende:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Anwender beginnt mit der Erfassung des Polygons / Multipolygons in &lt;em&gt;QGIS&lt;/em&gt;. Diese Geometrie erhält einen fixen Identifikator. Diesen Identifikator muss man in die Fachanwendung übertragen und dort können die weiteren Informationen zum Geschäftsfall erfasst werden. Dieses Vorgehen öffnet natürlich Tür und Tor für Fehler. Wie damit umgehen? Wie verhindern? INTERLIS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir müssen nur ein Datenmodell erstellen, das die Geometrie von den Sachdaten in zwei Klassen aufteilt und eine Beziehung zwischen den Klassen herstellen. Die Beziehung muss jedoch so definiert werden (Zauberwort &lt;code&gt;EXTERNAL&lt;/code&gt;), dass Bezüge zwischen Objekten in unterschiedlichen Behältern zugelassen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;MODEL SO_AFU_Abbaustellen_20200918 (de)
AT &quot;http://afu.so.ch&quot;
VERSION &quot;2020-09-18&quot;  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC Abbaustellen =
    OID AS INTERLIS.UUIDOID;

    /** Abbaustelle (aus Fachanwendung ohne Geometrie)
     */
    CLASS Abbaustelle =
      Nummer : MANDATORY TEXT*1024;
      Name : MANDATORY TEXT*1024;
      Bemerkungen : MTEXT*1024;
    END Abbaustelle;

    /** Geometrie zu einer Abbaustelle. Getrennte Erfassung (Fachanwendung - Desktop-GIS)
     */
    CLASS Geometrie =
      Geometrie : MANDATORY GeometryCHLV95_V1.SurfaceWithOverlaps2mm;
    END Geometrie;

    /** Verknüpfung zwischen genau einer Abbaustelle und einer Geometrie.
     *
     * In welcher Tabellen wird der Fremdschlüssel in der Datenbank angelegt? In welcher Klasse wird die Beziehung im XTF eingebettet?
     *
     * Falls bei beiden (Basis-)Rollen die maximale Kardinalität kleiner gleich 1 ist, wird bei der Ziel-Klasse der zweiten  Rolle eingebettet. (Kap. 3.3.9 (und 3.3.7)).
     */
    ASSOCIATION Abbaustelle_Geometrie =
      Geometrie (EXTERNAL) -- {1} Geometrie;
      Abbaustelle -- {1} Abbaustelle;
    END Abbaustelle_Geometrie;

  END Abbaustellen;

END SO_AFU_Abbaustellen_20200918.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Fachanwendung schickt uns für die Integration in die GDI (zur Publikation im Web GIS Client) nur eine INTERLIS-Transferdatei mit Objekten der Klasse &lt;code&gt;Abbaustelle&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot;&amp;gt;
  &amp;lt;HEADERSECTION SENDER=&quot;ili2pg-4.4.2-7b1d50437cd6970a801b16d177c4e27151414569&quot; VERSION=&quot;2.3&quot;&amp;gt;
    &amp;lt;MODELS&amp;gt;
      &amp;lt;MODEL NAME=&quot;CoordSys&quot; VERSION=&quot;2015-11-24&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;Units&quot; VERSION=&quot;2012-02-20&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV03_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV95_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;SO_AFU_Abbaustellen_20200918&quot; VERSION=&quot;2020-09-18&quot; URI=&quot;http://afu.so.ch&quot;/&amp;gt;
    &amp;lt;/MODELS&amp;gt;
  &amp;lt;/HEADERSECTION&amp;gt;
  &amp;lt;DATASECTION&amp;gt;
    &amp;lt;SO_AFU_Abbaustellen_20200918.Abbaustellen BID=&quot;bX&quot;&amp;gt;
      &amp;lt;SO_AFU_Abbaustellen_20200918.Abbaustellen.Abbaustelle TID=&quot;5c6be6dd-7111-42fd-9eae-bd46fefa3c93&quot;&amp;gt;
        &amp;lt;Nummer&amp;gt;5432&amp;lt;/Nummer&amp;gt;
        &amp;lt;Name&amp;gt;Hellstätt&amp;lt;/Name&amp;gt;
        &amp;lt;Bemerkungen&amp;gt;Fubar&amp;lt;/Bemerkungen&amp;gt;
        &amp;lt;Geometrie REF=&quot;5e5bb99e-2f68-499e-aebe-d01f05b9ea88&quot;/&amp;gt;
      &amp;lt;/SO_AFU_Abbaustellen_20200918.Abbaustellen.Abbaustelle&amp;gt;
    &amp;lt;/SO_AFU_Abbaustellen_20200918.Abbaustellen&amp;gt;
  &amp;lt;/DATASECTION&amp;gt;
&amp;lt;/TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Attribut &lt;code&gt;REF&lt;/code&gt; im Element &lt;code&gt;Geometrie&lt;/code&gt; enthält den fixen Identifikator der Geometrie. Der GIS-Teil sieht in INTERLIS so aus (siehe &lt;code&gt;TID&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;TRANSFER xmlns=&quot;http://www.interlis.ch/INTERLIS2.3&quot;&amp;gt;
  &amp;lt;HEADERSECTION SENDER=&quot;ili2pg-4.4.2-7b1d50437cd6970a801b16d177c4e27151414569&quot; VERSION=&quot;2.3&quot;&amp;gt;
    &amp;lt;MODELS&amp;gt;
      &amp;lt;MODEL NAME=&quot;CoordSys&quot; VERSION=&quot;2015-11-24&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;Units&quot; VERSION=&quot;2012-02-20&quot; URI=&quot;http://www.interlis.ch/models&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV03_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;GeometryCHLV95_V1&quot; VERSION=&quot;2017-12-04&quot; URI=&quot;http://www.geo.admin.ch&quot;/&amp;gt;
      &amp;lt;MODEL NAME=&quot;SO_AFU_Abbaustellen_20200918&quot; VERSION=&quot;2020-09-18&quot; URI=&quot;http://afu.so.ch&quot;/&amp;gt;
    &amp;lt;/MODELS&amp;gt;
  &amp;lt;/HEADERSECTION&amp;gt;
  &amp;lt;DATASECTION&amp;gt;
    &amp;lt;SO_AFU_Abbaustellen_20200918.Abbaustellen BID=&quot;b1&quot;&amp;gt;
      &amp;lt;SO_AFU_Abbaustellen_20200918.Abbaustellen.Geometrie TID=&quot;5e5bb99e-2f68-499e-aebe-d01f05b9ea88&quot;&amp;gt;
        &amp;lt;Geometrie&amp;gt;
          &amp;lt;SURFACE&amp;gt;
            &amp;lt;BOUNDARY&amp;gt;
              &amp;lt;POLYLINE&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629140.305&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245681.759&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629143.746&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245586.181&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629227.280&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245582.550&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629240.470&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245648.689&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629196.696&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245685.773&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
                &amp;lt;COORD&amp;gt;
                  &amp;lt;C1&amp;gt;2629140.305&amp;lt;/C1&amp;gt;
                  &amp;lt;C2&amp;gt;1245681.759&amp;lt;/C2&amp;gt;
                &amp;lt;/COORD&amp;gt;
              &amp;lt;/POLYLINE&amp;gt;
            &amp;lt;/BOUNDARY&amp;gt;
          &amp;lt;/SURFACE&amp;gt;
        &amp;lt;/Geometrie&amp;gt;
      &amp;lt;/SO_AFU_Abbaustellen_20200918.Abbaustellen.Geometrie&amp;gt;
    &amp;lt;/SO_AFU_Abbaustellen_20200918.Abbaustellen&amp;gt;
  &amp;lt;/DATASECTION&amp;gt;
&amp;lt;/TRANSFER&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Liegen sowohl die Daten aus der Fachanwendung wie auch die Geometrien als INTERLIS-Transferdatei vor, kann &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; die Daten prüfen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar ilivalidator-1.11.6.jar --allObjectsAccessible abbaustellen_geometrie.xtf abbaustellen_fachanwendung.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wobei es hier noch einen Bug gibt: &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/276&quot;&gt;https://github.com/claeis/ilivalidator/issues/276&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man muss aber die Geometrien gar nicht nach INTERLIS exportieren, um die Konsistenz zwischen Fachanwendungsdaten und Geometriedaten zu prüfen. Der Versuch eines Importes der Fachanwendungsdaten in die Datenbank reicht für den Fall von Sachobjekten, die ins Nirvana zeigen. In diesem Fall können die Daten gar nicht importiert werden, weil der Primary Key (zum Fremdschlüssel) fehlt (&amp;laquo;dangling reference&amp;raquo;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie überzeugt man aber die Firma, die kein GIS machen will, von INTERLIS? Gar nicht. Man sagt einfach, dass sie einfachstes XML herstellen müssen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Variante 1: Jaxb&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Möglichkeit ist der Weg über das automatische Erzeugen von Java-Klassen aus dem XSD, welches aus dem INTERLIS-Datenmodell einmalig automatisch erstellt werden muss. Diese Java-Klassen muss ich dann nur noch mit Inhalt befüllen und kann sie nach XML (also XTF) serialisieren. Dieses Serialisieren übernimmt ebenfalls die Programmierbibliothek. D.h. ich muss mich nicht um XML-Formatierungen etc. kümmern, sondern nur um den Inhalt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Abbaustellen-Element wird z.B. wie folgt erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;SOAFUAbbaustellen20200918AbbaustellenAbbaustelle abbaustelle = new SOAFUAbbaustellen20200918AbbaustellenAbbaustelle();
abbaustelle.setTID(abbauObj.getTid());
abbaustelle.setNummer(abbauObj.getNummer());
abbaustelle.setName(abbauObj.getName());
abbaustelle.setBemerkungen(abbauObj.getBemerkungen());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein komplettes Minimalbeispiel gibt es &lt;a href=&quot;https://github.com/edigonzales/afu_abbaustellen_jaxb/blob/master/src/main/java/ch/so/agi/XtfWriter.java&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Variante 2: Templating&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine zweite Variante ist die Verwendung einer Templating-Engine. Templating klingt zuerst immer einfach und effizient, hat aber meines Erachtens den Nachteil wenn es um Fehlersuche geht und/oder wenn es komplizierter wird. In diesem Fall ist es natürlich sehr einfach. Ein wenig Groovy-Magie:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration

class Abbaustelle {
    String tid
    String nummer
    String name
    String bemerkungen
    String geomRef
}

def model = [abbaustellen: [new Abbaustelle(tid: &quot;5c6be6dd-7111-42fd-9eae-bd46fefa3c93&quot;, nummer: &quot;5432&quot;, name: &quot;Hellstätt&quot;, bemerkungen: &quot;Fubar&quot;, geomRef: &quot;5e5bb99e-2f68-499e-aebe-d01f05b9ea88&quot;)]]

def template = &quot;&quot;&quot;
xmlDeclaration()
TRANSFER(xmlns: &quot;http://www.interlis.ch/INTERLIS2.3&quot;) {
    HEADERSECTION(SENDER: &quot;some-groovy-fairy-dust&quot;, VERSION: &quot;2.3&quot;) {
        MODELS {
            MODEL(NAME: &quot;CoordSys&quot;, VERSION: &quot;2015-11-24&quot;, URI: &quot;http://www.interlis.ch/models&quot;)
            MODEL(NAME: &quot;GeometryCHLV03_V1&quot;, VERSION: &quot;2017-12-04&quot;, URI: &quot;http://www.geo.admin.ch&quot;)
            MODEL(NAME: &quot;GeometryCHLV95_V1&quot;, VERSION: &quot;2017-12-04&quot;, URI: &quot;http://www.geo.admin.ch&quot;)
            MODEL(NAME: &quot;SO_AFU_Abbaustellen_20200918&quot;, VERSION: &quot;2020-09-18&quot;, URI: &quot;http://afu.so.ch&quot;)
        }
    }
    DATASECTION {
        &apos;SO_AFU_Abbaustellen_20200918.Abbaustellen&apos;(BID: &quot;bX&quot;) {
            abbaustellen.each { abbauObj -&amp;gt;
                &apos;SO_AFU_Abbaustellen_20200918.Abbaustellen.Abbaustelle&apos;(TID: abbauObj.tid) {
                    Nummer(abbauObj.nummer)
                    Name(abbauObj.name)
                    Bemerkung(abbauObj.bemerkungen)
                    Geometrie(REF: abbauObj.geomRef)
                }
            }
        }
    }
}
&quot;&quot;&quot;
TemplateConfiguration config = new TemplateConfiguration();
config.setAutoIndent(true)
config.setAutoNewLine(true)
def abbaustellenXml = new MarkupTemplateEngine(config).createTemplate(template).make(model)

println abbaustellenXml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;INTERLIS ohne INTERLIS für Fachanwendungen, die GIS machen müssen aber kein GIS machen können und kein GIS machen wollen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #20 - ilivalidator goodies</title>
      <link>http://blog.sogeo.services/blog/2019/09/23/interlis-leicht-gemacht-number-20.html</link>
      <pubDate>Mon, 23 Sep 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2019/09/23/interlis-leicht-gemacht-number-20.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Daten der Nutzungsplanung nehmen beim ÖREB-Kataster eine sehr wichtige Rolle ein. Das setzt eine hohe inhaltliche, aber auch strukturelle Qualität der digitalen Daten voraus. Die Daten werden in einem kantonalen &lt;a href=&quot;http://geo.so.ch/models/ARP/SO_Nutzungsplanung_20171118.ili&quot;&gt;Datenmodell&lt;/a&gt; erfasst, das sowohl &lt;a href=&quot;http://models.geo.admin.ch/ARE/Nutzungsplanung_V1_1.ili&quot;&gt;MGDM&lt;/a&gt; wie auch &lt;a href=&quot;http://models.geo.admin.ch/V_D/OeREB/OeREBKRMtrsfr_V1_1.ili&quot;&gt;ÖREB-Rahmenmodell&lt;/a&gt; kompatibel ist. Die Daten werden durch externe Planungs- und Ingenieurbüros im Auftrag der Gemeinde erfasst. So weit, so gut.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo gearbeitet wird, passieren auch Fehler. Insbesondere schmerzen diese in der Weiterverarbeitung der Daten, wie jetzt bei der Integration in den ÖREB-Kataster. Damit diese möglichst frühzeitg entdeckt werden, drängt sich natürlich die maschinelle Prüfung mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; auf. Das wird zwar von Beginn an gemacht, wie sich aber jetzt herausstellt, reicht die pure Modellprüfung nicht, damit eine robuste und transparente Weiterverarbeitung sichergestellt ist. Dank den Möglichkeiten von &lt;em&gt;ilivalidator&lt;/em&gt; kann die Prüfung an notwendigen Stellen verschärft werden. Bei einigen Veschärfungen ist mir - Stand heute - schlichtweg nicht klar, warum man damals nicht das Modell bereits so modelliert hat&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Multiplicity&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Boolean-Attribut &lt;code&gt;Rechtsvorschrift&lt;/code&gt; in der Klasse &lt;code&gt;Dokument&lt;/code&gt; ist &lt;em&gt;nicht&lt;/em&gt; mandatory. Für die Weiterverarbeitung müssen wir aber wissen, ob ein Dokument eine Rechtsvorschrift ist oder eben nicht. Um das Datenmodell nicht ändern zu müssen, kann man in einem &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-web-service-nplso/blob/master/src/main/resources/ili/SO_Nutzungsplanung_20171118_Validierung_20190129_UTF8.ili&quot;&gt;&amp;laquo;Valididierungsmodell&amp;raquo;&lt;/a&gt; einen zusätzlichen Constraint definieren. Dazu muss man eine View der Dokumentenklasse erstellen und in dieser View den Constraint definieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;CONTRACTED MODEL SO_Nutzungsplanung_20171118_Validierung_20190129 (de)
AT &quot;http://www.geo.so.ch&quot;
VERSION &quot;2019-01-29&quot;  =
  IMPORTS SO_Nutzungsplanung_20171118;

  VIEW TOPIC Rechtsvorschriften_Validierung =
  DEPENDS ON SO_Nutzungsplanung_20171118.Rechtsvorschriften;

	VIEW v_Dokument
    	PROJECTION OF SO_Nutzungsplanung_20171118.Rechtsvorschriften.Dokument;
    =
        ALL OF Dokument;
        !!@ ilivalid.msg = &quot;Attribut &apos;Rechtsvorschrift&apos; muss definiert sein.&quot;
        MANDATORY CONSTRAINT DEFINED(Rechtsvorschrift);
    END v_Dokument;

  END Rechtsvorschriften_Validierung;

END SO_Nutzungsplanung_20171118_Validierung_20190129.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Validierungsmodell muss in der Config-Toml-Datei referenziert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;[&quot;PARAMETER&quot;]
additionalModels=&quot;SO_Nutzungsplanung_20171118_Validierung_20190129&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Programmaufruf ist wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar ilivalidator.jar --config myconfig.toml 2502.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Falsche Dokumentenlinks&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dabei geht es nicht um falsche Links im INTERLIS-Sinn (das prüft &lt;em&gt;ilivalidator&lt;/em&gt; von sich aus), sondern um Links, die nicht auf eine vorhandene HTTP-Ressource zeigen. Dazu macht man sich die Möglichkeit zu Nutze &lt;em&gt;ilivalidator&lt;/em&gt; mit eigenen in Java geschriebenen Funktionen zu erweitern. Im Prinzip müsste die Funktion nur den Status Code eines &lt;code&gt;HEAD&lt;/code&gt;-Befehls überprüfen. Das mit &lt;code&gt;HEAD&lt;/code&gt; ist aber tricky, da der Befehl aufgrund von Firewalls resp. falsch konfigurierten Webservern nicht immer etwas sinnvolles oder richtiges zurückliefert. Daher muss man wohl oder übel trotzdem einen &lt;code&gt;GET&lt;/code&gt;-Request absetzen. Bei mir sind jetzt alle Status Codes &lt;a href=&quot;https://github.com/sogis/ilivalidator-extension-functions/blob/master/src/main/java/ch/so/agi/ilivalidator/ext/CheckHttpRessourceIoxPlugin.java&quot;&gt;grösser 200 und kleiner 400&lt;/a&gt; in Ordnung. Alles andere führt zu einem Fehler.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die kompilierte Java-Klasse muss beim Aufruf &lt;em&gt;ilivalidator&lt;/em&gt; bekannt gemacht werden indem man ein Plugins-Verzeichnis angibt (&lt;code&gt;--plugins /path/to/plugin/dir/&lt;/code&gt;). Es reicht nicht, sie einfach im Classpath vorzuhalten. Dies ist vor allem dann nicht immer ideal, wenn man &lt;em&gt;ilivalidator&lt;/em&gt; nicht bloss auf der Kommandozeile aufruft, sondern z.B. als Web Service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit die Funktion in einem Constraint verwendet werden kann, muss sie vorgängig in einem Modell deklariert werden. Entweder macht man das im oben erwähnten Validierungsmodell oder man macht ein &lt;a href=&quot;https://github.com/edigonzales/ilivalidator-web-service-nplso/blob/master/src/main/resources/ili/SO_FunctionsExt.ili&quot;&gt;weiteres Modell&lt;/a&gt;, damit die Funktion mittels &lt;code&gt;IMPORTS&lt;/code&gt; auch in weiteren Modellen verwendet werden kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Validierungsmodell muss dann zusätzlich zu &lt;code&gt;SO_Nutzungsplanung_20171118&lt;/code&gt; auch das neue Modell importieren. Anschliessend kann der Constraint definiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;!!@ ilivalid.msg = &quot;Dokument &apos;https://geo.so.ch/docs/ch.so.arp.zonenplaene/Zonenplaene_pdf/{TextImWeb}&apos; wurde nicht gefunden.&quot;
MANDATORY CONSTRAINT SO_FunctionsExt.checkHttpRessource(TextImWeb, &quot;https://geo.so.ch/docs/ch.so.arp.zonenplaene/Zonenplaene_pdf/&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Falsch verknüpfte Dokumente in HinweisWeitereDokumente&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich zu den Rechtsvorschriften &lt;em&gt;müssen&lt;/em&gt; die gesetzlichen Grundlagen und &lt;em&gt;können&lt;/em&gt; weitere Informationen und Hinweise im ÖREB-Kataster erfasst werden. Entsprechend wurde das Rahmenmodell modelliert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p20/hinweisweiteredokumente_uml.png&quot; alt=&quot;HinweisWeitereDokumente UML&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;ASSOCIATION HinweisWeitereDokumente =
    Ursprung -- {0..*} Dokument;
    Hinweis (EXTERNAL) -- {0..*} Dokument;
    /** Hinweis auf spezifische Artikel.
    */
    ArtikelNr : BAG {0..*} OF OeREBKRM_V1_1.ArtikelNummer_;
END HinweisWeitereDokumente;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das sieht auf den ersten Blick relativ harmlos aus. Zur Nemesis wird es aber beim Prozessieren in einer Datenbank. Abgebildet wird die Assoziation klassisch in einer Cross-Reference-Table. Theoretisch kann diese Spirale unendlich sein. D.h. um alle Dokumente zu einem ÖREB zu erhalten, muss die &lt;a href=&quot;https://www.postgresql.org/docs/11/queries-with.html&quot;&gt;SQL-Query rekursiv&lt;/a&gt; sein. Rekursion bedeutet aber immer auch: &amp;laquo;Kann gnadenlos in die Hosen gehen.&amp;raquo; Nämlich dann, wenn kein geeignetes Abbruchkriterium definiert wurde. Das lernt man auf die schmerzhafte Weise: Zum Beispiel dann, wenn der DB-Server nur noch abgeschossen werden kann. Mit blindlings in guten Treu und Glaube die Query rattern lassen, ist somit nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt verschiedene Hebel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Die Query so schreiben, dass nur bis zu einer Tiefe von z.B. zehn Iterationen Dokumente gesucht werden. Viel mehr ist wohl kaum sinnvoll und auch kaum so erfasst.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Auf Datenbankseite die Executiontime einschränken oder die Grösse der Logfiles beschränken.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Daten vor dem Import in die Datenbank sauber prüfen, weil hier die Ursache der Probleme liegt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der erste aufgetretene Problemfall war, dass ein Dokument A auf sich selber zeigt. Das kann man mit einem &lt;code&gt;MANDATORY CONSTRAINT&lt;/code&gt; in der Assoziation prüfen. Kniffliger wurden folgende Fälle: Dokument A &amp;rarr; Dokument B &amp;rarr; Dokument C &amp;rarr; Dokument A. Dass ich eine eigene Java-Funktion schreiben musste, war mir schnell klar. Der erste Gedanken war zudem auch: kleine Sache. Überlegt man sich das aber ein wenig, merkt man schnell, dass so einfach die Sache nicht ist. Der dritte Gedanken war dann: Das Problem hat sicher schon jemand anderes gelöst und in letzer Konsequenz ist diese ganze Verschachtelung/Verknüpfung bloss ein Graph. Der Graph ist gerichtet und es dürfen &lt;a href=&quot;https://jgrapht.org/guide/UserOverview#graph-structures&quot;&gt;keine Self-Loops und Kanten dürfen nicht mehrfach vorkommen&lt;/a&gt;. Bibliotheken dazu gibt es mehr als genug. Ich habe mich für &lt;a href=&quot;https://jgrapht.org&quot;&gt;&lt;em&gt;jgrapht&lt;/em&gt;&lt;/a&gt; entschieden. Die ganze herausfordernde Arbeit - zu prüfen, &lt;a href=&quot;https://github.com/sogis/ilivalidator-extension-functions/blob/master/src/main/java/ch/so/agi/ilivalidator/ext/oereb/LinkGraphCache.java&quot;&gt;ob falsche Verknüpfungen vorliegen&lt;/a&gt; - nimmt mir die Bibliothek ab und ich muss mich nur noch um das &lt;a href=&quot;https://github.com/sogis/ilivalidator-extension-functions/blob/master/src/main/java/ch/so/agi/ilivalidator/ext/oereb/DocumentsCycleCheckIoxPlugin.java&quot;&gt;Integrieren&lt;/a&gt; in die &lt;em&gt;ilivalidator&lt;/em&gt;-Welt bemühen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier wird die Integration der Zusatzfunktion für den Betrieb noch ein wenig komplizierter, da wir eine Drittbibliothek verwenden müssen. Für den Aufruf auf der Konsole ist es - soweit ich mich erinnere - noch mühsamer. &lt;em&gt;Ilivalidator&lt;/em&gt; findet auf Anhieb die Drittbibliothek nicht und mann muss den Befehl &lt;a href=&quot;http://blog.sogeo.services/blog/2017/02/13/interlis-leicht-gemacht-number-14.html&quot;&gt;anders formulieren&lt;/a&gt;. Wenn man die Zusatzfunktion in einem Web Service einsetzen will, ist es wieder einfacher, da man sowieso ein Buildtool verwendet, das sich um die Abhängigkeiten kümmert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nichtsdestotrotz muss sinnvollerweise zusätzlich auch an den ersten beiden Hebeln angesetzt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Typen müssen zwingend mit einem Dokument verknüpft sein&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im kantonalen Nutzungsplanungsmodell ist die Assoziation zwischen den Typen (= Eigentumsbeschränkungen) und den Dokumenten sehr lasch. Ein Typ kann keines oder mehrere Dokumente haben und ein Dokument kann keinem oder mehreren Typen zugeordnet sein. Im ÖREB-Kataster muss jede Eigentumsbeschränkung mindestens ein Dokument haben. D.h. wir müssen für verschiedene Typen zwingend prüfen, ob sie mit einem Dokument verknüpft sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der erste Teil geht sehr elegant:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;CONSTRAINTS OF Typ_Grundnutzung =
    !!@ ilivalid.msg = &quot;Typ &apos;{Typ_Kt}&apos; (Typ_Grundnutzung) ist mit keinem Dokument verknüpft.&quot;
    MANDATORY CONSTRAINT INTERLIS.objectCount(Dokument)&amp;gt;=1
END;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Constraint muss im Modell &lt;em&gt;nach&lt;/em&gt; der Assoziation stehen. Er prüft ob die Summe der verknüpften Dokumente zu einem Objekt &lt;code&gt;Typ_Grundnutzung&lt;/code&gt; grösser gleich 1 ist. Das gilt bei uns nicht für alle Typen der Grundnutzung, sondern nur für ein Subset. Ob es kürzer und schöner geht, weiss ich nicht. Bei mir sieht das Filtering der Typen so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;CONSTRAINTS OF Typ_Grundnutzung =
    !!@ ilivalid.msg = &quot;Typ &apos;{Typ_Kt}&apos; (Typ_Grundnutzung) ist mit keinem Dokument verknüpft.&quot;
    MANDATORY CONSTRAINT
        (
            INTERLIS.objectCount(Dokument)&amp;gt;=1
            AND
            (
                Typ_Kt == #N110_Wohnzone_1_G
                OR
                Typ_Kt == #N111_Wohnzone_2_G
                OR
                .... viele mehr
            )
        )
        OR
        (
            Typ_Kt == #N180_Verkehrszone_Strasse
            OR
            Typ_Kt == #N181_Verkehrszone_Bahnareal
            OR
            ... viele mehr
        );
END;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Subset von verknüpften Objekten bilden eine AREA&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Objekte der Lärmempfindlichkeitstufen werden im kantonalen Modell bei den überlagernden Flächen der Nutzungsplanung erfasst. Die Geometrien der Lärmempfindlichkeitsstufen alleine betrachtet, müssen eine AREA bilden, d.h. sie dürfen sich nicht überlappen. Da die Geometrien und die Typen aber in zwei verschiedenen Klassen verwaltet werden, muss ähnlich vorgegangen werden wie beim vorangegangenen Beispiel, nur dass dieses Mal ein &lt;code&gt;SET CONSTRAINT&lt;/code&gt; verwendet werden muss:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;CONSTRAINTS OF Ueberlagernd_Flaeche =
    !!@ name = laermempfindlichkeitsAreaCheck
    !! !!@ ilivalid.msg = &quot;Lärmempfindlichkeitstypen überlappen sich.&quot;
    SET CONSTRAINT
        WHERE
        (
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N680_Empfindlichkeitsstufe_I
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N681_Empfindlichkeitsstufe_II
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N682_Empfindlichkeitsstufe_II_aufgestuft
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N683_Empfindlichkeitsstufe_III
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N684_Empfindlichkeitsstufe_III_aufgestuft
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N685_Empfindlichkeitsstufe_IV
            OR
            Typ_Ueberlagernd_Flaeche-&amp;gt;Typ_Kt==#N686_keine_Empfindlichkeitsstufe
        ) : INTERLIS.areAreas(ALL, UNDEFINED, &amp;gt;&amp;gt; Geometrie);
END;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Bugs&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es sind bei der Erarbeitung dieser zusätzlichen aber notwendigen Validierungen einige Bugs in &lt;em&gt;ilivalidator&lt;/em&gt; entdeckt worden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/180&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ilivalidator/issues/180&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/196&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ilivalidator/issues/196&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/203&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ilivalidator/issues/203&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/204&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ilivalidator/issues/204&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/205&quot; class=&quot;bare&quot;&gt;https://github.com/claeis/ilivalidator/issues/205&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die meisten konnte ich einen, teilweise sehr unschönen, Workaround finden. Aber das Bugfixing müssen wir zügig angehen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GraalVM #1 - INTERLIS polyglot gemacht</title>
      <link>http://blog.sogeo.services/blog/2019/02/23/graalvm-p1-interlis-polyglot-gemacht.html</link>
      <pubDate>Sat, 23 Feb 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2019/02/23/graalvm-p1-interlis-polyglot-gemacht.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM? Was ist das? Eine &amp;laquo;High-performance polyglot VM&amp;raquo;. Fazit: Wirklich sowas wie der Heilige Gral. Oder gemäss &lt;a href=&quot;https://www.graalvm.org/&quot;&gt;Homepage&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM is a universal virtual machine for running applications written in JavaScript, Python, Ruby, R, JVM-based languages like Java, Scala, Kotlin, Clojure, and LLVM-based languages such as C and C++.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GraalVM removes the isolation between programming languages and enables interoperability in a shared runtime. It can run either standalone or in the context of OpenJDK, Node.js, Oracle Database, or MySQL.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;attribution&quot;&gt;
&amp;#8212; https://www.graalvm.org/
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das erste Mal bin ich darübergestolpert als Oracle eine Open Source Community Version veröffentlicht hat und das sicher den üblichen Kanälen eine Nachricht wert war. Vor kurzem habe ich wieder was darüber gelesen, als ich was mit &lt;a href=&quot;http://blog.sogeo.services/blog/2018/12/10/faas-1-interlis-webservice.html&quot;&gt;&lt;em&gt;Project Fn&lt;/em&gt;&lt;/a&gt; machen wollte. Das Problem mit Java sind bei FaaS-Geschichten die &amp;laquo;cold start times&amp;raquo;. Das &lt;a href=&quot;https://medium.com/criciumadev/serverless-native-java-functions-using-graalvm-and-fn-project-c9b10a4a4859&quot;&gt;soll mit &lt;em&gt;GraalVM&lt;/em&gt;&lt;/a&gt; besser sein. Ein wenig rumgegoogelt und eine &lt;a href=&quot;https://chrisseaton.com/truffleruby/tenthings/&quot;&gt;höchst interessante Zusammenstellung&lt;/a&gt; der Möglichkeiten von &lt;em&gt;GraalVM&lt;/em&gt; gefunden. Ganz abgefahren wird es bei Punkt 8 &amp;laquo;Java code as native library&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man kann mit &lt;em&gt;Graal&lt;/em&gt; Java-Code zu &lt;em&gt;native executables&lt;/em&gt; kompilieren. Damit wird eine JVM für die Ausführung des Programmes unnötig. Es funktioniert jedoch nicht nur mit kompletten Programmen, sondern auch mit Bibliotheken, die anschliessend z.B. in einem C-Pogramm als &lt;em&gt;shared libraries&lt;/em&gt; eingebunden werden können. Ganz so einfach oder perfekt wie man sich das jetzt vielleicht vorstellt, ist es natürlich nicht. Es gibt verschiedene &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md&quot;&gt;Bedingungen&lt;/a&gt; und &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md&quot;&gt;Einschränkungen&lt;/a&gt;, die leider (noch) viel zu schnell auftreten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So ein kleines Programm wie im verlinkten Blog geht natürlich problemlos. Funktioniert aber auch was grösseres wie z.B. &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes besorgt man sich GraalVM von &lt;a href=&quot;https://github.com/oracle/graal/releases&quot;&gt;Github&lt;/a&gt;. Ich habe bei mir die Umgebungsvariable &lt;code&gt;GRAALVM_HOME&lt;/code&gt; gesetzt, die auf das Verzeichnis &lt;code&gt;/Users/stefan/apps/graalvm-ce-1.0.0-rc12/Contents/Home&lt;/code&gt; zeigt. Unter Linux kann das z.B. so ausssehen: &lt;code&gt;/home/vagrant/graalvm-ce-1.0.0-rc12&lt;/code&gt;. Die &lt;code&gt;JAVA_HOME&lt;/code&gt;-Umgebungsvariable zeigt ebenfalls auf dieses Verzeichnis.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;java -version&lt;/code&gt; liefert bei mir folgenden Output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;openjdk version &quot;1.8.0_192&quot;
OpenJDK Runtime Environment (build 1.8.0_192-20181024123616.buildslave.jdk8u-src-tar--b12)
GraalVM 1.0.0-rc12 (build 25.192-b12-jvmci-0.54, mixed mode)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes wird der ilivalidator-Quellcode heruntergeladen und mit &lt;em&gt;Gradle&lt;/em&gt; kompiliert: &lt;code&gt;gradle clean build bindist -x test&lt;/code&gt;. Einige der Tests laufen bei mir nicht durch, darum das &lt;code&gt;-x&lt;/code&gt;. &lt;code&gt;bindist&lt;/code&gt; erstellt eine Zipdatei mit der ilivalidator.jar-Datei und den benötigten Bibliotheken (iox-ili, ili2c, &amp;#8230;&amp;#8203;). Am besten hängt man folgende Befehle zusammen, da die Zip-Datei sowieso immer ausgepackt werden muss:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;gradle clean build bindist -x test &amp;amp;&amp;amp; unzip dist/ilivalidator-1.10.1-SNAPSHOT.zip -d dist/ilivalidator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem Programm &lt;code&gt;$GRAALVM_HOME/bin/native-image&lt;/code&gt; kann &lt;em&gt;ilivalidator&lt;/em&gt; in eine &lt;em&gt;native executable&lt;/em&gt; kompiliert werden, die keine virtuelle Maschine mehr benötigt. So plusminus aus ein wenig Anleitung und Internet der erste Versuch:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;$GRAALVM_HOME/bin/native-image --verbose --no-server -cp &quot;libs/antlr-2.7.6.jar:libs/ehibasics-1.2.0.jar:libs/ili2c-core-4.7.10.jar:libs/ili2c-tool-4.7.10.jar:libs/iox-api-1.0.3.jar:libs/iox-ili-1.20.10.jar:libs/jts-core-1.14.0.jar:ilivalidator-1.10.1-SNAPSHOT.jar&quot; org.interlis2.validator.Main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Läuft nicht durch. Es gibt Fehler bezüglich java2d- und awt-Klassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/graalvm_p1/graalvm_01.png&quot; alt=&quot;graalvm_01&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier entschied ich mich alles GUI-Zeugs aus &lt;em&gt;ilivalidator&lt;/em&gt; zu entfernen. Gleichzeitig habe ich die Möglichkeit der &lt;em&gt;ilivalidator&lt;/em&gt; Custom Functions entfernt, da - soweit ich es verstanden habe - zum Zeitpunkt des Kompilierens klar sein muss, welche Klassen verwendet werden. Das ist bei den Custom Functions nicht der Fall, da diese erst beim Starten von &lt;em&gt;ilivalidator&lt;/em&gt; geladen/registriert werden. Also: Brutal viel gelöscht und auskommentiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Irgendeinmal war das Erstellen des Binaries erfolgreich. Das Resultat ist eine circa 20MB grosse ausführbare Datei &lt;code&gt;org.interlis2.validator.main&lt;/code&gt;. Der anschliessende Versuch &lt;em&gt;ilivalidator&lt;/em&gt; auszuführen, war leider nur halb erfolgreich, da das &lt;em&gt;native executable&lt;/em&gt; eine Ressource nicht finden konnte. Es handelte sich dabei um eine &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/src/org/interlis2/validator/Version.properties&quot;&gt;Versions-Properties-Datei&lt;/a&gt;. Der Umgang mit &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md&quot;&gt;Ressourcen&lt;/a&gt; ist klar geregelt. Da bin ich wahrscheinlich falsch abgebogen: Ich dachte, es wäre einfacher das zu ignorieren und dass es einfacher wäre kurz mal den Quellcode anzupassen. Falsch, weil andere benötigte Bibliotheken (&lt;em&gt;iox-ili&lt;/em&gt;, &lt;em&gt;ili2c&lt;/em&gt;) ebenfalls eine solche Datei haben und diese zur Laufzeit ausgelesen werden und der Inhalt in die Konsole geschrieben wird. D.h. den Quellcode der weiteren Bibliotheken musste ich auch anpassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Trotzdem herrschte Freude, als &lt;code&gt;./org.interlis2.validator.main --help&lt;/code&gt; funktionierte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/graalvm_p1/graalvm_02.png&quot; alt=&quot;graalvm_02&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der logische nächste Schritt ist das Prüfen einer INTERLIS-Transferdatei, was natürlich auch nicht auf Anhieb funktionierte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/graalvm_p1/graalvm_03.png&quot; alt=&quot;graalvm_03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Fehlermeldung ist klar und die Lösung auch: Man muss das HTTP- und HTTPS-Protokoll freischalten. Das passt, da &lt;em&gt;ilivalidator&lt;/em&gt; die benötigten INTERLIS-Modelle in den Modellablagen suchen geht und daher via HTTP und HTTPS kommunizieren muss. &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/URL-PROTOCOLS.md&quot;&gt;Mehr Informationen&lt;/a&gt; zu diesem Thema findet sich ebenfalls im Github-Repo von &lt;em&gt;Graal&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nächster Versuch, nächster Fehler:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/graalvm_p1/graalvm_04.png&quot; alt=&quot;graalvm_04&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;ClassNotFoundException&lt;/code&gt;&amp;#8230;&amp;#8203; Irgendeinmal versteht man auch mit Halbwissen immer wie mehr und/oder bekommt den richtigen Riecher für des Rätsels Lösung. Liest man sich durch die &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md&quot;&gt;Limitations&lt;/a&gt;, bleibt man hoffentlich beim Kapitel &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md#reflection&quot;&gt;&amp;laquo;Reflection&amp;raquo;&lt;/a&gt; hängen. Meine &lt;code&gt;ClassNotFoundException&lt;/code&gt; könnte (?) ja in diese Kategorie fallen. Die Lösung ist auch relativ einfach. Man muss dem &lt;code&gt;native-image&lt;/code&gt;-Befehl (&lt;code&gt;-H:ReflectionConfigurationFiles=../../reflection.json&lt;/code&gt;) in einer JSON-Datei mitteilen, welche Klassen mittels Reflection verwendet werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;[
  {
    &quot;name&quot; : &quot;antlr.CommonToken&quot;,
    &quot;methods&quot;: [
      { &quot;name&quot;: &quot;&amp;lt;init&amp;gt;&quot;, &quot;parameterTypes&quot;: [] }
    ]
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es folgen noch weitere Fehlermeldungen bezüglich fehlenden Klassen. Diese habe allesamt in der JSON-Datei eingetragen. Der nächste Fehler war wieder eine nicht vorhandene Ressource. Da wurde es mir zu blöde und anstatt den Code anzupassen, habe ich einfach dem &lt;code&gt;native-image&lt;/code&gt;-Befehl die Option gemäss Fehlermeldung (&lt;code&gt;-H:IncludeResourceBundles=&amp;#8230;&amp;#8203;&lt;/code&gt;) mitgeliefert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und voilà, die Validierung läuft durch:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/graalvm_p1/graalvm_05.png&quot; alt=&quot;graalvm_05&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mein vollständiger Befehl lautet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;$GRAALVM_HOME/bin/native-image --verbose --enable-url-protocols=http,https -H:ReflectionConfigurationFiles=../../reflection.json -H:IncludeResourceBundles=ch.interlis.ili2c.metamodel.ErrorMessages --report-unsupported-elements-at-runtime --allow-incomplete-classpath -H:-UseServiceLoaderFeature --delay-class-initialization-to-runtime=com.sun.naming.internal.ResourceManager$AppletParameter -H:+ReportExceptionStackTraces --no-server -cp &quot;libs/antlr-2.7.6.jar:libs/ehibasics-1.2.0.jar:libs/ili2c-core-4.7.10.jar:libs/ili2c-tool-4.7.10.jar:libs/iox-api-1.0.3.jar:libs/iox-ili-1.20.11-SNAPSHOT.jar:libs/jts-core-1.14.0.jar:ilivalidator-1.10.1-SNAPSHOT.jar&quot; org.interlis2.validator.Main&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einen kleinen Haken hat die Sache noch:  Wenn &lt;em&gt;ilivalidator&lt;/em&gt; zur Laufzeit wirklich via HTTPS kommunizieren muss, fliegt es mir um die Ohren, wegen einer nicht vorhandenen Bibliothek: &lt;code&gt;The sunec native library, required by the SunEC provider, could not be loaded&lt;/code&gt;. Auch dazu gibt es bereits &lt;a href=&quot;https://github.com/oracle/graal/blob/master/substratevm/JCA-SECURITY-SERVICES.md&quot;&gt;Informationen&lt;/a&gt; und einen aufschlussreichen &lt;a href=&quot;https://github.com/oracle/graal/issues/951&quot;&gt;Issue&lt;/a&gt; auf Github. Ich habe es gleich gelöst wie der Reporter des Tickets, d.h. in der Datei &lt;code&gt;$GRAALVM_HOME/jre/lib/security/java.security&lt;/code&gt; den SunEC-Provider auskommentiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Links, die geholfen haben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://chrisseaton.com/truffleruby/tenthings/&quot; class=&quot;bare&quot;&gt;https://chrisseaton.com/truffleruby/tenthings/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://picocli.info/picocli-on-graalvm.html&quot; class=&quot;bare&quot;&gt;https://picocli.info/picocli-on-graalvm.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://royvanrijn.com/blog/2018/09/part-1-java-to-native-using-graalvm/&quot; class=&quot;bare&quot;&gt;https://royvanrijn.com/blog/2018/09/part-1-java-to-native-using-graalvm/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://royvanrijn.com/blog/2018/09/part-2-native-microservice-in-graalvm/&quot; class=&quot;bare&quot;&gt;https://royvanrijn.com/blog/2018/09/part-2-native-microservice-in-graalvm/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/criciumadev/serverless-native-java-functions-using-graalvm-and-fn-project-c9b10a4a4859&quot; class=&quot;bare&quot;&gt;https://medium.com/criciumadev/serverless-native-java-functions-using-graalvm-and-fn-project-c9b10a4a4859&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sites.google.com/a/athaydes.com/renato-athaydes/posts/a7mbnative-imagejavaappthatrunsin30msandusesonly4mbofram&quot; class=&quot;bare&quot;&gt;https://sites.google.com/a/athaydes.com/renato-athaydes/posts/a7mbnative-imagejavaappthatrunsin30msandusesonly4mbofram&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://e.printstacktrace.blog/graalvm-groovy-grape-creating-native-image-of-standalone-script/&quot; class=&quot;bare&quot;&gt;https://e.printstacktrace.blog/graalvm-groovy-grape-creating-native-image-of-standalone-script/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ob &lt;em&gt;ilivalidator&lt;/em&gt; das ideale Beispiel / der ideale Einsatzzweck für &lt;em&gt;native executables&lt;/em&gt; ist, weiss ich nicht. Performancemässig bringt es wohl gar nichts. Interessanter dürfte es vielleicht für das &lt;a href=&quot;https://github.com/opengisch/QgisModelBaker&quot;&gt;&lt;em&gt;QGIS Model Baker&lt;/em&gt;&lt;/a&gt;-Projekt sein. Da kann man sich vorstellen, dass der Umgang der &lt;em&gt;ili2pg/ili2gpkg&lt;/em&gt;-Abhängigkeiten einfacher werden könnte. Oder man erstellt nicht die ganze Anwendung, sondern bloss eine &lt;em&gt;shared library&lt;/em&gt;, damit die INTERLIS-Validierungsfunktionen von &lt;em&gt;ilivalidator&lt;/em&gt; einfacher in anderen Programmiersprachen resp. Programmen eingebunden werden kann und die Validierungsfunktionen nicht mittels externen Java-Aufrufen erfolgen müssen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #19 - Modellablage automatisieren</title>
      <link>http://blog.sogeo.services/blog/2019/02/22/interlis-leicht-gemacht-number-19.html</link>
      <pubDate>Fri, 22 Feb 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2019/02/22/interlis-leicht-gemacht-number-19.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es ging relativ lange bis ich überhaupt geschnallt habe, dass die &lt;a href=&quot;http://models.interlis.ch/ModelRepository.pdf&quot;&gt;INTERLIS-Modellablage&lt;/a&gt;  auch eine INTERLIS-Transferdatei mit dazugehörigem INTERLIS-Modell ist. Im Grunde genommen sind es zwei Modelle (&lt;code&gt;IliRepository09&lt;/code&gt; und &lt;code&gt;IliSite09&lt;/code&gt;) und zwei Transferdateien, wobei letztere in der Regel kaum nachgeführt werden muss. Bei &lt;a href=&quot;https://agi.so.ch&quot;&gt;uns&lt;/a&gt; ist diese Nachführung ein händischer Prozess:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Neues Modell in einen Filesystem-Ordner auf einem Server kopieren.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Anpassen der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei inkl. Berechnen md5-Prüfsumme der neuen Modell-Datei.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Überprüfen der Dateirechte auf dem Filesystem, damit die Dateien später für andere Mitarbeiter und den Webserver lesbar bleiben&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validieren der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prüfen der INTERLIS-Modellablage mit &lt;em&gt;ili2c&lt;/em&gt; und dem &lt;code&gt;--check-repo-ilis&lt;/code&gt;-Befehl.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ein Nacht warten bis der Filesystem-Ordner in die globale Zone kopiert wurde. Erst dann steht das Modell im Internet allen zur Verfügung.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei diesem Nachführungsprozess nervt fast alles. Zuviele manuelle Prozesse, furchtbares Gefrickel mit den Dateirechten, eine Nacht warten, bis wirklich verfügbar&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine erste Idee war den Nachführungsprozess &amp;laquo;irgendwie&amp;raquo; mit &lt;a href=&quot;https://qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; zu machen. Ich hätte alles in einem GitHub-Repository abgelegt, d.h. sowohl Modelle wie auch QGIS und Datenbank (mit &lt;a href=&quot;https://www.vagrantup.com/&quot;&gt;&lt;em&gt;Vagrant&lt;/em&gt;&lt;/a&gt;). Schien schlussendlich auch wieder kompliziert. Überhaupt war da plötzlich die Frage, ob man menschlichen Input braucht, ausser das Modell irgendwo hinzukopieren? Es stellte sich heraus, dass es unter gewissen Umständen keinen manuellen Eingriff braucht. Was heisst das?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die erste mir &lt;a href=&quot;http://models.geo.admin.ch&quot;&gt;bekannte Modellablage&lt;/a&gt; hat die TID in der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei stark &lt;a href=&quot;http://models.geo.admin.ch/ilimodels.xml&quot;&gt;formalisiert&lt;/a&gt;. Jedes Amt bekommt einen eigenen Range. Dieses Vorgehen haben wir bei der Einführung &lt;a href=&quot;http://geo.so.ch/models&quot;&gt;unserer Modellablage&lt;/a&gt; übernommen. Will man das nicht, ist die Automatisierung schon wieder einfacher geworden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige optionale Attribute in der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei ergeben sich nicht zwingend oder direkt aus dem eigentlichen Modell. Oftmals könnte man sich mit INTERLIS-Metaattributen behelfen. Bedingt aber, dass diese für jedes Modell sauber erfasst sind. Die gemäss &lt;code&gt;IliRepository09&lt;/code&gt;-Modell &lt;a href=&quot;https://github.com/claeis/ili2c/blob/master/standard/IliRepository09.ili&quot;&gt;zwingend notwendigen Attribute&lt;/a&gt; ergeben sich jedoch problemlos aus den zu publizierenden Modellen selber.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zwischenfazit: Es braucht keine menschlichen und manuellen Eingriffe für das Erstellen der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei, wenn man Zugriff auf die zu publizierenden INTERLIS-Modelle hat. Das führt zur Idee, dass man die INTERLIS-Modelle in einem &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository&quot;&gt;Github-Repository&lt;/a&gt; verwalten kann. Bei Änderungen im Github-Repository läuft eine CI/CD-Pipeline ab, welche die INTERLIS-Modellablage mindestens in eine Testumgebung deployed. Sowas in der Art wäre schön.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst braucht es etwas, dass aus einem Haufen von INTERLIS-Modellen die &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei erstellt. Das scheint mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/iox-ili/&quot;&gt;&lt;em&gt;iox-ili&lt;/em&gt;&lt;/a&gt; kein Hexenwerk zu sein, sondern ein paar Zeilen Java-Code. Diese &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/gretl/src/main/java/ch/so/agi/gretl/steps/IliRepositorizerStep.java&quot;&gt;paar Zeilen Java-Code&lt;/a&gt; packt man in einen &lt;a href=&quot;https://docs.gradle.org/current/userguide/custom_tasks.html&quot;&gt;Gradle-Custom-Task&lt;/a&gt; und schon kann man mit &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; die &lt;code&gt;ilimodels-xml&lt;/code&gt;-Datei erstellen. D.h. das Erstellen der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei wird losgetriggert durch einen Commit in das Github-Repository und mit einem Gradle-Build-Job, der das &lt;a href=&quot;https://plugins.gradle.org/plugin/ch.so.agi.gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;-Plugin&lt;/a&gt; verwendet, ausgeführt. Als CI-Werkzeug wird &lt;a href=&quot;https://travis-ci.org&quot;&gt;Travis CI&lt;/a&gt; verwendet. Ginge aber auch problemlos mit z.B. &lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als &lt;em&gt;GRETL&lt;/em&gt;-Task ist die Herstellung der &lt;code&gt;ilimodels.xml&lt;/code&gt;-Datei ein blosses Konfigurieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;task createIliModelsXml(type: IliRepositorizer) {
    description = &quot;Create ilimodels.xml file.&quot;
    modelsDir = file(&quot;models/&quot;)
    dataFile = &quot;ilimodels.xml&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die entstandene Datei kann man mit einem ilivalidator-Task prüfen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;task validateIliModelsXml(type: IliValidator) {
    description = &quot;Validate ilimodels.xml file.&quot;
    dataFiles = [&quot;ilimodels.xml&quot;]
    logFile = &quot;ilivalidator.log&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aber: Ich will nicht bloss das Erstellen der Datei automatisieren, sondern auch das Deployment schneller und effizienter gestalten und mich nicht wieder mit Dateirechten herumschlagen müssen. Warum also nicht die INTERLIS-Modelle und die &lt;code&gt;ilimodels.xml&lt;/code&gt;- und &lt;code&gt;ilisite.xml&lt;/code&gt;-Datei in ein Docker-Image brennen und mit &lt;code&gt;nginx&lt;/code&gt; bereitstellen. Das Docker-Image kann in der Openshift-Infrastruktur des Kantons deployed werden. In Openshift kann man &lt;a href=&quot;https://blog.openshift.com/image-streams-faq/&quot;&gt;Image Streams als &amp;laquo;scheduled&amp;raquo; taggen&lt;/a&gt;. In diesem Fall wird alle z.B. 15 Minuten geprüft, ob sich im Image in der Registry was geändert hat. Falls ja, wird das Image neu deployed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Erstellen des Docker-Images kann man entweder mit einem Shell-Skript automatisieren oder auch direkt im Gradle Build-File mit einem Plugin steuern. Das &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; ist einmalig zu schreiben. Dieses ändert sich in der Regel nicht bei einer Änderung von Modellen, ausser wenn &lt;a href=&quot;https://github.com/sogis/sogis-interlis-repository/blob/master/Dockerfile#L11&quot;&gt;neue Ordner&lt;/a&gt; reinkopiert werden müssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bevor ich das hergestellte Image in die Docker-Registry hochladen kann/darf, muss ich die darin enthaltene INTERLIS-Modellablage prüfen. D.h. ein Docker-Container muss gestartet werden und mit &lt;em&gt;ili2c&lt;/em&gt; die Modellablage geprüft werden. Den Docker-Container hochfahren, mache ich ebenfalls mit &lt;em&gt;Gradle&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie soll man den &lt;em&gt;ili2c&lt;/em&gt;-Befehl für die Prüfung der Modellablage absetzen und wie das Ergebnis der Prüfung richtig interpretieren? Es bestünde natürlich auch hier die Möglichkeit einen eigenen Custom Task zu schreiben. Aber es geht einfacher: &lt;em&gt;Gradle&lt;/em&gt; kennt den Task-Typ &amp;laquo;JavaExec&amp;raquo;. &lt;a href=&quot;https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html&quot;&gt;Damit&lt;/a&gt; lässt sich eine Java-Applikation (in einem neuen Prozess) starten. Die Java-Applikation (in unserem Fall &lt;em&gt;ili2c&lt;/em&gt;) wird als Buildscript-Dependency definiert, damit sie automatisch herunterladen wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der eigentliche Task sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;task checkInterlisRepository(type: JavaExec) {
    classpath = buildscript.configurations.classpath
    main = &apos;ch.interlis.ili2c.Main&apos;
    args  &quot;--check-repo-ilis&quot;, &quot;http://localhost:8080&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Simpel. Der Task wirft korrekterweise eine Fehlermeldung, wenn die Prüfung der INTERLIS-Modellablage nicht erfolgreich ist. Damit werden auch die nachfolgenden Gradle-Tasks nicht ausgeführt (d.h. das Hochladen in die Docker-Registry).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Ausprobieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;docker run -p 8080:8080 sogis/sogis-interlis-repository&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Browser:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;localhost:8080&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bonus: Was ich momentan gerne mache, ist das Erstellen einer &lt;code&gt;version.txt&lt;/code&gt;-Datei, die als statische Datei von einem Webserver (wenn er eh schon vorhanden ist) bereitgestellt wird. Damit sehe ich, ohne Einblick in Openshift etc. zu haben, ob die automatischen Deployments wirklich auch funktioniert haben. Der Task dazu ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;task versionTxt()  {
    description = &quot;Create a version.txt file with some information about the build.&quot;
    outputs.upToDateWhen { false }
    doLast {
        new File(&quot;version.txt&quot;).text = &quot;&quot;&quot;
Version: $version
Revision: ${Grgit.open(dir: &apos;.&apos;).head().id}
Buildtime: ${new SimpleDateFormat(&quot;dd-MM-yyyy HH:mm:ss&quot;).format(new Date())}
Application-name: sogis-interlis-repository
&quot;&quot;&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>XSLT / XSL-FO #2 - PDF4OEREB</title>
      <link>http://blog.sogeo.services/blog/2018/12/31/xslt-xslfo-2-pdf4oereb.html</link>
      <pubDate>Mon, 31 Dec 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/12/31/xslt-xslfo-2-pdf4oereb.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun ist es doch passiert, ein paar Feierabende und die freien Tage um Weihnachten herum investiert und - voilà - der ÖREB-PDF-Auszug mit XSLT und XSL-FO: &lt;a href=&quot;https://gitlab.com/sogis/pdf4oereb&quot;&gt;https://gitlab.com/sogis/pdf4oereb&lt;/a&gt;. Will oder muss man es bloss für die eigene katasterverantwortliche Stelle umsetzen, ist es relativ schnell getan. Soll es generischer werden, muss man sich auch um die zeitintensiveren Details kümmern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Umwandlung einer XML-Datei in eine PDF-Datei mit XSLT und XSL-FO besteht immer aus zwei Schritten. Im ersten wird die XML-Datei (in unserem Fall die DATA-Extract-XML-Datei) mit einem sogenannten Stylesheet in eine XSL-FO-Datei transformiert. Dies geschieht mit XSLT und XPath. Die XSL-FO-Datei wird im zweiten Schritt in eine PDF-Datei umformatiert. Die Hauptaufgabe besteht also im Erstellen des Stylesheets mit dem die erste Transformation durchgeführt werden kann. Dazu muss man sich in XSLT und XPath reinkämpfen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Grossen und Ganzen ist das benötigte Stylesheet für das ÖREB-PDF keine Raketenwissenschaft. Trotzdem gelangt man an einen Punkt, wo es anscheinend mit purem XSLT nicht mehr weiter geht. Ein solches Beispiel sind die codierten URL in der XML-Datei: &lt;code&gt;http%3A%2F%2Fwww.binningen.ch&lt;/code&gt;. Diese URL soll nicht so im PDF gerendert werden, sondern als &lt;code&gt;&lt;a href=&quot;http://www.binningen.ch&quot; class=&quot;bare&quot;&gt;http://www.binningen.ch&lt;/a&gt;&lt;/code&gt;. Man könnte sich mühsam eine eigene XSLT-Funktion im Stylesheet schreiben oder man weicht (mit &lt;a href=&quot;https://www.saxonica.com/&quot;&gt;&lt;em&gt;Saxon&lt;/em&gt;&lt;/a&gt;) auf sogenannte &lt;em&gt;extension functions&lt;/em&gt; aus. Dies sind Funktionen, welche man selber in Java implementiert und auf die man anschliessend in der XSLT-Transformation zurückgreifen kann. In unserem Beispiel ein URL-Decoder. Dazu muss ein &lt;a href=&quot;http://www.saxonica.com/html/documentation/extensibility/integratedfunctions/ext-simple-J.html&quot;&gt;Inferface implementiert&lt;/a&gt; werden. In einer Bezahlversion von &lt;em&gt;Saxon&lt;/em&gt; wäre dieses sehr einfache Beispiel noch einfacher umsetzbar. Denn hier bestünde die Möglichkeit direkt in XSLT auf einzelne Java-Methoden zurückzugreifen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben dem Decodieren der URL, musste ich noch vier weitere Funktionen in Java schreiben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://xmlgraphics.apache.org/fop/&quot;&gt;&lt;em&gt;Apache FOP&lt;/em&gt;&lt;/a&gt; bekundet anscheinend Mühe mit 8bit-PNG-Bildern. Jedenfalls wurden einige (nicht alle waren betroffen) Legendensymbole eines Kantons, der sie als 8bit-PNG ausliefert nicht im PDF gerendert. Eine &lt;code&gt;fixSymbol&lt;/code&gt;-Funktion wandelt die 8bit-Bilder in 24bit-Bilder um. Diese verursachen keine Probleme mehr.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Bandierung des Grundstückes, der Nordpfeil und der Massstabsbalken werden in einem &amp;laquo;Overlay-Image&amp;raquo; für die spätere Verwendung (Punkt 3. und 4.) gerendert. Die Geometrie des Grunstückes und die Georeferenzierung des Kartenausschnittes müssen in diesem Fall im XML mitgeliefert werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kartenausschnitt der Titelseite: Zusammenfügen des in Punkt 2 erstellten Overlay-Images und des Kartenausschnittes der Titelseite, der im XML mitgeliefert wird (&lt;code&gt;PlanForLandRegisterMainPage&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Zusammenfügen der Kartenausschnitte der einzelnen ÖREB (&lt;code&gt;RestrictionOnLandownership&lt;/code&gt;) und Überlagern des Planes für das Grundbuch  und des Overlay-Images.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese insgesamt fünf zusätzlichen Funktionen müssen dem XSLT-Transformer bekannt gemacht werden. D.h. sie müssen im CLASSPATH sein, damit Java sie findet und sie müssen beim XSLT-Transformer registriert werden. Das Registrieren kann über eine Config-Datei gemacht werden, was bei mir aber nur funktioniert hat, wenn sie ein &lt;a href=&quot;https://www.saxonica.com/html/documentation/extensibility/integratedfunctions/ext-full-J.html&quot;&gt;bestimmtes Interface&lt;/a&gt; implementieren. Meine &lt;em&gt;extension functions&lt;/em&gt; implementieren jedoch das einfachere Interface. Aus diesem Grund können die Funktionen nicht mit dem Standalone-Tool von &lt;em&gt;Saxon&lt;/em&gt; verwendet werden. Anhand der Dokumentation bin ich mir aber auch nicht sicher, ob das überhaupt mit der OpenSource-Edition funktionieren darf. Testeshalber hatte es das jedenfalls. Weil ich im Grunde sowieso eine Bibliothek schreiben will, welche die gesamte Umwandlung vornimmt, ist das Nicht-Funktionieren mit dem Standalone-Tool nicht weiter tragisch. Für den Notfall habe ich eine kleines Programm geschrieben, das genau das macht (XML&amp;#8594;PDF). Eventuell erleichtert es die Entwicklung des Stylesheets, wenn man nichts mehr an den &lt;em&gt;extension functions&lt;/em&gt; rumschrauben muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Bilder, Logos und Symbole des XML-Auszuges können entweder eingebettet werden (als Base64-String) oder als WMS-GetMap-Request resp. URL referenziert werden. Die XSLT-Transformation versucht zuerst das eingebettete Bild zu finden, falls es nicht vorhanden ist, wird das referenzierte Bild verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Im Prinzip&amp;raquo; unterstützt die Transformation bereits mehrere Sprachen. Eine XSLT-Transformation ist gut parametrisierbar. Die statischen Texte (z.B. der Titel des Dokumentes) werden in Übersetzungsdateien (auch XML) ausgelagert. Je nach Parameter wird die passende Übersetzungsdatei gewählt. Die dynamischen Inhalte sind bereits &lt;code&gt;MultilingualText&lt;/code&gt;-Elemente. Falls im Auszug mehrere Sprachelemente mitgeliefert werden, muss die entsprechende Sprache ausgewählt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Herausfordernd war/ist die Gruppierung der einzelnen ÖREB (&lt;code&gt;RestrictionOnLandownership&lt;/code&gt;). Damit ist gemeint, nach welchem Kriterium die ÖREB auf welche PDF-Seite kommen. Auf den ersten Blick ist alles klar. Es gibt 17 Themen, also maximal 17 PDF-Seiten (natürlich mehr, falls ein Thema aus Platzgründen auf zwei oder mehr Seiten gerendert werden muss). Die Weisung zum statischen Auszug lässt bereits - korrekterweise - Spielraum bei der Nutzungsplanung zu (wobei unnötig einschränkend). Dann könnte man auf die Idee kommen die Nutzungsplanung mit Subthemen abzubilden. Zwei meiner drei Beispiel-Kantone machen jedoch für die Nutzungplanung einzelne, eigene (Ober-)Themen. Ein Beispiel-Kanton macht es noch anders: Er verwendet zwar den &lt;code&gt;LandUsePlan&lt;/code&gt;-Code aber unterschiedliche Texte dazu. Ob es hier ein richtig oder falsch gibt, weiss ich noch nicht. Wahrscheinlich mehrere richtig und hoffentlich einige falsch. Dass ein generisches Gruppieren inkl. gewünschter Sortierung in jedem Fall funktioniert, glaube ich in diesem Fall aber nicht mehr. Ziel für mich ist jedenfalls die Unterstützung von Subthemen, d.h. es wird zuerst nach Thema gruppiert und falls ein Thema noch zusätzliche Subthemen hat, wird nach diesen gruppiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zwei Resultate der Umwandlung der XML-Datei in die PDF-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/xslt_xslfo_p2/example1.png&quot; alt=&quot;PDF-Titelseite&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p2/CH282399917939_geometry_wms.xml&quot;&gt;XML&lt;/a&gt; / &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p2/CH282399917939_geometry_wms.pdf&quot;&gt;PDF&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gut zu sehen ist der französische Titel und ein weiteres französisches Attribut (&amp;laquo;Organisme responsable du cadastre&amp;raquo;). Für beide Texte habe ich als proof-of-concept &lt;a href=&quot;https://gitlab.com/sogis/pdf4oereb/blob/master/library/src/main/resources/Resources.fr.resx&quot;&gt;französische Übersetzungen&lt;/a&gt; bereitgestellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/xslt_xslfo_p2/example2.png&quot; alt=&quot;PDF-ÖREB-Seite&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p2/CH567107399166_geometry_images.xml&quot;&gt;XML&lt;/a&gt; / &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p2/CH567107399166_geometry_images.pdf&quot;&gt;PDF&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie eingangs erwähnt, denke ich, dass mit XSLT und XSL-FO der PDF-Auszug einfach zu erstellen ist, wenn man nur auf sich selber schauen mussen. Ich habe Testdatensätze dreier Kantone verwendet und der scheinbare Spielraum mit dem man das XML abfüllen kann/darf, scheint gross zu sein. D.h. kein Kanton hat es gleich gemacht. Bei einigen Feststellungen dürfte es sich wohl auch um ordinäre Bugs handeln. Aber sicher bin ich mir eigentlich fast nie. Einige Beispiele:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Kodierung von GML. Ohne es zu prüfen, scheint mir die unterschiedliche GML-Kodierung erlaubt zu sein. GML halt&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Symbole haben nicht die korrekte Grösse und das korrekte Längen- und Breitenverhältnis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die vom WMS gelieferten Bilder beinhalten bereits die Bandierung des Grundstückes und den Nordfpfeil und Massstabsbalken. Sollte meines Erachtens nicht so sein.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;OtherLegend&lt;/code&gt; eines &lt;code&gt;RestrictionOnLandownership.Map&lt;/code&gt;-Elementes tauchen die Symbole der betroffenen ÖREB auch wieder auf. Ist für mich nicht plausibel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;LegalProvisions&lt;/code&gt; können sowohl direkt unter dem &lt;code&gt;RestrictionOnLandownership&lt;/code&gt;-Element stehen oder als Verweis innerhalb eines &lt;code&gt;LegalProvisions&lt;/code&gt;-Elementes. Das erscheint mir sinnvoll, wenn man innerhalb einer Rechtsvorschrift auf die gesetzliche Grundlage verweist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fehlende &lt;code&gt;RestrictionOnLandownership&lt;/code&gt;-Elemente, falls es vom gleichen Typ (z.B. &amp;laquo;Freihaltezone&amp;raquo;) mehrere Geometrien gibt. Oder falsche AreaShare-Werte.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Doppelte &lt;code&gt;RestrictionOnLandownership&lt;/code&gt;-Elemente im XML: Auf dem Grundstück gibt es dann z.B. 200% Grundnutzung.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Falsch abgefüllte &lt;code&gt;Information&lt;/code&gt;-Elemente im &lt;code&gt;RestrictionOnLandownership&lt;/code&gt;-Element. Da gehört m.E. die Aussage rein, also &amp;laquo;Wohnen W2&amp;raquo; und nicht &amp;laquo;Grundnutzung Gemeinde XY&amp;raquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;layerOpacity&lt;/code&gt; von Kartenausschnitten ist 0. D.h. sie sind 100% transparent und werden nicht dargestellt (wenn man sich daran halten würde).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die &lt;code&gt;extensions&lt;/code&gt;-Möglichkeit wird relativ häufig verwendet, sei es z.B. zum Speichern des &lt;code&gt;LengthShare&lt;/code&gt;-Attributes oder zum Speichern der AreaUnit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man sieht: Fragen über Fragen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mehr Informationen zum Entwicklen und Ausführen des Programmes (Standalone oder als Web Service) gibt es im &lt;a href=&quot;https://gitlab.com/sogis/pdf4oereb/blob/master/README.md&quot;&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Function as a Service #1 - Der kleinstmögliche INTERLIS-Webservice</title>
      <link>http://blog.sogeo.services/blog/2018/12/10/faas-1-interlis-webservice.html</link>
      <pubDate>Mon, 10 Dec 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/12/10/faas-1-interlis-webservice.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Lange Zeit hatte ich überhaupt keinen Plan was genau eigentlich Serverless und/oder Function as as Service (FaaS) genau bringen soll. Serverless ist vielleicht auch ein doofer Begriff, da ja trotzdem irgendwo Server laufen müssen. Nur, dass ich mich genau nicht um Server kümmern muss, sondern ich sie als gegeben betrachten kann. FaaS deutet es dann besser an: Es geht um die Funktion. Also um die eigentliche Businesslogik. Zu guter Letzt heisst das für mich, dass ich mich nur noch darum kümmern brauche. Nicht mehr um (virtualiserte) Server, nicht mehr um Webserver in der meine Anwendung läuft, sondern nur noch um die reine Businesslogik. Was auch das Deployment vereinfachen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man das so halbwegs geschnallt (hoffe ich wenigstens), sucht man sich natürlich Aufgaben, die damit gelöst werden könnten. Interessanterweise gibt es tatsächlich einiges, das man so relativ elegant umsetzen kann. Ein kleines Beispiel ist ein INTERLIS-Webservice mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;. Momentan stellen wir für die Büros, welche die Nutzungsplanung für uns digitalisieren, einen &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service&quot;&gt;Webservice&lt;/a&gt; auf Basis von &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; zur Verfügung. Die Anwendung wird gedockert und läuft zukünftig in der OpenShift-Umgebung des Kantons. Das Verhältnis zwischen eigentlicher Businesslogik und dem Rest ist krass: Die Prüfung der INTERLIS-Transferdatei sind bloss ein paar Zeilen Code, alles andere das x-fache.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Ablauf der Prüfung ist sehr einfach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Benutzer lädt die INTERLIS-Transferdatei hoch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Datei wird lokal gespeichert.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;ilivalidator&lt;/em&gt; prüft die Datei und speichert das Logfile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Das Logfile wird an den Benutzer zurückgesendet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sowohl &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/welcome.html&quot;&gt;Amazon&lt;/a&gt;, wie auch &lt;a href=&quot;https://cloud.google.com/functions/&quot;&gt;Google&lt;/a&gt; und &lt;a href=&quot;https://azure.microsoft.com/en-us/services/functions/&quot;&gt;Microsoft&lt;/a&gt; bieten auf ihren Plattformen FaaS an. Google ist raus, weil sie zur Zeit kein Java unterstützen. Amazon ist wohl am ältesten resp. erfahrensten, ist mir aber sogar noch unsympathischer als Microsoft&amp;#8230;&amp;#8203; Ausschlaggebend war aber, dass ich auf die Schnelle besser verstanden habe, wie man Dateien hochladen kann. Das scheint nicht ganz so der Fokus dieser FaaS-Implementierungen zu sein. Oder aber es wird davon ausgegangen, dass die Daten in einen S3-Bucket (AWS) oder Blob-Storage (Azure?) hochgeladen werden und die Funktion dann durch den Upload getriggert wird. Das schien mir zum Ausprobieren dann doch arg kompliziert, auch wenn es vielleicht die nachhaltigere Variante wäre. Oracle hat mit &lt;a href=&quot;https://github.com/fnproject&quot;&gt;&lt;em&gt;Fn Project&lt;/em&gt;&lt;/a&gt; auch was am Start aber noch nicht live verfügbar, daher viel auch das weg. Bei &lt;em&gt;Fn Project&lt;/em&gt; wird stark auf Docker gesetzt, was beim Entwicklen zur einer Docker-Image-Orgie ausartet. Die Fehlersuche dünkt mich so auch schwieriger, weil alles nur einem Container läuft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes habe ich mir mit &lt;a href=&quot;https://www.vagrantup.com/&quot;&gt;&lt;em&gt;Vagrant&lt;/em&gt;&lt;/a&gt; eine &lt;a href=&quot;https://github.com/edigonzales/azure-functions-test/blob/master/Vagrantfile&quot;&gt;virtuelle Maschine&lt;/a&gt; mit all den &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven&quot;&gt;benötigten lokalen Azure-Tools&lt;/a&gt; gebastelt. Die lokale Entwicklungsumgebung von Azure hört auf dem Port 7071, d.h. man darf die Portweiterleitung für die virtuelle Maschine nicht vergessen. Läuft alles einwandfrei, kann die erste Java-Funktion entwickelt werden. Java für Azure Functions ist immer noch im sogenannten &amp;laquo;Preview&amp;raquo;-Status, d.h. im Gegensatz zu anderen Sprachen ist die funktionale Unterstützung eher bescheiden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;a href=&quot;https://maven.apache.org/&quot;&gt;&lt;em&gt;Maven&lt;/em&gt;&lt;/a&gt; kann man sich ein Beispielprojekt erstellen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn nach der Region gefragt wird, wo die Funktion rattern soll, muss man auf Anhieb die richtige wählen. So wie ich es verstanden habe, kann man das nachträglich nicht mehr so einfach ändern. In der IDE der Wahl importiert man anschliessend das Maven-Projekt. Im einfachsten Fall schreibt man wirklich nur eine einzige Funktion. Der INTERLIS-Webservice sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;public class Function {
    /**
     * This function listens at endpoint &quot;/api/validate&quot;. Invoke it using &quot;curl&quot; command in bash:
     * _ curl --request POST --header &quot;Content-Type:application/octet-stream&quot; --data-binary @ch_254900.itf http://localhost:7071/api/validate&amp;amp;code={your function key}
     * Function Key is not needed when running locally, to invoke HttpTrigger deployed to Azure, see here(https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook#authorization-keys) on how to get function key for your app.
     */
    @FunctionName(&quot;validate&quot;)
    public String run(
            @HttpTrigger(name = &quot;req&quot;, methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION, dataType=&quot;binary&quot;) HttpRequestMessage&amp;lt;Byte[]&amp;gt; req,
            final ExecutionContext context) {

        System.setProperty(&quot;user.home&quot;, &quot;/tmp&quot;);

        try {
            byte[] byteFile = ArrayUtils.toPrimitive(req.getBody());
            File uploadedFile = File.createTempFile(&quot;upload&quot;, &quot;.interlis&quot;);
            FileUtils.writeByteArrayToFile(uploadedFile, byteFile);

            String logFileName = uploadedFile.getAbsolutePath() + &quot;.log&quot;;

            Settings settings = new Settings();
            settings.setValue(Validator.SETTING_ILIDIRS, Validator.SETTING_DEFAULT_ILIDIRS);
            settings.setValue(Validator.SETTING_LOGFILE, logFileName);

            Validator.runValidation(uploadedFile.getAbsolutePath(), settings);

            String logFileContent = new String(Files.readAllBytes(Paths.get(logFileName)));
            return logFileContent;
        } catch (Exception e) {
            e.printStackTrace();
            context.getLogger().info(e.getMessage());
            return e.getMessage();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 7:&lt;/strong&gt; Mit dieser Annotation wird Name der Funktion, wie sie von Aussen aufrufbar ist, definiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 9:&lt;/strong&gt; Mit &lt;code&gt;HttpTrigger&lt;/code&gt; wird bestimmt, dass die Funktion durch einen HTTP-Aufruf getriggert wird. Es stehen noch andere Trigger zur &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings#supported-bindings&quot;&gt;Verfügung&lt;/a&gt;, z.B. eben Blobstorage oder Timer (was dann etwas wie einem Cronjob entspräche). &lt;code&gt;authLevel&lt;/code&gt; definiert wie ich mich authorisieren muss. &lt;code&gt;AuthorizationLevel.FUNCTION&lt;/code&gt; bedeutet, dass ich mich mit einem Token (aka &amp;laquo;Function key&amp;raquo;) authorisieren muss, der an den Funktionsaufruf als GET-Parameter angehängt wird. &lt;code&gt;HttpRequestMessage&amp;lt;Byte[]&amp;gt; req&lt;/code&gt; definiert die Input Bindings. In meinem Fall erwarte ich ein Byte-Array, weil ich eine Datei hochladen will. Hier wird es mit Java schon mal knifflig. Irgendwie ist hier noch nicht alles so wie es sein sollte oder wie ich es möchte. Einerseits sind noch spezifische Java-Bugs vorhanden und andererseits ist mir nicht ganz klar, warum man nicht Multipart-File-Uploads unterstützt. Vielleicht es es nur noch nicht umgesetzt oder aber man will es nicht unterstützen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 12:&lt;/strong&gt; &lt;em&gt;ilivalidator&lt;/em&gt; resp. der INTERLIS-Compiler versucht einen lokalen Cache der benötigten Modelle im User-Home-Verzeichnis anzulegen. In der lokalen Entwicklungsumgebung hat das auch wunderbar funktioniert. War die Funktion aber auf Azure deployed, kam eine Fehlermeldung, dass das Verzeichnis nicht angelegt werden könne. Lustigerweise zeigt &lt;code&gt;&quot;user.home&quot;&lt;/code&gt; auf Azure auf das &lt;code&gt;C:\&lt;/code&gt;-Laufwerk. Workaround ist das Verändern des Properties. Ganz &lt;a href=&quot;https://github.com/claeis/ili2c/blob/b7d6ed2ab3ace4a4c4f1a980cf831ae6fd53ea29/src/ch/interlis/ilirepository/impl/RepositoryAccess.java#L70&quot;&gt;neu&lt;/a&gt; kann man im INTERLIS-Compiler (unreleased, resp. nur Snapshots) das Cache-Verzeichnis via ENV-Variable setzen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 15 - 17:&lt;/strong&gt; Das Speichern des Request-Bodies in einer Datei hat mich einiges an Zeit gekostet. Probleme machten natürlich wieder einmal die Umlaute. So scheint es jedenfalls zu funktionieren. Mit einem Multipart-File-Upload hat das nie solche Probleme gemacht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Rest ist altbekannt und nicht Azure-relevant.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Lokal builden und starten kann ich die Funktion mit folgendem Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;mvn clean package &amp;amp;&amp;amp; mvn azure-functions:run&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;curl&lt;/code&gt; kann ich die Funktion testen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;curl --request POST --header &quot;Content-Type:application/octet-stream&quot; --data-binary @ch_254900.itf http://localhost:7071/api/validate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einen ordinären Mulitpart-Form-File-Upload würde ich mit &lt;code&gt;-F file=@ch_254900.itf&lt;/code&gt; anstelle von &lt;code&gt;--data-binary @ch_254900.itf&lt;/code&gt; machen. Wenn ich das - auch mit korrektem Header - probiere, schaffe ich das Parsen den Request-Bodies nicht und die Umlaute funktionieren auch nicht. Vielleicht geht das schon, wenn man in dieser Thematik versierter ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat des &lt;code&gt;curl&lt;/code&gt;-Aufrufes ist der Inhalt der ilivalidator-Logdatei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich die Funktion nun auf Azure deployen will, muss ich mich zuerst auf der Konsole mit &lt;code&gt;az login&lt;/code&gt; einloggen. Komischerweise öffnet sich ein Browser, wo man anschliessend die Credentials eintippen muss. Ich gehe davon, dass das auch ohne GUI geht. Deployen geht mit &lt;em&gt;Maven&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;mvn azure-functions:deploy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim allerersten Mal dauert das relativ lange, weil komplett alles angelegt werden muss. Redeployments gehen viel schneller. Wenn die Funktion mit einem Function key (den man im Azure Web-Portal findet) geschützt ist, sieht der Aufruf so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;curl --request POST --header &quot;Content-Type:application/octet-stream&quot; --data-binary @ch_254900.itf https://ilivalidator-functions-20181205142252080.azurewebsites.net/api/validate?code=&amp;lt;my function key&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Schöne an den Azure Functions ist, dass man nur &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/functions/&quot;&gt;bezahlt&lt;/a&gt; wenn sie wirklich aufgerufen werden. Man zahlt etwas pro Aufruf und für die Ausführungsdauer. Es gibt auch einen &amp;laquo;Free Grant&amp;raquo; pro Monat, der gemäss unserer Benutzerstatistik völlig ausreichen würde. Hier sieht man bereits einen Vorteil von FaaS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo Licht ist, ist auch Schatten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Man kann niemandem zumuten, dass man mit &lt;code&gt;curl&lt;/code&gt; die Daten hochladen muss. Es fehlt ein einfaches GUI resp. eine einfache Webseite zum Hochladen der Dateien. Vielleicht kann man die Webseite sogar in einem Storage auf Azure hosten, dann wäre man wieder fein raus.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es gibt sowohl Limits was die &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook#trigger---limits&quot;&gt;Uploadgrösse wie auch die Ausführungszeit&lt;/a&gt; betrifft. Dauert die Ausführung länger oder will man grössere Dateien hochladen, muss man andere Wege mit Azure finden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Performance ist auf den ersten Blick so lala. &lt;a href=&quot;https://www.azurefromthetrenches.com/azure-functions-vs-aws-lambda-scaling-face-off/&quot;&gt;Vergleiche&lt;/a&gt; mit AWS Lambda zeigen, dass da tatsächlich noch Aufholbedarf ist. Wie matchentscheidend das ist, kommt natürlich auf den Use Case an.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>XSLT / XSL-FO #1 - Mein Hammer. Wo sind die Nägel?</title>
      <link>http://blog.sogeo.services/blog/2018/12/09/xslt-xslfo-1.html</link>
      <pubDate>Sun, 9 Dec 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/12/09/xslt-xslfo-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie wahrscheinlich bei vielen anderen auch, gibt es bei uns für verschiedene Layer im Web GIS Client zusätzliche PDF-Auszüge. Wir nennen sie Objektblätter und sie sind in der Regel einem einzelnen Objekt zugewiesen. Oder anders formuliert: Die Informationen eines Objektes werden aufgehübscht und angereichtert mit z.B. Fotos und einem Übersichtskärtli in einer PDF-Datei gerendert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Geotope: &lt;a href=&quot;https://geo.so.ch/map/?k=3e1f80c22&quot;&gt;Kartenausschnitt&lt;/a&gt; / &lt;a href=&quot;https://geo.so.ch/api/v1/document/geotope?feature=2430&amp;amp;x=2607404.7787109376&amp;amp;y=1230195.9923828125&amp;amp;crs=EPSG%3A2056&quot;&gt;Objektblatt&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grundstücksbeschrieb: &lt;a href=&quot;https://geo.so.ch/map/?k=8f1c3a50e&quot;&gt;Kartenausschnitt&lt;/a&gt; / &lt;a href=&quot;https://geo.so.ch/api/v1/document/grundstuecksbeschrieb?feature=281000848&amp;amp;x=2608143.5842610677&amp;amp;y=1225612.3837890625&amp;amp;crs=EPSG%3A2056&quot;&gt;Objektblatt&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In einigen Fällen ist der PDF-Auszug nicht direkt an ein Objekt gebunden, sondern vielmehr abhängig vom Ort, wo der Benutzer in die Karte geklickt hat. In diesem Fall ähnelt es dem ÖREB-Auszug. Wobei die Unterschiede im Prinzip egal sind, da es schlussendlich praktisch auf das Gleiche herauskommt. Der Benutzer klickt in die Karte, im Hintergrund werden die Daten zusammengesucht und anschliessend als PDF zurückgeliefert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für diesen Arbeitsschritt setzen wir zur Zeit &lt;a href=&quot;https://community.jaspersoft.com/project/jasperreports-library&quot;&gt;&lt;em&gt;JasperReports&lt;/em&gt;&lt;/a&gt; ein. Damit können wir auf Datenbanken und Filesysteme (für Fotos etc.) zugreifen. Ebenfalls möglich sind WMS-Request, um die Übersichtskarten ins PDF zu bringen. Soweit funktioniert das ganz passabel. Der grösste Mehrwert ist jedenfalls, dass wir im Gegensatz zu früher die PDF-Auszüge immer gleich machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ÖREB-Kataster mit seinem &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/publication/instruction.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/OEREB-XML-Aufruf_de.pdf.html&quot;&gt;Webservice&lt;/a&gt; hat da einen leicht saubereren Ansatz: Es gibt einen &amp;laquo;Rohdatenoutput&amp;raquo; in den Formaten XML und JSON, und einen &amp;laquo;lesbaren&amp;raquo; Output im PDF-Format. Ich habe natürlich genau nichts gewonnen, wenn ich das XML und das PDF komplett separiert herstelle. Sinnvoller ist es das XML als Input für das PDF zu verwenden. Dann weiss ich haargenau, dass sowohl XML wie auch das PDF auf den selben Daten beruhren und nicht durch unterschiedliche Datenbankqueries oder Businesslogik im Webservice zu stande gekommen sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Am Beispiel unseres Grundstückbeschriebs habe ich den &amp;laquo;ÖREB-Auszugs-Ansatz&amp;raquo; komplett durchgespielt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Erstellen des XML-Schemas&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;XML-Output mit &lt;a href=&quot;https://de.wikipedia.org/wiki/Java_Architecture_for_XML_Binding&quot;&gt;JAXB&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transformieren der XML-Datei in eine XSL-FO-Datei mit einem XSLT-Prozessor (&lt;a href=&quot;https://www.saxonica.com&quot;&gt;Saxon&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Umwandeln/Formatieren der XSL-FO-Datei in ein PDF mit einem Formatting Objects Processor (&lt;a href=&quot;https://xmlgraphics.apache.org/fop/&quot;&gt;Apache Fop&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ganze noch eingepackt in eine &lt;a href=&quot;http://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; Applikation, damit ähnliche Aufrufe wie beim ÖREB-Webservice möglich sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/Extract.xsd&quot;&gt;XSD&lt;/a&gt; kann man sich in Eclipse zusammenklicken. Nach längerem Suchen habe ich nichts besseres gefunden (jedenfalls nichts kostenloses):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/xslt_xslfo_p1/xsd_eclipse.png&quot; alt=&quot;XSD Eclipse&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JAXB erlaubt es mir mich &lt;em&gt;nicht&lt;/em&gt; um das XML-Format zu kümmern. Ich kann mich auf die eigentliche Businesslogik konzentrieren, in diesem Fall auf das Zusammensuchen der Informationen in der Datenbank für das gewählte Grundstück und muss diese Informationen bloss in dumme Java-Klassen schreiben. Das Umformatieren der Java-Objekte nach XML übernimmt JAXB. Dafür muss ich als erstes  aus dem XML-Schema die Java-Klassen herstellen. Dazu reicht folgender Aufruf:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;xjc -b binding.xjb -extension Extract.xsd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;code&gt;-b&lt;/code&gt;-Option ist notwendig, damit das XML-Rootelement korrekt annnotiert wird. Fehlt die Option wirft Spring Boot auf den ersten Blick eine nicht ganz einfach verständliche Fehlermeldung. Das Resultat des Aufrufes sind in meinem Fall sechs Java-Klassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/xslt_xslfo_p1/java_classes.png&quot; alt=&quot;Java-Klassen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für &lt;a href=&quot;http://www.gradle.org&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt; gibt es ein &lt;a href=&quot;https://plugins.gradle.org/plugin/org.unbroken-dome.xjc&quot;&gt;xjc-Plugin&lt;/a&gt;. Die Anwendung ist einfach:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;xjcGenerate {
    source = fileTree(&apos;src/main/schema&apos;) { include &apos;*.xsd&apos; }
    bindingFiles = fileTree(&apos;src/main/schema&apos;) { include &apos;*.xjb&apos; }
    outputDirectory = file(&apos;src/generated/java&apos;)
    extension = true
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es steht jetzt ein &lt;code&gt;xjcGenerate&lt;/code&gt;-Task zur Verfügung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was jetzt folgt ist das Zusammensuchen der Informationen mit ein paar SQL-Queries und das &lt;a href=&quot;https://github.com/edigonzales/LandRegisterParcelDescription/blob/master/src/main/java/ch/so/agi/landregisterparceldescription/webservice/services/GetExtractByIdResponseTypeServiceImpl.java&quot;&gt;Abfüllen und Zusammenstöpseln&lt;/a&gt; der soeben erstellen Java-Klassen. Dem &lt;a href=&quot;https://github.com/edigonzales/LandRegisterParcelDescription/blob/master/src/main/java/ch/so/agi/landregisterparceldescription/webservice/controllers/MainController.java#L46&quot;&gt;Spring Boot Controller&lt;/a&gt; reicht als Return-Value sogar nur eine &amp;laquo;Root-Klasse&amp;raquo; und er macht das Marshalling automatisch. Der Aufruf&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;http://localhost:8887/parcel/extract/xml/CH310663327779&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert das folgende &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH310663327779.xml&quot;&gt;XML&lt;/a&gt; zurück: (Das base64-Encoding der Bilder habe ich hier gekürzt. Im verlinkten &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH310663327779.xml&quot;&gt;XML&lt;/a&gt; sind sie vorhanden.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&amp;gt;
&amp;lt;GetExtractByIdResponse xmlns=&quot;http://geo.so.ch/schema/AGI/LandRegisterParcelDescription/1.0/Extract&quot;&amp;gt;
  &amp;lt;Extract&amp;gt;
    &amp;lt;CantonalLogo&amp;gt;iVBORw0KGgoAAAANSUhEUgAABLAAAACBCAAAAAD1IxwIAAAABGdBTUEAALGPC/xhBQAADPNpQ0NQa0NHQ29s....&amp;lt;/CantonalLogo&amp;gt;
    &amp;lt;ResponsibleOffice&amp;gt;
      &amp;lt;Name&amp;gt;Amt für Geoinformation&amp;lt;/Name&amp;gt;
      &amp;lt;OfficeAtWeb&amp;gt;http://agi.so.ch&amp;lt;/OfficeAtWeb&amp;gt;
      &amp;lt;Email&amp;gt;agi@bd.so.ch&amp;lt;/Email&amp;gt;
      &amp;lt;Street&amp;gt;Rötistrasse&amp;lt;/Street&amp;gt;
      &amp;lt;Number&amp;gt;4&amp;lt;/Number&amp;gt;
      &amp;lt;PostalCode&amp;gt;4501&amp;lt;/PostalCode&amp;gt;
      &amp;lt;City&amp;gt;Solothurn&amp;lt;/City&amp;gt;
      &amp;lt;Phone&amp;gt;032 627 75 92&amp;lt;/Phone&amp;gt;
    &amp;lt;/ResponsibleOffice&amp;gt;
    &amp;lt;RealEstate&amp;gt;
      &amp;lt;Number&amp;gt;680&amp;lt;/Number&amp;gt;
      &amp;lt;IdentND&amp;gt;SO0200002513&amp;lt;/IdentND&amp;gt;
      &amp;lt;EGRID&amp;gt;CH310663327779&amp;lt;/EGRID&amp;gt;
      &amp;lt;LocalNames&amp;gt;Brunngrabenacker, Oberstmatt, Rumi, Rütiacker&amp;lt;/LocalNames&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;Acker_Wiese&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;7194&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;Gartenanlage&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;1623&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;Gebaeude&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;1606&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;Gebaeudeerschliessung&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;2580&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;Obstkultur&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;48714&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;LandCoverShare&amp;gt;
        &amp;lt;Type&amp;gt;uebrige_Intensivkultur&amp;lt;/Type&amp;gt;
        &amp;lt;Area&amp;gt;3358&amp;lt;/Area&amp;gt;
      &amp;lt;/LandCoverShare&amp;gt;
      &amp;lt;SurveyorOffice&amp;gt;
        &amp;lt;Name&amp;gt;Reto Meile&amp;lt;/Name&amp;gt;
        &amp;lt;OfficeAtWeb&amp;gt;www.w-h.ch&amp;lt;/OfficeAtWeb&amp;gt;
        &amp;lt;Email&amp;gt;admin@w-h.ch&amp;lt;/Email&amp;gt;
        &amp;lt;Line1&amp;gt;W+H AG&amp;lt;/Line1&amp;gt;
        &amp;lt;Street&amp;gt;Blümlisalpstrasse&amp;lt;/Street&amp;gt;
        &amp;lt;Number&amp;gt;6&amp;lt;/Number&amp;gt;
        &amp;lt;PostalCode&amp;gt;4562&amp;lt;/PostalCode&amp;gt;
        &amp;lt;City&amp;gt;Biberist&amp;lt;/City&amp;gt;
        &amp;lt;Phone&amp;gt;032 671 26 30&amp;lt;/Phone&amp;gt;
      &amp;lt;/SurveyorOffice&amp;gt;
      &amp;lt;LandRegisterOffice&amp;gt;
        &amp;lt;Name&amp;gt;Amtschreiberei Region Solothurn&amp;lt;/Name&amp;gt;
        &amp;lt;OfficeAtWeb&amp;gt;www.so.ch/verwaltung/finanzdepartement/grundbuchaemter/&amp;lt;/OfficeAtWeb&amp;gt;
        &amp;lt;Email&amp;gt;gb.so@fd.so.ch&amp;lt;/Email&amp;gt;
        &amp;lt;Line1&amp;gt;Grundbuchamt&amp;lt;/Line1&amp;gt;
        &amp;lt;Street&amp;gt;Rötistrasse&amp;lt;/Street&amp;gt;
        &amp;lt;Number&amp;gt;4&amp;lt;/Number&amp;gt;
        &amp;lt;PostalCode&amp;gt;4501&amp;lt;/PostalCode&amp;gt;
        &amp;lt;City&amp;gt;Solothurn&amp;lt;/City&amp;gt;
        &amp;lt;Phone&amp;gt;032 627 75 11&amp;lt;/Phone&amp;gt;
      &amp;lt;/LandRegisterOffice&amp;gt;
      &amp;lt;Type&amp;gt;Liegenschaft&amp;lt;/Type&amp;gt;
      &amp;lt;Municipality&amp;gt;Biberist&amp;lt;/Municipality&amp;gt;
      &amp;lt;SubunitOfLandRegister&amp;gt;Biberist&amp;lt;/SubunitOfLandRegister&amp;gt;
      &amp;lt;LandRegistryArea&amp;gt;65076&amp;lt;/LandRegistryArea&amp;gt;
      &amp;lt;Map&amp;gt;iVBORw0KGgoAAAANSUhEUgAACGYAAAoDCAYAAAB/VcSAAACAAElEQVR42uzdBZiUVfs/cLeXhWVZukGlVLo7pQQE6....&amp;lt;/Map&amp;gt;
    &amp;lt;/RealEstate&amp;gt;
    &amp;lt;CreationDate&amp;gt;2018-12-08T18:01:10.592+01:00&amp;lt;/CreationDate&amp;gt;
  &amp;lt;/Extract&amp;gt;
&amp;lt;/GetExtractByIdResponse&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Umwandeln der XML-Datei in die PDF-Datei geschieht in zwei Schritten. Der erste Schritt ist das Transformieren der XML-Datei in eine XSL-FO-Datei (ebenfalls eine XML-Datei), welche die eigentlichen Anweisungen (Layout, Styling etc.) enthält, damit ein FO-Prozessor aus dieser Zwischen-XML-Datei das PDF rendern kann. Ohne grosse Erfahrung ist es einfacher, das ganze Rumtransformieren zuerst stand-alone auf der Konsole zu machen und die Integration in die &lt;em&gt;Spring Boot&lt;/em&gt; Applikation erst zu machen, wenn man etwas funktionierendes hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den ersten Schritt verwende ich &lt;a href=&quot;https://www.saxonica.com/welcome/welcome.xml&quot;&gt;Saxon&lt;/a&gt; als XSLT-Prozessor. Das Wichtigste für die Umwandlung ist aber ein &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/parceldescription_extract_fo.xslt&quot;&gt;Stylesheet&lt;/a&gt; (*.xslt), das die Anweisung für die Transformation enthält:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;xsl:stylesheet xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot; xmlns:fo=&quot;http://www.w3.org/1999/XSL/Format&quot; xmlns:extract=&quot;http://geo.so.ch/schema/AGI/LandRegisterParcelDescription/1.0/Extract&quot; exclude-result-prefixes=&quot;extract&quot; version=&quot;1.0&quot;&amp;gt;
  &amp;lt;xsl:output method=&quot;xml&quot; indent=&quot;yes&quot;/&amp;gt;
  &amp;lt;xsl:decimal-format name=&quot;swiss&quot; decimal-separator=&quot;.&quot; grouping-separator=&quot;&apos;&quot;/&amp;gt;
  &amp;lt;xsl:template match=&quot;extract:GetExtractByIdResponse&quot;&amp;gt;
    &amp;lt;fo:root xmlns:fo=&quot;http://www.w3.org/1999/XSL/Format&quot; xmlns:xsd=&quot;https://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&amp;gt;
      &amp;lt;fo:layout-master-set&amp;gt;
        &amp;lt;fo:simple-page-master master-name=&quot;mainPage&quot; page-height=&quot;297mm&quot; page-width=&quot;210mm&quot; margin-top=&quot;12mm&quot; margin-bottom=&quot;12mm&quot; margin-left=&quot;15mm&quot; margin-right=&quot;12mm&quot;&amp;gt;
          &amp;lt;fo:region-body margin-top=&quot;45mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
          &amp;lt;fo:region-before extent=&quot;40mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
          &amp;lt;fo:region-after extent=&quot;10mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
        &amp;lt;/fo:simple-page-master&amp;gt;
      &amp;lt;/fo:layout-master-set&amp;gt;
      &amp;lt;xsl:apply-templates/&amp;gt;
    &amp;lt;/fo:root&amp;gt;
  &amp;lt;/xsl:template&amp;gt;
  &amp;lt;xsl:template match=&quot;extract:Extract&quot;&amp;gt;
    &amp;lt;fo:page-sequence master-reference=&quot;mainPage&quot; id=&quot;my-sequence-id&quot;&amp;gt;
      &amp;lt;fo:static-content flow-name=&quot;xsl-region-before&quot;&amp;gt;
        &amp;lt;fo:block&amp;gt;
          &amp;lt;fo:block-container absolute-position=&quot;absolute&quot; top=&quot;6.7mm&quot; left=&quot;0mm&quot; line-height=&quot;1em&quot; background-color=&quot;#FFFFFF&quot;&amp;gt;
            &amp;lt;fo:block font-size=&quot;10pt&quot; font-style=&quot;italic&quot; font-weight=&quot;700&quot; font-family=&quot;Frutiger&quot;&amp;gt;
              &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:Name&quot;/&amp;gt;
            &amp;lt;/fo:block&amp;gt;
            &amp;lt;fo:block font-size=&quot;10pt&quot; font-style=&quot;italic&quot; font-weight=&quot;400&quot; font-family=&quot;Frutiger&quot; margin-left=&quot;6mm&quot; margin-top=&quot;1mm&quot;&amp;gt;
              &amp;lt;fo:block&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:Street&quot;/&amp;gt;
                &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:Number&quot;/&amp;gt;
              &amp;lt;/fo:block&amp;gt;
              &amp;lt;fo:block&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:PostalCode&quot;/&amp;gt;
                &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:City&quot;/&amp;gt;
              &amp;lt;/fo:block&amp;gt;
              &amp;lt;fo:block&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:Phone&quot;/&amp;gt;
              &amp;lt;/fo:block&amp;gt;
              &amp;lt;fo:block&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:Email&quot;/&amp;gt;
              &amp;lt;/fo:block&amp;gt;
              &amp;lt;fo:block&amp;gt;
                &amp;lt;xsl:value-of select=&quot;extract:ResponsibleOffice/extract:OfficeAtWeb&quot;/&amp;gt;
              &amp;lt;/fo:block&amp;gt;
            &amp;lt;/fo:block&amp;gt;
          &amp;lt;/fo:block-container&amp;gt;
          &amp;lt;fo:block-container absolute-position=&quot;absolute&quot; top=&quot;0mm&quot; left=&quot;123mm&quot; background-color=&quot;#FFFFFF&quot;&amp;gt;
            &amp;lt;fo:block&amp;gt;
              &amp;lt;fo:external-graphic height=&quot;6.7mm&quot; width=&quot;60mm&quot; content-width=&quot;scale-to-fit&quot; content-height=&quot;scale-to-fit&quot;&amp;gt;
                &amp;lt;xsl:attribute name=&quot;src&quot;&amp;gt;
                  &amp;lt;xsl:text&amp;gt;url(&apos;data:&amp;lt;/xsl:text&amp;gt;
                  &amp;lt;xsl:text&amp;gt;image/png;base64,&amp;lt;/xsl:text&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:CantonalLogo&quot;/&amp;gt;
                  &amp;lt;xsl:text&amp;gt;&apos;)&amp;lt;/xsl:text&amp;gt;
                &amp;lt;/xsl:attribute&amp;gt;
              &amp;lt;/fo:external-graphic&amp;gt;
            &amp;lt;/fo:block&amp;gt;
          &amp;lt;/fo:block-container&amp;gt;
        &amp;lt;/fo:block&amp;gt;
      &amp;lt;/fo:static-content&amp;gt;
      &amp;lt;fo:static-content flow-name=&quot;xsl-region-after&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;4mm&quot; font-size=&quot;7pt&quot; font-style=&quot;italic&quot; font-weight=&quot;400&quot; font-family=&quot;Frutiger&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;50%&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;50%&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;format-dateTime(extract:CreationDate,&apos;[Y0001]-[M01]-[D01] [H01]:[m01]:[s01]&apos;)&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Seite &amp;lt;fo:page-number/&amp;gt;/&amp;lt;fo:page-number-citation-last ref-id=&quot;my-sequence-id&quot;/&amp;gt;&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:static-content&amp;gt;
      &amp;lt;xsl:apply-templates select=&quot;extract:RealEstate&quot;/&amp;gt;
    &amp;lt;/fo:page-sequence&amp;gt;
  &amp;lt;/xsl:template&amp;gt;
  &amp;lt;xsl:template match=&quot;extract:RealEstate&quot;&amp;gt;
    &amp;lt;fo:flow flow-name=&quot;xsl-region-body&quot;&amp;gt;
      &amp;lt;fo:block-container wrap-option=&quot;wrap&quot; hyphenate=&quot;false&quot; hyphenation-character=&quot;-&quot; font-weight=&quot;700&quot; font-size=&quot;14pt&quot; font-family=&quot;Frutiger&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell&amp;gt;
                &amp;lt;fo:block&amp;gt;Grundstücksbeschrieb&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell&amp;gt;
                &amp;lt;fo:block&amp;gt;GB-Nr. &amp;lt;xsl:value-of select=&quot;extract:Number&quot;/&amp;gt;&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;fo:block-container wrap-option=&quot;wrap&quot; hyphenate=&quot;false&quot; hyphenation-character=&quot;-&quot; font-weight=&quot;400&quot; font-size=&quot;10pt&quot; font-family=&quot;Frutiger&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;8mm&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;20mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Gemeinde:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:Municipality&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block/&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;EGRID:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:EGRID&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Grundbuch:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:SubunitOfLandRegister&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block/&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;NBIdent:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:IdentND&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;fo:block-container wrap-option=&quot;wrap&quot; hyphenate=&quot;false&quot; hyphenation-character=&quot;-&quot; font-weight=&quot;400&quot; font-size=&quot;10pt&quot; font-family=&quot;Frutiger&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Grundstücksart:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:Type&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Grundstücksfläche:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;&amp;lt;xsl:value-of select=&quot;format-number(extract:LandRegistryArea, &amp;amp;quot;#&apos;###&amp;amp;quot;, &amp;amp;quot;swiss&amp;amp;quot;)&quot;/&amp;gt; m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;fo:block-container wrap-option=&quot;wrap&quot; hyphenate=&quot;false&quot; hyphenation-character=&quot;-&quot; font-weight=&quot;400&quot; font-size=&quot;10pt&quot; font-family=&quot;Frutiger&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Bodenbedeckung:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Flurnamen:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;1mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;0mm&quot;&amp;gt;
                    &amp;lt;fo:table-column column-width=&quot;50mm&quot;/&amp;gt;
                    &amp;lt;fo:table-column column-width=&quot;20mm&quot;/&amp;gt;
                    &amp;lt;fo:table-column column-width=&quot;10mm&quot;/&amp;gt;
                    &amp;lt;fo:table-body border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                      &amp;lt;xsl:for-each select=&quot;extract:LandCoverShare&quot;&amp;gt;
                        &amp;lt;xsl:sort select=&quot;extract:Type&quot;/&amp;gt;
                        &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                          &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block&amp;gt;&amp;lt;xsl:value-of select=&quot;extract:Type&quot;/&amp;gt;&amp;lt;/fo:block&amp;gt;
                       &amp;lt;/fo:table-cell&amp;gt;
                          &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;&amp;lt;xsl:value-of select=&quot;format-number(extract:Area, &amp;amp;quot;#&apos;###&amp;amp;quot;, &amp;amp;quot;swiss&amp;amp;quot;)&quot;/&amp;gt;&amp;lt;/fo:block&amp;gt;
                       &amp;lt;/fo:table-cell&amp;gt;
                          &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;&amp;lt;/fo:block&amp;gt;
                       &amp;lt;/fo:table-cell&amp;gt;
                        &amp;lt;/fo:table-row&amp;gt;
                      &amp;lt;/xsl:for-each&amp;gt;
                      &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot; font-weight=&quot;400&quot; font-style=&quot;italic&quot;&amp;gt;
                        &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block&amp;gt;Total&amp;lt;/fo:block&amp;gt;
                        &amp;lt;/fo:table-cell&amp;gt;
                        &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;&amp;lt;xsl:value-of select=&quot;format-number(sum(extract:LandCoverShare/extract:Area), &amp;amp;quot;#&apos;###&amp;amp;quot;, &amp;amp;quot;swiss&amp;amp;quot;)&quot;/&amp;gt;&amp;lt;/fo:block&amp;gt;
                        &amp;lt;/fo:table-cell&amp;gt;
                        &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;&amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;&amp;lt;/fo:block&amp;gt;
                        &amp;lt;/fo:table-cell&amp;gt;
                      &amp;lt;/fo:table-row&amp;gt;
                    &amp;lt;/fo:table-body&amp;gt;
                  &amp;lt;/fo:table&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;
                  &amp;lt;xsl:value-of select=&quot;extract:LocalNames&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;fo:block-container wrap-option=&quot;wrap&quot; hyphenate=&quot;false&quot; hyphenation-character=&quot;-&quot; font-weight=&quot;400&quot; font-size=&quot;10pt&quot; font-family=&quot;Frutiger&quot;&amp;gt;
        &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
          &amp;lt;fo:table-body&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Grundbuchamt:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block&amp;gt;Nachführungsgeometer:&amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
            &amp;lt;fo:table-row&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block linefeed-treatment=&quot;preserve&quot;&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Name&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Line1&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Street&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Number&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:PostalCode&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:City&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:text&amp;gt;Telefon &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Phone&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:Email&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:LandRegisterOffice/extract:OfficeAtWeb&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
              &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                &amp;lt;fo:block linefeed-treatment=&quot;preserve&quot;&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Name&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Line1&quot;/&amp;gt;
                    &amp;lt;xsl:if test=&quot;extract:SurveyorOffice/extract:Line2&quot;&amp;gt;
                        &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                        &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Line2&quot;/&amp;gt;
                    &amp;lt;/xsl:if&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Street&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Number&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:PostalCode&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:City&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:text&amp;gt;Telefon &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Phone&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:Email&quot;/&amp;gt;
                    &amp;lt;xsl:text&amp;gt;&amp;amp;#xA;&amp;lt;/xsl:text&amp;gt;
                    &amp;lt;xsl:value-of select=&quot;extract:SurveyorOffice/extract:OfficeAtWeb&quot;/&amp;gt;
                &amp;lt;/fo:block&amp;gt;
              &amp;lt;/fo:table-cell&amp;gt;
            &amp;lt;/fo:table-row&amp;gt;
          &amp;lt;/fo:table-body&amp;gt;
        &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;fo:block-container page-break-before=&quot;always&quot; margin=&quot;0mm&quot; padding=&quot;0mm&quot; space-before=&quot;0mm&quot;&amp;gt;
        &amp;lt;fo:block margin=&quot;0mm&quot; padding=&quot;0mm&quot; space-before=&quot;0mm&quot;&amp;gt;
          &amp;lt;fo:external-graphic height=&quot;217mm&quot; width=&quot;182mm&quot; content-height=&quot;scale-to-fit&quot; margin=&quot;0mm&quot; padding=&quot;0mm&quot; space-before=&quot;0mm&quot; border=&quot;0.5pt solid black&quot;&amp;gt;
            &amp;lt;xsl:attribute name=&quot;src&quot;&amp;gt;
              &amp;lt;xsl:text&amp;gt;url(&apos;data:&amp;lt;/xsl:text&amp;gt;
              &amp;lt;xsl:text&amp;gt;image/png;base64,&amp;lt;/xsl:text&amp;gt;
              &amp;lt;xsl:value-of select=&quot;extract:Map&quot;/&amp;gt;
              &amp;lt;xsl:text&amp;gt;&apos;)&amp;lt;/xsl:text&amp;gt;
            &amp;lt;/xsl:attribute&amp;gt;
          &amp;lt;/fo:external-graphic&amp;gt;
        &amp;lt;/fo:block&amp;gt;
      &amp;lt;/fo:block-container&amp;gt;
    &amp;lt;/fo:flow&amp;gt;
  &amp;lt;/xsl:template&amp;gt;
&amp;lt;/xsl:stylesheet&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das Entwicklen des Stylesheets muss man sowohl Wissen über XSLT wie auch über XSL-FO haben. XSLT-Wissen, das über &lt;code&gt;apply-templates&lt;/code&gt; hinausgeht, wird benötigt wenn man z.B. eine For-Schleife über die Bodenbedeckungsanteile (&lt;code&gt;LandCoverShare&lt;/code&gt;) anwenden will und die Summe über alle Anteile bilden möchte (Zeile 200ff.). XSL-FO-Wissen braucht man, um das PDF zu designen/stylen und die Daten am gewünschten Ort zu platzieren. Der Saxon-Befehl ist wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar saxon9he.jar -s:CH310663327779.xml -xsl:parceldescription_extract_fo.xslt -o:CH310663327779.fo&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat ist eine &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH310663327779.fo&quot;&gt;XSL-FO-Datei&lt;/a&gt; (base64 wiederum gekürzt):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;fo:root xmlns:fo=&quot;http://www.w3.org/1999/XSL/Format&quot;
         xmlns:xsd=&quot;https://www.w3.org/2001/XMLSchema&quot;
         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&amp;gt;
   &amp;lt;fo:layout-master-set&amp;gt;
      &amp;lt;fo:simple-page-master master-name=&quot;mainPage&quot;
                             page-height=&quot;297mm&quot;
                             page-width=&quot;210mm&quot;
                             margin-top=&quot;12mm&quot;
                             margin-bottom=&quot;12mm&quot;
                             margin-left=&quot;15mm&quot;
                             margin-right=&quot;12mm&quot;&amp;gt;
         &amp;lt;fo:region-body margin-top=&quot;45mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
         &amp;lt;fo:region-before extent=&quot;40mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
         &amp;lt;fo:region-after extent=&quot;10mm&quot; background-color=&quot;#FFFFFF&quot;/&amp;gt;
      &amp;lt;/fo:simple-page-master&amp;gt;
   &amp;lt;/fo:layout-master-set&amp;gt;
   &amp;lt;fo:page-sequence master-reference=&quot;mainPage&quot; id=&quot;my-sequence-id&quot;&amp;gt;
      &amp;lt;fo:static-content flow-name=&quot;xsl-region-before&quot;&amp;gt;
         &amp;lt;fo:block&amp;gt;
            &amp;lt;fo:block-container absolute-position=&quot;absolute&quot;
                                top=&quot;6.7mm&quot;
                                left=&quot;0mm&quot;
                                line-height=&quot;1em&quot;
                                background-color=&quot;#FFFFFF&quot;&amp;gt;
               &amp;lt;fo:block font-size=&quot;10pt&quot;
                         font-style=&quot;italic&quot;
                         font-weight=&quot;700&quot;
                         font-family=&quot;Frutiger&quot;&amp;gt;Amt für Geoinformation&amp;lt;/fo:block&amp;gt;
               &amp;lt;fo:block font-size=&quot;10pt&quot;
                         font-style=&quot;italic&quot;
                         font-weight=&quot;400&quot;
                         font-family=&quot;Frutiger&quot;
                         margin-left=&quot;6mm&quot;
                         margin-top=&quot;1mm&quot;&amp;gt;
                  &amp;lt;fo:block&amp;gt;Rötistrasse 4&amp;lt;/fo:block&amp;gt;
                  &amp;lt;fo:block&amp;gt;4501 Solothurn&amp;lt;/fo:block&amp;gt;
                  &amp;lt;fo:block&amp;gt;032 627 75 92&amp;lt;/fo:block&amp;gt;
                  &amp;lt;fo:block&amp;gt;agi@bd.so.ch&amp;lt;/fo:block&amp;gt;
                  &amp;lt;fo:block&amp;gt;http://agi.so.ch&amp;lt;/fo:block&amp;gt;
               &amp;lt;/fo:block&amp;gt;
            &amp;lt;/fo:block-container&amp;gt;
            &amp;lt;fo:block-container absolute-position=&quot;absolute&quot;
                                top=&quot;0mm&quot;
                                left=&quot;123mm&quot;
                                background-color=&quot;#FFFFFF&quot;&amp;gt;
               &amp;lt;fo:block&amp;gt;
                  &amp;lt;fo:external-graphic height=&quot;6.7mm&quot;
                                       width=&quot;60mm&quot;
                                       content-width=&quot;scale-to-fit&quot;
                                       content-height=&quot;scale-to-fit&quot;
                                       src=&quot;url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABLAAAACBCAAAAAD1IxwIAAAABGdBTUEAALGPC/xhBQ....&apos;)&quot;/&amp;gt;
               &amp;lt;/fo:block&amp;gt;
            &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;/fo:block&amp;gt;
      &amp;lt;/fo:static-content&amp;gt;
      &amp;lt;fo:static-content flow-name=&quot;xsl-region-after&quot;&amp;gt;
         &amp;lt;fo:table table-layout=&quot;fixed&quot;
                   width=&quot;100%&quot;
                   margin-top=&quot;4mm&quot;
                   font-size=&quot;7pt&quot;
                   font-style=&quot;italic&quot;
                   font-weight=&quot;400&quot;
                   font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table-column column-width=&quot;50%&quot;/&amp;gt;
            &amp;lt;fo:table-column column-width=&quot;50%&quot;/&amp;gt;
            &amp;lt;fo:table-body&amp;gt;
               &amp;lt;fo:table-row&amp;gt;
                  &amp;lt;fo:table-cell&amp;gt;
                     &amp;lt;fo:block&amp;gt;2018-12-08 18:01:10&amp;lt;/fo:block&amp;gt;
                  &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;fo:table-cell text-align=&quot;right&quot;&amp;gt;
                     &amp;lt;fo:block&amp;gt;Seite &amp;lt;fo:page-number/&amp;gt;/&amp;lt;fo:page-number-citation-last ref-id=&quot;my-sequence-id&quot;/&amp;gt;
                     &amp;lt;/fo:block&amp;gt;
                  &amp;lt;/fo:table-cell&amp;gt;
               &amp;lt;/fo:table-row&amp;gt;
            &amp;lt;/fo:table-body&amp;gt;
         &amp;lt;/fo:table&amp;gt;
      &amp;lt;/fo:static-content&amp;gt;
      &amp;lt;fo:flow flow-name=&quot;xsl-region-body&quot;&amp;gt;
         &amp;lt;fo:block-container wrap-option=&quot;wrap&quot;
                             hyphenate=&quot;false&quot;
                             hyphenation-character=&quot;-&quot;
                             font-weight=&quot;700&quot;
                             font-size=&quot;14pt&quot;
                             font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot;&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-body&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell&amp;gt;
                        &amp;lt;fo:block&amp;gt;Grundstücksbeschrieb&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell&amp;gt;
                        &amp;lt;fo:block&amp;gt;GB-Nr. 680&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
               &amp;lt;/fo:table-body&amp;gt;
            &amp;lt;/fo:table&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;fo:block-container wrap-option=&quot;wrap&quot;
                             hyphenate=&quot;false&quot;
                             hyphenation-character=&quot;-&quot;
                             font-weight=&quot;400&quot;
                             font-size=&quot;10pt&quot;
                             font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;8mm&quot;&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;20mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
               &amp;lt;fo:table-body&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Gemeinde:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Biberist&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block/&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;EGRID:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;CH310663327779&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Grundbuch:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Biberist&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block/&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;NBIdent:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;SO0200002513&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
               &amp;lt;/fo:table-body&amp;gt;
            &amp;lt;/fo:table&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;fo:block-container wrap-option=&quot;wrap&quot;
                             hyphenate=&quot;false&quot;
                             hyphenation-character=&quot;-&quot;
                             font-weight=&quot;400&quot;
                             font-size=&quot;10pt&quot;
                             font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;40mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;30mm&quot;/&amp;gt;
               &amp;lt;fo:table-body&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Grundstücksart:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Liegenschaft&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Grundstücksfläche:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell text-align=&quot;right&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;65&apos;076 m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                        &amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
               &amp;lt;/fo:table-body&amp;gt;
            &amp;lt;/fo:table&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;fo:block-container wrap-option=&quot;wrap&quot;
                             hyphenate=&quot;false&quot;
                             hyphenation-character=&quot;-&quot;
                             font-weight=&quot;400&quot;
                             font-size=&quot;10pt&quot;
                             font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-body&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Bodenbedeckung:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Flurnamen:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;1mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;
                           &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;0mm&quot;&amp;gt;
                              &amp;lt;fo:table-column column-width=&quot;50mm&quot;/&amp;gt;
                              &amp;lt;fo:table-column column-width=&quot;20mm&quot;/&amp;gt;
                              &amp;lt;fo:table-column column-width=&quot;10mm&quot;/&amp;gt;
                              &amp;lt;fo:table-body border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Acker_Wiese&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;7&apos;194&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Gartenanlage&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;1&apos;623&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Gebaeude&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;1&apos;606&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Gebaeudeerschliessung&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;2&apos;580&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Obstkultur&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;48&apos;714&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot; border-style=&quot;solid&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;uebrige_Intensivkultur&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;3&apos;358&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                                 &amp;lt;fo:table-row border-width=&quot;0pt&quot;
                                               border-style=&quot;solid&quot;
                                               font-weight=&quot;400&quot;
                                               font-style=&quot;italic&quot;&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block&amp;gt;Total&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block text-align=&quot;right&quot;&amp;gt;65&apos;075&amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                    &amp;lt;fo:table-cell padding-top=&quot;1mm&quot;&amp;gt;
                                       &amp;lt;fo:block margin-left=&quot;1mm&quot; line-height-shift-adjustment=&quot;disregard-shifts&quot;&amp;gt;m&amp;lt;fo:inline baseline-shift=&quot;super&quot; font-size=&quot;60%&quot;&amp;gt;2&amp;lt;/fo:inline&amp;gt;
                                       &amp;lt;/fo:block&amp;gt;
                                    &amp;lt;/fo:table-cell&amp;gt;
                                 &amp;lt;/fo:table-row&amp;gt;
                              &amp;lt;/fo:table-body&amp;gt;
                           &amp;lt;/fo:table&amp;gt;
                        &amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Brunngrabenacker, Oberstmatt, Rumi, Rütiacker&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
               &amp;lt;/fo:table-body&amp;gt;
            &amp;lt;/fo:table&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;fo:block-container wrap-option=&quot;wrap&quot;
                             hyphenate=&quot;false&quot;
                             hyphenation-character=&quot;-&quot;
                             font-weight=&quot;400&quot;
                             font-size=&quot;10pt&quot;
                             font-family=&quot;Frutiger&quot;&amp;gt;
            &amp;lt;fo:table table-layout=&quot;fixed&quot; width=&quot;100%&quot; margin-top=&quot;12mm&quot;&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-column column-width=&quot;90mm&quot;/&amp;gt;
               &amp;lt;fo:table-body&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Grundbuchamt:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;700&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block&amp;gt;Nachführungsgeometer:&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
                  &amp;lt;fo:table-row&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block linefeed-treatment=&quot;preserve&quot;&amp;gt;Amtschreiberei Region Solothurn
Grundbuchamt
Rötistrasse 4
4501 Solothurn

Telefon 032 627 75 11
gb.so@fd.so.ch
www.so.ch/verwaltung/finanzdepartement/grundbuchaemter/&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                     &amp;lt;fo:table-cell font-weight=&quot;400&quot; padding-top=&quot;2mm&quot;&amp;gt;
                        &amp;lt;fo:block linefeed-treatment=&quot;preserve&quot;&amp;gt;Reto Meile
W+H AG
Blümlisalpstrasse 6
4562 Biberist

Telefon 032 671 26 30
admin@w-h.ch
www.w-h.ch&amp;lt;/fo:block&amp;gt;
                     &amp;lt;/fo:table-cell&amp;gt;
                  &amp;lt;/fo:table-row&amp;gt;
               &amp;lt;/fo:table-body&amp;gt;
            &amp;lt;/fo:table&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
         &amp;lt;fo:block-container page-break-before=&quot;always&quot;
                             margin=&quot;0mm&quot;
                             padding=&quot;0mm&quot;
                             space-before=&quot;0mm&quot;&amp;gt;
            &amp;lt;fo:block margin=&quot;0mm&quot; padding=&quot;0mm&quot; space-before=&quot;0mm&quot;&amp;gt;
               &amp;lt;fo:external-graphic height=&quot;217mm&quot;
                                    width=&quot;182mm&quot;
                                    content-height=&quot;scale-to-fit&quot;
                                    margin=&quot;0mm&quot;
                                    padding=&quot;0mm&quot;
                                    space-before=&quot;0mm&quot;
                                    border=&quot;0.5pt solid black&quot;
                                    src=&quot;url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACGYAAAoDCAYAAAB/VcSAAACAAElEQVR42uzdBZiUVfs...&apos;)&quot;/&amp;gt;
            &amp;lt;/fo:block&amp;gt;
         &amp;lt;/fo:block-container&amp;gt;
      &amp;lt;/fo:flow&amp;gt;
   &amp;lt;/fo:page-sequence&amp;gt;
&amp;lt;/fo:root&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der letzer Schritt ist die Umwandlung (oder das Formatieren) der XSL-FO-Datei in die PDF-Datei. Dazu kann man &lt;a href=&quot;https://xmlgraphics.apache.org/fop/&quot;&gt;Apache Fop&lt;/a&gt; verwenden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;./fop-2.3/fop/fop -fo CH310663327779.fo -pdf CH310663327779.pdf -c fop.xconf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/fop.xconf&quot;&gt;fop.xconf&lt;/a&gt;-Datei wird in meinem Fall benötigt, weil ich zusätzliche Schriftarten benötige.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH310663327779.pdf&quot;&gt;Resultat&lt;/a&gt; ist so wie es Jasper nicht besser machen könnte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/xslt_xslfo_p1/pdf_output.png&quot; alt=&quot;PDF Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der ersten Seite sollten die Bodenbedeckungsanteile noch bündig mit den restlichen Informationen in der linken Hälfte sein. Auf der zweiten Seite ist die Amtsinformation im Header überflüssig. Aber das ist bloss noch einfaches Handwerk.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Highlighting des Grundstückes habe ich nicht vom WMS-Server machen lassen, sondern bewusst programmatisch mit &lt;a href=&quot;http://www.geotools.org/&quot;&gt;&lt;em&gt;GeoTools&lt;/em&gt;&lt;/a&gt; umgesetzt. Dies im Hinblick auf eine mögliche PDF-Erstellungslösung für den ÖREB-Kataster. Dort werden im XML-Auszug die Geometrien zum Highlighten mitgeliefert und es kann nicht via WMS-Server gemacht werden (jedenfalls habe ich es so verstanden).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man die beiden Schritte zusammen hat, kann man diese als allerletzten Schritt in die &lt;em&gt;Spring Boot&lt;/em&gt; Applikation einbinden. Um sich über die API schlau zu machen, hilft meistens ein Blick in die Beispiele oder Tests, die im Quellcode der jeweiligen Programme vorhanden sind. Das Ziel ist erreicht, wenn der folgende Aufruf ein PDF liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;http://localhost:8887/parcel/extract/pdf/CH310663327779&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Ansatz lässt sich jetzt nicht nur ein PDF aus dem XML herstellen, sondern auch andere Formate. Massiv einfacher wird es wenn man nur HTML erzeugen will. Dann fällt der zweite Schritte mit XSL-FO weg und es reicht eine Transformation mit XSLT. Für die einfachere Verifikation der Verknüpfung der &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH970687433258.xml&quot;&gt;Dokumente&lt;/a&gt; im kantonalen Nutzungsplanungsmodell habe ich das ÖREB-Auszugsschema verwendet und daraus eine &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/CH970687433258.html&quot;&gt;HTML-Datei&lt;/a&gt; &lt;a href=&quot;http://blog.sogeo.services/data/xslt_xslfo_p1/landuseplans_extract_html.xslt&quot;&gt;generiert&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist das die Zukunft der PDF-Herstellung in einer GDI? Leider ist XSL-FO mehr oder weniger tot. Eher mehr, weil es nicht mehr weiterentwickelt wird. XSLT scheint lebendiger zu sein. Trotzdem muss man aufgrund der vielen Informationen im Internet schliessen, dass es zumindest jetzt noch relativ häufig im Einsatz ist. Kostenpflichtige XSL-FO-Prozessoren sind ebenfalls immer noch verfügbar. Eine zukünftige Alternative könnte/dürfte &lt;a href=&quot;https://print-css.rocks/&quot;&gt;CSS Paged Media&lt;/a&gt; sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich finde XSLT/XSL-FO aber ein sauberer und transparenterer Ansatz als Jasper. Aber vielleicht habe ich bloss einen neuen Lieblingshammer und suche Nägel.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster: As low level as possible</title>
      <link>http://blog.sogeo.services/blog/2018/10/23/oereb-kataster-2-as-low-level-as-possible.html</link>
      <pubDate>Tue, 23 Oct 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/10/23/oereb-kataster-2-as-low-level-as-possible.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Frage aus dem &lt;a href=&quot;http://blog.sogeo.services/blog/2018/10/21/oereb-kataster-1-as-a-gradle-script.html&quot;&gt;letzten Beitrag&lt;/a&gt; &amp;laquo;ÖREBlex: ja oder nein?&amp;raquo; ist  falsch gestellt. Es geht nicht direkt um die Anschaffung von OEREBlex, sondern vielmehr wie Dokumente im kantonalen ÖREB-Gesamtsystem (nennen wir es mal so) in den Daten eingebunden werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Gedanke, dass die Geobasisdaten mit den Rechtsvorschriften eine Einheit bilden und so die Eigentumsbeschränkung unmittelbar beschreiben, finde ich gut und plausibel. Das Transferstrukturteilmodell des Rahmenmodells und daran angelehnte MGDM und wohl einige KGDM bilden diese Aussage ab. Ganz vereinfacht sieht das so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk-p2/geometrie-oereb-dokument.png&quot; alt=&quot;Beziehung Geometrie - Oereb - Dokument&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der modellbasierten Numerisierung der Nutzungsplanung war für uns - losgelöst vom Thema ÖEREB-Kataster - wichtig, dass neben den Geometrien eben auch die dazugehörigen Rechtsvorschriften und Beschlussdokumente miterfasst werden. Das kantonale Nutzungsplanungsmodell (resp. in letzter Konsequenz eben das Rahmenmodell) lässt dies zu. Gemäss der Abbildung von oben entspricht die Klasse &lt;code&gt;Eigentumsbeschränkung&lt;/code&gt; in diesem Fall der Klasse &lt;code&gt;Typ&lt;/code&gt; im kantonalen Modell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bestehende Ortsplanungen zu digitalisieren und die Verknüpfung mit Dokumenten herzustellen, die existieren und als HTTP-Ressource verfügbar sind, ist keine Hexerei. Die Herausforderung beginnt erst, wenn die Dokumente noch nicht existieren. Z.B. der Beschluss-RRB, Zonen- und Baureglemente oder allfällige Gestaltungspläne sind noch nicht vorhanden, falls eine komplette Ortsplanungsrevision oder Teilrevision gemacht wird. Erst recht ist die zukünftige URL noch nicht bekannt. Und so wie ich es verstanden habe, kommt hier ÖREBlex ins Spiel. Es kann unter anderem als &amp;laquo;Bündelungsschicht&amp;raquo; wie auch als Blick in die Zukunft dienen. D.h. man macht ein Päckli und das Päckli bekommt eine URL. Im Päckli selber muss noch kein Dokument vorhanden sein. Der Datenerfasser muss jetzt zu seinem &lt;code&gt;Typ&lt;/code&gt; bloss einen Link erfassen. Ob die Dokumente, die für diese Eigentumsbeschränkung bereits vorhanden sind oder nicht, spielt keine Rolle. Dem Päckli kann man nun bei Abschluss den Beschluss-RRB und Rechtsvorschriften zuweisen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben den auf der Hand liegenden Vorteilen, geht für mich hier aber die Einheit zwischen Geometrie und Rechtsvorschrift verloren. Zumindest auf der Persistenzschicht. Wenn wir das so machen würden und ein Kunde das KGDM der Nutzungsplanung bezieht, bekommt er die Geometrien und Typen, die auf einen Link zeigen, der mir eine Liste (oder XML, HTML, &amp;#8230;&amp;#8203;) mit weiteren Links zu den Dokumenten zurückliefert. Der Kunde muss mit diesen Rückgaberesultaten umgehen können und braucht dazu wohl oder übel weitere Software oder Skripte. Darum: Dokumente gehören gleichberechtigt zu den Geometrien in ein Datenmodell (sei es KGDM, MDGM oder in die Transferstruktur).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man das, braucht es wohl mehr organisatorische Koordination, was ich aber für machbar halte. Ein technisches Problem ist der Umgang mit der URL der zu verlinkenden aber noch nicht vorhandenen Dokumenten. Ab einem gewissen Zeitpunkt bei einer Ortsplanung, dürfte man wohl wissen, welche Dokumente entstehen werden. Frage: Was für Möglichkeiten gibt es, das nicht vorhandene Dokument resp. die URL zum nicht vorhandenen Dokument im Modell zu erfassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jemand erstellt ein Dummy-PDF und stellt es als HTTP-Ressource bereit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Man definiert zusammen Konventionen und garantiert die Einhaltung dieser Konventionen, z.B. Link zur HTTP-Ressource besteht aus BFS-Nummer, Typ des Dokumentes etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oder man verwendet das Prinzip der &lt;a href=&quot;http://bibpurl.oclc.org/faq.html&quot;&gt;PURL&lt;/a&gt;: Eine PURL ist &lt;a href=&quot;http://bibpurl.oclc.org/faq.html#toc1.2&quot;&gt;funktional eine URL&lt;/a&gt;. Anstelle der direkten URL auf das Dokument, erfasst/verlinkt man die PURL. Die PURL zeigt auf einen Server, der die zur PURL passende URL heraussucht und anschliessend ein simples HTTP redirect ausführt. Soweit technisch nichts neues. Was es nun braucht, ist jemand der dem Datenerfasser die PURL bei Bedarf liefert und wenn das Dokument vorhanden ist, dieses auf der Fileablage o.ä. ablegt. Garantiert werden muss das P = Persistent. Die PURL darf sich nicht ändern, sonst gewinnt man genau nichts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Solange das Dokumente noch nicht real exisitert, liefert der PURL-Server den Statuscode 404 zurück. Wenn das Dokument vorhanden ist, wird ein Redirect mit Statuscode 302 gemacht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie bekomme ich einen PURL-Server? Im Grundsatz braucht es sehr wenig: &lt;a href=&quot;https://httpd.apache.org/&quot;&gt;&lt;em&gt;Apache&lt;/em&gt;&lt;/a&gt; und eine Datenbank. Rewriten und Redirecten kann Apache mit dem &lt;code&gt;mod_rewrite&lt;/code&gt; &lt;a href=&quot;https://httpd.apache.org/docs/current/mod/mod_rewrite.html&quot;&gt;Modul&lt;/a&gt;. Völlig unpraktikabel wäre die Verwaltung der Redirects von PURL auf die URL direkt in der Apache-Config Datei. Apache kennt die &lt;a href=&quot;https://httpd.apache.org/docs/current/mod/mod_rewrite.html&quot;&gt;Direktive&lt;/a&gt; &lt;code&gt;RewriteMap&lt;/code&gt;. Diese Direktive definiert externe Funktionen im Kontext von &lt;code&gt;RewriteRule&lt;/code&gt; und &lt;code&gt;RewriteCond&lt;/code&gt;. So kann man als Datenquelle eine Textdatei angeben oder auch ein externes Programm (ganz allgemein), das via &lt;code&gt;STDIN&lt;/code&gt; und &lt;code&gt;STDOUT&lt;/code&gt; kommuniziert. Für unseren Anwendungsfall ist das Einbinden einer Datenbank verheissungsvoll. Dazu braucht Apache das &lt;a href=&quot;https://httpd.apache.org/docs/2.4/mod/mod_dbd.html&quot;&gt;Modul&lt;/a&gt; &lt;code&gt;mod_dbd&lt;/code&gt;. Mehr nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unter der Annahme, dass auf einem Ubuntu-Rechner &lt;em&gt;Apache&lt;/em&gt; und &lt;em&gt;PostgreSQL&lt;/em&gt; bereits vorhanden sind, braucht es folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;sudo apt-get install libaprutil1-dbd-pgsql
sudo a2enmod rewrite
sudo a2enmod dbd
sudo /etc/init.d/apache2 restart&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unter Umständen muss der Datenbanktreiber in das modules-Verzeichnis von Apache kopiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;sudo cp /usr/lib/x86_64-linux-gnu/apr-util-1/apr_dbd_pgsql*  /usr/lib/apache2/modules/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Apache-Config-Datei muss ergänzt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Folgende Befehle sind auszuführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;Directory /var/www/html&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
&amp;lt;/Directory&amp;gt;

&amp;lt;IfModule mod_rewrite.c&amp;gt;
    DBDriver pgsql
    DBDParams &quot;hostaddr=192.168.50.8 user=ddluser password=ddluser dbname=edit&quot;
    DBDMin 4
    DBDKeep 8
    DBDMax 20
    DBDExptime 300

    RewriteEngine on

    RewriteMap data &quot;dbd:select resource_url from agi_oereb_purl.redirect_map where input_url=%s&quot;

    RewriteCond ${data:$1} =&quot;&quot;
    RewriteRule ^/(.*) - [R=404,L]
    RewriteCond ${data:$1} !=&quot;&quot;
    RewriteRule ^/(.*) ${data:$1} [R=302]
&amp;lt;/IfModule&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vieles ist selbsterklärend. &lt;code&gt;RewriteMap&lt;/code&gt; macht die Abfrage in der Datenbank mit &lt;code&gt;%s&lt;/code&gt;= Pfad der URL. Dies &lt;a href=&quot;http://bibpurl.oclc.org/faq.html#toc1.4&quot;&gt;entspricht&lt;/a&gt; dem &lt;code&gt;name&lt;/code&gt;-Teil der PURL. Die Query liefert die URL zum Dokument zurück.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls die Query nichts findet (Zeile 19), wird der Statuscode 404 vom Webserver zurückgeliefert (Zeile 20). Wird etwas gefunden (Zeile 21), wird ein Redirect zum Dokument ausgeführt (Zeile 22).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Testhalber habe ich zwei Dummyrecords in der Tabelle erfasst:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk-p2/redirect-table.png&quot; alt=&quot;Redirect Table DBeaver&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;curl&lt;/code&gt; kann man die Abfragen testen und sich die Statuscodes ausgeben lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;curl -X HEAD -I  http://192.168.50.8/foo&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;HTTP/1.1 302 Found
Date: Tue, 23 Oct 2018 18:59:26 GMT
Server: Apache/2.4.29 (Ubuntu)
Location: https://geoweb.so.ch/zonenplaene/Zonenplaene_pdf/34-Messen/Reglemente/034_ZR.pdf
Content-Type: text/html; charset=iso-8859-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wird ein Dokument nicht gefunden, soll 404 ausgeliefert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;curl -X HEAD -I  http://192.168.50.8/fubar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;HTTP/1.1 404 Not Found
Date: Tue, 23 Oct 2018 19:00:15 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Type: text/html; charset=iso-8859-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Blick in die Zukunft kann mit dem Prinzip PURL gemacht werden. Es muss dafür nicht eine teure Spezialsoftwarelösung her, die für etwas entwickelt wurde, für das es Lösungen gibt, die sehr einfach und entsprechend transparent sind. Eine einfache Lösung mit Apache und PostgreSQL passt zu unserer Organisation, die sich mit beiden Produkten bestens auskennt und bereits täglich im Einsatz hat.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ÖREB-Kataster as a Gradle Script</title>
      <link>http://blog.sogeo.services/blog/2018/10/21/oereb-kataster-1-as-a-gradle-script.html</link>
      <pubDate>Sun, 21 Oct 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/10/21/oereb-kataster-1-as-a-gradle-script.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit einigen Jahren investieren wir in fundamentale Werkzeuge, Arbeitsweisen und Basisinfrastrukturen. Es ist als würde die Saat bei der technischen Umsetzung des ÖREB-Katasters langsam aufgehen. Dank (oder trotz?) den vielen Weisungen zum ÖREB-Kataster ist relativ klar was man als Kanton bieten muss. Zentral ist der &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/publication/instruction.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/OEREB-XML-Aufruf_de.pdf.html&quot;&gt;ÖREB-Webservice&lt;/a&gt;. Dieser ist das Fleisch am Knochen. Er stellt ein paar einfache Funktionen (wie z.B. EGRID mittels Koordinate eruieren) zur Verfügung, ist aber ebenso verantwortlich für den Output des &lt;a href=&quot;https://www.cadastre.ch/de/services/publication.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/OEREB-Data-Extract_de.pdf.html&quot;&gt;XML-/JSON-Auszugs&lt;/a&gt; und des &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/service/extract/static.html&quot;&gt;statischen Auszuges (PDF)&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine der Fragestellungen für den Kanton ist wie und mit welchen Mitteln man das umsetzt. Um das Fuder nicht zu überladen, lassen wir den dynamischen Auszug mal weg. Das wird sowieso eine Speziellanfertigung für den jeweilig eingesetzten Web GIS Client. Oder man nimmt gleich so etwas wie den &lt;a href=&quot;https://oerebview.apps.be.ch&quot;&gt;Smart-Auszug&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mir gefiel immer der Gedanke eines gemeinsamen Rahmenmodelles. Wenn man das vom Ende her denkt, ist der XML-Auszug nämlich nichts anderes als das Umformatieren des Resultates einer Datenbankabfrage. Die Datenbankabfrage selber entspricht einer absolut klassischen GIS-Aufgabe: Dem Ausstanzen eines Polygons (in unserem Fall eines Grundstückes) aus einer Datenbanktabelle mit den aggretierten ÖREB-Daten von Bund und Kanton. Und wenn wir schon dabei sind, ist das etwa das einzige Mal, dass es hier um Geoinformation geht und nicht bloss um gewöhnliches Datenmanagement.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Grundgedanken kann man sich dann an eine grobe Systemarchitektur machen. Die technischen und organisatorischen Rahmenbedinungungen bei uns sind:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PostgreSQL/PostGIS-Datenbanken&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;INTERLIS-Werkzeuge vorhanden. Daten können mit &lt;em&gt;ili2pg&lt;/em&gt; und &lt;em&gt;ilivalidator&lt;/em&gt; einfachst importiert, exportiert und validiert werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.sogeo.services/blog/2018/02/11/datenfluesse-mit-gradle-3.html&quot;&gt;&lt;em&gt;GRETL/Jenkins&lt;/em&gt;&lt;/a&gt; als Orchestrierungswerkzeug für beliebige Prozesse&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Datenumbauten (kantonale Struktur &amp;#8594; ÖREB-Transferstruktur) werden mit SQL gemacht&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus diesen Rahmenbedinungungen und ein wenig überlegen, folgt die folgende Architektur:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk-p1/oereb-kataster-systemarchitektur.png&quot; alt=&quot;ÖREB-Systemarchitektur&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ÖREB-Bundesdaten werden heruntergeladen und in die ÖREB-Katasterdatenbank mit &lt;em&gt;ili2pg&lt;/em&gt; importiert. Gleich behandelt werden kantonale Themen (z.B. KbS), die in einer Fachanwendung nachgeführt werden. Diese Fachanwendung ist für das Bereitstellen der ÖREB-Transferstruktur verantwortlich. Weitere kantonale Daten (z.B. Nutzungsplanung) werden modelläquivalent in einer Erfassungsdatenbank vorgehalten. Mit SQL werden diese Daten in die modelläquivalente Transferstruktur in einem Staging-Schema in der Erfassungsdatenbank gebracht und mit Verweisen auf die gesetzlichen Grundlagen angereichert. Dies verbucht man hauptsächlich unter Fleissarbeit, wobei hier natürlich Besonderheiten und Datenfehler das Leben schwer machen können. Sind sie umgebaut werden sie mit &lt;em&gt;ili2pg&lt;/em&gt; in die Transferstruktur exportiert, mit &lt;em&gt;ilivalidator&lt;/em&gt; geprüft und anschliessend in die ÖREB-Katasterdatenbank - wo bereits die Bundesdaten warten - importiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Verweise auf gesetzliche Grundlagen und zuständige Stellen sind etwas, was wahrscheinlich selten in kantonalen Geodatensätzen bereits vorhanden sind. Diese Daten können in einem separaten Datenbankschema in der Erfassungsdatenbank aktuell gehalten werden. Die passenden Teilmodelle im Rahmenmodell gibt es ja, um modelläquivalente Strukturen in der Datenbank anzulegen und anschliessend mit QGIS nachzuführen. Der Nachführungsauwand dürfte sich vor allem bei den gesetzlichen Grundlagen in Grenzen halten. Bei Bedarf können die Daten wieder exportiert werden und in der ÖREB-Katasterdatenbank ausgetauscht werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um den XML-Auszug erstellen zu können, fehlen noch ein paar Kleinigkeiten (die in der Transferstruktur nicht vorhanden sind) wie Logos, Glossar etc. Dazu schreibt man sich ein kleines INTERLIS-Modell und erfasst die notwendigen Daten in der Erfassungsdatenbank, exportiert die Daten und importiert sie in der ÖREB-Katasterdatenbank.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um das eher theoretische Konzept zur Architektur mit was Greifbaren zu untermauern, habe ich mir mit &lt;em&gt;GRETL&lt;/em&gt; ein paar Tasks zusammengeschustert, die mit den Bundesdaten und Nutzungsplanungsdaten (im kantonalen Modell) den Prozess durchspielen (&lt;a href=&quot;https://github.com/edigonzales/oereb-kataster&quot;&gt;Github-Repository&lt;/a&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Erstellen und Initialisieren der Datenbank-Schemas mit Sekundärdaten (Gesetzliche Grundlagen, zuständige Stellen, Annex Modell mit Logos, Glossar etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download und Import der Bundesdaten und der amtlichen Vermessung in die ÖREB-Datenbank und der kantonalen Daten (Nutzungpslanung) in die Erfassungsdatenbank.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Datenumbau Nutzungsplanung: Kantonales Modell &amp;#8594; Transferstruktur&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export der Nutzungsplanung in die Transferstruktur und Import in die ÖREB-Katasterdatenbank&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Erwähnenswert ist das (&lt;strong&gt;PRE-ALPHA!&lt;/strong&gt;) Erstellen der einzelnen Symbole für den Legendeneintrag. Diese werden mittels &lt;code&gt;GetLegendGraphic&lt;/code&gt;-Request erstellt. Zuvor muss jedoch ein &lt;code&gt;GetStyles&lt;/code&gt;-Request erfolgen, um die Namen der Symbol-Regel zu kennen. Wahrscheinlich kommt man nicht umher hier gewisse Abmachungen mit der zuständigen Stelle bei der Erarbeitung der Legenden zu treffen, damit die Maschine auch wieder die Zuordnung zum Legendeneintrag in der Datenbank machen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Notwendig - wenn man das mal durchspielen will - ist &lt;a href=&quot;https://www.vagrantup.com/&quot;&gt;&lt;em&gt;Vagrant&lt;/em&gt;&lt;/a&gt;, &lt;em&gt;Java&lt;/em&gt; und &lt;a href=&quot;https://gradle.org/&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;Vagrant&lt;/em&gt; wird für die Datenbanken und QGIS-Server benötigt. &lt;em&gt;Gradle&lt;/em&gt; für das Ausführen der GRETL-Tasks. &lt;a href=&quot;https://plugins.gradle.org/plugin/ch.so.agi.gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; selber wird vom Gradle-Plugin-Repository automatisch heruntergeladen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Folgende Befehle sind auszuführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;vagrant up&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warten bis virtuelle Maschine hochgefahren ist, anschliessend:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;gradle createSchemaOereb importFederalCodesets importFederalLegalBasis importCantonalLegalBasisToOereb createSchemaOerebAuszugAnnex createSchemaOerebNutzungsplanung importFederalLegalBasisToOerebNutzungsplanung importCantonalLegalBasisToOerebNutzungsplanung importResponsibleOfficesToOerebNutzungsplanung createOerebNutzungsplanungViews createSchemaNutzungsplanung createSchemaAmtlicheVermessung
gradle replaceFederalData replaceCantonalLandUsePlansData replaceCadastralSurveyingData importOerebAuszugAnnex
gradle deleteStaging insertStaging updateLegendEntrySymbols
gradle exportLandUsePlansToXtf replaceOerebNutzungsplanung&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Kann je nach Entwicklungsstand auch mal nicht funktionieren.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat alles geklappt sind jetzt sowohl die vorhandenen vier Bundesdatensätze wie auch die Nutzungsplanung in der ÖREB-Katasterdatenbank vorhanden und können z.B. in QGIS angezeigt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/oerebk-p1/qgis-transferstruktur.png&quot; alt=&quot;QGIS-Transferstruktur&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man die ÖREB-Daten in einer Datenbank, fehlt noch der ÖREB-Webservice, der mir PDF- und XML-Auszug liefert. Ich bin ein Freund des Gedankens, dass der Service selber nur XML (oder JSON) liefern soll resp. das PDF soll aus dem XML erstellt werden. Verhindert werden soll, dass unterschiedliche Abfragen für die jeweilige Herstellung eines Formates verwendet werden müssen. Das XML ist die &amp;laquo;single source of truth&amp;raquo;. Für das Herstellen eines PDF-Auszuges aus einem XML, gibt es den &lt;a href=&quot;https://github.com/geocloud/oereb_xml_pdf_service&quot;&gt;XML2PDF-Service&lt;/a&gt;, der in einem Schwergewichtsprojekt noch elaboriert wurde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn es einzig noch um den XML-Auszug geht, kann man etwas selber machen oder etwas bestehendes nehmen, wie z.B. &lt;a href=&quot;https://github.com/camptocamp/pyramid_oereb&quot;&gt;pyramid_oereb&lt;/a&gt;. Bei pyramid_oereb müsste man wohl die modelläquivalente Struktur nochmals umbauen in die eigene Datenbankstruktur. Wenn man etwas selber macht, ist man natürlich freier. Als Proof-of-Concept und als Entscheidungsgrundlage, ob es etwas vorhandenes werden soll oder ob noch Effort in eine Entwicklung gesteckt werden soll, habe ich mir einen Webservice zusammengeschustert, der funktional natürlich völlig unvollständig ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Muss man XML erstellen, gibt es verschiedene Möglichkeiten dies zu tun. In unserem Fall sind &lt;a href=&quot;http://schemas.geo.admin.ch/V_D/OeREB/&quot;&gt;XML Schema Definitionen&lt;/a&gt; vorhanden. Mit XML-Binding kann man automatisch Java-Klassen ableiten lassen. Das hat den Vorteil, dass ich mich als Programmierer nicht um das eigentliche Formatieren kümmern muss, sondern ich muss nur die Java-Klassen korrekt mit Daten abfüllen. Eine andere Variante wäre das Arbeiten mit einer Templating Engine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das automatische Herstellen der Java-Klassen mit &lt;code&gt;xjc&lt;/code&gt; war - diplomatisch formuliert - spannend. Das ist vor allem darauf zurückzuführen, dass im ExtractData-Schema für die Geometrie-Kodierung GML verwendet wird. Von den insgesamt circa 420 Klassen, sind 357 Klassen GML-spezifisch. Notwendig für das erfolgreiche Erstellen der Klassen sind einige Konfigurationsdateien (für die GML-Besonderheiten), die man Gott sei Dank mit ein wenig Googlen im Internet findet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &lt;a href=&quot;https://github.com/edigonzales/oereb-kataster/tree/master/web-service&quot;&gt;Service&lt;/a&gt; selber wurde mit &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; gemacht. Cloned man das &lt;a href=&quot;https://github.com/edigonzales/oereb-kataster.git&quot;&gt;Repository&lt;/a&gt; reicht nach dem Wechseln in das &lt;code&gt;web-service&lt;/code&gt;-Verzeichnis:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;./gradlew bootRun&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;um den Webservice zu starten. Funktional umgesetzt ist, wie bereits erwähnt, wenig (war vor einem Jahr schon mal &lt;a href=&quot;https://github.com/edigonzales/oereb-web-service&quot;&gt;weiter&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was funktioniert ist die Suche nach einem EGRID mittels Koordinate:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;http://localhost:8888/oereb/getegrid/xml/?XY=2598098,1225627&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das Anzeigen der betroffenen resp. nicht betroffenen und nicht vorhandenen ÖREB-Themen, dem Datum und dem Flavour beim Extract-Aufruf:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;http://localhost:8888/oereb/extract/reduced/xml/geometry/CH870672603279 (Flughafen Grenchen)
http://localhost:8888/oereb/extract/reduced/xml/geometry/CH933273065885 (nur Nutzungsplanung)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Meine Fazit: Identifiziert man die einzelne Teile und Prozesse des grossen Ganzen, zeigt sich schnell, dass vieles mit unseren vorhandenen Bordmitteln (aka Legobausteinen) effizient und transparent gelöst werden kann. Hilfreich ist hier sicher einmal mehr der modellbasierte Ansatz und ein unverkrampfter Umgang mit INTERLIS und Datenumbauten mit SQL (was mich immer wieder überrascht&amp;#8230;&amp;#8203;). Ebenso hilft das tägliche Mantra: &amp;laquo;Spatial is not special&amp;raquo;, was sich nochmals zeigen wird, wenn es um die Frage geht &amp;laquo;ÖREBlex: ja oder nein?&amp;raquo; (to be continued).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GeoServer Magie #1 - GetFeatureInfo mit Freemarker Templates</title>
      <link>http://blog.sogeo.services/blog/2018/09/10/geoserver-magie-1.html</link>
      <pubDate>Mon, 10 Sep 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/09/10/geoserver-magie-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten werden ja häufig - den Regeln der Kunst entsprechend - in einem normalisierten und relationalen Datenmodell erfasst. Da kann die Geometrie als solches auch schon mal die Nebenrolle spielen. Häufig haben wir den Fall, dass einer Geometrie mehrere Fotos oder PDF zugewiesen werden müssen. Soweit noch nichts Aussergewöhnliches und auch die Umsetzung geht rasch: Dank &lt;a href=&quot;https://qgis.org/&quot;&gt;QGIS&lt;/a&gt;, &lt;a href=&quot;http://interlis.ch/&quot;&gt;INTERLIS&lt;/a&gt; und dem &lt;a href=&quot;https://github.com/opengisch/projectgenerator&quot;&gt;QGIS-Projektgenerator&lt;/a&gt; ist die Sache in kürzester Zeit modelliert und die generische Fachschale auf Knopfdruck parat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Diskussion startet aber spätestens wenn man dieses normalisierte Erfassungsmodell in eine für die gebräuchlichen WMS-Softwaren (also QGIS-Server, &lt;a href=&quot;http://geoserver.org/&quot;&gt;GeoServer&lt;/a&gt; und &lt;a href=&quot;https://mapserver.org/&quot;&gt;MapServer&lt;/a&gt;) taugliche Struktur bringen muss. Wenn dieser normalisierte und relationale Ansatz mit QGIS-Desktop und seinen mächtigen Formularfunktionen bei der Datenerfassung noch gut funktioniert, geht das nicht mehr so wirklich mit den genannten WMS-Servern. Diese erwarten eher resp. funktionieren am besten mit flachen und &amp;laquo;dummen&amp;raquo; Tabellen. Man möchte ja bloss z.B. im Web GIS Client auf eine Geometrie klicken und dann soll zumindest als Link jedes Foto oder PDF aufgelistet werden. Wie so oft gibt es hier verschiedene Lösungen. Bei uns als Altlast noch häufig verbreitet: man löst es halt für jeden Layer separat in der Objektabfrage. Das heisst, dass mit einem Mischmasch aus einer Skriptsprache (PHP&amp;#8230;&amp;#8203;) und SQL was zusammengebastelt wird, so dass die Objektabfrage etwa das liefert, was man sich vorstellt. Vorteil: man kann sich selber verwirklichen. Nachteil: man verwirklicht sich selber.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Da wir die Daten sowieso praktisch immer von einem Erfassungsmodell in ein Publikationsmodell umbauen, kann man hier das flachwalzen durchführen. Wie walze ich aber eine 1:n-Beziehung flach? Ich kann die Links zu einem String aggregieren und mit einem Komma oder, falls es der Klient richtig rendern kann, mit einem HTML-Break trennen. Das ist dann einerseits auch nur halb-schön und funktioniert nur solange es sich bei flachzuwalzenden Daten um ein einzelnes Attribut handelt (eben ein Link zu einem Foto) und nicht komplette Objekte. Klar kann man jedes Attribut des Objektes genaus so behandeln aber das führt dann wirklich ins Nirwana.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und hier kommt JSON ins Spiel. Beim Datenumbau aggregiere ich die Objekte zu einem JSON-Array. Am einfachsten kann man das an einem konkreten Beispiel erläutern: Unser kantonales Nutzungsplanungsmodell orientiert sich am Bundesmodell. Dieses wiederum am &lt;a href=&quot;https://www.cadastre.ch/de/manual-oereb/publication/instruction.detail.document.html/cadastre-internet/de/documents/oereb-weisungen/Rahmenmodell-de.pdf.html&quot;&gt;ÖREB-Rahmenmodell&lt;/a&gt;. Jeder Geometrie wird ein Zonentyp zugewiesen. Jedem Zonentyp können mehrere Dokumente zugewiesen sein. Und um es noch spannender zu machen, gibt es diese Selbsreferenzierung der Dokumente auf sich selber. Als Übergangslösung bis zur Einführung des ÖREB-Katasters möchte man mit einem Klick im Web GIS Client auf eine Geometrie die wichtigsten Informationen präsentiert bekommen, d.h. neben dem Zonentyp sicher sämtliche dazugehörigen Dokumente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Tabelle im Publikationsmodell hat jetzt mindestens eine Geometriespalte, eine Spalte mit dem Zonentyp und eben eine Spalte vom Typ JSON. In dieser Spalte sind sämtliche Dokumente, die für diesen Typ gültig sind als JSON-Array kodiert (Attribut &amp;laquo;dokumente&amp;raquo;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p1/grundnutzung-json.png&quot; alt=&quot;Grundnutzung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man nun ein GetFeatureInfo auf diesen Layer absetzt, muss man sich zuerst entscheiden, was man vom WMS-Server retourniert haben will. Ob die Spezifikation überhaupt ein zwingendes Outputformat vorgibt, entzieht sich meiner Kenntnis, kann aber &lt;a href=&quot;http://portal.opengeospatial.org/files/?artifact_id=1081&amp;amp;version=1&amp;amp;format=pdf&quot;&gt;hier&lt;/a&gt; oder &lt;a href=&quot;http://portal.opengeospatial.org/files/?artifact_id=14416&quot;&gt;hier&lt;/a&gt; nachgelesen werden. Typischer Vertreter der Outputformate sind GML, HTML oder Text. Für jedes dieser Formate wird das JSON-Attribut standardmässig von GeoServer als String ausgeliefert. Somit hätte man noch nicht viel gewonnen. In QGIS sieht das so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p1/getfeatureinfo-feature.png&quot; alt=&quot;GetFeatureInfo Feature&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Entscheidet man sich, dass das Rendering des GetFeatureInfo-Requests auf dem Server und nicht auf dem Client geschehen soll, kann man HTML als Outputformat wählen. Standardmässig kommt hier in GeoServer eine relativ hässliche Tabelle. Aber jetzt kommt eben die Magie: Mit &lt;a href=&quot;https://freemarker.apache.org/&quot;&gt;Freemarker-Templates&lt;/a&gt; kann ich was Schönes selber &lt;a href=&quot;http://docs.geoserver.org/stable/en/user/tutorials/freemarker.html&quot;&gt;zusammenbasteln&lt;/a&gt;. Der Fokus liegt aber in meinem Fall weniger in &amp;laquo;schön&amp;raquo;, sondern dass ich eben das JSON-Array selber prozessieren kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Freemarker ist eine Template Engine, um Text-Output zu generieren. Für die GetFeatureInfo-Templates werden ein Header-, ein Content- und ein Footer-Template benötigt. &amp;laquo;Nomen est Omen&amp;raquo; in diesem Fall. Da man in unserem Fall HTML generieren will, steht im Header hauptsächlich CSS und der Beginn eines HTML-Dokumentes usw. Der Footer schliesst das sauber ab. Im Content-Template kann man das eigentliche Präsentieren des Features abhandeln:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;table class=&quot;featureInfo&quot;&amp;gt;
  &amp;lt;caption class=&quot;featureInfo&quot;&amp;gt;Zonenplan: Grundnutzungen&amp;lt;/caption&amp;gt;
  &amp;lt;col style=&quot;width:30%&quot;&amp;gt;
  &amp;lt;col style=&quot;width:70%&quot;&amp;gt;
  &amp;lt;#list features as feature&amp;gt;
    &amp;lt;#assign attrs = feature.attributes &amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Typ-Bezeichnung:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.typ_bezeichnung.value}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Kantonaler Typ:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.typ_kt.value}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Kommunaler Typ:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.typ_code_kommunal.value}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Verbindlichkeit:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.typ_verbindlichkeit.value}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Rechtsstatus:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.rechtsstatus.value}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Publiziert ab:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;${attrs.publiziertab.value?date(&apos;MM/dd/yy&apos;)?string[&quot;dd. MMMM yyyy&quot;]}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td colspan=&quot;2&quot;&amp;gt;&amp;lt;strong&amp;gt;Dokumente:&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
        &amp;lt;#if &quot;${attrs.dokumente.value}&quot; != &quot;&quot;&amp;gt;
          &amp;lt;#assign documents = &quot;${attrs.dokumente.value}&quot;?eval&amp;gt;
          &amp;lt;#list documents as document&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Titel:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;${document.titel}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Offizieller Titel:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;${document.offiziellertitel}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Nummer:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;${document.offiziellenr}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Rechtsstatus:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;${document.rechtsstatus}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Publiziert ab:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;${document.publiziertab?date(&apos;yyyy-MM-dd&apos;)?string[&quot;dd. MMMM yyyy&quot;]}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td style=&quot;font-weight:500;padding-left:2em;padding-top:0em;&quot;&amp;gt;Link:&amp;lt;/td&amp;gt;
                &amp;lt;td style=&quot;padding-top:0em;&quot;&amp;gt;&amp;lt;a href=&quot;${document.textimweb_absolut}&quot; target=&quot;_blank&quot;&amp;gt;${document.textimweb_absolut}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;td&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
          &amp;lt;/#list&amp;gt;
        &amp;lt;#else&amp;gt;
          &amp;amp;nbsp;
        &amp;lt;/#if&amp;gt;
  &amp;lt;/#list&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;br/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Sorry für das hässliche HTML.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Meiste ist ziemlich vorhersehbar. Wichtig ist die Zeile 35, wo mit &lt;code&gt;&amp;lt;#assign documents = &quot;${attrs.dokumente.value}&quot;?eval&amp;gt;&lt;/code&gt; aus dem JSON-Array-String für Freemarker eine Liste gemacht wird, die man iterieren kann. Heikel resp. wohl ein Bug ist der Umstand, dass JSON-null-Werte zu einer Exception führen. Hier kann man als Workaround beim Datenumbau in PostgreSQL die Funktion &lt;code&gt;json_strip_nulls()&lt;/code&gt; verwenden, die Attribute mit null-Werten wegputzt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat kann sich meines Erachtes sehen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p1/getfeatureinfo-html-chrome.png&quot; alt=&quot;GetFeatureInfo Chrome&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In QGIS funktioniert es auch:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoserver-magie-p1/getfeatureinfo-html.png&quot; alt=&quot;GetFeatureInfo HTML&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Da wir auch unsere Publikationsmodelle mit INTERLIS modellieren, haben wir das Dokumente-Attribut als reinen Text modelliert. In Zukunft kann man das dank einer Erweiterung von &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2db&lt;/em&gt;&lt;/a&gt; sauberer machen. Die Dokumente werden als BAG OF STRUCTURES modelliert und mit einem Meta-Attribut versehen. Dann weiss &lt;em&gt;ili2db&lt;/em&gt;, dass es diese BAG OF STRUCTURES als JSON-Attribut in der relationalen Datenbank abbilden muss. Diese Erweiterung wird Ende 2018 verfügbar sein.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #18 - Shapefiles validieren</title>
      <link>http://blog.sogeo.services/blog/2018/02/19/interlis-leicht-gemacht-number-18.html</link>
      <pubDate>Mon, 19 Feb 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/02/19/interlis-leicht-gemacht-number-18.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;http://blog.sogeo.services/blog/2018/02/11/datenfluesse-mit-gradle-3.html&quot;&gt;letzten Beitrag&lt;/a&gt; habe ich kurz erwähnt, dass wir für &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt; einen Shapefile-Validator-Task &lt;a href=&quot;http://www.eisenhutinformatik.ch&quot;&gt;entwickeln liessen&lt;/a&gt;. Hier nun kurz eine Einführung/Erläuterung dazu.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/shapefiie&quot;&gt;Shapefiles&lt;/a&gt; sind leider nicht wirklich aus der Welt zu schaffen. Auch wir importieren immer noch Shapefiles von externen Dienstleistern in unsere GDI. Jedenfalls bleibt der gute Vorsatz in unseren zukünftigen Datenabgabe &lt;em&gt;keine&lt;/em&gt; Shapefiles mehr anzubieten, sondern als besserer Ersatz GeoPackage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wir aber nicht wollen, ist treudoof die fremden Shapefiles in unsere Datenbank importieren. Ein Minimum an - nennen wir es mal - Schemaprüfung wollen wir trotzdem. Analog der Modellprüfung von INTERLIS-Transferdateien. Jetzt könnte man auf die Idee kommen irgendwas mit YAML o.ä. zu frickeln, um zu definieren was in einer Spalte erlaubt ist. Aber es geht viel besser: Schreiben wir doch lieber ein INTERLIS-Modell und definieren dort, was erlaubt ist und was nicht. Dann passt es tiptop zum &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;, der ebenfalls via &lt;a href=&quot;http://blog.sogeo.services/blog/2017/04/24/interlis-leicht-gemacht-number-16.html&quot;&gt;INTERLIS gesteuert und erweitert&lt;/a&gt; werden kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Am einfachsten kann man die Shapefile-Prüfung anhand eines GRETL-Jobs zeigen. Dazu müssen wir zuerst &lt;em&gt;GRETL&lt;/em&gt; installieren. Leider ist das &lt;a href=&quot;https://hub.docker.com/r/sogis/gretl-runtime/&quot;&gt;Docker-Image&lt;/a&gt; noch nicht wirklich production-ready. &lt;em&gt;GRETL&lt;/em&gt; lässt sich aber ganz einfach aus dem Quellcode kompilieren und installieren (Java und Gradle müssen bereits installiert sein):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;git clone &lt;a href=&quot;https://github.com/sogis/gretl&quot; class=&quot;bare&quot;&gt;https://github.com/sogis/gretl&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;code&gt;gretl&lt;/code&gt;-Verzeichnis:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;./gradlew clean build install&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Befehl erstellt das Binary und installiert es in das lokale Maven-Repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Beispiel-Shapefile verwende ich unsere &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-18/grundwasserschutzzonen.zip&quot;&gt;Grundwasserschutzzonen&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p18/gws-qgis.png&quot; alt=&quot;Grundwasserschutzzonen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes brauchen wir ein INTERLIS-Modell. Der Shapefile-Validator funktioniert so, dass er im Modell eine Klasse sucht, deren Attribute genau den Spaltennamen (Super: endlich wieder unlesbare Attributnamen schreiben im Modell) in der Shapedatei entsprechen (Gross- und Kleinschreibung wird ignoriert). Die gesamte Dokumentation dazu gibt es &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#shpvalidator&quot;&gt;hier&lt;/a&gt;. Ein erster Wurf kann so aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

!!@ technicalContact=mailto:agi@bd.so.ch
MODEL SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213 (de)
AT &quot;http://geo.so.ch/models/AFU&quot;
VERSION &quot;2018-02-13&quot;  =
  IMPORTS GeometryCHLV95_V1;

  TOPIC ShpValidatorTopic =

    CLASS ShpValidatorClass =
      ogc_fid : MANDATORY 0 .. 10000000;
      zone : MANDATORY TEXT*20;
      archive : MANDATORY 0 .. 1;
      rrbnr : MANDATORY 1 .. 9999;
      rrb_date : MANDATORY INTERLIS.XMLDate;
      the_geom : MANDATORY GeometryCHLV95_V1.SurfaceWithOverlaps2mm;
    END ShpValidatorClass;

  END ShpValidatorTopic;

END SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich erwarte für jedes Attribut einen Wert. Ansonsten ist es noch ziemlich ungeschliffen. Als nächstes brauche ich den GRETL-Job:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import ch.so.agi.gretl.api.*
import ch.so.agi.gretl.tasks.*

buildscript {
    repositories {
        mavenLocal()
        maven {
            url &quot;http://jars.interlis.ch&quot;
        }
        maven {
            url &quot;http://download.osgeo.org/webdav/geotools/&quot;
        }
        mavenCentral()
    }
    dependencies {
        classpath group: &apos;ch.so.agi&apos;, name: &apos;gretl&apos;,  version: &apos;1.0.4-SNAPSHOT&apos;
    }
}

apply plugin: &apos;ch.so.agi.gretl&apos;

task validateGrundwasserschutzzonenShapefile(type: ShpValidator){
    models = &quot;SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213&quot;
    dataFiles = [&quot;grundwasserschutzzonen.shp&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von Interesse sind eigentlich nur die Zeilen 22 bis 24. Hier wird der Validierungstask definiert. Zwingend ist soweit ich es verstehe nur der Dateinamen. Standardmässig wird als Modellnamen der Name der Shapedatei angenommen. Die Prüfung startet man mit:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;gradle validateGrundwasserschutzzonenShapefile&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Daten sollten &amp;laquo;modellkonform&amp;raquo; sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&amp;gt; Task :validateGrundwasserschutzzonenShapefile
Info: ilivalidator-1.6.0-5e18cf6587dae29c6452544e5667a1a2edc36574
Info: ili2c-4.7.8-20180208
Info: iox-ili-1.20.3-SNAPSHOT-85c29d5be771feeddc2dc74c1f5a1def73877570
Info: maxMemory 932352 KB
Info: dataFile &amp;lt;/Users/stefan/Downloads/shpvalidator/grundwasserschutzzonen.shp&amp;gt;
Info: modelNames &amp;lt;SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213&amp;gt;
Info: ilidirs &amp;lt;%ITF_DIR;http://models.interlis.ch/;%JAR_DIR/ilimodels&amp;gt;
Info: lookup model &amp;lt;SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213&amp;gt; in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator/%JAR_DIR/ilimodels&amp;gt;
Info: Folder /Users/stefan/Downloads/shpvalidator/%JAR_DIR/ilimodels doesn&apos;t exist; ignored
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;http://models.geo.admin.ch&amp;gt;
Info: lookup model &amp;lt;CoordSys&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;CoordSys&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: lookup model &amp;lt;Units&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;Units&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.interlis.ch/refhb23/CoordSys-20151124.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.interlis.ch/refhb23/Units-20120220.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.geo.admin.ch/CH/CHBase_Part1_GEOMETRY_20110830.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/Downloads/shpvalidator/SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ili&amp;gt;
Info: validate data...
Info: assume unknown/external objects
Info: first validation pass...
Info: second validation pass...
Info: /Users/stefan/Downloads/shpvalidator/grundwasserschutzzonen.shp: SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic BID=b1
Info:     664 objects in CLASS SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic.ShpValidatorClass
Info: ...validation done


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um zu prüfen, ob der Task auch Fehler findet, verändere ich das Modell leicht. Erlaubt sind nun nur noch RRB-Nummer kleiner 7200:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&amp;gt; Task :validateGrundwasserschutzzonenShapefile FAILED
Info: ilivalidator-1.6.0-5e18cf6587dae29c6452544e5667a1a2edc36574
Info: ili2c-4.7.8-20180208
Info: iox-ili-1.20.3-SNAPSHOT-85c29d5be771feeddc2dc74c1f5a1def73877570
Info: maxMemory 932352 KB
Info: dataFile &amp;lt;/Users/stefan/Downloads/shpvalidator/grundwasserschutzzonen.shp&amp;gt;
Info: modelNames &amp;lt;SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213&amp;gt;
Info: ilidirs &amp;lt;%ITF_DIR;http://models.interlis.ch/;%JAR_DIR/ilimodels&amp;gt;
Info: lookup model &amp;lt;SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213&amp;gt; in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator/%JAR_DIR/ilimodels&amp;gt;
Info: Folder /Users/stefan/Downloads/shpvalidator/%JAR_DIR/ilimodels doesn&apos;t exist; ignored
Info: lookup model &amp;lt;GeometryCHLV95_V1&amp;gt; 2.3 in repository &amp;lt;http://models.geo.admin.ch&amp;gt;
Info: lookup model &amp;lt;CoordSys&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;CoordSys&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: lookup model &amp;lt;Units&amp;gt; 2.3 in repository &amp;lt;/Users/stefan/Downloads/shpvalidator&amp;gt;
Info: lookup model &amp;lt;Units&amp;gt; 2.3 in repository &amp;lt;http://models.interlis.ch/&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.interlis.ch/refhb23/CoordSys-20151124.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.interlis.ch/refhb23/Units-20120220.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/.ilicache/models.geo.admin.ch/CH/CHBase_Part1_GEOMETRY_20110830.ili&amp;gt;
Info: ilifile &amp;lt;/Users/stefan/Downloads/shpvalidator/SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ili&amp;gt;
Info: validate data...
Info: assume unknown/external objects
Info: first validation pass...
value 7273 is out of range
line 0: SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic.ShpValidatorClass: tid o116: value 7273 is out of range
value 7273 is out of range
line 0: SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic.ShpValidatorClass: tid o219: value 7273 is out of range
value 7273 is out of range
line 0: SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic.ShpValidatorClass: tid o604: value 7273 is out of range
Info: second validation pass...
Info: /Users/stefan/Downloads/shpvalidator/grundwasserschutzzonen.shp: SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic BID=b1
Info:     664 objects in CLASS SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ShpValidatorTopic.ShpValidatorClass
Info: ...validation failed


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task &apos;:validateGrundwasserschutzzonenShapefile&apos;.
&amp;gt; validation failed

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mission Accomplished. Fehler gefunden. Man kann das Modell jetzt natürlich noch ein wenig elaborieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Das Attribut &lt;code&gt;zone&lt;/code&gt; dürte ein Aufzähltyp sein.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Das Datum des RRB &lt;code&gt;rrb_date&lt;/code&gt; kann man eingrenzen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gesagt getan. Das gepimpte Modell gibt es als &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-18/SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.ili&quot;&gt;ILI&lt;/a&gt; resp. &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-18/SO_AFU_Grundwasserschutzzonen_20180213_ShpValidator_20180213.uml&quot;&gt;UML&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Da wir uns ja im &lt;em&gt;ilivalidator&lt;/em&gt;-Universum befinden, kann man das Modell mit Constraints erweitern oder die Prüfungen mittels weiterem Modell (und INTERLIS-Views) und/oder eigenen Java-Prüfmethoden erweitern. In diesem Fall ist das wohl nicht ganz so zwingend, da wir ja in den meisten Fällen das Shapedatei-Validierungsmodell selber schreiben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ansonsten gibt es nicht mehr viel zu sagen. Das Modell kann selbstverständlich in eine INTERLIS-Modellablage kopiert werden und wird auch dort gefunden. Dokumentation ist schnell erledigt und dort wo sie hingehört, wenn man ein paar Kommentare ins Modell schreibt. CSV-Dateien können dank des &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#csvvalidator&quot;&gt;CsvValidators&lt;/a&gt; auf die gleiche Art und Weise geprüft werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Datenflüsse mit Gradle #3 - GRETL</title>
      <link>http://blog.sogeo.services/blog/2018/02/11/datenfluesse-mit-gradle-3.html</link>
      <pubDate>Sun, 11 Feb 2018 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2018/02/11/datenfluesse-mit-gradle-3.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vor etwas mehr als &lt;a href=&quot;http://blog.sogeo.services/blog/2017/01/19/datenfluesse-mit-gradle-1.html&quot;&gt;einem&lt;/a&gt; &lt;a href=&quot;http://blog.sogeo.services/blog/2017/02/08/datenfluesse-mit-gradle-2.html&quot;&gt;Jahr&lt;/a&gt; habe in zwei Beiträgen erläutert, wie wir in Zukunft die Datenflüsse rund um unsere GDI gestalten wollen. Im vergangenen Jahr haben wir das nun mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/&quot;&gt;externer Hilfe&lt;/a&gt; umgesetzt resp. einen ersten Wurf mit den - für uns - wichtigsten Funktionalitäten. Der Name für unser neues Lieblingsspielzeug ist GRETL: Gradle ETL.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Rekapitulation: Seit Anbeginn der (GIS-)Zeit dreht sich bei uns viel oder fast alles um die Datenbank (&amp;laquo;Datenbank-zentrisches Amt&amp;raquo;). Fast alles wurde von irgendwo her in die Datenbank importiert, vieles wurde auch darin berechnet und sämtliche Daten wurden in irgendeiner Form auch wieder exportiert. So weit, so gut. Die Schnittstellen wie die Daten in die Datenbank reinkommen oder wieder rauskommen oder wie die Berechnung durchgeführt wird, ist unendlich heterogen gelöst. Zuerst good old &lt;em&gt;PHP4&lt;/em&gt;, später dann Python-Skripte (&amp;laquo;Yeah, wir sind modern.&amp;raquo;) und auch mal Java hier und dort. Import, Datenumbau und Berechnung im Skript wild durcheinander. Und immer wieder Probleme mit Fremddatenbanken (Oracle, SQL-Server), da keine passenden Treiber im Haus. Wir haben festgestellt, dass sich die einzelnen Schritte eines solchen Prozesses leicht identifizieren lassen und auch kapseln lassen und diese eben auch immer etwa gleich aussehen. Die einzelnen Schritte werden als &lt;a href=&quot;http://www.gradle.org&quot;&gt;Gradle&lt;/a&gt;-Tasks erstellt und anschliessend zu einem Job aneinandergereit. Gradle haben wir gewählt, weil es bereits viel von unseren Anforderungen out-of-the-box oder durch Plugins abdecken kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Herunterladen, Entzippen und Kopieren von Daten&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Logging&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Abhängigkeiten zwischen den einzelnen Tasks (&lt;a href=&quot;http://trickyandroid.com/gradle-tip-3-tasks-ordering/&quot;&gt;tasks ordering&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Via SSH Befehle auf externen Servern ausführen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://flywaydb.org/&quot;&gt;Flyway&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployments auf AWS etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dokumentation mit Asciidoctor&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc. etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die fehlenden zusätzlichen Tasks ist &lt;em&gt;Gradle&lt;/em&gt; relativ einfach mit Java (oder Groovy) erweiterbar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Quellcode befindet sich im dazugehörigen &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;Github-Repository&lt;/a&gt;. Die ganze Geschichte ist momentan noch klein wenig unübersichtlich strukturiert. Neben den ganzen Funktionalitäten, gibt es im Repository noch viel anderen Code: CI/CD-Pipeline inkl. Deployment in einer OpenShift-Umgebung als Docker-Container und viel Code für die Abhängigkeiten der Integrationstests etc. Der eigentliche Quellcode liegt im Unterordner &lt;code&gt;gretl&lt;/code&gt;. Eine umfangreiche User-Dokumentation im Ordner &lt;code&gt;docs/user&lt;/code&gt; inkl. einem &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/docs/user/index.md#kleines-beispiel&quot;&gt;minimalen Beispiel&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum jetzigen Zeitpunkt gibt es folgende zusätzlichen Tasks:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQLExecutor&lt;/strong&gt;: Dieser Task führt beliebige parametrisierbare SQL-Befehle in einer Datenbank aus.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Db2Db&lt;/strong&gt;: Kopiert Tabellen aus einer Datenbank in einer andere. Einfache, parametrisierbare Datenumbauten sind möglich. Dank JDBC wird (theoretisch) jede Datenbank unterstützt für die ein JDBC-Treiber verfügbar ist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CsvImport / CsvExport&lt;/strong&gt;: Import und Export von CSV-Dateien in resp. aus einer Datenbank.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ShpImport / ShpExport&lt;/strong&gt;: Import und Export von Shapedateien. (Traurig aber wahr: Sogar noch vor GeoPackage realisiert&amp;#8230;&amp;#8203;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CsvValidator / ShpValidator&lt;/strong&gt;: Prüft den Inhalt der CSV- resp. Shapedateien gegenüber einer vorgegebenen Struktur. Die umgesetzte Lösung ist schlicht genial. Dazu in einem anderen Beitrag mehr.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ili2pg&lt;/strong&gt;: Für den Umgang mit INTERLIS wurden mit &lt;em&gt;ili2pg&lt;/em&gt; verschiedene Tasks gebaut (Import, Export, Replace, Schema-Import)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ilivalidator&lt;/strong&gt;: Zum Prüfen für die INTERLIS-Transferdateien wurde ein ilivalidator-Task programmiert.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesen funktional atomaren Tasks (und den in &lt;em&gt;Gradle&lt;/em&gt; vorhandenen Funktionalitäten) kommt man bereits jetzt ziemlich weit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Builden und installieren funktioniert wie man es von &lt;em&gt;Gradle&lt;/em&gt; kennt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;./gradlew clean build install&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das erstellte Produkt ist ein Gradle-Plugin, das im lokalen Maven-Respository installiert wird und anschliessend in den Jobs (= build.gradle) angesprochen werden kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Herausfordernd ist die Testerei: Will man möglichst Nahe an der Realität testen, kommt man nicht um eine PostgreSQL-/PostGIS-Datenbank herum. Viel JDBC-mässiger Code lässt sich zwar auch mit einer Derby-Datenbank testen, aber wenn es um &amp;laquo;Geo&amp;raquo; geht, muss PostgreSQL/PostGIS her. Momentan ist das so gelöst, dass der Entwickler manuell eine dockerisierte PostgreSQL/PostGIS-Datenbank hochfährt und anschliessend die Tests einer bestimmten Kategorie durchführen kann. Denkbar wäre auch, dass man in &lt;em&gt;Gradle&lt;/em&gt; verschiedene &lt;a href=&quot;https://www.petrikainulainen.net/programming/gradle/getting-started-with-gradle-integration-testing-with-the-testsets-plugin/&quot;&gt;TestSets&lt;/a&gt; &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service/blob/master/build.gradle#L41&quot;&gt;einführt&lt;/a&gt; und so eine bessere Steuerbarkeit der Tests herbeiführt. Anstatt die dockerisierte Datenbank manuell hochzufahren, fände ich z.B. &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;TestContainers&lt;/a&gt; eine sehr elegante Lösung. &amp;laquo;Passt scho.&amp;raquo;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir werden bei uns in der Produktion nicht das pure Plugin verwenden, sondern möchten eine GRETL-Runtime als Docker-Image in Betrieb nehmen. Im Image sind sämtliche benötigten Abhängigkeiten (im Sinne von Bibliotheken etc.). Gradles &lt;a href=&quot;https://docs.gradle.org/current/userguide/introduction_dependency_management.html&quot;&gt;Dependency Management&lt;/a&gt; ist ja schön und gut, im produktiven Betrieb will man aber nicht zu viel Feenstaub, sondern exakt wissen welche Version welcher Bibliothek in Betrieb ist oder zumindest sicher sein, dass heute nicht eine andere in Betrieb ist als gestern. Ebenso werden die benötigten INTERLIS-Modellablagen mit &lt;em&gt;ili2c&lt;/em&gt; geklont, um komplett offline agieren zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ganz abgefahren wird es bei der Orchestrierung der Jobs. Wir gehen davon aus, dass wir circa 100 bis 200 einzelne Jobs haben werden. Der grösste Teil sind Datenumbauten in der Datenbank resp. von einer Erfassungsdatenbank in eine Publikationsdatenbank. Bei dieser Anzahl von Jobs will man in keiner Software selber gross konfigurieren. Sei es erstmalig oder bei Änderungen, die sämtliche Jobs betreffen. Als Fundament für diese Orchestrierung setzen wir &lt;a href=&quot;https://jenkins.io/&quot;&gt;&lt;em&gt;Jenkins&lt;/em&gt;&lt;/a&gt; ein. Für die Konfiguration der Jobs wird es einen &amp;laquo;Seeder-Job&amp;raquo; geben, der sämtliche anderen Jobs aufgrund eines Jenkinsfiles in &lt;em&gt;Jenkins&lt;/em&gt; definiert. Diese Jobs können anschliessend cronjob-mässig automatisch ausgeführt werden oder auch on-demand durch einen Benutzer. Und wenn das alles so klappt wie gewünscht, habe ich ein paar graue Haare mehr und mache drei Kreuze an die Decke&amp;#8230;&amp;#8203; Jedenfalls ist es &amp;laquo;Spatial Is Not Special&amp;raquo; ziemlich auf die Spitze getrieben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Appetit kommt wie so oft mit dem Essen. Folgende zusätzlichen Tasks oder Funktionalitäten stehen auf dem Wunschzettel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Unterstützung von GeoPackage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bei den Export-Prozessen sollen auch SQL-Queries als Input möglich sein und nicht bloss Tabellen oder Views.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Geometrie (in einer noch zu bestimmenden Form) soll beim CSV-Export unterstützt werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unterstützung für JasperResports&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload-Funktionalität in die &lt;a href=&quot;http://www.geodienste.ch&quot;&gt;Aggregationsinfrastruktur der Kantone&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vielleicht ein wenig unorthodox: Ein &lt;a href=&quot;https://community.hds.com/docs/DOC-1009855&quot;&gt;Kettle-PDI&lt;/a&gt;-Ausführ-Task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;???&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>INTERLIS leicht gemacht #17: Polymorphes Schreiben</title>
      <link>http://blog.sogeo.services/blog/2017/11/06/interlis-leicht-gemacht-number-17.html</link>
      <pubDate>Mon, 6 Nov 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/11/06/interlis-leicht-gemacht-number-17.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für einige der Datensätze, die gemäss &lt;a href=&quot;https://www.admin.ch/opc/de/classified-compilation/20050726/index.html&quot;&gt;GeoIG&lt;/a&gt; / &lt;a href=&quot;https://www.admin.ch/opc/de/classified-compilation/20071088/index.html&quot;&gt;GeoIV&lt;/a&gt; als INTERLIS-Datensatz geliefert werden müssen, gibt es bereits in irgendeiner Struktur Daten in der kantonalen Geodateninfrastruktur. Dass gar keine Daten vorhanden sind, dürfte wohl eher die Ausnahme sein. In beiden Fällen dürfte die Umsetzung eine Themas aber die passende Gelegenheit sein, um sich Gedanken über die zukünftige Datenstruktur in der KGDI zu machen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wäre es nicht schön, wenn man das minimale Geodatenmodell nach seinen Wünschen erweitern könnte, dieses bei sich in der KGDI implementieren könnte und dann trotzdem automatisch das Bundes-MGDM exportieren und der &lt;a href=&quot;http://www.geodienste.ch&quot;&gt;Aggregationsinfrastruktur&lt;/a&gt; liefern könnte? Ja. Und seit &lt;a href=&quot;https://github.com/claeis/ili2db/commit/d5585aea95c99e9cc76e5888fbaf60c4a21bbbdb#diff-2e49d06dc97236450a7f7354b2f9ea60R153&quot;&gt;kurzem&lt;/a&gt; geht das mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; (und Variationen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nehmen wir mal an, dass wir nicht ein eigenes Datenmodell für die Nutzungplanung geschrieben hätten, sondern wir erweitern einfach das &lt;a href=&quot;http://models.geo.admin.ch/ARE/Nutzungsplanung_V1_1.ili&quot;&gt;Bundesmodell&lt;/a&gt;: Wir möchten ein zusätzliches Attribut &lt;code&gt;Baumassenziffer&lt;/code&gt; in der Klasse &lt;code&gt;Typ&lt;/code&gt; und eine zusätzliche Klasse &lt;code&gt;Grundnutzung_Zonenflaeche_Pos&lt;/code&gt;, um die Zonentypen anschreiben zu können (Wer will das heute noch?).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

MODEL SO_ARP_Nutzungsplanung_20171106 (en)
AT &quot;http://www.agi.so.ch&quot;
VERSION &quot;2017-11-06&quot;  =
  IMPORTS Nutzungsplanung_LV95_V1_1,GeometryCHLV95_V1;

  TOPIC Geobasisdaten
  EXTENDS Nutzungsplanung_LV95_V1_1.Geobasisdaten =

    CLASS Grundnutzung_Zonenflaeche_Pos =
      Ori : MANDATORY 0.0 .. 399.9;
      HAli : MANDATORY INTERLIS.HALIGNMENT;
      VAli : MANDATORY INTERLIS.VALIGNMENT;
      Pos : MANDATORY GeometryCHLV95_V1.Coord2;
    END Grundnutzung_Zonenflaeche_Pos;

    CLASS Typ (EXTENDED) =
      Baumassenziffer : 0.00 .. 9.00;
    END Typ;

    ASSOCIATION Grundnutzung_Grundnutzung_Pos =
      Grundnutzung -&amp;lt;&amp;gt; {1} Nutzungsplanung_LV95_V1_1.Geobasisdaten.Grundnutzung_Zonenflaeche;
      Grundnutzung_Pos -- {0..*} Grundnutzung_Zonenflaeche_Pos;
    END Grundnutzung_Grundnutzung_Pos;

  END Geobasisdaten;

END SO_ARP_Nutzungsplanung_20171106.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Erstellen der leeren Tabellen in der Datenbank geht wie bis anhin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -jar ili2pg.jar --dbhost geodb-dev.eu-central-1.rds.amazonaws.com --dbdatabase xanadu2 --dbusr YYYYYY --dbpwd XXXXXX --nameByTopic --disableValidation --defaultSrsCode 2056 --expandMultilingual --strokeArcs --createGeomIdx --createFkIdx --createEnumTabs --beautifyEnumDispName  --modeldir &quot;http://models.geo.admin.ch;.&quot; --models &quot;Nutzungsplanung_Hauptnutzung_V1_1;SO_ARP_Nutzungsplanung_20171106&quot; --dbschema npl_polymorph --schemaimport&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend importiere ich noch die Hauptnutzungen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -jar ili2pg.jar --dbhost geodb-dev.eu-central-1.rds.amazonaws.com --dbdatabase xanadu2 --dbusr YYYYYY --dbpwd XXXXXX --nameByTopic --disableValidation --defaultSrsCode 2056 --expandMultilingual --strokeArcs --createGeomIdx --createFkIdx --createEnumTabs --beautifyEnumDispName  --modeldir &quot;http://models.geo.admin.ch;.&quot; --models Nutzungsplanung_Hauptnutzung_V1_1 --dbschema npl_polymorph --import Hauptnutzung_CH_V1_1.xml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aufgrund der Vererbung erhält die Tabelle &lt;code&gt;geobasisdaten_typ&lt;/code&gt; in der Datenbank das zusätzliche Attribut &lt;code&gt;t_type&lt;/code&gt;. Das Attribut enthält den konkreten Klassennamen resp. den SQL-Namen des qualifizierten INTERLIS-Klassennamens. Zu finden in der Tabelle &lt;code&gt;t_ili2db_classname&lt;/code&gt;. Wenn ich jetzt in der Datenbank einen Record erfasse, der zur meiner erweiterten Typ-Klasse gehört, muss ich &lt;code&gt;so_rp_n0171106geobasisdaten_typ&lt;/code&gt; reinschreiben. Ich kann nun munter darauflos digitalisieren oder wie in meinem Fall von meinem richtigen kantonalen Nutzungsplanungsmodell in das Fantasiemodell Daten mittels SQL rüberschaufeln.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Magie und das Besondere beginnt erst beim Export. Wenn ich die Daten in meinem erweiterten kantonalen Modell exportieren will, reicht immer noch:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -jar ili2pg.jar --dbhost geodb-dev.eu-central-1.rds.amazonaws.com --dbdatabase xanadu2 --dbusr XXXXXX --dbpwd YYYYYY --nameByTopic --disableValidation --defaultSrsCode 2056 --expandMultilingual --strokeArcs --createGeomIdx --createFkIdx --createEnumTabs --beautifyEnumDispName  --modeldir &quot;http://models.geo.admin.ch;.&quot; --models SO_ARP_Nutzungsplanung_20171106 --dbschema npl_polymorph --export npl_so.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will ich nun direkt das Bundesmodell exportieren, muss ich &lt;em&gt;ili2pg&lt;/em&gt; die neue Option &lt;code&gt;--exportModels&lt;/code&gt; mitgeben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -jar ili2pg.jar --dbhost geodb-dev.eu-central-1.rds.amazonaws.com --dbdatabase xanadu2 --dbusr XXXXXX --dbpwd YYYYYY --nameByTopic --disableValidation --defaultSrsCode 2056 --expandMultilingual --strokeArcs --createGeomIdx --createFkIdx --createEnumTabs --beautifyEnumDispName  --modeldir &quot;http://models.geo.admin.ch;.&quot; --models SO_ARP_Nutzungsplanung_20171106 --exportModels Nutzungsplanung_LV95_V1_1 --dbschema npl_polymorph --export npl_ch.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;--exportModels Nutzungsplanung_LV95_V1_1&lt;/code&gt; wird die Transformation in das Basismodell gesteuert. Mit &lt;code&gt;--models SO_ARP_Nutzungsplanung_20171106&lt;/code&gt; wird der Dateninhalt des Exportes definiert. In meinem konkreten Fall bedeutet das, dass beim Export in das Bundesmodell (= Basismodell) die zusätzliche Klasse und das zusätzliche Attribut verloren gehen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #6: Datenvalidierung mit INTERLIS</title>
      <link>http://blog.sogeo.services/blog/2017/10/22/kgdi-the-next-generation-6.html</link>
      <pubDate>Sun, 22 Oct 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/10/22/kgdi-the-next-generation-6.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unser Projekt mit &lt;a href=&quot;http://gradle.org&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;http://blog.sogeo.services/blog/2017/01/19/datenfluesse-mit-gradle-1.html&quot;&gt;Datenflüsse&lt;/a&gt; &lt;a href=&quot;http://blog.sogeo.services/blog/2017/02/08/datenfluesse-mit-gradle-2.html&quot;&gt;durchzuführen&lt;/a&gt;, schreitet prächtig voran. Vieles bekommen wir mit &lt;em&gt;Gradle&lt;/em&gt; geschenkt, ein paar geo-spezifische Tasks programmieren wir selber resp. lassen wir programmieren. Diese sogenannten Custom Tasks lassen sich in einem Plugin bündeln: &lt;a href=&quot;https://github.com/sogis/gretl&quot;&gt;&lt;em&gt;GRETL&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit circa 1.5 Jahren (versuchen) wir die &lt;a href=&quot;http://blog.sogeo.services/blog/2017/01/02/kgdi-the-next-generation-3.html&quot;&gt;&lt;em&gt;model-driven GDI&lt;/em&gt;&lt;/a&gt; zu leben. Das bedeutet für die Datenerfassung, dass wir INTERLIS-Modelle schreiben und diese mit &lt;a href=&quot;https://github.com/claeis/ili2db&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; in der Datenbank abbilden. Der Benutzer verwendet anschliessend &lt;a href=&quot;http://www.qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; für die Erfassung/Manipulation der Daten. Nun, wo Menschen arbeiten, passieren Fehler. So möchte man sicherstellen, dass die erfassten Daten in der Datenbank dem Modell entsprechen. &lt;em&gt;Ili2pg&lt;/em&gt; bildet heute bereits wichtige Constraints in der Datenbank ab, z.B. Länge von Texten, Ranges von numerischen Attributen, Fremdschlüssel etc. Somit hilft uns bei der Datenerfassung die Datenbank, weil fehlerhafte Daten schon gar nicht gespeichert werden können. Meines Erachtens die Achillesferse im Ganzen QGIS-PostgreSQL-Zusammenspiel sind die Geometrien. Da ist es anscheinend schnell möglich Fehler zu speichern. Auch ohne die &amp;laquo;Geometrie-Schwäche&amp;raquo; bin ich der Meinung, dass eine unabhängige Datenprüfung gut tut. Und weil wir ja INTERLIS verwenden, kann uns das bei der Datenprüfung helfen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee ist so einfach wie genial: Wir exportieren die erfassten Daten einfach mit &lt;em&gt;ili2pg&lt;/em&gt; und weil &lt;em&gt;ili2pg&lt;/em&gt; die Validierungsbibliothek &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; eingebaut hat, wird beim Export auch gleich die Modellkonformatität geprüft. Das exportierte XTF interessiert uns gar nicht, sondern eben nur das Validierungsresultat. Und weil für zukünftig nicht für jede Aufgabe ein Skript von Null an schreiben wollen, können wir für diesen Anwendungsfall &lt;em&gt;Gradle&lt;/em&gt; resp. &lt;em&gt;GRETL&lt;/em&gt; einsetzen. &lt;em&gt;GRETL&lt;/em&gt; hat bereits einige &lt;a href=&quot;https://github.com/sogis/gretl/tree/master/src/main/java/ch/so/agi/gretl/tasks&quot;&gt;INTERLIS-spezifische&lt;/a&gt; Custom Tasks implementiert, so wie den &lt;a href=&quot;https://github.com/sogis/gretl/blob/master/src/main/java/ch/so/agi/gretl/tasks/Ili2pgExport.java&quot;&gt;Ili2pgExport-Task&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die daraus resultierende &lt;code&gt;build.gradle&lt;/code&gt;-Datei kann so aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;import java.nio.file.Paths
import ch.so.agi.gretl.tasks.*
import ch.so.agi.gretl.steps.*

buildscript {
    repositories {
        mavenCentral()
        jcenter()
        maven { url &apos;http://sogeo.services:8081/artifactory/libs-snapshot&apos; }
        maven { url &apos;http://jars.interlis.ch&apos; }
    }
    dependencies {
        classpath &apos;ch.so.agi:gretl:1.0.+&apos;
        classpath &apos;de.undercouch:gradle-download-task:3.3.0&apos;
    }
}

apply plugin: &apos;ch.so.agi.gretl&apos;

ext {
    sourceDbUrl = &quot;jdbc:postgresql://geodb-t.verw.rootso.org:5432/sogis&quot;
    sourceDbUser = System.env.sourceDbUser
    sourceDbPass = System.env.sourceDbPass

    outputDir = rootProject.projectDir

    todaysDate = new Date().format(&apos;yyyy-MM-dd&apos;)
    models = []
    models.add([&quot;SO_Agglomerationsprogramme_20170512&quot;, &quot;arp_aggloprogramme&quot;, &quot;arp_aggloprogramme_&quot; + todaysDate ])
    models.add([&quot;SO_ARP_Nutzungsvereinbarung_20170512&quot;, &quot;arp_nutzungsvereinbarung&quot;, &quot;arp_nutzungsvereinbarung_&quot; + todaysDate ])
    models.add([&quot;SO_Forstreviere_20170512&quot;, &quot;awjf_forstreviere&quot;, &quot;awjf_forstreviere_&quot; + todaysDate ])
    models.add([&quot;SO_Hoheitsgrenzen_20170623&quot;, &quot;agi_hoheitsgrenzen&quot;, &quot;agi_hoheitsgrenzen_&quot; + todaysDate ])
    models.add([&quot;SO_AWJF_Wegsanierungen_20170629&quot;, &quot;awjf_wegsanierungen&quot;, &quot;awjf_wegsanierungen_&quot; + todaysDate ])
}

// Create a dynamic task for every model we
// want to validate and for cleaning up if
// validaton was successful.
models.each { model -&amp;gt;
    def modelName = model.getAt(0)
    def dbSchema = model.getAt(1)

    task &quot;checkModel_$modelName&quot;(type: Ili2pgExport) {
        description = &quot;INTERLIS validation against database schema: $dbSchema ($modelName)&quot;
        database = [sourceDbUrl, sourceDbUser, sourceDbPass]
        dbschema = dbSchema
        models = modelName
        disableValidation = false
        logFile = file(Paths.get(outputDir.toString(), dbSchema+&quot;.log&quot;))
        dataFile = file(Paths.get(outputDir.toString(), dbSchema+&quot;.xtf&quot;))

        finalizedBy &quot;removeFiles_$modelName&quot;
    }

    task &quot;removeFiles_$modelName&quot;(type: Delete) {
        description = &quot;Remove files from model export: $modelName&quot;

        onlyIf {
            project.getTasksByName(&quot;checkModel_$modelName&quot;, false).getAt(0).state.failure == null
        }

        delete file(Paths.get(outputDir.toString(), dbSchema+&quot;.log&quot;)), file(Paths.get(outputDir.toString(), dbSchema+&quot;.xtf&quot;))
    }
}

// This is kinda dummy task. The magic is the &apos;dependsOn&apos; logic.
task checkAllModels() {
    description = &quot;Validate all models.&quot;
}

checkAllModels.dependsOn {
    tasks.findAll { task -&amp;gt; task.name.startsWith(&apos;checkModel_&apos;) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Liste &lt;code&gt;models&lt;/code&gt; sind die benötigten Informationen zu den INTERLIS-Modellen, die in der Datenbank abgebildet sind, gespeichert. Also Modellname, Schemaname und für den Export die Basis der Dateinamen von Logfile und XTF-Transferdatei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gradles Magie startet in Zeile 39: Anstatt für jedes zu prüfende Modell einen Task zu definieren, werden sogenannte dynamische Tasks definiert indem einfach über die &lt;code&gt;models&lt;/code&gt;-Liste interiert wird. Dabei werden pro Modell zwei Tasks erstellt. Einen Export-Task und einen Lösch-Task für die erstellten Dateien (Log- und Transferfile), wobei nur gelöscht wird, falls der Export erfolgreich war (&lt;code&gt;onlyIf&lt;/code&gt;). Gab es Fehler, will man wahrscheinlich wissen, was das Problem ist, darum werden die Dateien nicht gelöscht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;gradle tasks --all&lt;/code&gt; erscheinen alle Tasks in der Konsole:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;Other tasks
-----------
checkAllModels - Validate all models.
checkModel_SO_Agglomerationsprogramme_20170512 - INTERLIS validation against database schema: arp_aggloprogramme (SO_Agglomerationsprogramme_20170512)
checkModel_SO_ARP_Nutzungsvereinbarung_20170512 - INTERLIS validation against database schema: arp_nutzungsvereinbarung (SO_ARP_Nutzungsvereinbarung_20170512)
checkModel_SO_AWJF_Wegsanierungen_20170629 - INTERLIS validation against database schema: awjf_wegsanierungen (SO_AWJF_Wegsanierungen_20170629)
checkModel_SO_Forstreviere_20170512 - INTERLIS validation against database schema: awjf_forstreviere (SO_Forstreviere_20170512)
checkModel_SO_Hoheitsgrenzen_20170623 - INTERLIS validation against database schema: agi_hoheitsgrenzen (SO_Hoheitsgrenzen_20170623)
removeFiles_SO_Agglomerationsprogramme_20170512 - Remove files from model export: SO_Agglomerationsprogramme_20170512
removeFiles_SO_ARP_Nutzungsvereinbarung_20170512 - Remove files from model export: SO_ARP_Nutzungsvereinbarung_20170512
removeFiles_SO_AWJF_Wegsanierungen_20170629 - Remove files from model export: SO_AWJF_Wegsanierungen_20170629
removeFiles_SO_Forstreviere_20170512 - Remove files from model export: SO_Forstreviere_20170512
removeFiles_SO_Hoheitsgrenzen_20170623 - Remove files from model export: SO_Hoheitsgrenzen_20170623&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jeder Task kann einzeln aufgerufen werden, wobei aufgrund der &lt;code&gt;finalizedBy&lt;/code&gt;-Bedingung (Zeile 52) die &lt;code&gt;removeFiles&lt;/code&gt;-Tasks jeweils automatisch nach den &lt;code&gt;checkModel&lt;/code&gt;-Tasks ausgeführt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn wir alle Modelle prüfen wollen, müssen wir nicht jeden Task auflisten, sondern es gibt einen &lt;code&gt;checkAllModels&lt;/code&gt;-Task, der alle Modell prüft. Der einzige Sinn dieses Tasks ist, dass er abhängig von allen Tasks, die mit &lt;code&gt;checkModel_&lt;/code&gt; beginnen, ist. Somit müssen sämtlicher dieser Tasks ausgeführt werden, bevor der &lt;code&gt;checkAllModels&lt;/code&gt;-Task ausgeführt wird (der aber nichts macht).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Normalerweise bricht &lt;em&gt;Gradle&lt;/em&gt; den Prozess ab, falls in einem einzelnen Task Fehler auftreten. Der &lt;code&gt;Ili2pgExport&lt;/code&gt;-Task ist genau so designt, dass er eine Exception wirft, wenn beim Export (resp. bei der Validierung während des Exports) ein Fehler auftritt. Das heisst, wir müssen dafür sorgen, dass der Gradle-Prozess nach einem Auftreten eines Fehler nicht abbricht, sondern weiterläuft. Dies erreicht man mit der &lt;code&gt;--continue&lt;/code&gt;
Option:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;gradle checkAllModells --continue&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Gute daran ist, dass der Gradle Build zwar durchläuft aber trotzdem am Ende den Status &amp;laquo;failed&amp;raquo; erhält. Dieser Validierungs-Job wird wie unsere anderen Datenflüsse in Zukunft mit &lt;a href=&quot;https://jenkins.io/&quot;&gt;&lt;em&gt;Jenkins&lt;/em&gt;&lt;/a&gt; orchestriert werden. Damit können auf einfachste Weise E-Mails beim Auftreten von Fehler verschickt werden oder der Prozess kann ins kantonale &lt;a href=&quot;https://www.nagios.org/&quot;&gt;&lt;em&gt;Nagios&lt;/em&gt;&lt;/a&gt; eingebunden werden. Auch bei der Wahl des Orchestrierungstool gilt &amp;laquo;Spatial Is Not Special&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #5: CI/CD mit AWS</title>
      <link>http://blog.sogeo.services/blog/2017/09/12/kgdi-the-next-generation-5.html</link>
      <pubDate>Tue, 12 Sep 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/09/12/kgdi-the-next-generation-5.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit einiger Zeit habe &lt;a href=&quot;https://interlis2.ch/ilivalidator&quot;&gt;hier&lt;/a&gt; einen INTERLIS-Checkservice auf Basis von &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; am Laufen. Der Webservice-Teil ist mit &lt;a href=&quot;https://projects.spring.io/spring-boot/&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; umgesetzt. &lt;em&gt;Spring Boot&lt;/em&gt; macht das Entwickeln von Spring- und Webanwendungen sehr einfach. Insbesondere das Endprodukt ist ganz praktisch: eine einzige ausführbare JAR-Datei inkl. Servlet-Container. Den ganzen Service in einer einzigen Datei macht das Deployment natürlich auch einiges einfacher. Trotzdem muss ich die JAR-Datei zuerst herstellen, dann auf meinen Server hochladen und den Dienst neu starten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schöner wäre es, wenn jeder Commit automatisch dazu führt, dass die Anwendung kompiliert wird, getestet wird und falls alle Tests durchlaufen, in einer Testumgebung installiert wird. Weil wir zudem momentan total auf &lt;a href=&quot;https://www.docker.com&quot;&gt;&lt;em&gt;Docker&lt;/em&gt;&lt;/a&gt; abfahren, soll das Ganze als Docker-Image deployed werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Umgesetzt werden kann so ein Prozess natürlich auf ganz unterschiedliche Weise mit verschiedensten Werkzeugen. Ich habe mich für &lt;a href=&quot;https://aws.amazon.com/codepipeline/&quot;&gt;&lt;em&gt;AWS CodePipeline&lt;/em&gt;&lt;/a&gt; entschieden. So muss ich überhaupt nichts installieren, sondern nur ein paar AWS-Dienste zusammenstöpseln.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In meinem sehr einfachen Fall einer Pipeline, gibt es ingesamt nur drei Schritte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Source: Download des Quellcodes von &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service&quot;&gt;&lt;em&gt;Github&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build: Kompilieren und Testen der Anwendung mittels &lt;a href=&quot;https://aws.amazon.com/codebuild/&quot;&gt;&lt;em&gt;AWS CodeBuild&lt;/em&gt;&lt;/a&gt;. Und anschliessendes Herstellen des Docker-Images.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Staging: Deployment des Docker-Images auf &lt;a href=&quot;https://aws.amazon.com/elasticbeanstalk&quot;&gt;&lt;em&gt;AWS Elastic Beanstalk&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Grafisch sieht das so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p5/aws-codepipeline.png&quot; alt=&quot;AWS CodePipeline&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe gute Erfahrung gemacht mit dem - für mich - kompliziertesten Schritt zu beginnen: Build. &lt;em&gt;AWS CodeBuild&lt;/em&gt; kann man dazu verwenden sein Projekt (das auf Github o.ä. liegt) auf einer von AWS automatisch bereitgestellten Umgebung zu kompilieren und zu testen. Konkret heisst das, AWS fährt eine EC2-Instanz hoch und installiert die Programme, die man zum Builden seines Projektes braucht, automatisch. AWS stellt sogenannte Umgebungen als Docker-Image bereit. Diese Umgebungen sind ganz spezifisch für unterschiedliche Programmiersprachen ausgelegt. Man wählt dann das passende Docker-Image für sein Projekt aus (z.B. Java, Android etc.). Oder baut sich selber ein Docker-Image zusammenbauen, falls man spezielle Anforderungen an seine Build-Umgebung hat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In meinen Fall brauche ich in meiner Build-Umgebung Java und Docker, da ich ja eine Java-Anwendung kompilieren will und anschliessend ein Docker-Image herstellen will. In dieser Kombiniation gibt es das aber nicht. Was aber relativ einfach geht, ist vor dem eigentlichen Builden des Codes noch Software nachzuinstallieren. Dh. ich kann die Docker-Umgebung verwenden und noch zusätzlich Java installieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Steuern lässt sich das Ganze über eine einzige Datei &lt;code&gt;buildspec.yml&lt;/code&gt;, die im &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service/blob/master/buildspec.yml&quot;&gt;Code-Repository&lt;/a&gt; liegen muss:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;version: 0.2

env:
  variables:
    JAVA_HOME: &quot;/usr/lib/jvm/java-8-openjdk-amd64&quot;

phases:
  install:
    commands:
      - apt-get update -y
      - apt-get install -y software-properties-common
      - add-apt-repository ppa:openjdk-r/ppa
      - apt-get update -y
      - apt-get install -y openjdk-8-jdk
  pre_build:
    commands:
      - echo Logging in to Docker Hub...
      - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD
  build:
    commands:
      - echo Starting build `date`
      - chmod +rx -R *
      - ./gradlew clean build
      # git metadata is not available yet when using
      # codepipeline. At the moment we just use a
      # date string.
      - APP_VERSION=$(./gradlew -q getVersion | tail -1)
      - docker build -t sogis/ilivalidator-web-service:$APP_VERSION -t sogis/ilivalidator-web-service:latest .
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push sogis/ilivalidator-web-service:$APP_VERSION
      - docker push sogis/ilivalidator-web-service:latest
      - echo Deleting Dockerfile
      - rm Dockerfile
      - sed -i -e &quot;s/IMAGE_TAG/$APP_VERSION/&quot; Dockerrun.aws.json
artifacts:
  files:
    - &apos;**/*&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vieles daran ist selbsterklärend. Der Build-Prozess ist unterteilt in Phasen. In jeder Phase kann man mehr oder weniger beliebige Shell-Kommandos ausführen lassen. In der &lt;code&gt;install&lt;/code&gt;-Phase installiere ich noch zusätzliche Software. In der &lt;code&gt;pre-build&lt;/code&gt;-Phase logge ich mich auf &lt;a href=&quot;https://hub.docker.com&quot;&gt;hub.docker.com&lt;/a&gt; ein, damit ich anschliessend mein Image hochladen kann. &lt;code&gt;$DOCKER_HUB_USERNAME&lt;/code&gt; und &lt;code&gt;$DOCKER_HUB_PASSWORD&lt;/code&gt; sind Umgebungsvariablen, die z.B. via WebGUI definiert werden können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Kompilieren, Testen (&lt;code&gt;./gradlew clean build&lt;/code&gt;) und Herstellen des Docker-Images (&lt;code&gt;docker build &amp;#8230;&amp;#8203;&lt;/code&gt;) geschieht in der &lt;code&gt;build&lt;/code&gt;-Phase. Weil mein &lt;em&gt;AWS CodeBuild&lt;/em&gt;-Projekt später ein Teil einer Codepipeline wird, muss man ein paar Sachen leider noch frickelig lösen. Beim Kopieren vom Stage- in den Build-Schritt der Codepipeline werden die git-Metadaten nicht mitkopiert, somit kann man leider das Docker-Image nicht mit einem git-sha versehen, sondern nur z.B. mit einem Timestamp. Wenn man das &lt;em&gt;CodeBuild&lt;/em&gt;-Projekt autark verwenden würde, wäre das nicht der Fall und die git-Sachen wären vorhanden. Zum Builden des Docker-Images braucht es auch ein &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service/blob/master/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt;, das ebenfalls Bestandteil des Codes ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der &lt;code&gt;post-build&lt;/code&gt;-Phase lade das Docker-Image auf &lt;a href=&quot;https://hub.docker.com/r/sogis/ilivalidator-web-service/&quot;&gt;hub.docker.com&lt;/a&gt; hoch. Interessanterweise hat es bei mir ohne Löschen des Dockerfiles nicht funktioniert (was aber noch nichts heissen muss). Als letztes muss man in dieser Phase die &lt;code&gt;Dockerrun.aws.json&lt;/code&gt;-Datei so bearbeiten, dass der richtige Tag des Docker-Images drin steht, welches man im nächsten Schritt der Codepipeline auf &lt;em&gt;AWS Elastic Beanstalk&lt;/em&gt; deployen will. Im meinem Fall ein simples &lt;code&gt;sed&lt;/code&gt;-Kommando.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unter &lt;code&gt;artifacts&lt;/code&gt; werden sämtliche Dateien wegkopiert. Wenn CodeBuild Bestandteil einer Codepipeline ist, ist mir noch nicht ganz klar, wohin das kopiert wird. Normalerweise irgendwo in einen S3-Bucket. Zum Zwischenlagern quasi.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p5/aws-codebuild.png&quot; alt=&quot;AWS CodeBuild&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Läuft das CodeBuild-Projekt durch (&lt;code&gt;Succeeded&lt;/code&gt;), kann man sich noch an den &lt;em&gt;Staging&lt;/em&gt;-Schritt der Codepipeline machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier mache ich es wieder gleich wie beim &lt;em&gt;Build&lt;/em&gt;-Schritt. Ich erstelle mir zuerst eine &lt;em&gt;AWS Elastic Beanstalk&lt;/em&gt;-Anwendung alleine, dh. eine Anwendung, die noch nicht Bestandteil der Codepipeline ist. Das geht via Browser sehr schnell. Eine Beanstalk-Anwendung kann mehrere Environments haben, was z.B. sehr praktisch für Blue/Green-Deployment ist. In einem solchen Fall kann man ganz einfach die Environments auf Knopfdruck tauschen und fertig.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst muss man beim Erstellen eines Environments wählen, ob es sich um ein Webserver-Environment oder um ein Worker-Environment handelt. Der INTERLIS-Checkservice ist - nomen est omen - ein Webserver-Environment. Anschliessend wählt man noch die Plattform aus. In meinem Fall &lt;code&gt;Docker&lt;/code&gt;, weil ich ja ein Docker-Image deployen will. Zu guter Letzt muss man nur noch die &lt;a href=&quot;https://github.com/sogis/ilivalidator-web-service/blob/master/Dockerrun.aws.json&quot;&gt;Dockerrun.aws.json&lt;/a&gt;-Datei hochladen und die Magie beginnt. Nun wird eine EC2-Instanz hochgefahren und noch vieles mehr, was mich aber nicht kümmern muss, da alles sauber automatisch abläuft. Hat das funktioniert, sollte das Environment grün erscheinen und der Service erreichbar sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p5/aws-beanstalk.png&quot; alt=&quot;AWS Beanstalk&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;AWS Elastic Beanstalk&lt;/em&gt; bietet noch einiges mehr. So kann man z.B. auf Knopfdruck ein Load Balancing und Auto Scaling einrichten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man den &lt;em&gt;Staging&lt;/em&gt;-Schritt und &lt;em&gt;Build&lt;/em&gt;-Schritt als &amp;laquo;Einzelanwendung&amp;raquo; zum Laufen gebracht, kann man an die Codepipeline gehen. Dort kann man sich durch den Wizard klicken und eigentlich nicht mehr viel falsch machen, da man nur noch bestehende &lt;em&gt;Build&lt;/em&gt;- resp. &lt;em&gt;Staging&lt;/em&gt;-Schritte auswählen muss und ganz zu Beginn beim &lt;em&gt;Source&lt;/em&gt;-Schritt das Github-Repository mit seinem Projekt angeben. Nach dem Speichern der Konfiguration, wird die Pipeline ein erstes Mal ausgeführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Benachrichtigungen kann man mittels &lt;a href=&quot;https://aws.amazon.com/cloudwatch&quot;&gt;&lt;em&gt;AWS CloudWatch&lt;/em&gt;&lt;/a&gt; sehr detailliert konfigurieren. Und zwar so detailliert, dass ich es momentan doch eher unübersichtlich und nicht intuitiv finde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fazit: Mit &lt;em&gt;AWS CodePipeline&lt;/em&gt; und weiteren Diensten kann man sich sehr einfach eine saubere CI/CD-Pipeline zusammenklicken, die dazu führt, dass jeder Commit nach erfolgreichen Tests auf einer (Test-)Umgebung zum Laufen kommt.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #4: AWSome</title>
      <link>http://blog.sogeo.services/blog/2017/08/27/kgdi-the-next-generation-4.html</link>
      <pubDate>Sun, 27 Aug 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/08/27/kgdi-the-next-generation-4.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Rahmen unseres Infrastrukturprojektes &lt;em&gt;SO!GIS 2.0&lt;/em&gt; bekommen wir einen neuen WebGIS Client. Das ist der passende Zeitpunkt sich um die in die Jahre gekommene &lt;a href=&quot;https://www.so.ch/?id=9218&quot;&gt;Hintergrundkarte&lt;/a&gt; zu kümmern. Circa 10-jährig (gefühlte 50) hat sie eine Ablösung verdient. Entstanden ist sie damals mit dem Aufkommen von Google Maps und dem Umstand, dass das Publizieren der Landeskarten im Internet (uns zu) teuer war. Mit dem &lt;a href=&quot;https://www.swisstopo.admin.ch/de/wissen-fakten/geoinformation/austausch-unter-behoerden.html&quot;&gt;Vertrag &amp;laquo;Geodatenaustausch unter Behörden&amp;raquo;&lt;/a&gt; ist das aber kein Thema mehr und wir müssen uns nicht selber eine neue landeskarten-lose Hintergrundkarte zusammenschustern, sondern können die schönen Landeskarten der swisstopo als Basis verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das ganze Vorhaben hat ja etwas von Rumspielen. Ich muss ein paar Varianten (in welcher Zoomstufe welche Landeskarte, wann kommt die amtliche Vermessung, was ist mit Maskierungen etc.) zusammenstellen und diese dann dem Publikum zeigen und Feedback abholen und teilweise wieder einfliessen lassen. Am einfachsten ist es, wenn die neuen Hintergrundkartenvarianten nicht nur im Intranet zur Verfügung stehen, sondern gleich im Internet. Auch macht das die Entwicklung einfacher: einfach schnell etwas von zu Hause ändern ist nicht, wenn man sich noch zweimal einloggen muss und sich dann trotzdem alles nicht super schnell anfühlt durch das mehrfache tunneln etc. Zudem bin ich während des Entwickelns definitiv gerne mein eigener Herr und Meister und will nicht auf einen Root/Admin warten, damit ich eine neue Variante deployen kann. Für das alles gibt es ja &amp;laquo;die Cloud&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich brauche also etwas, wo ich ein paar Gigabyte Rasterdaten speichern kann und diese per WMS zur Verfügung stellen kann. Dh. es ist die Zeit gekommen, die nicht gerade wenigen Dienste von &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt; kennen zu lernen. Viel brauche ich für meine Bedürfnisse zuerst einmal nicht: Plattenplatz und einen Server auf dem ich den WMS-Server (&lt;em&gt;QGIS-Server&lt;/em&gt; in meinem Fall) installieren kann. Weil ich den Server nicht 24/7 laufen lassen will und mich sowieso nicht ständig durch das Webinterface von AWS klicken will, brauche ich etwas damit ich meine Server- und Storagedefinitionen maschinell verwalten und ausführen kann. Aus Transparenz- und Dokumentationsgründen sowieso. Dazu dient mir &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform: &amp;laquo;Write, Plan, and Create Infrastructure as Code&amp;raquo;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes erstelle ich ein &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumes.html&quot;&gt;&lt;em&gt;Volume&lt;/em&gt;&lt;/a&gt;, um darauf die Landeskarten zu speichern. Das &lt;em&gt;Volume&lt;/em&gt; soll dann bei Bedarf in meinen virtuellen Server eingehängt werden, damit der WMS-Server Zugriff auf die Karten hat. Für &lt;em&gt;Terraform&lt;/em&gt; reicht es, wenn ich eine einzelne &lt;code&gt;volume.tf&lt;/code&gt;-Datei schreibe. &lt;em&gt;Terraform&lt;/em&gt; ist so gestrickt, dass es sämtliche &lt;code&gt;*.tf&lt;/code&gt;-Dateien in einem Verzeichnis ausführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;provider &quot;aws&quot; {
  region = &quot;eu-central-1&quot;
}

resource &quot;aws_ebs_volume&quot; &quot;lk&quot; {
  availability_zone = &quot;eu-central-1a&quot;
  size = 50
  type = &quot;gp2&quot;
  tags {
    Name = &quot;ch.swisstopo.landeskarten&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Authentifizerung findet via Access-Keys statt. Da genügt es beide Keys in einem File &lt;code&gt;credentials&lt;/code&gt; im Ordner &lt;code&gt;$HOME/.aws/&lt;/code&gt; zu speichern. Ein wichtiges Konzept von &lt;em&gt;Terraform&lt;/em&gt; sind die &lt;em&gt;Resourcen&lt;/em&gt;. In meinem Fall möchte ich eine &lt;em&gt;Elastic Block Storage&lt;/em&gt;-Resource in der Zone &lt;code&gt;eu-central-1a&lt;/code&gt;. Das &lt;em&gt;Volume&lt;/em&gt; soll 50 GB gross und vom Typ &lt;code&gt;gp2&lt;/code&gt; (08/15-SSD) sein. Mit &lt;em&gt;Terraform&lt;/em&gt; beschreibe ich was das Ziel sein soll und nicht wie ich an das Ziel gelange (also nicht eine Abfolge von Befehlen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit einem einfachen &lt;code&gt;terraform apply&lt;/code&gt; führt es alle notwendigen Befehle aus und das Resultat ist ein verfügbares &lt;em&gt;Volume&lt;/em&gt;, das ich beliebigen virtuellen Servern anhängen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes erstelle ich händisch (also im Webinterface) einmalig eine &lt;em&gt;EC2&lt;/em&gt;-Instanz, um anschliessend das gerade vorher erstellte &lt;em&gt;Volume&lt;/em&gt; einzuhängen und die Landeskarten darauf zu speichern. Ist die &lt;em&gt;EC2&lt;/em&gt;-Instanz gestartet, kann ich ebenfalls im Webinterface das &lt;em&gt;Volume&lt;/em&gt; einhängen. Zusätzlich zum Einhängen muss ich aber einmalig ein Filesystem auf dem &lt;em&gt;Volume&lt;/em&gt; erstellen (&lt;code&gt;mkfs -t ext4 /dev/xvdg&lt;/code&gt;) und es mounten (&lt;code&gt;mkdir /opt/geodata &amp;amp;&amp;amp; mount -o noacl /dev/xvdg /opt/geodata&lt;/code&gt;). Hat das alles funktioniert, kann ich alle benötigten Rasterdaten auf das &lt;em&gt;Volume&lt;/em&gt; kopieren. Zu guter Letzt wird das &lt;em&gt;Volume&lt;/em&gt; wieder &amp;laquo;unmounted&amp;raquo; und von der &lt;em&gt;EC2&lt;/em&gt;-Instanz &amp;laquo;detached&amp;raquo; und diese terminiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der nächste Schritt ist das automatische Hochfahren eines virtuellen Servers und das Installieren sämtlich benötigter Software, wie z.B &lt;em&gt;QGIS&lt;/em&gt; und &lt;em&gt;QGIS-Server&lt;/em&gt; aber auch &lt;a href=&quot;https://wiki.x2go.org/doku.php&quot;&gt;&lt;em&gt;x2go&lt;/em&gt;-Server&lt;/a&gt;, um auf dem Server selber mit &lt;em&gt;QGIS&lt;/em&gt; arbeiten zu können und allenfalls Anpassungen an der Definition der Hintergrundkarte vorzunehmen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das automatische Hochfahren mache ich natürlich wieder mit einer &lt;em&gt;Terraform&lt;/em&gt;-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;provider &quot;aws&quot; {
  region = &quot;eu-central-1&quot;
}

resource &quot;aws_security_group&quot; &quot;allow_all&quot; {
  name        = &quot;allow_all&quot;
  description = &quot;Allow all inbound traffic&quot;

  ingress {
    from_port = 0
    to_port = 65535
    protocol = &quot;tcp&quot;
    cidr_blocks = [&quot;0.0.0.0/0&quot;]
  }

   egress {
    from_port = 0
    to_port = 0
    protocol = &quot;-1&quot;
    cidr_blocks = [&quot;0.0.0.0/0&quot;]
  }

  tags {
    Name = &quot;allow_all&quot;
  }
}

# EBS volume must exist.
resource &quot;aws_volume_attachment&quot; &quot;ebs_att&quot; {
  device_name = &quot;/dev/sdg&quot;
  volume_id   = &quot;vol-01a63e9115f061d94&quot;
}

resource &quot;aws_instance&quot; &quot;qgis-gis-server&quot; {
  availability_zone = &quot;eu-central-1a&quot;
  ami = &quot;ami-1e339e71&quot;
  instance_type = &quot;t2.xlarge&quot;
  key_name = &quot;aws-demo&quot;
  vpc_security_group_ids = [&quot;${aws_security_group.allow_all.id}&quot;]
  user_data = &quot;${file(&quot;qgis-gis-server.conf&quot;)}&quot;

  root_block_device {
    volume_type           = &quot;gp2&quot;
    volume_size           = 12
    delete_on_termination = true
  }

  tags {
    Name = &quot;qgis-gis-server&quot;
  }
}

output &quot;public_ip&quot; {
  value = &quot;${aws_instance.qgis-gis-server.public_ip}&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich benötige drei Resourcen: eine zum Definieren einer &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html&quot;&gt;&lt;em&gt;Security Group&lt;/em&gt;&lt;/a&gt;, eine zum Einhängen des &lt;em&gt;Volumes&lt;/em&gt; und natürlich noch eine zum Definieren meiner &lt;em&gt;EC2&lt;/em&gt;-Instanz.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;em&gt;Security Group&lt;/em&gt; ist in meinem Fall sehr offen, da sie so ziemlich alles durchlässt (sowohl Ports wie auch IPs).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Volume-Einhäng-Resource muss auf ein bestehendes &lt;em&gt;Volume&lt;/em&gt; verweisen. Hier mittels &lt;code&gt;volume_id&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;em&gt;aws_instance&lt;/em&gt;-Resource definiert mir meinen virtuellen Server. Wichtig ist das Attribut &lt;code&gt;key_name&lt;/code&gt;, welches das &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html&quot;&gt;Key-Pair&lt;/a&gt; definiert, das zum &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html&quot;&gt;Einloggen via SSH&lt;/a&gt; verwendet werden soll. Da die &lt;em&gt;Security Group&lt;/em&gt; ja ebenfalls erst erstellt wird, kann ich darauf mit einer Variablen zugreifen: &lt;code&gt;${aws_security_group.allow_all.id}&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die benötigte Software wird unter &lt;code&gt;user_data&lt;/code&gt; installiert. Das kann entweder ein simples Shell-Skript sein oder ein &lt;a href=&quot;https://cloud-init.io/&quot;&gt;&lt;em&gt;cloud init&lt;/em&gt;&lt;/a&gt;-Skript:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;#cloud-config
package_update: true
package_upgrade: true

apt_sources:
 - source: &quot;ppa:x2go/stable&quot;

packages:
 - xfce4
 - xfce4-whiskermenu-plugin
 - xfce4-terminal
 - thunar-archive-plugin
 - x2goserver
 - x2goserver-xsession
 - apache2
 - libapache2-mod-fcgid
 - firefox
 - gedit
 - filezilla

runcmd:
 # Install QGIS and other gis stuff.
 - &apos;echo &quot;deb http://qgis.org/ubuntugis xenial main&quot; &amp;gt;&amp;gt; /etc/apt/sources.list&apos;
 - &apos;echo &quot;deb-src http://qgis.org/ubuntugis xenial main&quot; &amp;gt;&amp;gt; /etc/apt/sources.list&apos;
 - &apos;echo &quot;deb http://ppa.launchpad.net/ubuntugis/ubuntugis-unstable/ubuntu xenial main&quot; &amp;gt;&amp;gt; /etc/apt/sources.list&apos;
 - &apos;echo &quot;deb-src http://ppa.launchpad.net/ubuntugis/ubuntugis-unstable/ubuntu xenial main&quot; &amp;gt;&amp;gt; /etc/apt/sources.list&apos;
 - apt-key adv --keyserver keyserver.ubuntu.com --recv-key 073D307A618E5811 # qgis
 - apt-key adv --keyserver keyserver.ubuntu.com --recv-key 089EBE08314DF160 # ubuntugis-(un)stable
 - apt-get update
 - apt-get --yes --allow-unauthenticated install qgis python-qgis qgis-plugin-grass qgis-server
 - apt-get --yes install mapcache-tools libapache2-mod-mapcache libmapcache1-dev
 # Copy apache conf file w/ qgis server stuff (fcgi...).
 - git clone https://github.com/edigonzales/somap20-hintergrundkarte.git /tmp/somap20-hintergrundkarte
 - cp /tmp/somap20-hintergrundkarte/terraform/create-gis-ec2-instance/apache/000-default.conf /etc/apache2/sites-available/000-default.conf
 - chown -R ubuntu:ubuntu /tmp/somap20-hintergrundkarte/
 - service apache2 restart
 # Mount EBS volume.
 # Filesystem already exists (mkfs -t ext4 /dev/xvdg).
 - mkdir /opt/geodata
 - mount -o noacl /dev/xvdg /opt/geodata
 - &apos;echo /dev/xvdg  /opt/geodata ext4 defaults,nofail,rw,user,exec,umask=000 0 0 &amp;gt;&amp;gt; /etc/fstab&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie man sieht, ist es schlussendlich - oder so wie ich es verwende - auch nicht gross etwas Anderes als ein abstrahiertes Shell-Skript. Viele der &lt;code&gt;runcmd&lt;/code&gt;-Aufrufe waren notwendig, weil ich es nicht schaffte nicht-ppa-Repositories einfach hinzuzufügen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das &lt;em&gt;cloud init&lt;/em&gt;-Skript wird jetzt einmalig beim Erstellen des virtuellen Servers ausgeführt. Weil ich den virtuellen Server nach getaner Arbeit immer terminiere, ist das ok. Wenn ich ihn aber nur herunterfahren würde, müsste man sicherstellen, dass das &lt;em&gt;Volume&lt;/em&gt; beim Hochfahren gemountet wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Terraform-Datei kann mit &lt;code&gt;output&lt;/code&gt; ein beliebiger Output in der Konsole erzeugt werden. Für mich ist natürlich die IP interessant, da ich ja mit &lt;em&gt;x2go&lt;/em&gt; darauf zugreifen und arbeiten will.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;terraform plan&lt;/code&gt; kann ich mir anzeigen lassen, was ein allfälliger &lt;code&gt;apply&lt;/code&gt;-Befehl alles machen würde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;+ aws_instance.qgis-gis-server
    ami:                                       &quot;ami-1e339e71&quot;
    associate_public_ip_address:               &quot;&amp;lt;computed&amp;gt;&quot;
    availability_zone:                         &quot;eu-central-1a&quot;
    ebs_block_device.#:                        &quot;&amp;lt;computed&amp;gt;&quot;
    ephemeral_block_device.#:                  &quot;&amp;lt;computed&amp;gt;&quot;
    instance_state:                            &quot;&amp;lt;computed&amp;gt;&quot;
    instance_type:                             &quot;t2.xlarge&quot;
    ipv6_address_count:                        &quot;&amp;lt;computed&amp;gt;&quot;
    ipv6_addresses.#:                          &quot;&amp;lt;computed&amp;gt;&quot;
    key_name:                                  &quot;aws-demo&quot;
    network_interface.#:                       &quot;&amp;lt;computed&amp;gt;&quot;
    network_interface_id:                      &quot;&amp;lt;computed&amp;gt;&quot;
    placement_group:                           &quot;&amp;lt;computed&amp;gt;&quot;
    primary_network_interface_id:              &quot;&amp;lt;computed&amp;gt;&quot;
    private_dns:                               &quot;&amp;lt;computed&amp;gt;&quot;
    private_ip:                                &quot;&amp;lt;computed&amp;gt;&quot;
    public_dns:                                &quot;&amp;lt;computed&amp;gt;&quot;
    public_ip:                                 &quot;&amp;lt;computed&amp;gt;&quot;
    root_block_device.#:                       &quot;1&quot;
    root_block_device.0.delete_on_termination: &quot;true&quot;
    root_block_device.0.iops:                  &quot;&amp;lt;computed&amp;gt;&quot;
    root_block_device.0.volume_size:           &quot;12&quot;
    root_block_device.0.volume_type:           &quot;gp2&quot;
    security_groups.#:                         &quot;&amp;lt;computed&amp;gt;&quot;
    source_dest_check:                         &quot;true&quot;
    subnet_id:                                 &quot;&amp;lt;computed&amp;gt;&quot;
    tags.%:                                    &quot;1&quot;
    tags.Name:                                 &quot;qgis-gis-server&quot;
    tenancy:                                   &quot;&amp;lt;computed&amp;gt;&quot;
    user_data:                                 &quot;f61a6ca2bf27043bc9a74c638f0211cf5d7c8e15&quot;
    volume_tags.%:                             &quot;&amp;lt;computed&amp;gt;&quot;
    vpc_security_group_ids.#:                  &quot;&amp;lt;computed&amp;gt;&quot;

+ aws_security_group.allow_all
    description:                           &quot;Allow all inbound traffic&quot;
    egress.#:                              &quot;1&quot;
    egress.482069346.cidr_blocks.#:        &quot;1&quot;
    egress.482069346.cidr_blocks.0:        &quot;0.0.0.0/0&quot;
    egress.482069346.from_port:            &quot;0&quot;
    egress.482069346.ipv6_cidr_blocks.#:   &quot;0&quot;
    egress.482069346.prefix_list_ids.#:    &quot;0&quot;
    egress.482069346.protocol:             &quot;-1&quot;
    egress.482069346.security_groups.#:    &quot;0&quot;
    egress.482069346.self:                 &quot;false&quot;
    egress.482069346.to_port:              &quot;0&quot;
    ingress.#:                             &quot;1&quot;
    ingress.1403647648.cidr_blocks.#:      &quot;1&quot;
    ingress.1403647648.cidr_blocks.0:      &quot;0.0.0.0/0&quot;
    ingress.1403647648.from_port:          &quot;0&quot;
    ingress.1403647648.ipv6_cidr_blocks.#: &quot;0&quot;
    ingress.1403647648.protocol:           &quot;tcp&quot;
    ingress.1403647648.security_groups.#:  &quot;0&quot;
    ingress.1403647648.self:               &quot;false&quot;
    ingress.1403647648.to_port:            &quot;65535&quot;
    name:                                  &quot;allow_all&quot;
    owner_id:                              &quot;&amp;lt;computed&amp;gt;&quot;
    tags.%:                                &quot;1&quot;
    tags.Name:                             &quot;allow_all&quot;
    vpc_id:                                &quot;&amp;lt;computed&amp;gt;&quot;

+ aws_volume_attachment.ebs_att
    device_name:  &quot;/dev/sdg&quot;
    force_detach: &quot;&amp;lt;computed&amp;gt;&quot;
    instance_id:  &quot;${aws_instance.qgis-gis-server.id}&quot;
    skip_destroy: &quot;&amp;lt;computed&amp;gt;&quot;
    volume_id:    &quot;vol-01a63e9115f061d94&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gefällt mir das, reicht ein &lt;code&gt;terraform apply&lt;/code&gt; und nach ein paar Minuten habe ich meinen WMS-Server mit den Hintergrundkartenvarianten. Will ich etwas anpassen, kann ich mich mittels &lt;em&gt;x2go&lt;/em&gt; auf Server einloggen und gleich dort mit &lt;em&gt;QGIS&lt;/em&gt; die Kartendefinitionen ändern. Sind alle Arbeiten erledigt, kann man alles mit einem &lt;code&gt;terraform destroy&lt;/code&gt; terminieren. Damit das erfolgreich ist, muss ich aber vorher das &lt;em&gt;Volume&lt;/em&gt; unmounten, sonst erschienen Fehlermeldungen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Damit nach all der trockenen Theorie noch etwas gezeigt wird, hier eine Variante mit Orthofoto (Landsat) und grauer Hintergrundkarte (die Farben für die Maskierung der grauen Variante stammen von &lt;a href=&quot;https://services.geo.zg.ch/qwc2&quot;&gt;hier&lt;/a&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p4/ortho.png&quot; alt=&quot;Orthofoto/Landsat&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p4/lk-relief.png&quot; alt=&quot;Landeskarte grau mit Relief&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gorbatschow hat ja bekanntlich &lt;a href=&quot;http://www.zeit.de/wissen/geschichte/2010-03/gorbatschow-sowjetunion&quot;&gt;(nicht)&lt;/a&gt; gesagt: &amp;laquo;Wer zu spät kommt, den bestraft das Leben.&amp;raquo; Ich wünschte mir im Umgang mit den ganzen &lt;em&gt;IaaS&lt;/em&gt;-, &lt;em&gt;PaaS&lt;/em&gt;-, &lt;em&gt;SaaS&lt;/em&gt;- etc. pp. -Anbietern etwas mehr Mut und Unverkrampfheit. Es kann helfen effizient und sauber strukturiert Aufgaben zu erledigen. Spass macht es allemal. Mit einer totalen Verweigerungshaltung ist man halt irgendeinmal zu spät.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Github-Repo mit allen Skripts: &lt;a href=&quot;https://github.com/edigonzales/somap20-hintergrundkarte&quot;&gt;somap20-hintergrundkarte&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #16</title>
      <link>http://blog.sogeo.services/blog/2017/04/24/interlis-leicht-gemacht-number-16.html</link>
      <pubDate>Mon, 24 Apr 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/04/24/interlis-leicht-gemacht-number-16.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Wochenstart ein klein wenig &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;ilivalidator&lt;/a&gt;-Magie. Im &lt;a href=&quot;http://blog.sogeo.services/blog/2017/04/10/interlis-leicht-gemacht-number-15.html&quot;&gt;vorangegangen Beitrag&lt;/a&gt; habe ich gezeigt, wie man sogenannte Custom Functions in Java programmiert, um beliebige Validierungen zu implementieren. Die dabei resultierende INTERLIS-Funktion war nicht viel mehr als eine Substring-Funktion. Also nichts Besonderes. Häufig will man aber seine Daten mit Referenzdaten, die irgendwo anders gespeichert sind, vergleichen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In diesem Beispiel - auch weil es momentan so richtig &lt;a href=&quot;https://www.cadastre.ch/de/home.detail.news.html/2017/AV-Express2.html&quot;&gt;trendy&lt;/a&gt; ist - vergleiche ich Daten der amtlichen Vermessung mit dem GWR. Genauer: Ist der EGID aus den Daten der amtlichen Vermessung im GWR enthalten. Für die Daten der amtlichen Vermessung verwende ich das &lt;a href=&quot;http://models.geo.admin.ch/V_D/MOpublic95_ili2_v1.3.ili&quot;&gt;MOpublic-Datenmodell&lt;/a&gt;. Hauptgrund ist, dass Custom Functions resp. die Definition von eigenen Constraints mit INTERLIS 1 mit &lt;em&gt;ilivalidator&lt;/em&gt; nicht funktionieren. Für die GWR-Referenz verwende ich den &lt;a href=&quot;https://api3.geo.admin.ch/services/sdiservices.html#find&quot;&gt;&amp;laquo;find&amp;raquo;-Restservice&lt;/a&gt; der BGDI. Es ist kein spezifischer Suchdienst für EGID/EDID im GWR, sondern ein Suchdienst für Datensätze, die in der BGDI publiziert sind. Praktischerweise gibt es ein &lt;code&gt;contains&lt;/code&gt;-Parameter. Mittels diesem Parameter lässt sich steuern, ob eine exakte Suche durchgeführt werden soll oder nicht. In unserem Fall sind wir an einer exakten Suche interessiert. Falls die Suche in unserem Fall also genau ein Resultat liefert, gibt es den EGID aus den Daten der amtlichen Vermessung im GWR. Soviel zur Logik der Custom Function.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst definieren müssen wir das &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-16/MOpublic_Check.ili&quot;&gt;Check-Modell&lt;/a&gt; mit den Views erstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL MOpublic95_ili2_v13_Check (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-14&quot; =
  IMPORTS MOpublic95_ili2_v13;
  IMPORTS SO_FunctionsExt;

  VIEW TOPIC Land_cover =
  DEPENDS ON MOpublic95_ili2_v13.Land_cover;

  	VIEW v_LCSurface
    	PROJECTION OF MOpublic95_ili2_v13.Land_cover.LCSurface;
    =
      ALL OF LCSurface;

      !!@ name = LCSurface_Egid
      !!@ ilivalid.msg = &quot;EGID {RegBL_EGID} wurde im GWR nicht gefunden.&quot;
      MANDATORY CONSTRAINT SO_FunctionsExt.check4GWR(RegBL_EGID);

    END v_LCSurface;

END Land_cover;

END MOpublic95_ili2_v13_Check.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Funktion &lt;code&gt;SO_FunctionsExt.check4GWR&lt;/code&gt; muss in unserem &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-16/SO_FunctionsExt.ili&quot;&gt;Funktions-Modell&lt;/a&gt; deklariert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL SO_FunctionsExt (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-14&quot; =

  FUNCTION check4GWR (egid: NUMERIC): BOOLEAN;

END SO_FunctionsExt.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt ist die Custom Function in Java zu implementieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;package org.catais.ilivalidator.ext;

import ch.ehi.basics.logging.EhiLogger;
import ch.ehi.basics.settings.Settings;
import ch.interlis.ili2c.metamodel.NumericType;
import ch.interlis.ili2c.metamodel.TransferDescription;
import ch.interlis.iom.IomObject;
import ch.interlis.iox.IoxValidationConfig;
import ch.interlis.iox_j.logging.LogEventFactory;
import ch.interlis.iox_j.validator.InterlisFunction;
import ch.interlis.iox_j.validator.ObjectPool;
import ch.interlis.iox_j.validator.Value;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Check4GWRIoxPlugin implements InterlisFunction {
    @Override
    public void init(TransferDescription td, Settings settings, IoxValidationConfig validationConfig, ObjectPool objectPool, LogEventFactory logEventFactory) {
            logger = logEventFactory;
    }

    @Override
    public Value evaluate(String validationKind, String usageScope, IomObject mainObj, Value[] args) {
        if (args[0].skipEvaluation()) {
            return args[0];
        }
        if (args[0].isUndefined()) {
            return Value.createSkipEvaluation();
        }
        String egidString = args[0].getValue();

        // OMG: SSLHandshakeExceptions all over.
        // Do not use HTTPS!
        CloseableHttpClient httpClient = HttpClients.createDefault();

        try {
            HttpGet getRequest = new HttpGet(
                    &quot;http://api3.geo.admin.ch/rest/services/api/MapServer/find?layer=ch.bfs.gebaeude_wohnungs_register&amp;amp;searchText=&quot;
                            + egidString +&quot;&amp;amp;searchField=egid&amp;amp;returnGeometry=false&amp;amp;contains=false&quot;);
            getRequest.addHeader(&quot;accept&quot;, &quot;application/json&quot;);

            HttpResponse response = httpClient.execute(getRequest);

            if (response.getStatusLine().getStatusCode() != 200) {
                throw new RuntimeException(&quot;Failed : HTTP error code : &quot;
                        + response.getStatusLine().getStatusCode());
            }

            BufferedReader br = new BufferedReader(
                    new InputStreamReader((response.getEntity().getContent())));

            String output;
            String jsonString = &quot;&quot;;
            while ((output = br.readLine()) != null) {
                jsonString += output;
            }

            JSONObject jsonObject = new JSONObject(jsonString);
            JSONArray resultsArray = jsonObject.getJSONArray(&quot;results&quot;);

            if (resultsArray.length() == 1) {
                return new Value(true);
            } else {
                return new Value(false);
            }

        } catch (IOException e) {
            logger.addEvent(logger.logErrorMsg(e.getMessage()));
            return new Value(false);
        }
    }

    @Override
    public String getQualifiedIliName() {
        return &quot;SO_FunctionsExt.check4GWR&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Keine Rocket Science. Das Interessante passiert in den Zeilen 43 bis circa 68. Für jeden EGID aus der Klasse &lt;code&gt;LCSurface&lt;/code&gt; wird ein GET-Request gemacht. Wird der EGID im Datensatz der BGDI / des GWR gefunden, wird &lt;code&gt;true&lt;/code&gt; zurückgeliefert. Und funktionieren tut es tadellos. Vielleicht aber nicht nicht die performanteste Art den EGID in den Daten der amtlichen Vermessung zu validieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil wir in diesem Fall nicht nur Standard-Java-Bibliotheken verwenden (json und http) müssen diese auch beim Validieren in &lt;em&gt;ilivalidator&lt;/em&gt; verfügbar sein. Momentan kopiere ich diese in ein Verzeichnis von &lt;em&gt;ilivalidator&lt;/em&gt; (z.B. &lt;code&gt;libs-ext/&lt;/code&gt;). Der Aufruf ist in nun klein wenig &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/52&quot;&gt;komplizierter&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -cp  &apos;../apps/ilivalidator/ilivalidator.jar:../apps/ilivalidator/libs/*:../apps/ilivalidator/plugins/*&apos; org.interlis2.validator.Main --config ../examples/06/mopublic.toml  ../examples/06/mopublic_errors.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die gefundenen Fehler können z.B. zusätzlich in eine &lt;a href=&quot;http://models.interlis.ch/models/tools/IliVErrors.ili&quot;&gt;XTF-Errordatei&lt;/a&gt; geschrieben werden und anschliessend mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; in die Datenbank importiert und auf &lt;a href=&quot;https://map.geo.admin.ch&quot;&gt;map.geo.admin.ch&lt;/a&gt; visualisiert werden. Einfacher geht es nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Beispiel ist eines von vielen eines vor kurzem gehaltenen &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-demo-2017-04-20&quot;&gt;ilivalidator-Workshops&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #15</title>
      <link>http://blog.sogeo.services/blog/2017/04/10/interlis-leicht-gemacht-number-15.html</link>
      <pubDate>Mon, 10 Apr 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/04/10/interlis-leicht-gemacht-number-15.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im letzten &lt;a href=&quot;http://blog.sogeo.services/blog/2017/02/13/interlis-leicht-gemacht-number-14.html&quot;&gt;Beitrag&lt;/a&gt; zeigte ich wie man in &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; eigene, zusätzliche Constraints definieren kann und eigene Java-Funktionen schreiben kann. Beides hat zum Ziel, dass man neben der eigentlichen Modellprüfung zusätzliche Prüfungen durchführen kann. Von der Version 0.10.0 zur Version &lt;a href=&quot;https://github.com/claeis/ilivalidator/releases&quot;&gt;1.0.0&lt;/a&gt; hat sich da noch einiges getan. Und eines vorweg: es ist ziemlich genial geworden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich habe den fast gleichen &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-15/control_points.xtf&quot;&gt;Test-Rumspiel-Datensatz&lt;/a&gt; wie beim letzten Mal verwendet (ein paar Fixpunkte). Einzig den Modellnamen habe ich leicht angepasst: &lt;code&gt;AVLight&lt;/code&gt;. Das &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-15/AVLight.ili&quot;&gt;Modell&lt;/a&gt; sieht jetzt so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

MODEL AVLight (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-02-11&quot; =

  IMPORTS UNQUALIFIED INTERLIS;
  IMPORTS UNQUALIFIED GeometryCHLV95_V1;

    UNIT
      Grads = 200.0 / PI [rad];
      SquareMeters [m2] = (m * m);

    DOMAIN

      Altitude = -200.000 .. 5000.000 [m];
      Rotation = 0.0 .. 399.9 [Grads];
      Accuracy = 0.0 .. 700.0;

   TOPIC Control_points =
      OID AS INTERLIS.UUIDOID;

      CLASS Control_point =
        Category : MANDATORY 0..5;
        IdentND : MANDATORY TEXT*12;
        Number : MANDATORY TEXT*12;
        Geometry : MANDATORY Coord2;
        Plan_accuracy : Accuracy;
        Geom_alt : Altitude;
        Alt_accuracy : Accuracy;
        Mark : MANDATORY 0..8;
        State_of : XMLDateTime;
        FOSNr : 0 .. 9999;
      UNIQUE IdentND, Number;
      END Control_point;

    END Control_points;

  END AVLight.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich jetzt einen &amp;laquo;no-frills&amp;raquo; ilivalidator-Aufruf mache, meldet es mir bereits einen Fehler. Eine Höhe ist viel zu gross und entspricht nicht den Vorgaben des Modelles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich nun weiss, dass die Nummern der Fixpunkte bei uns &lt;strong&gt;immer&lt;/strong&gt; nur acht Zeichen lang sind, kann/muss ich einen zusätzlichen Contraint definieren. Beim letzten Mal habe ich diesen Contraint mit der vordefinierten INTERLIS-Funktion &lt;code&gt;INTERLIS.len()&lt;/code&gt; direkt in das Original-Modell geschrieben. Das geht natürlich, ist aber nicht sonderlich elegant und man müsste immer so etwas wie eine Schattenkopie des wirklichen Originals vorliegen haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eleganter geht es mit Views. INTERLIS-Views sind mir soweit gänzlich unbekannt und die Syntax auch nicht nicht wirklich geläuft, aber für etwas gibt es ja das &lt;a href=&quot;http://interlis.ch/interlis2/docs23/ili2-refman_2006-04-13_d.pdf&quot;&gt;Referenzhandbuch&lt;/a&gt; und die &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/docs/ilivalidator.rst&quot;&gt;ilivalidator-Dokumentation&lt;/a&gt;. Man schreibt sich jetzt also ein &lt;code&gt;AVLight_Check&lt;/code&gt;-Modell und definiert dort die Views mit den zusätzlichen Constraints:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-07&quot; =
  IMPORTS AVLight;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um die Prüfung mit dem zusätzlichen Constraint durchzuführen, braucht es einzig noch einen Eintrag in der &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-15/AVLight.toml&quot;&gt;Konfigurationsdatei&lt;/a&gt; &lt;code&gt;AVLight.toml&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;[&quot;PARAMETER&quot;]
additionalModels=&quot;AVLight_Check&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So wird &lt;em&gt;ilivalidator&lt;/em&gt; mitgeteilt, dass es ein weiteres zu berücksichtigendes Modell gibt. Der Aufruf erfolgt mit dem &lt;code&gt;--config&lt;/code&gt;-Parameter und liefert folgenden zusätzlichen Fehler:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Mandatory Constraint AVLight_Check.Control_points_Check.v_Control_point.Constraint1 is not true.
Info: ...validation failed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schön. Nur nicht sonderlich leserlich. Mit Metattributen &lt;code&gt;!!@&amp;#8230;&amp;#8203;&lt;/code&gt; (Zeilen 14 und 15) kann man nun einerseits den Constraint mit einem Namen versehen und auch eine für den Menschen lesbare Fehlermeldung ausgeben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-07&quot; =
  IMPORTS AVLight;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      !!@ name = Punktnummer_Laenge
      !!@ ilivalid.msg = &quot;Laenge der Punktenummer {Number} ist falsch. Erwartet wird 8.&quot;
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Metattribute müssen vor den betroffenen Constraint platziert werden. Das Ergebnis ist definitiv informativer:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Laenge der Punktenummer 1066703099 ist falsch. Erwartet wird 8.
Info: ...validation failed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich jetzt Dinge prüfen will, die ich nicht mehr mit den INTERLIS-Standardfunktion umsetzen kann, muss ich selber in die Java-Tasten hauen. Grundsätzlich gilt weiterhin, dass man ein &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/main/java/ch/interlis/iox_j/validator/InterlisFunction.java&quot;&gt;Java-Interface&lt;/a&gt; implementieren muss. Bei uns beginnen alle BfS-Nummern mit einer &amp;laquo;2&amp;raquo;. Jetzt kann ich also eine &amp;laquo;SubText&amp;raquo;-Funktion schreiben, die von einem String einen Teil extrahiert und mit einer &amp;laquo;2&amp;raquo; vergleicht und das Ganze in einen MandatoryConstraint packen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;MANDATORY CONSTRAINT SubText(FOSNr,&quot;0&quot;,&quot;1&quot;) == &quot;2&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Disclamer: Die Methode habe ich gewählt, weil sie bereits aus Testzwecken im &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/test/java/ch/interlis/iox_j/validator/SubText.java&quot;&gt;iox-ili-Code&lt;/a&gt; vorhanden ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neu müssen die - nennen wir sie mal - Custom Functions eine Methode &lt;code&gt;getQualifiedIliName()&lt;/code&gt; implementieren. Der Rückgabewert ist ein qualifizierter Name, wie man die Methode in Modellen ansprechen kann. Dafür fällt in der Deklaration im Modell das Metaattribut weg. Zuerst sah ich die Eleganz nicht und dachte, dass man ja jetzt gleiche Methoden für verschiedene Modelle schreiben muss. Aber weit gefehlt. Als man es mir dann erklärte, dämmerte es: Man (z.B. eine Organisation, ein Checkservice-Anbieter) erstellt für die Deklaration der Methoden ein eigenes INTERLIS-Modell, das man - analog wie das &lt;code&gt;AVLight&lt;/code&gt;-Modell - jeweils in das Check-Modell importiert. Daher ist der qualifizierte Name &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-extensions/src/master/src/ilivalidator-extensions/src/main/java/org.catais.ilivalidator.ext/MySubTextIoxPlugin.java&quot;&gt;meiner Implementierung&lt;/a&gt; der SubText-Methode &lt;code&gt;SO_FunctionsExt.mySubText&lt;/code&gt;. &lt;code&gt;SO_FunctionsExt&lt;/code&gt; ist das Modell in dem einzig meine Funktion deklariert wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Methode wird jetzt kompiliert und als JAR-Datei in einen Ordner kopiert. Standardmässig lädt &lt;em&gt;ilivalidator&lt;/em&gt; seit Version 1.0.0 die Custom Functions aus dem &lt;code&gt;plugins&lt;/code&gt;-Verzeichnis (muss erstellt werden) innerhalb der Applikation. Dies kann aber mit dem Parameter &lt;code&gt;--plugins&lt;/code&gt; übersteuert werden. Bei mir hat das Laden meiner SubText-Funktion nicht auf Anhieb funktioniert. Ich habe das Gefühl, dass im iox-ili-Code noch ein &lt;a href=&quot;https://github.com/claeis/ilivalidator/issues/47&quot;&gt;Fehler&lt;/a&gt; versteckt ist. Erst eine zusätzliche Zeile und das anschliessende Kompilieren von &lt;em&gt;iox-ili&lt;/em&gt; und kopieren der Bibliothek in das &lt;code&gt;libs&lt;/code&gt;-Verzeichnis von &lt;em&gt;ilivalidator&lt;/em&gt; hat die Custom Functions gefunden (resp. eben die Klasse geladen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend muss ich das erwähnte &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-15/SO_FunctionsExt.ili&quot;&gt;&lt;code&gt;SO_FunctionsExt&lt;/code&gt;-Modell&lt;/a&gt; erstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL SO_FunctionsExt (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-09&quot; =

  FUNCTION mySubText (text: TEXT; from: NUMERIC; to: NUMERIC): TEXT;

END SO_FunctionsExt.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ebenfalls leicht anpassen muss ich das &lt;code&gt;AVLight_Check&lt;/code&gt;-Modell:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT &quot;http://sogeo.services&quot;
  VERSION &quot;2017-04-07&quot; =
  IMPORTS AVLight;
  IMPORTS SO_FunctionsExt;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      !! Constraint name will not be shown if ilivalid.msg is defined!?
      !!@ name = Punktnummer_Laenge
      !!@ ilivalid.msg = &quot;Laenge der Punktenummer {Number} ist falsch. Erwartet wird 8.&quot;
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
      !!@ name = BfS_Nummer_erste_Ziffer
      !!@ ilivalid.msg = &quot;Erste Ziffer der BfS-Nummer {FOSNr} ist falsch. Erwartet wird 2.&quot;
      MANDATORY CONSTRAINT SO_FunctionsExt.mySubText(FOSNr,&quot;0&quot;,&quot;1&quot;) == &quot;2&quot;;
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In Zeile 6 wird das &amp;laquo;Funktions-Deklarations-Modell&amp;raquo; importiert. Damit hat sich es erledigt. Natürlich muss man noch den Constraint selber ausformulieren und falls gewünscht die passende Fehlermeldung dazu (Zeilen 19 - 21).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ilivalidator-Aufruf liefert korrekterweise einen Fehler:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Laenge der Punktenummer 1066703099 ist falsch. Erwartet wird 8.
Error: line 82: AVLight.Control_points.Control_point: tid 2d8fef45-cc65-4305-a59a-5e046afd2fcb: Erste Ziffer der BfS-Nummer 4479 ist falsch. Erwartet wird 2.
Info: ...validation failed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um eigene Bedingungen zu formulieren mit oder ohne eigenen Tests muss ich in erster Linie nichts Anderes verstehen als INTERLIS. Keine zusätzliche INTERLIS-Validierungssyntax oder ähnliches. Ich brauche keinen neuen Skriptsyntaxparser/-validator, denn den gibt es ja bereits: den &lt;a href=&quot;https://sourceforge.net/projects/umleditor/files/ili2c/&quot;&gt;INTERLIS-Compiler&lt;/a&gt;. Klar, will ich eigene Funktionen schreiben, muss man ein wenig Java beherrschen. Aber that&amp;#8217;s it. Die Verwaltung der zusätzlichen Check-Modelle? Gelöst, nennt sich &lt;a href=&quot;http://www.interlis.ch/models/ModelRepository.pdf&quot;&gt;INTERLIS-Modellablage&lt;/a&gt; und hat sich etabliert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sämtliche Daten finden sich &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-15/&quot;&gt;hier&lt;/a&gt;. Das Git-Repo mit meiner Custom Function gibt es &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-extensions&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #14</title>
      <link>http://blog.sogeo.services/blog/2017/02/13/interlis-leicht-gemacht-number-14.html</link>
      <pubDate>Mon, 13 Feb 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/02/13/interlis-leicht-gemacht-number-14.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Letzte Woche wurde von &lt;em&gt;ilivalidator&lt;/em&gt; die Version &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;0.10.0&lt;/a&gt; veröffentlicht. Diese Version beinhaltet ein neues, cooles Feature. Nämlich die Erweiterung der INTERLIS-Prüfung mit eigenen Tests. Hier eine &lt;em&gt;Sneak Preview&lt;/em&gt; was schon geht (resp. was ich rausgefunden habe, was geht):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gemäss &lt;a href=&quot;http://blog.sogeo.services/blog/2016/05/30/interlis-leicht-gemacht-number-9.html&quot;&gt;Auftragsspezifikation&lt;/a&gt; dürfte das R2.6 und R2.7 sein. Das will heissen, dass jetzt einerseits die INTERLIS-Standardfunktionen (kannte ich nicht, siehe Kapitel 2.14 des &lt;a href=&quot;http://www.interlis.ch/interlis2/docs23/ili2-refman_2006-04-13_d.pdf&quot;&gt;INTERLIS-Referenzhandbuches&lt;/a&gt;) vorhanden sind und andererseits lassen sich eigene Funktionen für die Validierung definieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Definiert werden diese zum Ausgangsmodell zusätzlichen Prüfungen wiederum mit INTERLIS. Nämlich mit CONSTRAINTS. Gegenwärtig muss man es noch direkt im Ausgangsmodell machen, was natürlich unschön ist. Soweit ich mich noch erinnere, soll das zukünftig aber über VIEWS in einem abgeleiteten Modell passieren. Dann hätte man das &amp;laquo;Original&amp;raquo;-Ausgangsmodell und ein Modell mit den zusätzlich definierten Checks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Definition mit INTERLIS in einem Modell mag auf den ersten Blick exotisch wirken. Wahrscheinlich weil man eher etwas wie eine &amp;laquo;Check-Definitions-Sprache&amp;raquo; erwartet. Aber eben, diese braucht es gar nicht, weil man das genau so gut mit INTERLIS machen kann. Zudem kann ich diese zusätzlichen Modelle wiederum in einer INTERLIS-Modellablage verwalten und zur Verfügung stellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zurück zum eigentlichen Validieren: Zum Rumspielen habe ich mir ein &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-14/FunctionTests.ili&quot;&gt;Test-Modell&lt;/a&gt; (angelehnt an das MOpublic) für Fixpunkte gebastelt und dazu ein paar Daten in eine &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-14/control_points.xtf&quot;&gt;XTF-Datei&lt;/a&gt; gespielt. Will ich jetzt z.B. die Länge der Fixpunktnummer auf acht Zeichen beschränken (erlaubt sind gemäss Modell zwölf), kann ich mit einer Standardfunktion einen zusätzlichen CONSTRAINT definieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;CLASS Control_point =
  Category : MANDATORY 0..5;
  IdentND : MANDATORY TEXT*12;
  Number : MANDATORY TEXT*12;
  Geometry : MANDATORY Coord2;
  Plan_accuracy : Accuracy;
  Geom_alt : Altitude;
  Alt_accuracy : Accuracy;
  Mark : MANDATORY 0..8;
  State_of : XMLDateTime;
  FOSNr : 0 .. 9999;
  UNIQUE IdentND, Number;
  MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
END Control_point;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sollte soweit selbsterklärend sein. Wenn ich jetzt bewusst einen Fehler - also eine Nummer eines Fixpunktes länger als acht Zeichen - einbaue, erscheint auf der Konsole folgende Fehlermeldung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Error: line 68: FunctionTests.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Mandatory Constraint FunctionTests.Control_points.Control_point.Constraint2 is not true.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das ist natürlich noch nicht das Gelbe vom Ei. Immerhin stehen TID und die Zeilennummer in der Meldung. Aber wichtig wären selber definierbare Fehlermeldungen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn ich jetzt die absolute Validierungsfreiheit will, kann ich meine Funktion in &lt;em&gt;Java&lt;/em&gt; selber schreiben. Dazu muss man bloss ein &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/feature/validator1/src/main/java/ch/interlis/iox_j/validator/InterlisFunction.java&quot;&gt;Interface&lt;/a&gt; implementieren. Als Beispiel diente mir &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/feature/validator1/src/test/java/ch/interlis/iox_j/validator/SubText.java&quot;&gt;eine Klasse&lt;/a&gt;, die ich unter den Tests gefunden haben. Ich bin dann so vorgegangen, dass ich mir die benötigten Bibliotheken aus der Fatjar-Ilivalidator-Bibliothek, die ich mir mit jedem neuen Release generiere, aus meinem quick &apos;n&apos; dirty Maven-Repository &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-extensions/src/master/src/ilivalidator-extensions/build.gradle&quot;&gt;hole&lt;/a&gt;. Programmieren kann man mit der IDE seiner Wahl.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als zusätzliche Validierung möchte ich prüfen, ob die ersten vier Zeichen der LFP2-Fixpunktnummer mit einem Prefix zusammen dem Attribut &lt;em&gt;IdentND&lt;/em&gt; (= NBIdent) entspricht. Da bin ich eigentlich davon ausgegangen, dass das immer so sein sollte. Die ersten vier Zeichen der LFP2-Punkte entsprechen der 1:25&apos;000-Landeskartenblattnummer und der Prefix (= CH030000) ist für LFP2 ebenfalls fix definiert. Die Kombination sollte dem &lt;em&gt;NBIdent&lt;/em&gt; entsprechen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Gegensatz zu den Standardfunktionen, muss ich im INTERLIS-Modell die Funktion zuerst deklarieren und mit einem INTELRIS-Metaattribut versehen, damit &lt;em&gt;ilivalidator&lt;/em&gt; weiss, in welcher Java-Klasse diese Funktion steckt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;!!@ilivalid.impl.java=org.catais.ilivalidator.ext.IdentND
FUNCTION identND (number: TEXT): TEXT;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hier steckt die Funktion also in der Klasse &lt;code&gt;org.catais.ilivalidator.ext.IdentND&lt;/code&gt;. Die Funktion erwartet einen Parameter vom Typ TEXT und gibt ebenfalls wieder einen Wert vom Typ TEXT zurück.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Defintion des CONSTRAINTS ist genau gleich wie bei den Standardfunktionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;MANDATORY CONSTRAINT identND(Number) == IdentND;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich übergebe der Funktion also die Fixpunktnummer und die Funktion bastelt mir daraus den &amp;laquo;theoretischen&amp;raquo; &lt;em&gt;NBIdent&lt;/em&gt;. Diesen vergleicht der CONSTRAINT mit dem &lt;em&gt;NBIdent&lt;/em&gt; aus dem Datensatz. Der Java-Code ist keine Rocket-Science:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;package org.catais.ilivalidator.ext;

import ch.ehi.basics.settings.Settings;
import ch.interlis.ili2c.metamodel.FunctionCall;
import ch.interlis.ili2c.metamodel.TextType;
import ch.interlis.ili2c.metamodel.TransferDescription;
import ch.interlis.iom.IomObject;
import ch.interlis.iox.IoxValidationConfig;
import ch.interlis.iox_j.validator.InterlisFunction;
import ch.interlis.iox_j.validator.Value;

public class IdentND implements InterlisFunction {
    private IomObject mainObj;
    private Value[] actualArguments;

    private String prefix = &quot;CH030000&quot;;

    @Override
    public void init(TransferDescription td, FunctionCall func,Settings settings,IoxValidationConfig validationConfig) {
    }

    @Override
    public void addObject(IomObject mainObj, Value[] actualArguments) {
        this.setMainObj(mainObj);
        this.setActualArguments(actualArguments);
    }

    @Override
    public Value evaluate() {
        Value[] args = getActualArguments();

        if (args[0].skipEvaluation()) {
            return args[0];
        }
        if (args[0].isUndefined()) {
            return Value.createSkipEvaluation();
        }
        String number = args[0].getValue();

        String identnd = prefix + number.substring(0,4);
        TextType text = new TextType();
        return new Value(text, identnd);
    }

    private IomObject getMainObj() {
        return mainObj;
    }

    private void setMainObj(IomObject mainObj) {
        this.mainObj = mainObj;
    }

    private Value[] getActualArguments() {
        return actualArguments;
    }

    private void setActualArguments(Value[] actualArguments) {
        this.actualArguments = actualArguments;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Methode &lt;code&gt;evaluate()&lt;/code&gt; findet das Zusammenbasteln des &lt;em&gt;NBIdents&lt;/em&gt; statt. Grundsätzlich stehen verschiedene Objekte und Werte in der Klasse zur Verfügung. So z.B. auch das komplette INTERLIS-Objekt (&lt;code&gt;mainObj&lt;/code&gt;) bei dem der CONSTRAINT definiert ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist man damit fertig, muss man &lt;em&gt;ilivalidator&lt;/em&gt; nur noch davon in Kenntnis setzen, dass es jetzt eine zusätzlich Klasse gibt, der er bitteschön berücksichtigen soll. Leicht naiv dachte ich, dass es reicht, wenn man die generierte Jar-Datei einfach in das &lt;code&gt;libs&lt;/code&gt;-Verzeichnis von &lt;em&gt;ilivalidator&lt;/em&gt; kopiert. Aber er wollte und wollte die Funktion nicht finden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Error: line 3972: FunctionTests.Control_points.Control_point: tid 90379a91-29e2-4960-a88f-822c16b8ef3b: Function is not yet implemented.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch das explizite Setzen des Classpaths beim Java-Aufruf brachte nichts:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -cp &apos;libs/ilivalidator-extensions-0.0.1-SNAPSHOT.jar&apos; -jar ilivalidator.jar --modeldir &quot;http://models.geo.admin.ch;.&quot; control_points.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Profis schütteln den Kopf: Anscheinend kann man &lt;code&gt;-cp&lt;/code&gt; und &lt;code&gt;-jar&lt;/code&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=CptdTBDkK_g&quot;&gt;nicht kombinieren&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;java -cp &apos;/Users/stefan/Apps/ilivalidator-0.10.0/ilivalidator.jar:/Users/stefan/Apps/ilivalidator-0.10.0/libs/*&apos; org.interlis2.validator.Main --modeldir &quot;http://models.geo.admin.ch;.&quot; control_points.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sieht doof aus, ist aber egal. &lt;em&gt;In Production&lt;/em&gt; kann man das sicher übersichtlicher lösen. Jedenfalls hat die Prüfung mit meiner Klasse ergeben, dass bei einem Fixpunkt die Nummer nicht stimmt. Oder das Abweichen von der definierten Logik wurde bewusst in Kauf genommen, da der Punkt ein Teil einer Punktgruppe ist und nur &lt;a href=&quot;https://s.geo.admin.ch/7178989444&quot;&gt;wenige Meter im anderen Kartenblatt&lt;/a&gt; liegt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Möglichkeit eigener Validierungsfunktionen öffnen sich nun viele Türen: So können Webservice oder Datenbanken angezapft werden, um die zu prüfenden Daten mit Referenz- oder Drittdaten (z.B. GWR) zu vergleichen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf die Schnelle konnte ich zwei Dinge nicht umsetzen, die ich gerne wollte. Da ist der Vergleich eines Attributes mit einem Teil des Transferdateinamens. So wie ich den Code verstehe, ist der Transferdateiname unbekannt, da pro &lt;code&gt;IoxEvent&lt;/code&gt; &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/src/org/interlis2/validator/Validator.java#L130&quot;&gt;geprüft&lt;/a&gt; wird. Eventuell könnte man ihn in den Settings unterbringen. Die Settings stehen im &lt;code&gt;InterlisFunction&lt;/code&gt;-Interface zur Verfügung. Vielleicht auch super unelegant&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das zweite Problem ist das Prüfen von LIST und BAG OF Geschichten. Da weiss ich sowieso noch nicht, ob ich die Syntax richtig verstanden habe und ich wünschte mir, dass man mit einem CONSTRAINT alle Elemente (in meinem Fall STRUCTURE) der BAG/LIST prüfen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Demo mit einer eigenen Funktion findet sich &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-demo-2017-02-14&quot;&gt;hier&lt;/a&gt;. Das dazugehörige Java-Gefrickel gibt es &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-extensions/src/master/src/ilivalidator-extensions&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Datenflüsse mit Gradle #2</title>
      <link>http://blog.sogeo.services/blog/2017/02/08/datenfluesse-mit-gradle-2.html</link>
      <pubDate>Wed, 8 Feb 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/02/08/datenfluesse-mit-gradle-2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Beitrag dürfte auch den Namen &amp;laquo;INTERLIS leicht gemacht #14&amp;raquo; erhalten, da er sehr schön die Vorzüge einer &lt;a href=&quot;http://blog.sogeo.services/blog/2017/01/02/kgdi-the-next-generation-3.html&quot;&gt;model-driven GDI&lt;/a&gt; mit INTERLIS zeigt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee mit &lt;a href=&quot;https://gradle.org/&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt; Datenflüsse zu orchestrieren resp. aus einzelnen immer wiederkehrenden Schritten verschiedene Jobs zusammenstöpseln, wurde in &lt;a href=&quot;http://blog.sogeo.services/blog/2017/01/19/datenfluesse-mit-gradle-1.html&quot;&gt;diesem Beitrag&lt;/a&gt; erläutert. Eine Herausforderung, die auftritt, ist der Umgang mit Wiederholungen. Wenn es nun häufig darum gehen soll, Daten von A nach B zu kopieren und diese in eine Datenbank zu importieren, merkt man sofort, dass auf dem fremden Server ja nicht nur &lt;em&gt;eine&lt;/em&gt; Datei liegt, die es zu importieren gilt, sondern &lt;em&gt;viele&lt;/em&gt;. Jetzt könnte man natürlich das build-File einfach mehrfach kopieren. Bei zwei Dateien geht das ja vielleicht noch, aber bei 109 Gemeinden mit je einem AV-Datensatz will man mit diesem Ansatz nie mehr was verändern. Oder die einzelnen Schritte sind so ausgelegt, dass sie nicht nur mit einer Datei umgehen können, sondern mit einer beliebigen Anzahl. Das ist wahrscheinlich komplizierter zu programmieren und ich finde es &amp;laquo;schöner&amp;raquo;, wenn die einzelnen &lt;em&gt;Steps&lt;/em&gt; möglichst atomar sind. Vor allem möchte ich aber die Ressource kennen, mit der ich mich während des Jobs beschäftigen muss. Was wenn z.B. auf dem FTP-Server eine unbekannte 10GB-Datei liegt und einfach alles vom FTP-Server herunterladen wird und versucht wird zu importieren? Uncool. Also muss was her, dass zwar tendenziell auf die einzelne Datei zielt aber es soll so etwas geben wie eine Forschleife für die Wiederverwendbarkeit von Jobs (mit anderen Parameter).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Gradle&lt;/em&gt; kennt &lt;a href=&quot;https://docs.gradle.org/current/userguide/multi_project_builds.html&quot;&gt;Multiprojekte&lt;/a&gt;. Es gibt ein Root-Projekt und &lt;a href=&quot;https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:subproject_configuration&quot;&gt;Subprojekte&lt;/a&gt;. Das Root-Projekt soll in unserem Fall dazu dienen die 1 bis n (identischen) Subprojekte zu konfigurieren und auszuführen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Probe aufs Exempel will ich Daten, die in unserer model-driven GDI, erfasst werden täglich prüfen. Dazu muss ich die Daten aus der PostgreSQL-Datenbank nach INTERLIS mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; exportieren und anschliessend mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; auf ihre Modellkonformität prüfen. Genau genommen reicht der Export mit &lt;em&gt;ili2pg&lt;/em&gt;, da die ilivalidator-Prüfbibliotheken bequemerweise bereits eingebaut sind. Trotz den praktischen QGIS-Editier-Widgets und -Formularen sind Fehler bei der Erfassung schnell passiert. Vor allem wenn der Benutzer Beziehungen manuell korrigiert und erfasst. Darum lohnt sich dieses tägliche Validieren. Insbesondere können so auch Geometriefehler gefunden werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Testen stehen zwei kleinere Modelle zur Verfügung: &lt;a href=&quot;http://blog.sogeo.services/data/datenfluesse-mit-gradle-2/SO_Hoheitsgrenzen_20170203.ili&quot;&gt;Hoheitsgrenzen&lt;/a&gt; und die &lt;a href=&quot;http://blog.sogeo.services/data/datenfluesse-mit-gradle-2/SO_AV_Nachfuehrungskreise_2016-11-26.ili&quot;&gt;Nachführungskreise der amtlichen Vermessung&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die einzelnen Subprojekte müssen in &lt;em&gt;Gradle&lt;/em&gt; in der Datei &lt;code&gt;settings.gradle&lt;/code&gt; definiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;include &apos;:av_hoheitsgrenzen&apos;
include &apos;:av_nachfuehrungskreise&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Namen der Subprojekte sind frei wählbar. Weil aber diese Namen über ein Property im build-File ansprechbar sind, wählt man sinnigerweise etwas aus, das man im weiteren Verlauf noch verwenden kann. Diese Subprojekte befinden sich &amp;laquo;eigentlich&amp;raquo; in Unterordnern. Weil wir aber sowieso keinen Code in diesen Unterordnern resp. Subprojekten haben, müssen diese Ordner auch nicht existieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun kommt das erste Mal die Magie zum Vorschein. Im Haupt-Build-File &lt;code&gt;build.gradle&lt;/code&gt; können den Subprojekten Properties mitgegeben werden. Wir müssen ja z.B. wissen in welchem Datenbankschema die Daten liegen und welches INTERLIS-Datenmodell verwendet werden muss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;project(&apos;:av_hoheitsgrenzen&apos;) {
    ext.dbschema = &quot;av_hoheitsgrenzen&quot;
    ext.ilimodels = &quot;SO_Hoheitsgrenzen_20170203&quot;
}

project(&apos;:av_nachfuehrungskreise&apos;) {
    ext.dbschema = &quot;av_nachfuehrungskreise&quot;
    ext.ilimodels = &quot;SO_AV_Nachfuehrungskreise_20161126&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wo werden die Daten jetzt aber exportiert und geprüft? Gleich nachfolgend, hier:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;subprojects {
    apply from: &quot;${rootDir}/validation.gradle&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das bedeutet, dass für jedes Subprojekt das Build-Skript &lt;code&gt;validation.gradle&lt;/code&gt; ausgeführt wird. Und in genau diesem &lt;code&gt;validation.gradle&lt;/code&gt; findet der eigentliche Validierungs-Job statt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import ch.so.agi.gdi.tasks.io.InterlisExportTask

ext {
    exportDir = &quot;/opt/tmp/validation/&quot;
}

task validateData(type: InterlisExportTask) {
    host = db_host
    port = db_port
    database = db_database
    usr = db_usr
    pwd = db_pwd

    schema = dbschema
    models = ilimodels

    xtfFileName = exportDir + project.name + &quot;.xtf&quot;
    logfile = exportDir + project.name + &quot;.log&quot;
}

task cleanUp(type: Delete) {
    outputs.upToDateWhen {false}

    onlyIf {
        validateData.state.failure == null
    }

    delete fileTree(dir: exportDir, include: &quot;**/${project.name}.*&quot;)
}

validateData.finalizedBy(&apos;cleanUp&apos;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst wird der selber geschriebene - auf &lt;em&gt;ili2pg&lt;/em&gt; basierende - &lt;code&gt;InterlisExportTask&lt;/code&gt; importiert. Anschliessend wird das Exportverzeichnis definiert. Der Task &lt;code&gt;validateData&lt;/code&gt; ist vom Typ &lt;code&gt;InterlisExportTask&lt;/code&gt; und prüft gleich während des Exportierens die Daten. Falls sie fehlerhaft sind, ist der Build nicht erfolgreich. Nach dem Validieren werden die exportierten Daten wieder gelöscht. Dies macht der &lt;code&gt;cleanUp&lt;/code&gt;-Task vom Typ &lt;code&gt;Delete&lt;/code&gt;. Dieser Typ ist bereits Bestandteil von &lt;em&gt;Gradle&lt;/em&gt; und musste nicht selber geschrieben werden. Gelöscht werden die Daten aber nur, falls die Validierung erfolgreich war: &lt;code&gt;onlyIf&lt;/code&gt; in Verbindung mit &lt;code&gt;validateData.state.failure == null&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt möchten wir mit einer E-Mail benachrichtigt werden, falls etwas schief lief. Dies können wir im Root-Build-File mit dem &lt;code&gt;buildFinished&lt;/code&gt;-Hook erledigen. Das ist dann kein Task, denn wir aufrufen, sondern ein paar Zeilen selbst erklärendes &lt;a href=&quot;http://www.groovy-lang.org/&quot;&gt;Groovy&lt;/a&gt; pur.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import org.apache.commons.mail.DefaultAuthenticator
import org.apache.commons.mail.Email
import org.apache.commons.mail.SimpleEmail

gradle.buildFinished { buildResult -&amp;gt;
    println &quot;\nBUILD FINISHED&quot;

    if (buildResult.failure) {
        logger.error(&quot;build failure - &quot; + buildResult.failure)
    }

    if (buildResult.failure) {
        def message = buildResult.failure.getMessage()

        Email email = new SimpleEmail()
        email.setHostName(email_host)
        email.setSmtpPort(Integer.parseInt(email_port))
        email.setAuthenticator(new DefaultAuthenticator(email_login, email_password))
        email.setSSLOnConnect(true)
        email.setFrom(email_from)
        email.setSubject(project.name)
        email.setMsg(message)
        email.addTo(email_receiver)
        email.send()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man einzelne Subprojekte ausführen, muss man den vollständigen Pfad (mit Doppelpunkt getrennt) zum Task angeben. In unserem Fall für die Hoheitsgrenzen:  &lt;code&gt;gradle :av_hoheitsgrenzen:validateData&lt;/code&gt;. Oder aber wenn wir alle Subprojekte am Stück ausführen lassen wollen: &lt;code&gt;gradle validateData&lt;/code&gt;. Falls jetzt das erste Subprojekt einen Fehler wirft, wird das zweite nicht mehr ausgeführt. Dieses Verhalten können wir mit der &lt;a href=&quot;https://docs.gradle.org/current/userguide/tutorial_gradle_command_line.html#sec:continue_build_on_failure&quot;&gt;Option&lt;/a&gt; &lt;code&gt;--continue&lt;/code&gt; ändern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiteres kleines Goodie ist die &lt;a href=&quot;https://docs.gradle.org/current/userguide/tutorial_gradle_command_line.html#sec:profiling_build&quot;&gt;Option&lt;/a&gt; &lt;code&gt;--profile&lt;/code&gt;. Das liefert eine nette HTML-Seite mit &lt;a href=&quot;http://blog.sogeo.services/data/datenfluesse-mit-gradle-2/profile-01/profile-2017-02-08-20-55-10.html&quot;&gt;Informationen zum Build&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanter ist so eine Auswertung aber für ein anderes Projekt: Von &lt;a href=&quot;https://s.geo.admin.ch/714aaee117&quot;&gt;map.geo.admin.ch&lt;/a&gt; werden sämtliche 230 frei verfügbaren AV-Datensätze herunterladen und mit &lt;em&gt;ilivalidator&lt;/em&gt; geprüft. Da gibt der &lt;a href=&quot;http://blog.sogeo.services/data/datenfluesse-mit-gradle-2/profile-02/profile-2017-02-08-08-22-49.html&quot;&gt;Report&lt;/a&gt; schon ein bisschen mehr her. Mit der Performanz von ilivalidator darf man schon mal ganz zufrieden sein. 230 AV-Datensätze in 1:11h. Die grafische Aufbereitung der Resultate gibt es &lt;a href=&quot;https://s.geo.admin.ch/713a84cb4a&quot;&gt;hier&lt;/a&gt;, die natürlich täglich mittels cronjob und Gradle-Build-File nachgeführt werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Datenflüsse mit Gradle #1</title>
      <link>http://blog.sogeo.services/blog/2017/01/19/datenfluesse-mit-gradle-1.html</link>
      <pubDate>Thu, 19 Jan 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/01/19/datenfluesse-mit-gradle-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Datenflüsse sind ja &lt;a href=&quot;http://blog.sogeo.services/blog/2016/12/29/kgdi-the-next-generation-2.html&quot;&gt;momentan bei uns&lt;/a&gt; ein wichtiges Thema. Bereits heute schieben wir tagein tagaus viele Daten umher. Importieren sie und unsere Datenbank, bauen sie um und exportieren sie auch wieder. Und in Zukunft werden Datenflüsse für uns noch zentraler werden. Aus diesem Grund wollen und brauchen wir etwas Generisches und Rock-solides.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schaut man sich unsere heutigen Anwendungsfälle an, sieht man viele Gemeinsamkeiten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Herunterladen / Umherkopieren von Dateien (INTERLIS, Shapefiles, CSV)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Import der Dateien in die PostgreSQL-Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Umbauen der Daten mit einer SQL-Query in der Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Daten aus - juhee - einer Oracle-Datenbank in unsere PostgreSQL-Datenbank importieren.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das heisst, wir brauchen nicht die eierlegende Wollmilchsau, sondern können uns beschränken auf:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;eine geringe Anzahl an zu unterstützenden Datenformaten.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;einen Datenumbau in der Datenbank (SQL to the rescue).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil wir in Zukunft verschiedene Datenbanken für die verschiedenen Zwecke verwenden wollen, kann der Datenumbau zwar mit SQL in der Quell-Datenbank stattfinden, trotzdem müssen anschliessend die Daten in die Ziel-Datenbank transportiert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;An einer höchst konstruktiven Brainstormingsitzung mit &lt;a href=&quot;http://eisenhutinformatik.ch/&quot;&gt;Claude Eisenhut&lt;/a&gt; haben wir über diese ganze generische Datenfluss/-integrationsgeschichte noch weiter räsoniert und versucht solche Prozesse weiter &amp;laquo;auseinanderzubeinlen&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der ganze Prozess ist ein &lt;em&gt;Job&lt;/em&gt;. Ein &lt;em&gt;Job&lt;/em&gt; kann aus mehreren &lt;em&gt;Steps&lt;/em&gt; bestehen. Ein &lt;em&gt;Step&lt;/em&gt; ist z.B. das Herunterladen von Dateien &lt;strong&gt;oder&lt;/strong&gt; das Importieren einer Datei in die Datenbank etc. Ziel sollte es sein, möglichst wiederverwendbare &lt;em&gt;Steps&lt;/em&gt; zu haben, die man nur noch aneinanderzureihen hat, um eine zuverlässige Datenintegration inkl. Datenumbau zu erhalten. Der AGI-Mitarbeiter muss also nicht mehr das hundertste Individualskript mit &lt;em&gt;php4&lt;/em&gt; oder &lt;em&gt;Python&lt;/em&gt; schreiben, sondern muss nur noch &lt;em&gt;Steps&lt;/em&gt; zusammenstöpseln und die gewünschten Parameter eintragen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den Datenimport bräuchten wir als &lt;em&gt;Steps&lt;/em&gt; ein paar robuste 1:1-Transformatoren, wie z.B. CSV in die Datenbank oder INTERLIS in die Datenbank. Für den Datenumbau braucht es einen DB2DB-Step, dem man einfach SQL übergeben kann und anschliessend das Resultat der Query in die Ziel-Datenbank schreibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es war schon Aufbruchstimmung als die Diskussion auf das Thema &amp;laquo;Ausführen eines &lt;em&gt;Jobs&lt;/em&gt;&amp;raquo; kam. Und da fiel das Zauberwort von Claude: &amp;laquo;Ihr könnt auch &lt;a href=&quot;http://ant.apache.org/&quot;&gt;&lt;em&gt;ant&lt;/em&gt;&lt;/a&gt; verwenden.&amp;raquo; Ein Build-Tool. Das macht ja genau das, was wir wollen. Es hat (voneinander abhängige) einzelne Schritte, die ausgeführt werden, z.B.:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Quellcode kompilieren&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kompilierte Klassen zu einem Paket/Programm schnüren.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Das Programm irgendwo hinkopieren.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls das Kompilieren Fehler wirft, wird das Paketieren und Kopieren gar nicht erst ausgeführt. Analog dazu soll bei uns der Importschritt nicht stattfinden, falls beim Herunterladen der Datei ein Fehler auftaucht ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anstelle von &lt;em&gt;ant&lt;/em&gt; habe ich mich zum Ausprobieren aber für &lt;a href=&quot;https://gradle.org/&quot;&gt;&lt;em&gt;Gradle&lt;/em&gt;&lt;/a&gt; entschieden. Was bekommt sonst noch &amp;laquo;gratis&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Steps (&lt;em&gt;Tasks&lt;/em&gt; in Gradle) können einzeln angestossen werden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ein Job (&lt;em&gt;Project&lt;/em&gt;) - also ein Build - kann auch innerhalb eines &lt;a href=&quot;http://www.groovy-lang.org/&quot;&gt;&lt;em&gt;Groovy&lt;/em&gt;&lt;/a&gt;-Skript ausgeführt werden und nicht nur auf der Konsole.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Eigene &lt;em&gt;Tasks&lt;/em&gt; sind einfach selber in &lt;em&gt;Groovy&lt;/em&gt; oder &lt;em&gt;Java&lt;/em&gt; programmierbar.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als kleiner Teaser hier ein Build-Skript, das eine INTERLIS-Datei kopiert und anschliessend mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; prüft:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;import ch.ehi.basics.settings.Settings
import org.interlis2.validator.Validator
import ch.ehi.basics.logging.EhiLogger

buildscript {
    repositories {
        mavenCentral()
        maven {
            url &quot;http://www.catais.org/maven/repository/release/&quot;
        }
    }
    dependencies {
        classpath group: &apos;org.interlis2&apos;, name: &apos;ilivalidator&apos;, version: &apos;0.9.0&apos;
    }
}

// Copy file
task copyFile(type: Copy) {
    from(&quot;/Users/stefan/Projekte/agi-data-integrator/data/ch_254900.itf&quot;)
    into(&quot;/opt/tmp/&quot;)
}

// Validate INTERLIS file
task validate(type: IlivalidatorTask, dependsOn: &apos;copyFile&apos;) {
    fileName = &quot;/opt/tmp/ch_254900.itf&quot;
}

// Custom Task
class IlivalidatorTask extends DefaultTask {
    @Input String fileName

    Boolean success

    @TaskAction
    def validate() {
        def settings = new ch.ehi.basics.settings.Settings()
        settings.setValue(Validator.SETTING_ILIDIRS, Validator.SETTING_DEFAULT_ILIDIRS)
        success = Validator.runValidation(fileName, settings)
        if (!success) {
            throw new GradleException(&quot;INTERLIS validation failed.&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Beispiel ist natürlich nicht sonderlich sinnvoll, aber es zeigt ein paar Grundprinzipien ganz gut. Ausführen muss man den Build auf der Konsole mit &lt;code&gt;gradle validate&lt;/code&gt;, um das ITF zuerst zu kopieren und anschliessend zu validieren. Will man nur validieren ohne vorgänging zu kopieren, kann man den copy-Task auch ausschliessen: &lt;code&gt;gradle validate -x copyFile&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls die INTERLIS-Datei fehlerfrei ist, wird der Build erfolgreich beendet: &lt;code&gt;BUILD SUCCESSFUL&lt;/code&gt;. Falls sich Fehler in der INTERLIS-Datei eingeschlichen haben, wird der Build mit &lt;code&gt;BUILD FAILED&lt;/code&gt; und weiteren Fehlermeldungen beendet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/datenfluesse-mit-gradle-1/build_failed.png&quot; alt=&quot;BUILD FAILED&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die selber geschriebenen &lt;em&gt;Tasks&lt;/em&gt; (hier &lt;em&gt;IlivalidatorTask&lt;/em&gt;) kann man natürlich noch auf verschiedene Weisen auslagern und so wiederverwenden. So würden dann auch noch ein Zeilen zu Beginn des Skripts wegfallen und es blieben die beiden &lt;em&gt;Tasks&lt;/em&gt; übrig (7 Zeilen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ganze ist momentan noch nicht mehr als eine Spielerei. Ob weitere unserer Anforderungen so einfach abgedeckt werden können, muss sich noch zeigen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stay tuned for more gradle data integration magic.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #3</title>
      <link>http://blog.sogeo.services/blog/2017/01/02/kgdi-the-next-generation-3.html</link>
      <pubDate>Mon, 2 Jan 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/01/02/kgdi-the-next-generation-3.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;Model-driven GDI&amp;raquo; klingt ja schon mal cool. Von wem genau der Begriff stammt, weiss ich leider nicht mehr. Es kann gut sein, dass er sich nach einem Vortrag von &lt;a href=&quot;https://twitter.com/gl_geoportal&quot;&gt;Peter Staub&lt;/a&gt; bei uns herauskristallisiert hat oder vielleicht auch während des Vortrages bereits gefallen ist. Was verstehe ich darunter?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Grundsätzlich dazu gehört der modellbasierte Ansatz mit INTERLIS. Beschrieben wird er, wie auch die Vorzüge von Datenmodellen, im Dokument &lt;em&gt;Allgemeine Empfehlungen zur Methodik der Definition &amp;laquo;minimaler Geodatenmodelle&amp;raquo;&lt;/em&gt;, das &lt;a href=&quot;https://www.geo.admin.ch/de/geoinformation-schweiz/geobasisdaten/geodatenmodelle.html&quot;&gt;hier&lt;/a&gt; zu finden ist. Diese Gedanken sind ja nicht nur richtig wenn es um minimale Geodatenmodelle des Bundes oder der Kantone geht, sondern sie passen auch wenn es ganz allgemein um Geodatenmodellierung geht (und wenn kein Gesetz es verlangt). Dieser modellbasierte Ansatz soll dann integraler Bestandteil der GDI werden. Da müssen &lt;em&gt;Prozesse&lt;/em&gt; und &lt;em&gt;Technik&lt;/em&gt; sauber darauf abgestimmt werden. Vor allem braucht es auch ein Ökosystem, damit überhaupt damit gearbeit werden kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Früher begann ein Projekt oder ein Erfassung von Daten häufig mit einem Anruf beim AGI mit der Bitte um die Erstellung von ein paar Datenbanktabellen. Gesagt getan: &lt;code&gt;CREATE TABLE &amp;#8230;&amp;#8203;&lt;/code&gt;. Es folgte anschliessend ein weiterer Anruf, weil dieses und jenes Attribut in den Tabellen fehlte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oftmals stand auch das Tool im Vordergrund. Es ging fast weniger um die Erfassung von Daten, als um die Entwicklung eines coolen neuen Tools (was für den Entwickler verständlicherweise spannender ist).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der &amp;laquo;model-driven GDI&amp;raquo; sollen jetzt wieder die Daten und die Datenerfassung ins Zentrum gerückt werden. Damit gewinnen wir eine bessere Dokumentation, eine sauberere Strukturierung der einzelnen Teile und ein nachhaltigeres &amp;laquo;Ganzes&amp;raquo;. Klar braucht es dazu auch Tools aber in den allermeisten Fällen geht es zuerst einmal um die Daten und die Strukturierung dieser Daten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Überlegungen wie die Daten strukturiert werden sollen, muss man sich ja sowieso machen. Auch wenn das vielleicht häufig nicht formalisiert stattfindet. Weil man sich die Arbeiten ja eben sowieso machen muss, kann man das daraus folgende Resultat gleich im &lt;a href=&quot;http://www.umleditor.org/&quot;&gt;UML/INTERLIS-Editor&lt;/a&gt; erfassen. Hier ein Beispiel eines Datenmodelles für Nutzungsvereinbarungen des Amtes für Raumplanung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung.png&quot; alt=&quot;Datenmodell Nutzungsvereinbarung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wichtig bereits bei diesem Schritt ist das Beschreiben der Attribute und Klassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung_description_umleditor.png&quot; alt=&quot;Description Datenmodell Nutzungsvereinbarung UML/INTERLIS-Editor&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das vom UML/INTERLIS-Editor erzeugte INTERLIS-Modell kann noch mit weiteren Kommentaren in einem beliebigen Texteditor (hier &lt;a href=&quot;http://www.jedit.org/&quot;&gt;jEdit&lt;/a&gt;) ergänzt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung_jedit.png&quot; alt=&quot;Datenmodell Nutzungsvereinbarung jEdit&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Frage, die schnell auftaucht, ist der Umgang mit benötigten Fremddaten. Häufig braucht man ja Daten, wo der &amp;laquo;Master&amp;raquo; in einem anderen Modell (resp. Schema/Tabelle in der DB) verwaltet werden, z.B. Gemeindegrenzen, Grundstücke etc. Soll man diese jetzt nochmals im eigenen Modell verwalten? Diese Fragestellung ist ein Teil des Modellierungsprozesses und muss dort beantwortet werden. Häufig reicht wahrscheinlich ein Fremdschlüssel. Der Fremdschlüssel kann in diesem Fall entweder ein Sachattribut (amtliche Gemeindenummer) oder die Geometrie selber sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es lohnt sich auch bereits zu diesem Zeitpunkt Gedanken über die Publikation der Daten zu machen. In erster Linie geht es zwar um die Datenerfassung und diese geschieht tendenziell in einem normalisierten Datenmodell. In den meisten Fällen will man die Daten aber in einer einfacheren, flachgedrückten Form publizieren. Unter Publikation kann das Bereitstellen für anderen Dienststellen verstanden werden (read-only, als Hintergrundlayer in &lt;em&gt;QGIS&lt;/em&gt;), die Bereitstellung im WMS / WebGIS oder die Datenabgabe (GeoPackage, DXF etc.). Wenn sich Erfassung und Publikation nicht viel nehmen, darf man wahrscheinlich auch beim (Erfassungs-)Modell Kompromisse eingehen, um sich ein eigenes Publikationsmodell zu sparen. Klare Vorstellungen was publiziert werden soll, dient auch dem Aufdecken allfälliger Fehler in einem Erfassungsmodell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ob für das allfällig benötigte, separate Publikationsmodell &lt;em&gt;immer&lt;/em&gt; auch mit INTERLIS modelliert werden muss, bedarf noch ein paar weiterführenden Diskussionen und Gedanken. Tendenziell bin ich Freund dieses Ansatzes. Im ersten Blick wirkt es ein wenig übertrieben, wenn man an kleine Modelle denkt, aus denen eine flachgedrückte Tabelle für die Publikation entstehen. Andererseits kann man alles über einen Kamm scheren. Damit entsteht auch kein Wildwuchs. Und wie gesagt, Gedanken wie ich was publizieren will, muss man ja sowieso machen. Sowie auch die Dokumentation. Es ist also nicht so, dass ohne INTERLIS-Publikationsmodell alles 10x mal schneller geht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus dem INTERLIS-Modell erstelle ich mit einem Einzeiler und &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; die leeren Datenbanktabellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar /Users/stefan/Apps/ili2pg-3.5.1/ili2pg.jar --dbhost 192.168.50.4 --dbdatabase xanadu2 --dbusr stefan --dbpwd ziegler12 --dbschema arp_nutzungsvereinbarungen --disableValidation --nameByTopic --sqlEnableNull --createGeomIdx --createFkIdx --strokeArcs --models SO_ARP_Nutzungsvereinbarung_20160726 --modeldir &quot;http://models.geo.admin.ch/;.&quot; --defaultSrsCode 2056 --schemaimport&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein absolutes Goodie ist das Handling von Kommentaren in diesem Ökosystem. Die Kommentare, die man mühelos im GUI des UML/INTERLIS-Editors erfasst hat, werden in der Datenbank abgebildet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung_description_postico.png&quot; alt=&quot;Datenmodell Nutzungsvereinbarung Kommentare DB&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://qgis.org/&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; passt da wunderprächtig in das gesamte Ökosystem. Dank den sehr &lt;a href=&quot;https://sogeo.services/slides/qgis_anwendertreffen/2016-qgis-ili2pg-workshop_v03.pdf&quot;&gt;mächtigen Formularfunktionen&lt;/a&gt;, kann man auch in normalisierten Datenmodellen direkt und bequem Daten erfassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung_erfassung_qgis.png&quot; alt=&quot;Datenmodell Nutzungsvereinbarung Erfassung in QGIS&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei kleineren Modellen geht das Zusammenstöpseln der Beziehungen und der richtigen Widget-Typen schnell. Bei grösseren Modellen ist es dann schon brutale Fleissarbeit. Vor allem wenn das Modell nach einer Testphase wieder ändert. Dann muss unter Umständen das gesamte QGIS-Projekt neu erstellt werden. Aus diesem Grund ist eine Arbeitsgruppe zur Zeit an der Entwicklung eines &lt;em&gt;QGIS-Projektgenerators&lt;/em&gt;, der dem Menschen bei dieser doch eher mühseligen Arbeiten helfen soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wir in QGIS wieder entdecken, sind die Kommentare zu den einzelnen Attributen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p3/dm_nutzungsvereinbarung_description_qgis.png&quot; alt=&quot;Datenmodell Nutzungsvereinbarung Kommentare in QGIS&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will man die Daten in dieser Form bereitstellen (oder eben dann das Publikationsmodell), lässt sich die erstellte INTERLIS-Transferdatei einfach mit dem &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://interlis2.ch/ilivalidator/&quot;&gt;prüfen&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für eine &amp;laquo;model-driven GDI&amp;raquo; steht heute ein sich stetig weiterentwickelndes Ökosystem zur Verfügung. Gedankliche und konzeptionelle Ansätze sind auch vorhanden. Sicher müssen Details noch geklärt werden. Meines Erachtens ist der vermeintliche Mehraufwand mehr als gerechtfertig. Die Qualität und Zuverlässigkeit des gesamten Systems wird besser. Ebenso bekommen die Daten, als teuerstes und wertvollstes Gut in der GDI, einen grösseren Stellenwert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt: Löst das Datenmodell (resp. das Transferformat dazu) jetzt das Shapefile ab? Nein. Wenn überhaupt löst GeoPackage das Shapefile ab. Für mich sind INTERLIS-Transferformate und Shapefiles et al. immer noch zwei paar Schuhe&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>REST-Service mit Spring Boot</title>
      <link>http://blog.sogeo.services/blog/2017/01/01/rest-service-mit-spring-boot.html</link>
      <pubDate>Sun, 1 Jan 2017 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2017/01/01/rest-service-mit-spring-boot.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Frameworks, Bibliotheken und dergleichen für REST-Service gibt es wie Sand am Meer. Für &lt;a href=&quot;http://blog.sogeo.services/blog/2016/12/24/kgdi-the-next-generation-1.html&quot;&gt;SO!GIS 2.0&lt;/a&gt; werden wir sicher etwas Anständiges erhalten. Programmiersprachlich wollen wir uns in der möglichen Auswahl aber auf &lt;em&gt;Java&lt;/em&gt; und &lt;em&gt;Python&lt;/em&gt; beschränken. Als Freund von &lt;em&gt;Java&lt;/em&gt; und weil ich die Webdienste für &lt;a href=&quot;https://sogeo.services/ili2gpkg/&quot;&gt;ili2gpkg&lt;/a&gt;, &lt;a href=&quot;https://sogeo.services/freeframe/&quot;&gt;Freeframe&lt;/a&gt; und &lt;a href=&quot;https://interlis2.ch/ilivalidator/&quot;&gt;ilivalidator&lt;/a&gt; mit &lt;a href=&quot;https://projects.spring.io/spring-boot/&quot;&gt;&lt;em&gt;Spring Boot&lt;/em&gt;&lt;/a&gt; gemacht habe, wollte ich ausprobieren, wie weit und wie gut man im Spring-Universum einen REST-Service zusammenbasteln kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das soll kein Technologie-Vorentscheid sein, sondern mir das Gefühl geben, was geht, wo kann es klemmen, was sind mögliche Stolperfallen etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Etwas was mir an &lt;em&gt;Spring Boot&lt;/em&gt; gefällt, ist wie schnell man wie sehr wenig Code sehr weit kommt. Praktikabel finde ich ebenfalls, dass man die ganze Anwendung kapseln kann. Ob das genau einem &lt;a href=&quot;https://blog.codecentric.de/en/2015/01/self-contained-systems-roca-complete-example-using-spring-boot-thymeleaf-bootstrap/&quot;&gt;&amp;laquo;Self-Contained-System&amp;raquo;&lt;/a&gt; entspricht, weiss ich zwar nicht, jedoch passt es für mich. Anstelle einer WAR-Datei, die man in einem Servlet-Container deployen muss, kann die &lt;em&gt;Spring Boot&lt;/em&gt;-Anwendung mit einem &lt;em&gt;embedded&lt;/em&gt; Container ausgeliefert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ebenfalls interessant ist die Tatsache, dass man ein halbwegs &lt;a href=&quot;http://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html&quot;&gt;&amp;laquo;production ready deployment&amp;raquo;&lt;/a&gt; ziemlich einfach hinbekommt. Zumindest auf Linux-Maschinen. Viele Fragestellungen (Logging, Service) kann man elegant konfigurieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Umsetzung eines REST-Services mit &lt;em&gt;Spring Boot&lt;/em&gt; braucht es je nach Anforderung und Wünschen ein paar weitere &lt;a href=&quot;https://git.sogeo.services/stefan/agi-rest-service/src/master/src/agi-rest-service/pom.xml&quot;&gt;Abhängigkeiten&lt;/a&gt;. Entweder von Spring selbst oder dann halt Klassiker wir z.B. JDBC-Treiber. Erwähnenswert sind &lt;a href=&quot;http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#spatial&quot;&gt;&lt;em&gt;Hibernate Spatial&lt;/em&gt;&lt;/a&gt; und für das Serialisieren resp. Deserialisieren von Geometrien eine weitere &lt;a href=&quot;https://github.com/bedatadriven/jackson-datatype-jts&quot;&gt;Bibliothek&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Beispieldatensatz verwende ich die Bienenstandorte des Kantons. Die Datenbanktabelle habe ich in eine Shapedatei gedumped und das Ganze anschliessend in ein kleines INTERLIS-Modell gequetscht. Ganz simpel, nur eine Klasse:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/rest-service-mit-spring-boot/dm_bienenstandorte.png&quot; alt=&quot;Datenmodell Bienenstandorte&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den Demodatensatz ist das unnötig, aber wenn man das Ganze - auch nur aufgrund von Testzwecken - auf verschiedenen Systemen ein paar Mal aufsetzen will/muss, lernt man es zu schätzen, wenn man &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;plattformunabhängige Werkzeuge&lt;/a&gt; hat, die den Datenimport/-export immer gleich machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar /Users/stefan/Apps/ili2pg-3.5.1/ili2pg.jar --dbhost 192.168.50.4 --dbdatabase xanadu2 --dbusr stefan --dbpwd ziegler12 --dbschema alw_bienenstandorte --disableValidation --nameByTopic --sqlEnableNull --createGeomIdx --createFkIdx --strokeArcs --models SO_Bienenstandorte_20161227 --modeldir &quot;http://models.geo.admin.ch/;.&quot; --defaultSrsCode 2056 --import alw_bienenstandorte_20161225.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und schon sind die Bienenstandorte (893 Standorte) in der Datenbank:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/rest-service-mit-spring-boot/postico_01.png&quot; alt=&quot;Bienenstandorte in DB&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alles was es jetzt für einen funktionierende REST-Service braucht sind mindestens eine Klasse und ein Interface (natürlich neben den üblichen &lt;em&gt;Spring Boot&lt;/em&gt;-Klassen etc.). Als erstes braucht es eine &amp;laquo;resource representation class&amp;raquo;. Nomen est omen: diese Klasse repräsentiert die REST-Ressource. In unserem Fall müssen wir noch ein paar Annotations anbringen, damit &lt;em&gt;Spring Boot&lt;/em&gt; weiss, dass es sich um eine Datenbank-Tabelle handelt und wie das Schema und die Tabelle heisst. Schlussendlich ist es aber nicht Anderes als ein POJO. Das Beispiel gibt es &lt;a href=&quot;https://git.sogeo.services/stefan/agi-rest-service/src/master/src/agi-rest-service/src/main/java/org/catais/rest/domain/ch/so/alw/Bienenstandort.java&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun müssen wir ein Repository erstellen indem wir ein Interface &lt;a href=&quot;https://git.sogeo.services/stefan/agi-rest-service/src/master/src/agi-rest-service/src/main/java/org/catais/rest/repository/ch/so/alw/BienenstandortRepository.java&quot;&gt;erweitern&lt;/a&gt;. Interessanterweise müssen wir das Interface selber nicht implementieren. Auch die &lt;a href=&quot;https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods&quot;&gt;out-of-the-box Queries&lt;/a&gt; müssen wir nicht implementieren. Hier kommt der Spring-Feenstaub zum Zug. Diese &amp;laquo;findByXXX&amp;raquo;-Abfragen gibt es in Verbindung mit einer Datenbank gratis. Im Interface können wir auch noch den Namen wählen, wie die Ressource angesprochen werden soll. In unserem Fall: &lt;code&gt;ch.so.alw.bienenstandort&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der Klasse und dem Inteface bekommt man bereits einen funktionierenden REST-Service. Als Zugabe ist dieser REST-Service bereits &amp;laquo;Hypermedia-Driven&amp;raquo; mit dem &lt;a href=&quot;https://en.wikipedia.org/wiki/Hypertext_Application_Language&quot;&gt;HAL-Ausgabeformat&lt;/a&gt;. So in der Gänze verstanden, habe ich diese HATEOAS-Sache jedenfalls noch nicht. Erster Eindruck: da wird dem REST-Service noch bisschen &amp;laquo;Intelligenz&amp;raquo; eingehaucht. Und ebenfalls sind die Resultate &lt;em&gt;gepaged&lt;/em&gt; (resp. wurde so definiert im Repository), dh. es werden nicht alle 893 Bienenstandorte auf einmal zurückgeliefert, sondern nur immer 20 Stück und der Client muss die weiteren &lt;em&gt;Pages&lt;/em&gt; anfordern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;_embedded&quot; : {
    &quot;/ch.so.alw.bienenstandort&quot; : [ {
      &quot;bstnr&quot; : &quot;102920&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207814,
      &quot;pid_2011&quot; : 9209,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2612608.4948999994, 1250397.3973999992 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/4&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/4&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;48016&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 604435,
      &quot;pid_2011&quot; : 131414,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2601152.9639, 1258610.9426999986 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/5&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/5&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;72184&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 601831,
      &quot;pid_2011&quot; : 128795,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2597261.348700002, 1228547.4364 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/6&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/6&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;102924&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207788,
      &quot;pid_2011&quot; : 9182,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2613244.4734000005, 1248688.3872000016 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/7&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/7&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;102806&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207826,
      &quot;pid_2011&quot; : 9222,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2608493.4569999985, 1247794.4199 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/8&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/8&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;82568&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 205965,
      &quot;pid_2011&quot; : 7008,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2631996.5089999996, 1240138.5654999986 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/9&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/9&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;72272&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 473947,
      &quot;pid_2011&quot; : 103687,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2597749.344300002, 1227845.4373999983 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/10&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/10&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;41612&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 201334,
      &quot;pid_2011&quot; : 1346,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2616852.5084999986, 1258628.4717999995 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/11&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/11&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;62016&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 605250,
      &quot;pid_2011&quot; : 132231,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2609550.3222999983, 1225258.332800001 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/12&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/12&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;62140&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207633,
      &quot;pid_2011&quot; : 9020,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;1&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2609506.3486, 1229524.3418000005 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/13&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/13&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;102732&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 203788,
      &quot;pid_2011&quot; : 3813,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2608305.4657000005, 1250274.4360999987 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/14&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/14&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;62004&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 602420,
      &quot;pid_2011&quot; : 129390,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2604411.054299999, 1223172.8467000015 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/15&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/15&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;31484&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 602532,
      &quot;pid_2011&quot; : 129502,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2604137.3066000007, 1225322.382199999 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/16&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/16&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;41668&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207610,
      &quot;pid_2011&quot; : 8995,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2601171.401799999, 1256667.4970000014 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/17&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/17&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;58006&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 605293,
      &quot;pid_2011&quot; : 132274,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2639746.5154, 1254536.799800001 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/18&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/18&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;72284&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 605172,
      &quot;pid_2011&quot; : 132151,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2610135.3605000004, 1234627.3381999992 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/19&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/19&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;31520&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 201191,
      &quot;pid_2011&quot; : 1202,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2603626.3046000004, 1221320.3414999992 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/20&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/20&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;18006&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 605255,
      &quot;pid_2011&quot; : 132236,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2628381.2760000005, 1235816.4164999984 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/21&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/21&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;102936&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 604431,
      &quot;pid_2011&quot; : 131410,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2605077.9497000016, 1248073.3953999989 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/22&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/22&quot;
        }
      }
    }, {
      &quot;bstnr&quot; : &quot;41736&quot;,
      &quot;jahr&quot; : 2017,
      &quot;pid_2015&quot; : 207669,
      &quot;pid_2011&quot; : 9058,
      &quot;status_bst&quot; : &quot;Aktiv&quot;,
      &quot;saisonal&quot; : &quot;0&quot;,
      &quot;status_btr&quot; : &quot;Aktiv&quot;,
      &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,
      &quot;standort&quot; : {
        &quot;type&quot; : &quot;Point&quot;,
        &quot;coordinates&quot; : [ 2616021.4771, 1252594.4061999992 ]
      },
      &quot;_links&quot; : {
        &quot;self&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/23&quot;
        },
        &quot;bienenstandort&quot; : {
          &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/23&quot;
        }
      }
    } ]
  },
  &quot;_links&quot; : {
    &quot;first&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort?page=0&amp;amp;size=20&quot;
    },
    &quot;self&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort&quot;
    },
    &quot;next&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort?page=1&amp;amp;size=20&quot;
    },
    &quot;last&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort?page=44&amp;amp;size=20&quot;
    },
    &quot;profile&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/profile/ch.so.alw.bienenstandort&quot;
    },
    &quot;search&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/search&quot;
    }
  },
  &quot;page&quot; : {
    &quot;size&quot; : 20,
    &quot;totalElements&quot; : 893,
    &quot;totalPages&quot; : 45,
    &quot;number&quot; : 0
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese &amp;laquo;Intelligenz&amp;raquo; macht sich vor allem in der Verlinkung bemerkbar. So werden z.B. die Links auf die nächste und vorherige &lt;em&gt;Page&lt;/em&gt; angezeigt oder ein Link auf die vorhandenen Suchanfragen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;_links&quot; : {
    &quot;findByBstnr&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/search/findByBstnr{?bstnr,page,size,sort}&quot;,
      &quot;templated&quot; : true
    },
    &quot;self&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/search&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nachdem man sich die Zusatzfunktionalitäten ein wenig zu Gemüte geführt hat, kann man mit den bekannten vier Verben Daten abfragen, erfassen, ändern und löschen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten abfragen / GET:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;curl -X GET http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/search/findByBstnr?bstnr=102920&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten erfassen / POST:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;curl -H &quot;Content-Type: application/json&quot; -X POST -d &apos;{&quot;bstnr&quot; : &quot;1&quot;, &quot;jahr&quot; : 2017, &quot;pid_2015&quot; : 207669, &quot;pid_2011&quot; : 9058, &quot;status_bst&quot; : &quot;Aktiv&quot;, &quot;saisonal&quot; : &quot;0&quot;, &quot;status_btr&quot; : &quot;Aktiv&quot;, &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;, &quot;standort&quot; : {&quot;type&quot; : &quot;Point&quot;, &quot;coordinates&quot; : [ 2600000.123, 1200000.456 ]}}&apos; http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten löschen / PUT:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;curl -H &quot;Content-Type: application/json&quot; -X PUT -d &apos;{&quot;bstnr&quot; : &quot;1&quot;, &quot;jahr&quot; : 2525, &quot;pid_2015&quot; : 207669, &quot;pid_2011&quot; : 9058, &quot;status_bst&quot; : &quot;Aktiv&quot;, &quot;saisonal&quot; : &quot;0&quot;, &quot;status_btr&quot; : &quot;Aktiv&quot;, &quot;kommentar&quot; : &quot;Export Gelan-2015;(19.12.2016; 22:31)&quot;,&quot;standort&quot; : {&quot;type&quot; : &quot;Point&quot;, &quot;coordinates&quot; : [ 2600000.123, 1200000.456 ]}}&apos; http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Daten löschen / DELETE:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;curl -X DELETE http://localhost:8884/v1/rest/layer/ch.so.alw.bienenstandort/4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Etwas was mir momentan noch nicht klar ist, ist der Umgang mit verschiedenen Koordinatensystemen. Verwendet man als Codierung für die Geometrien GeoJSON gibt keine Möglichkeit mehr das Koordinatensystem &lt;a href=&quot;https://tools.ietf.org/html/rfc7946&quot;&gt;anzugeben&lt;/a&gt;. In der ersten Spezifikation gab es die Möglichkeit, nur hat man das in freier Wildbahn auch nie wirklich gesehen. Wahrscheinlich läuft es darauf hinaus, dass der REST-Service pro Ressource konsquenterweise nur ein Koordinatensystem unterstützen kann. Lieber in sauber nur ein Koordinatensystem als mit viel Geknorze was reinbasteln&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine andere Frage ist eher &lt;em&gt;Spring&lt;/em&gt;-bezogen. Nämlich der Umgang mit räumlichen Abfragen. Grundsätzlich ist dank &lt;em&gt;Hibernate Spatial&lt;/em&gt; und der Jackson-Erweiterung für das De-/Serialisieren der Geometrien der Umgang mit Geodaten soweit schmerzlos. Ganz so out-of-the-box scheinen die räumlichen Abfragen nicht zu gehen. Für einen anderen Testdatensatz (die Nachführungskreise der amtlichen Vermessung) habe ich &lt;a href=&quot;https://git.sogeo.services/stefan/agi-rest-service/src/master/src/agi-rest-service/src/main/java/org/catais/rest/repository/ch/so/agi/AV_NachfuehrungskreisRepository.java&quot;&gt;weitere Queries im Repository-Interface&lt;/a&gt; definiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;_links&quot; : {
    &quot;findByKreisname&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/nf_kreis/search/findByKreisname{?kreisname,page,size,sort}&quot;,
      &quot;templated&quot; : true
    },
    &quot;findByPerimeter&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/nf_kreis/search/findByPerimeter{?x,y}&quot;,
      &quot;templated&quot; : true
    },
    &quot;findByKreisnameLike&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/nf_kreis/search/findByKreisnameLike{?kreisname,page,size,sort}&quot;,
      &quot;templated&quot; : true
    },
    &quot;self&quot; : {
      &quot;href&quot; : &quot;http://localhost:8884/v1/rest/layer/nf_kreis/search&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben der schicken &amp;laquo;findByKreisnameLike&amp;raquo;-Query, mit der man genau wie bei Datenbanken &amp;laquo;Like&amp;raquo;-Abfragen durchführen kann, habe ich natürlich auch etwas Räumliches definiert. Hier im konkreten Fall etwas Ähnliches wie eine WMS-GetFeatureInfo-Abfrage. Um das mit möglichst wenig Aufwand hinzukriegen, habe ich eine native PostgreSQL/PostGIS-Query im Repository definiert, was jetzt natürlich sehr, sehr, seeeeehr unschön ist. Eine bessere und nachhaltigere Alternative ist das Schreiben von &amp;laquo;Custom Queries&amp;raquo; in dem man ein eigenes Repository-Interface implementiert (was man anscheinend sowieso für read-only Repositories machen muss). Da bin ich mir aber nicht mehr sicher, ob diese Queries noch im &lt;em&gt;search&lt;/em&gt;-Link auftauchen, was wiederum sehr schade wäre.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Fazit:&lt;/em&gt; Das Spring-Universum liefert schon sehr viel, um effizient REST-Service anbieten zu können. Hier gilt auch ausnahmsweise einmal &amp;laquo;Spatial is (ganz klein wenig) special&amp;raquo;, da man sicher noch für räumliche Abfragen klären muss, wie gut sich das umsetzen liesse. Die interessantere Frage ist aber unabhängig vom eingesetzten Produkt und betrifft den Betrieb/Konfiguration des REST-Services: Welche Tabellen / Views als REST-Service mit welchen Alias-Namen der Attribute angeboten werden, steht in Zukunft in unserem &lt;a href=&quot;http://blog.sogeo.services/blog/2016/12/24/kgdi-the-next-generation-1.html&quot;&gt;Metamodell&lt;/a&gt;. Was man wahrscheinlich nicht möchte, ist das manuelle Erstellen der benötigten Klassen, Interfaces, Rechte etc. im REST-Service. Entweder könnte sich der REST-Service direkt aus dem Metamodell bedienen. Da ist mir jedoch gar nichts bekannt. Oder aber es wird etwas Ähnliches wie einen Code-Generator für diese Objekte geben. Da müssen die Profis ran. TBD.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #2</title>
      <link>http://blog.sogeo.services/blog/2016/12/29/kgdi-the-next-generation-2.html</link>
      <pubDate>Thu, 29 Dec 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/12/29/kgdi-the-next-generation-2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Thema, das uns im Kontext von &lt;a href=&quot;http://blog.sogeo.services/blog/2016/12/24/kgdi-the-next-generation-1.html&quot;&gt;SO!GIS 2.0&lt;/a&gt; beschäftigt, sind Datenflüsse. Datenflüsse jeglicher Art. Also sowohl von aussen nach innen, von innen nach aussen und wie auch innerhalb unserer GDI selbst. Irgendwie müssen die Daten ja in unsere GDI kommen und oftmals müssen sich auch noch umgebaut werden. Darum sind es eigentlich zwei Aspekte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Datenintegration: Nennen wir es einfach mal so, obwohl damit nicht nur die Integration von Daten in unsere GDI gemeint ist, sondern eben auch Daten aus unserer GDI exportieren.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Datenumbau: Wenn Daten hin- und hergeschoben werden, müssen sie garantiert noch in irgendeiner Form - sei es auch nur minimal - umgebaut werden.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obschon es ja zwei paar Schuhe sind, gehören die beiden Prozesse irgendwie zusammen. Denn auch beim Datenumbau fliessen die Daten von A nach B.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum beschäftigt dieses Thema uns jetzt konkret? Dazu muss man ein wenig ausholen: Ein absolutes Fundament unserer KGDI ist die &lt;strong&gt;zentrale Datenhaltung&lt;/strong&gt;. Das bedeutet, dass die kantonalen Geodaten in unserem Amt (resp. in unserer Infrastruktur) liegen. Das Amt A hat - falls gewünscht - einfachen Zugriff auf Daten des Amtes B. Unser Amt koordiniert alles und kümmert sich auch um die Datenbereitstellung gegenüber internen und externen Kunden. Seien es blosse WebGIS-Kärtchen oder die Abgabe der Rohdaten. Die Ämter müssen/dürfen keine eigene GIS-Infrastruktur aufbauen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vielleicht wurde dieser Grundsatz der zentralen Datenhaltung über die Jahre etwas überinterpretiert: Zentrale Datenhaltung hiess dann auch, dass jede GIS-Applikation direkt mit unserer &lt;strong&gt;einzigen&lt;/strong&gt; Datenbank arbeiten muss. Und über die Jahre hinweg haben sich in den Ämtern etliche Applikationen angesammelt, die zwar einen kleinen Raumbezug haben und dementsprechend geografische Daten speichern müssen aber nicht von uns programmiert wurden. Oftmals wissen wir nicht genau, was diese Fremdapplikationen genau machen, geschweige denn wie sie funktionieren. Eine Art Blackbox also.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schematisch sieht die Architektur somit so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p2/sogis_alt.png&quot; alt=&quot;Ist-Architektur&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die blauen Anwendungen sind unsere Kernkomponenten (Desktop- und WebGIS) und selber programmierte Tools. Diese Anwendungen haben wir im Griff und wir kennen die Anforderungen und Problemchen relativ gut. Die grünen Anwendungen sind die Fremdapplikationen, die ebenfalls &lt;em&gt;direkten&lt;/em&gt; Lese- und Schreibzugriff auf unsere einzige Datenbank haben. Der rote Kasten symbolisiert die exportierten Daten aus der Datenbank.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese &lt;strong&gt;zentrale Datenbank&lt;/strong&gt; führte dann beispielhaft zu folgenden Problemfällen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(1) Die Fremd-Webapplikation läuft nicht auf unseren Servern, sondern auf den Servern des Informatikamtes (AIO). Die Applikation greift aber auf unsere Datenbank zu (lesend und schreibend). Weil wir relativ (seeeeehr) lange mit dem Datenbankupdate zugewartet haben, konnte auch der Hersteller der Fremdapplikation seine Software nie updaten und die Userexperience war nicht sonderlich gut, da kein Bugfixing-Release etc. eingespielt werden konnte. Schlussendlich war niemand mit der Situation zufrieden (Hersteller, AIO, Anwender und wir).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(2) Die Fremd-Webapplikation macht bei jeder Abfrage in unserer Datenbank dutzende (temporäre) Tabellen und löscht sie wieder. Wegen den vielen Vacuum-Prozessen in unserer Datenbank füllte es die Festplatte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(3) Ein Hersteller einer Fremdapplikation wurde verknurrt seine Applikation von &lt;em&gt;Oracle&lt;/em&gt; nach &lt;em&gt;PostgreSQL&lt;/em&gt; umzuschreiben, damit die Daten bei uns gespeichert werden können. Leider hat der Hersteller mit &lt;em&gt;PostgreSQL&lt;/em&gt; nicht viel Erfahrung und so schlichen sich unnötig viele Bugs in die Software. Ein grausiger Bug war das Verbrauchen von DB-Connections ohne sie wieder freizugeben. Das führte dazu, dass ein sämtliche QGIS-Benutzer keine Tabellen in &lt;em&gt;QGIS&lt;/em&gt; mehr laden konnten, weil eben alle Verbindungen aufgebraucht waren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für mich absolut entscheidend ist die Tatsache, dass Fremdapplikationen &lt;strong&gt;keinen negativen&lt;/strong&gt; Impact auf unsere Datenbank im engeren Sinne und auf unsere Infrastruktur im weiteren Sinne haben dürfen. Konsequenterweise bedeutet das, dass viele oder alle dieser Fremdapplikationen ihre eigene Datenhaltung mitbringen müssen. Was bei den meisten bereits der Standardfall ist. Viele der Daten dieser Anwendungen sollen trotzdem noch in unsere GDI integriert werden (Interesse anderer Ämter, Webkarten, Publikation als WMS etc.). Jedoch bestimmen &lt;strong&gt;wir&lt;/strong&gt; den Zeitpunkt und das Bereitstellen von Ressourcen (CPU, DB-Connections etc.) für die Integration und verlassen uns nicht auf das Prinzip Hoffnung, das schon nichts Schlimmes passiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was wir also brauchen, ist eine rock-solide und generische Lösung für die Integration von Daten, die in einem Fremdsystem liegen. Bereits heute importieren wir viele Daten aus verschiedenen Systemen. Jedoch wird dazu immer und immer wieder ein neues Skript geschrieben. Dieses quick &apos;n&apos; dirty Skript verleitet leider auch häufig dazu die Trennung von Integration und Datenumbau nicht einzuhalten, was dann wiederum den Betrieb und Unterhalt dieses Importprozesses erschwert. Vielmehr schwebt uns etwas vor, dass man konfigurien kann und nicht neu programmieren muss. Super viele Formate müssen nicht unterstützt werden: am Ehesten wohl CSV, Oracle-DB, PostGIS-DB, INTERLIS, Shapefiles, GeoPackage. Zudem sollte der Integrationsprozess sowohl manuell wie auch mittels Scheduler ausführbar sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Konzeptionell haben sich da z.B. die Spring-Leute mit &lt;a href=&quot;http://docs.spring.io/spring-batch/reference/html/spring-batch-intro.html&quot;&gt;Spring&lt;/a&gt; &lt;a href=&quot;http://docs.spring.io/spring-batch/reference/html/domain.html&quot;&gt;Batch&lt;/a&gt; schon mal ein paar gute Gedanken gemacht (resp. &lt;a href=&quot;https://www.jcp.org/en/jsr/detail?id=352&quot;&gt;JSR-352&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zusätzlich zu diesem &amp;laquo;Datenintegrator&amp;raquo; braucht es noch eine lightweight REST-Schnittstelle. Damit können Anwendungen schnell und schmerzlos an Daten kommen, die in unserer Datenbank liegen und falls die Berechtigung vorhanden ist auch verändern und zurückschreiben. Die REST-Schnittstelle ist nicht dazu gedacht Terabyte an Daten zu lesen und zu schreiben, sondern vielmehr für einzelne, wenige Erfassungen von z.B. Neophyten, Unfällen und dergleichen. Oftmals tuen sich ja Nicht-GIS-Softwarehersteller relativ schwer mit einer &amp;laquo;GIS-Schnittstelle&amp;raquo;. Damit man solche Fragestellungen nicht mit dem GIS-Schlachtrosse &lt;em&gt;WFS&lt;/em&gt; beantworten muss, ist neu die REST-Schnittstelle da. JSON versteht ja wirklich jeder Webentwickler. Und auch mit einem GET / POST / PUT und DELETE kann man umgehen. Wie im &lt;a href=&quot;http://blog.sogeo.services/blog/2016/12/24/kgdi-the-next-generation-1.html&quot;&gt;Metamodell&lt;/a&gt; vorgesehen, wird man bei jedem registrierten Datensatz wählen können, ob dieser als REST-Schnittstelle exponiert werden soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In Zukunft möchten wir auch unsere eigene Datenbank funktional trennen: Es wird eine Erfassungs-Datenbank, eine Publikations-Datenbank und eine Archiv-Datenbank geben. Heute passiert das alles in einer Datenbank in der gleichen Tabelle. Das ist zu starr, zu unflexibel und zu fehleranfällig. Zudem passt die angedachte Architektur besser zum Ansatz der &lt;strong&gt;model driven GDI&lt;/strong&gt; (dazu  in einem späteren Beitrag mehr). Für das Überführen der Daten von der Erfassungsdatenbank in die Publikationsdatenbank ist ein Datenumbau notwendig. Dieser Datenumbau geht einher mit einer Datenintegration. Nur halt innerhalb unserer eigenen Infrastruktur. Aufgrund der Tatsache, dass entweder das Ziel- oder Quellsystem in unserem Fall eine relationale Datenbank ist, kann man sich überlegen, ob der Datenumbau nicht auf Funktionen eben dieser Systeme zu beschränken ist resp. in diesen Systemen passieren soll. Damit ist auch die Lingua Franca für den Datenumbau klar: SQL. Für die Umsetzung kann man sich verschiedene Varianten vorstellen: Views, selber geschriebene DB-Funktionen oder pures SQL, das abgesetzt wird. Zum jetzigen Zeitpunkt ist mir die &amp;laquo;pure-SQL&amp;raquo;-Lösung am Liebsten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Idee eines solchen Werkzeuges scheint jedenfalls nicht super originär zu sein: &lt;a href=&quot;https://github.com/geops/batyr&quot;&gt;batyr&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In Zukunft dürfte es bei uns also circa so aussehen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p2/sogis_neu.png&quot; alt=&quot;Soll-Architektur&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das wahrscheinlich Wichtigste bei diesem Umkrempeln ist aber, dass wir die Softwarehersteller gut informieren, gut dokumentieren und eine gewisse Strenge an den Tag legen. Es bringt nichts, wenn wir bei jeder neuen Herausforderung gleich wieder in alte Verhaltensmuster zurückfallen und das Rad neu erfinden wollen, nur damit es kurzfristig schneller funktioniert.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>KGDI - The next generation #1</title>
      <link>http://blog.sogeo.services/blog/2016/12/24/kgdi-the-next-generation-1.html</link>
      <pubDate>Sat, 24 Dec 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/12/24/kgdi-the-next-generation-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-geoinformation/geoportal/&quot;&gt;Unsere&lt;/a&gt; Geodateninfrastruktur ist circa 16-jährig. So weit, so gut. Das heisst aber auch, dass viele der Komponenten und Prozesse ebenfalls etliche Jahre auf dem Buckel haben. Zu viele Jahre. Die Bedürfnisse und Anforderungen an eine GDI haben sich in diesen 16 Jahren stark verändert. Ein WebGIS-Client sieht heute anders aus und hat andere Funktionen und die Leute sind es sich gewohnt mit solchen exotischen Tools umzugehen. Auch die GIS-Kenntnisse in den Dienststellen ausserhalb einer GIS-Fachstelle sind gewachsen. Und zu guter Letzt darf man langsam zur Einsicht kommen, dass viele Herausforderungen in einer GDI nichts mit &amp;laquo;GIS&amp;raquo; zu tun haben, sondern pure IT ist und dementsprechend nicht durch den GIS-Spezialisten/-Informatiker/-Projektleiter (whatever&amp;#8230;&amp;#8203;) gelöst werden müssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn im Kontext der GDI der &lt;a href=&quot;https://www.so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-geoinformation/lv95/&quot;&gt;Bezugsrahmenwechsel&lt;/a&gt; etwas gebracht hat, dann die Einsicht, dass sich in diesen 16 Jahren sehr viel angesammelt. Bei dieser Ansammlung von selbst geschriebener Software, Schnittstellen, Daten etc. fehlt uns oft die Übersicht. Wer braucht noch was? Warum liegt diese Datei hier? Bei vielen eingesetzten Tools, Skripts und dergleichen darf oder muss man getrost sagen: &amp;laquo;End of Life&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Während dieser GIS-Pionierzeit endeten viele Entwicklungen (gefühlt einmal mit jeder Programmiersprache, die es gibt) als Individualkomponente. Das Rad wurde immer und immer wieder neu erfunden. Das macht als Entwickler natürlich Spass, ist aber für den Betrieb so ziemlich unangenehm. Oder wenn für den Bezugsrahmenwechsel jede dieser Individualkomponente geprüft und allenfalls angepasst werden muss. Zudem müssen mit diesem Approach immer mehr Ressourcen in die Aufrechterhaltung des Betriebes gesteckt werden. Interessanterweise wurde auch nie irgendetwas abgekündigt. Es läuft und läuft und läuft. Und es kam/kommt immer mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit ein paar kleinen kosmetischen Anpassungen kommt man da nicht mehr raus. Das &amp;laquo;Übel&amp;raquo; muss an der Wurzel gepackt werden. Aus diesem Grund haben wir das Projekt &lt;em&gt;SO!GIS 2.0&lt;/em&gt; gestartet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p1/sogis_next_generation.png&quot; alt=&quot;SO!GIS The Next Generation&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es hat unter anderem zum Ziel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Reduktion der Anzahl und der Heterogenität der GDI-Komponenten&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Erneuerung der veralteten GDI-Komponenten&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Schaffung der Prozesse zur nachhaltigen Weiterentwicklung der GDI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Definition der konzeptionellen Systemarchitektur für die anschliessenden Umsetzungsprojekte&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige Fragestellungen werden bereits kräftig bearbeitet oder aber sind zumindest gedanklich ziemlich gediehen. Ein paar Kernideen werden in loser Folge hier beschrieben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein grosses Thema, das uns absolut beschäftigt, kann man in drei Wörtern zusammenfassen: &lt;strong&gt;&amp;laquo;Know your GDI&amp;raquo;&lt;/strong&gt;. Uns ist zwar seit geraumer Zeit bekannt, dass wir die GDI eben nicht genau kennen, aber das Maximum der Unkenntnis wurde - wie erwähnt - beim Bezugsrahmenwechsel erreicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Da liegen auf einem Server ein paar Dutzend MapServer-Mapfiles herum aber niemand weiss, ob es die noch braucht und falls ja, für was genau. Oder der gern gesehene Klassiker: &amp;laquo;Wie schreibt jetzt die Fremdapplikation in unserer Datenbank?&amp;raquo;. Bis heute haben wir das nicht herausgefunden. Fairerweise muss man dazu sagen, dass der Softwarehersteller der Fremdapplikation auch nicht mehr weiss, wie seine Software in unsere Datenbank schreibt&amp;#8230;&amp;#8203; WMS-/WFS-Dienste gibt es en masse. Weil wir aber nicht wissen, wer die intern noch braucht, können wir die massig vorhandenen Redundanzen nicht abbauen. Das zieht dann Fäden: Tabellen und Views, die in den Mapfiles referenziert werden, können auch nicht bearbeitet oder gelöscht werden, weil irgend eine asbach-uralt Software diesen bestimmten WMS-Layer verwendet. Individualkomponente lässt grüssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Rahmen des Ablösungsprojektes unseres WebGIS-Clients (inkl. Backend) haben wir ein Metamodell entwickelt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/kgdi_the_next_generation_p1/metamodell.jpg&quot; alt=&quot;Metamodell&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus dem Teilmodell &lt;em&gt;GDI-Knoten&lt;/em&gt; wird es eine Implementierung in Form einer Webanwendung geben. In dieser Webanwendung wird man Tabellen, Views, Dateien, Dienste etc. erfassen müssen. Einerseits damit sie überhaupt z.B im WMS-Dienst erscheinen und andererseits damit wir wissen wie/was/wo verwendet wird. Kurz zusammengefasst: Was im Metamodell nicht registriert wird, existiert nicht in unserer GDI (und kann gelöscht werden). Detailliertere Informationen gibt es hoffentlich an der nächsten &lt;a href=&quot;https://fossgis-konferenz.de/2017&quot;&gt;FOSSGIS-Konferenz&lt;/a&gt;. Oliver Jeker (als eigentlicher Vater des Metamodelles) hat einen Abstract eingereicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Metamodell ist also ein erster, unverzichtbarer Schritt, um die GDI zu kennen und zu beherrschen.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #14</title>
      <link>http://blog.sogeo.services/blog/2016/11/09/interlis-leicht-gemacht-number-14.html</link>
      <pubDate>Wed, 9 Nov 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/11/09/interlis-leicht-gemacht-number-14.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um mit &lt;a href=&quot;https://en.wikipedia.org/wiki/JavaServer_Faces&quot;&gt;JavaServer Faces&lt;/a&gt; &amp;laquo;rumzupröbeln&amp;raquo;, habe ich vor geraumer Zeit kurzerhand einen kleinen &lt;a href=&quot;https://sogeo.services/ilivalidator/upload.xhtml&quot;&gt;Checkerservice&lt;/a&gt; mit den &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt;-Bibliotheken &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-jsf&quot;&gt;gemacht&lt;/a&gt;. Weil ich auch noch etwas Einfacheres haben wollte, habe ich mit &lt;a href=&quot;https://projects.spring.io/spring-boot/&quot;&gt;Spring Boot&lt;/a&gt; einen &amp;laquo;no-frills&amp;raquo; &lt;a href=&quot;https://interlis2.ch/ilivalidator/&quot;&gt;Webdienst&lt;/a&gt; auf die Beine gestellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/interlis2-checkservice.png&quot; alt=&quot;interlis2.ch ilivalidator&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Funktionalität ist eingeschränkt: Das Ein- und Ausschalten von Checks wird nicht unterstützt. Wäre aber z.B. machbar, wenn man ZIP-Dateien mit einer  TOML-Datei hochladen könnte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Fokus stand bei dieser Umsetzung weniger das GUI, sondern vielmehr die Webwendung &lt;em&gt;ohne&lt;/em&gt; GUI. Mit &lt;em&gt;curl&lt;/em&gt; und Konsorten geht das Prüfen von INTERLIS-Dateien so jetzt wunderbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/curl_01.png&quot; alt=&quot;ilivalidator mit curl #1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Syntax ist simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-v&lt;/code&gt;: &lt;em&gt;curl&lt;/em&gt; erzeugt möglichst viel Output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-XPOST&lt;/code&gt;: Es wird ein POST-Request durchgeführt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-F file=@ch_254900_error.itf&lt;/code&gt;: Mit diesem Parameter werden die Daten auf den Server hochgeladen. Dabei entspricht &lt;code&gt;file&lt;/code&gt; dem Namen des HTML-Input-Elementes vom Typ &lt;em&gt;file&lt;/em&gt; der HTML-Webseite.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es folgt die URL des Webdienstes (Achtung: Slash am Ende der URL). Wie einfach, effizient und im Prinzip elegant so ein Dienst geschrieben werden kann, sieht man im &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-spring-boot/src/master/src/ilivalidator/src/main/java/ch/so/agi/interlis/controllers/MainController.java&quot;&gt;Quellcode&lt;/a&gt;. Es wird zweimal auf die gleiche URL gemappt. Der einzige Unterschied ist die Request-Methode. Einmal GET und einmal POST. Je nach Request-Methode erscheint entweder die HTML-Webseite oder es wird die Prüfung der hochgeladenen Datei durchgeführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In die zu prüfendene INTERLIS-Datei habe ich absichtlich einen Fehler eingepflanzt. Der Output der INTERLIS-Prüfung ist ziemlich unübersichtlich. Vor allem auch wegen des Outputs von &lt;em&gt;curl&lt;/em&gt; selbst:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/curl_02.png&quot; alt=&quot;ilivalidator mit curl #2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf der viertletzten Zeile ist der Fehler ersichtlich: Ein LFP3 ohne Nummer. Um das Ganze übersichtlicher zu machen, lassen wir beim nächsten Aufruf den &lt;code&gt;-v&lt;/code&gt;-Parameter und den Parameter &lt;code&gt;-XPOST&lt;/code&gt;. &lt;code&gt;-XPOST&lt;/code&gt; hat zwar keinen Einfluss auf den Output, ist aber überflüssig, da &lt;em&gt;curl&lt;/em&gt; standardmässig einen POST-Request ausführt. Ist schon übersichtlicher geworden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/curl_03.png&quot; alt=&quot;ilivalidator mit curl #3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetzt ist nur noch Output von &lt;em&gt;ilivalidator&lt;/em&gt; sichtbar. Was mich aber in den allermeisten Fällen überhaupt nicht interessiert, sind die Information zu den Repositories und Programmversionen etc. etc. Oder anders formuliert: Ich will nur die Fehler sehen. Für diesen Usecase reicht ein wenig &amp;laquo;Pipe-und-Grep-Zauber&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/curl_04.png&quot; alt=&quot;ilivalidator mit curl #4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Pipe-Operator &lt;code&gt;|&lt;/code&gt; leitet die Ausgabe des Befehls (hier &lt;em&gt;curl&lt;/em&gt;) direkt an einen anderen Befehl. In unserem Fall ist dieser andere Befehl &lt;code&gt;grep&lt;/code&gt;. Mit &lt;em&gt;grep&lt;/em&gt; lassen Dateien etc. nach Texten durchsuchen. Weil wir eben nur an Fehlern interessiert sind, suchen wir nach &amp;laquo;Error&amp;raquo;. Eine Unschönheit gibt es noch: diese Datei-Hochlade-Information. Warum die jetzt plötzlich erscheint, ist mir schleierhaft. Mit einem simplen zusätzlichen &lt;code&gt;-s&lt;/code&gt; beim &lt;em&gt;curl&lt;/em&gt;-Befehl verschwindet diese auch noch:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p14/curl_05.png&quot; alt=&quot;ilivalidator mit curl #6&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei macOS und Linux ist &lt;em&gt;curl&lt;/em&gt; natürlich mit dabei oder zumindest sofort installiert. Unter Windows ist &lt;em&gt;curl&lt;/em&gt; jedenfalls auch &lt;a href=&quot;https://curl.haxx.se/download.html&quot;&gt;verfügbar&lt;/a&gt;. Eventuell reicht unter Windows 10 schon die &lt;a href=&quot;https://msdn.microsoft.com/en-us/commandline/wsl/about&quot;&gt;Linux Bash Shell&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #13</title>
      <link>http://blog.sogeo.services/blog/2016/10/23/interlis-leicht-gemacht-number-13.html</link>
      <pubDate>Sun, 23 Oct 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/10/23/interlis-leicht-gemacht-number-13.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von den letzten Wochen sind mir zwei frustrierende INTERLIS-Erlebnisse in Erinnerung geblieben. Nicht wegen INTERLIS selbst, nicht wegen der investierten Zeit, sondern viel mehr wegen der mangelnden Datenhygienie resp. der mangelnde technischen Datenqualität. INTERLIS = gute Datenqualität. Stimmt so nicht. Und das ist dann doch erstaunlich nach über 20 Jahren INTERLIS&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man versucht mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; diese Daten in die Datenbank zu importieren, kommt es zu Fehlermeldungen. Schlimmstenfalls gibt es eine &lt;code&gt;java.lang.NullPointException&lt;/code&gt;, was so nicht wirklich aussagekräftig ist und definitiv nicht der Weisheit letzter Schluss ist. Das Verhalten der Software in diesen Situationen resp. die Fehlermeldung werden in Zukunft sicher noch besser werden (müssen).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Tüpfelchen auf dem Frustrations-i war aber die Aussage (wobei ich die Aussage voll und ganz nachvollziehen kann):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;&amp;laquo;I&amp;#8217;m a bit puzzled&amp;#8230;&amp;#8203;I spent quite some time on trying a new procedure to integrate cadastral data. I wanted to use interlis, but it seems not possible at the moment, what a pity.&amp;raquo;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nun denn, um nicht nur rumzufrustrieren, im Folgenden ein paar konstruktive Tipps und Tricks zur Fehlersuche in den Daten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Fehlermeldung lesen&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Klingt blöd, ist aber so. Unter Umständen ist die Fehlermeldung relativ lang und der interessante Teil steht ganz zu Beginn:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Error: Object 12216773 at (line 6751147,col 0)
Error:   ERROR: duplicate key value violates unique constraint &quot;pipelines_linear_element_posname_pkey&quot;
  Detail: Key (t_id)=(1100350) already exists.
Error: Object 12216773 at (line 6751148,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751149,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751150,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751151,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751152,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751153,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751154,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751155,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751156,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751157,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751158,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751159,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751160,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751161,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751162,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751163,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751164,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751165,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751166,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751167,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751168,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751169,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751170,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751171,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751172,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751173,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751174,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751175,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751176,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: Object 12216773 at (line 6751177,col 0)
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: failed to query av_mopublic.t_ili2db_seq
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block
Error: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block
Error:   ERROR: current transaction is aborted, commands ignored until end of transaction block&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die zweite Zeile sagt bereits alles aus: irgendetwas mit einem &lt;code&gt;duplicate key&lt;/code&gt;. In der ersten Zeile schreibt &lt;em&gt;ili2pg&lt;/em&gt; netterweise sogar die Transfer-ID und die Zeile dieses Objektes in der Transferdatei. Schaut man sich diese Zeile an, wird sofort ersichtlich, was das Problem ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;TABL Linear_element_PosName
OBJE 12216771 8294270 IWB 2614693.048 1268727.154 186.0 1 2 2703
OBJE 12216772 8294270 IWB 2614676.986 1268794.383 175.7 1 2 2703
OBJE 12216773 8294324 IWB 2616025.963 1270865.401 28.1 1 2 2703
OBJE 12216773 8294359 IWB 2616025.963 1270865.401 28.1 1 2 2703
OBJE 12216773 8294390 IWB 2616025.963 1270865.401 28.1 1 2 2703
OBJE 12216773 8294397 IWB 2616025.963 1270865.401 28.1 1 2 2703
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein bestimmtes Objekt ist mehrfach in der Transferdatei vorhanden. Was aber das Hauptproblem ist, ist die mehrfach verwendete Transfer-ID (12216773). Das kann so natürlich nicht funktionieren. Da es sich um das &lt;a href=&quot;http://www.cadastre.ch/internet/kataster/de/home/manuel-av/service/mopublic.html&quot;&gt;
&lt;em&gt;MOpublic&lt;/em&gt;&lt;/a&gt;-Datenmodell handelt, liegt der Verdacht nahe, dass es sich um einen Fehler beim Datenumbau handelt. Insbesondere wenn man sich vergegenwärtigt, wie diese Tabelle entsteht und aus welchen Tabellen des &lt;em&gt;DM01&lt;/em&gt;-Datenmodells zusammengebastelt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;ili2pg-Optionen&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;ili2pg&lt;/em&gt; hat die Option &lt;code&gt;--trace&lt;/code&gt;, die während des Imports zusätzliche Informationen in die Konsole schreibt. In Kombination mit der Option &lt;code&gt;--log fubar.log&lt;/code&gt;, die diese Meldungen in eine Datei schreibt, hat man eine gute Ausgangslage für die Fehlersuche. Ohne &lt;code&gt;--trace&lt;/code&gt; meldet &lt;em&gt;ili2pg&lt;/em&gt; folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: ilifile &amp;lt;./NG_NaturgefahrenLV95.ili&amp;gt;
Info: create table structure...
Info: process data file...
Info: data &amp;lt;naturgefahren.itf&amp;gt;
Info: Basket NG_NaturgefahrenLV95.Gefahrengebiet(oid itf0)...
java.lang.NullPointerException&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;--trace&lt;/code&gt; erfolgt dieser Output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: buildSurfaces(): build surfaces..._itf_geom_Gefahrengebiet, maxOverlaps 0.0 (ItfSurfaceLinetable2Polygon.java:217)
Error: java.lang.NullPointerException
Error:     ch.interlis.iom_j.itf.impl.ItfSurfaceLinetable2Polygon.removeValidSelfIntersections(ItfSurfaceLinetable2Polygon.java:392)
Error:     ch.interlis.iom_j.itf.impl.ItfSurfaceLinetable2Polygon.buildSurfaces(ItfSurfaceLinetable2Polygon.java:230)
Error:     ch.interlis.iom_j.itf.ItfReader2.read(ItfReader2.java:313)
Error:     ch.ehi.ili2db.fromxtf.TransferFromXtf.doit(TransferFromXtf.java:389)
Error:     ch.ehi.ili2db.base.Ili2db.transferFromXtf(Ili2db.java:1786)
Error:     ch.ehi.ili2db.base.Ili2db.runUpdate(Ili2db.java:598)
Error:     ch.ehi.ili2db.base.Ili2db.runImport(Ili2db.java:195)
Error:     ch.ehi.ili2db.base.Ili2db.run(Ili2db.java:175)
Error:     ch.ehi.ili2db.AbstractMain.domain(AbstractMain.java:367)
Error:     ch.ehi.ili2pg.PgMain.main(PgMain.java:71)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Immerhin weiss ich jetzt, dass es beim Prozess der Flächenbildung und beim Löschen der &lt;a href=&quot;http://blog.sogeo.services/blog/2015/10/03/interlis-leicht-gemacht-number-5.html&quot;&gt;validen Self-Intersections&lt;/a&gt; Probleme gab. Diese validen Self-Intersections sind wahrscheinlich die Nemesis eines jeden Programmierers: INTERLIS lässt ja bekanntlich unter gewissen Voraussetzungen Self-Intersections zu. In der Datenbank möchte man aber keine nicht-konformen Simple-Feature-Geometrien. Das unter einen Hut zu bringen ist schwierig.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls es sich wirklich um ein Problem bei der Flächenbildung / Self-Intersections-Bereinigung handelt, weiss ich aber immer noch nicht &lt;strong&gt;wo&lt;/strong&gt; (also geografisch) das Problem liegt. In diesem Moment hilft mir die Option &lt;code&gt;--skipPolygonBuilding&lt;/code&gt;. Sie verhindert die Flächenbildung und importiert somit nur die Linien, wie sie in der ITF-Datei kodiert sind. Somit kann ich die Daten immerhin in die Datenbank importieren und in einem Desktop-GIS anschauen und prüfen. In &lt;a href=&quot;http://www.qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; gibt es dafür das &lt;a href=&quot;https://www.qgis.ch/de/ressourcen/anwendertreffen/2015/geometry-cleaning-plugins&quot;&gt;Geometry&lt;/a&gt; &lt;a href=&quot;https://docs.qgis.org/2.14/en/docs/user_manual/plugins/plugins_geometry_checker.html&quot;&gt;Checker&lt;/a&gt; Plugin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p13/overlap_00.png&quot; alt=&quot;Geometry Checker Plugin&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Moment bin ich nur an den Self-Intersections interessiert. Sämtliche anderen Prüfungen lasse ich links liegen. Das Resultat liefert mir dann die Liniengeometrien mit Self-Intersections. Sind diese zu gross, kann &lt;em&gt;ili2pg&lt;/em&gt; nicht mehr damit umgehen und auch kein Polygon daraus bilden. Einer der Fehler, die das Plugin aufgedeckt hat:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p13/overlap_01.png&quot; alt=&quot;Self-Intersection Resultat&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein weiterer häufig auftretender Fehler in (INTERLIS-)Daten sind doppelte Stützpunkte. Auch diese lassen sich im Geometry Checker Plugin entdecken.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Selber coden&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei anderen Daten wurde der Import mit dieser Fehlermeldung verweigert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: Basket MD01MOCH24MN95F.Points_fixesCategorie1(oid itf0)...
Info: Basket MD01MOCH24MN95F.Points_fixesCategorie2(oid itf1)...
Info: Basket MD01MOCH24MN95F.Points_fixesCategorie3(oid itf2)...
Error: failed to build polygons of MD01MOCH24MN95F.Points_fixesCategorie3.Mise_a_jourPFP3.Perimetre
Error:   no polygon
Info: Basket MD01MOCH24MN95F.Couverture_du_sol(oid itf3)...
java.lang.NullPointerException&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;--trace&lt;/code&gt; sieht die Fehlermeldung so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: buildSurfaces(): build surfaces..._itf_geom_Mise_a_jourCS, maxOverlaps 0.05 (ItfSurfaceLinetable2Polygon.java:217)
Info: removeValidSelfIntersections(): valoverlap Intersection overlap 8.527947694197402E-4, coord1 (2559588.499, 1144173.854, NaN), coord2 (2559591.1405376485, 1144172.0968293739, NaN), tid1 4955, tid2 4955, idx1 0, idx2 1, seg1 CIRCULARSTRING (2559591.251 1144171.994, 2559589.951 1144173.036, 2559588.499 1144173.854), seg2 CIRCULARSTRING (2559588.499 1144173.854, 2559589.948 1144173.037, 2559591.247 1144171.998) (ItfSurfaceLinetable2Polygon.java:397)
Info: removeValidSelfIntersections(): valoverlap Intersection overlap 3.728486693480781E-4, coord1 (2556237.844, 1145424.429, NaN), coord2 (2556240.125186811, 1145424.724616642, NaN), tid1 7439, tid2 7439, idx1 2, idx2 0, seg1 CIRCULARSTRING (2556240.715 1145424.465, 2556239.274 1145424.847, 2556237.844 1145424.429), seg2 CIRCULARSTRING (2556237.844 1145424.429, 2556239.38 1145424.846, 2556240.893 1145424.352) (ItfSurfaceLinetable2Polygon.java:397)
Info: removeValidSelfIntersections(): valoverlap Intersection overlap 8.527947694197402E-4, coord1 (2559588.499, 1144173.854, NaN), coord2 (2559591.1405376485, 1144172.0968293739, NaN), tid1 4199, tid2 4199, idx1 0, idx2 1, seg1 CIRCULARSTRING (2559591.251 1144171.994, 2559589.951 1144173.036, 2559588.499 1144173.854), seg2 CIRCULARSTRING (2559588.499 1144173.854, 2559589.948 1144173.037, 2559591.247 1144171.998) (ItfSurfaceLinetable2Polygon.java:397)
java.lang.NullPointerException
    ch.interlis.iom_j.itf.impl.LineSet.buildBoundaries(LineSet.java:51)
    ch.interlis.iom_j.itf.impl.ItfSurfaceLinetable2Polygon.buildSurfaces(ItfSurfaceLinetable2Polygon.java:228)
    ch.interlis.iom_j.itf.ItfReader2.read(ItfReader2.java:313)
    ch.ehi.ili2db.fromxtf.TransferFromXtf.doit(TransferFromXtf.java:389)
    ch.ehi.ili2db.base.Ili2db.transferFromXtf(Ili2db.java:1786)
    ch.ehi.ili2db.base.Ili2db.runUpdate(Ili2db.java:598)
    ch.ehi.ili2db.base.Ili2db.runImport(Ili2db.java:195)
    ch.ehi.ili2db.base.Ili2db.run(Ili2db.java:175)
    ch.ehi.ili2db.AbstractMain.domain(AbstractMain.java:367)
    ch.ehi.ili2pg.PgMain.main(PgMain.java:71)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also irgendwie wieder bei der Flächenbildung aber nicht mehr beim Löschen der Overlaps, sondern beim &amp;laquo;Erstellen der Grenze/Kanten.&amp;raquo; (buildBoundaries) in der Klasse &lt;code&gt;LineSet&lt;/code&gt;. Der Trick mit &lt;code&gt;--skipPolygonBuilding&lt;/code&gt; funktioniert hier leider nicht, da weitere Fehler auftauchen und kein Import möglich ist. Was machen? Weil der Quellcode ja öffentlich und frei verfügbar ist, kann ich mir den Code anschauen und vielleicht eine zusätzliche Meldung reinbasteln, die mir sagt, bei welchem Objekt genau das Problem auftaucht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Besagte Klasse ist nicht im Code von &lt;em&gt;ili2pg&lt;/em&gt;, sondern sie ist Bestandteil der Bibliothek &lt;a href=&quot;https://github.com/claeis/iox-ili/&quot;&gt;&lt;em&gt;iox-ili&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;ili2pg&lt;/em&gt; wiederum verwendet diese Bibliothek. Das geklonte Projekt ist ruckzuck in &lt;a href=&quot;https://www.eclipse.org&quot;&gt;&lt;em&gt;Eclipse&lt;/em&gt;&lt;/a&gt; importiert. Falls ich was ändere, kann ich die notwendige Jar-Datei mit &lt;code&gt;ant jar&lt;/code&gt; neu erzeugen und in das &lt;code&gt;libs&lt;/code&gt;-Verzeichnis von &lt;em&gt;ili2pg&lt;/em&gt; kopieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dank der Fehlermeldung weiss man, dass bei &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/main/java/ch/interlis/iom_j/itf/impl/LineSet.java#L51&quot;&gt;Zeile 51&lt;/a&gt; der Hund begraben sein muss. In Zeile 51 wird die Methode &lt;code&gt;getattrobj&lt;/code&gt; aufgerufen. Das Problem liegt also wahrscheinlich beim Objekt &lt;code&gt;polyline&lt;/code&gt;. Dieses wiederum entsteht ein paar Zeilen weiter oben auf &lt;a href=&quot;https://github.com/claeis/iox-ili/blob/master/src/main/java/ch/interlis/iom_j/itf/impl/LineSet.java#L48&quot;&gt;Zeile 48&lt;/a&gt;. Mit ein paar sinnvollen Debugmeldungen vor- und nachher ist man um einiges schlauer:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;EhiLogger.debug(&quot;t_id: &quot; + line_tid);
IomObject polyline=lines.get(line_tid).getattrobj(helperTableGeomAttrName, 0);
EhiLogger.debug(&quot;IomObject (polyline): &quot; + polyline);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein erneuter Aufruf von &lt;em&gt;ili2pg&lt;/em&gt; liefert neu zusätzlichen Output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;Info: buildBoundaries(): t_id: 98624113 (LineSet.java:49)
Info: buildBoundaries(): IomObject (polyline): null (LineSet.java:51)
java.lang.NullPointerException
    ch.interlis.iom_j.itf.impl.LineSet.buildBoundaries(LineSet.java:55)
    ch.interlis.iom_j.itf.impl.ItfSurfaceLinetable2Polygon.buildSurfaces(ItfSurfaceLinetable2Polygon.java:228)
    ch.interlis.iom_j.itf.ItfReader2.read(ItfReader2.java:313)
    ch.ehi.ili2db.fromxtf.TransferFromXtf.doit(TransferFromXtf.java:389)
    ch.ehi.ili2db.base.Ili2db.transferFromXtf(Ili2db.java:1786)
    ch.ehi.ili2db.base.Ili2db.runUpdate(Ili2db.java:598)
    ch.ehi.ili2db.base.Ili2db.runImport(Ili2db.java:195)
    ch.ehi.ili2db.base.Ili2db.run(Ili2db.java:175)
    ch.ehi.ili2db.AbstractMain.domain(AbstractMain.java:367)
    ch.ehi.ili2pg.PgMain.main(PgMain.java:71)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie vermutet, ist das &lt;code&gt;polyline&lt;/code&gt;-Objekt &lt;code&gt;null&lt;/code&gt;. In der Transferdatei muss man sich jetzt nur das Objekt mit der &lt;code&gt;t_id&lt;/code&gt; 98624113 anschauen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;OBJE 98624113 98624113
ELIN&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Linie ohne Stützpunkte. Mit dem kann &lt;em&gt;ili2pg&lt;/em&gt; nicht umgehen und wirft daher eine &lt;code&gt;java.lang.NullPointerException&lt;/code&gt;. Auch nach dem Löschen dieser leeren Linie hat der Import leider nicht funktioniert. Zu viele andere Modellfehler.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Was ich mir wünsche&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bessere Datenqualität&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bessere Fehlermeldungen und besserer Umgang mit Fehlern. Das heisst nicht, dass jeder Mumpitz importiert werden soll. Aber diese NullPointerException sind unschön.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In Zukunft &lt;strong&gt;vor&lt;/strong&gt; einer Datenabgabe die Daten z.B. mit &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; prüfen &lt;strong&gt;und&lt;/strong&gt; anschliessend bereinigen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #12</title>
      <link>http://blog.sogeo.services/blog/2016/09/11/interlis-leicht-gemacht-number-12.html</link>
      <pubDate>Sun, 11 Sep 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/09/11/interlis-leicht-gemacht-number-12.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Open Source INTERLIS-Checker &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; entwickelt sich prächtig. Mittlerweile kann man bereits die Version 0.3.0 &lt;a href=&quot;https://github.com/claeis/ilivalidator/releases&quot;&gt;herunterladen&lt;/a&gt;. Ganz &lt;a href=&quot;https://github.com/claeis/ilivalidator/commit/bb8e7fd0f03956439a1e93a484f96a220e9cf084&quot;&gt;neu&lt;/a&gt; ist, dass die Methode &lt;code&gt;runValidation()&lt;/code&gt; einen Rückgabewert besitzt. Der Rückgabewert ist &lt;code&gt;true&lt;/code&gt;, falls die Prüfung erfolgreich war resp. &lt;code&gt;false&lt;/code&gt;, falls nicht. Somit wird es einfacher, wenn man &lt;em&gt;ilivalidator&lt;/em&gt; als Programmbibliothek einsetzen will und verschiedene weitere Schritte vom Resultat der Prüfung abhängig machen will. Diese neue Funktion wird in der nächsten Version verfügbar sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Will nicht &amp;laquo;bloss&amp;raquo; eine INTERLIS-Transferdatei prüfen (z.B. mit einem simplen &lt;a href=&quot;https://sogeo.services/ilivalidator/upload.xhtml&quot;&gt;Webservice&lt;/a&gt;), sondern eine ganzes Verzeichnis mit Daten prüfen und Logdateien von nicht bestandenen Prüfungen mit einer E-Mail verschicken, schreibt man entweder ein kleines Skript oder aber man verwendet spezielle Software dazu. &lt;a href=&quot;http://community.pentaho.com/projects/data-integration/&quot;&gt;Kettle&lt;/a&gt; ist so eine typische ETL-Software. Daten rumkopieren, Verzeichnisse auslesen und E-Mails verschicken gehören zum Standard eines solchen Werkzeuges. Es fehlt bloss die INTERLIS-Prüfung. Weil sich Kettle einfach erweitern lässt und dazu noch mit Java geschrieben ist, passt das alles sehr gut zusammen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Erweiterungsmöglichkeit von &lt;em&gt;Kettle&lt;/em&gt; ist die &amp;laquo;User Definied Java Class&amp;raquo; (UDJC). Damit lässt sich ein Transformation-Step mit Java-Code relativ einfach und effizient selber schreiben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;import org.interlis2.validator.Validator;
import ch.ehi.basics.settings.Settings;

private FieldHelper inputField = null;
private Settings settings = null;

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException
{
	Object[] r = getRow();

	if (r == null) {
		setOutputDone();
		return false;
	}

	if (first) {
		first = false;
		// get the input and output fields
		inputField = get(Fields.In, &quot;filename&quot;);

		// Default settings for ilivalidator.
		settings = new Settings();
		settings.setValue(Validator.SETTING_ILIDIRS, Validator.SETTING_DEFAULT_ILIDIRS);
	}

	String value = inputField.getString(r);
	logBasic(&quot;INTERLIS transfer file: &quot; + value);

	boolean ret = Validator.runValidation(value, settings);
	logBasic(&quot;Validation successful? &quot; + ret);

	return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einzig die Java-Bibliotheken von &lt;em&gt;ilivalidator&lt;/em&gt; muss man in das &lt;code&gt;lib/&lt;/code&gt;-Verzeichnis von &lt;em&gt;Kettle&lt;/em&gt; kopieren. Als Proof-Of-Concept ist das ideal und funktioniert einwandfrei. Wenn man aber mehr Steuerungsmöglichkeiten will oder aber den Transformation-Step einfacher verfügbar machen will, dient sich die zweite Erweiterungsmöglichkeit an: ein Plugin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den Bezugsrahmenwechsel habe ich für &lt;em&gt;GeoKettle&lt;/em&gt; vor längerer Zeit ein &lt;a href=&quot;http://blog.sogeo.services/blog/2014/02/09/fun-with-geokettle-episode-1.html&quot;&gt;Plugin&lt;/a&gt; geschrieben. Der Nachteil von Plugins ist, dass man sich da ein wenig reinkämpfen muss und unter anderem in die &lt;a href=&quot;https://www.eclipse.org/swt/widgets/&quot;&gt;SWT&lt;/a&gt;-Hölle abtauchen muss. Mit viel Copy/Paste vom Bezugsrahmenwechsel-Plugin habe ich einen ersten Wurf eines &lt;em&gt;ilivalidator&lt;/em&gt;-Plugins geschrieben. Dieses lässt sich jetzt sehr bequem wie alle anderen Transformation-Steps in &lt;em&gt;Kettle&lt;/em&gt; nutzen. Als Input erwartet das Plugin den Namen einer INTERLIS-Transferdatei. Und es kann mittels Checkbox gewählt werden, ob ein Logfile erzeugt werden soll:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p12/kettle-ilivalidator01.png&quot; alt=&quot;kettle-plugin ilivalidator optionen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Plugin fügt dem Datenstrom ein Attribut &lt;code&gt;valid&lt;/code&gt; hinzu. Je nach Resultat der Prüfung ist der Wert dieses Attributes &lt;code&gt;true&lt;/code&gt; oder &lt;code&gt;false&lt;/code&gt; (also der Rückgabewert der Methode &lt;code&gt;runValidation()&lt;/code&gt;). Zusätzlich wird auch das Attribut &lt;code&gt;logfile&lt;/code&gt; hinzugefügt. Der Wert ist der Name der erzeugten Logdatei. Schöner wäre es natürlich, wenn man die Namen dieser neuen Attribute selber wählen könnte. Zudem fehlen noch weitere Optionen von &lt;em&gt;ilivalidator&lt;/em&gt;, wie z.B. der XTF-Logfile-Output oder ein sauberer Umgang mit den INTERLIS-Modelablagen. Momentan wird einfach die INTERLIS-Compiler-Standard-Variante verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Beispiel-Workflow will ich ein ganzes Verzeichnis mit INTERLIS-Dateien prüfen und die Logdateien, der nicht erfolgreichen Prüfugen per E-Mail verschicken. Sämtliche Prüfungen sollen in eine Excel-Datei geloggt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p12/kettle-ilivalidator02.png&quot; alt=&quot;kettle-plugin ilivalidator workflow&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diesen Workflow kann man sich jetzt einfach in Kettle zusammenklicken. Tricky war noch das Verschicken der E-Mails. Zuerst müssen E-Mail-Credentials dem Datenstrom hinzugefügt werden. Das geht mit dem &lt;code&gt;Add constants&lt;/code&gt;-Step. Im &lt;code&gt;Mail&lt;/code&gt;-Step muss man dann für seinen E-Mail-Provider die richtigen Parameter finden. Für GMail ist das der Port 465 für SSL. Als &lt;code&gt;Authentification user&lt;/code&gt; muss nicht die ganze E-Mail-Adresse angegeben werden, sondern nur das vor dem at-Zeichen. Um die Logfiles als Attachement zu verschicken, muss man &lt;code&gt;Dynamic filenames&lt;/code&gt; anwählen und das Feld mit dem Logfile-Namen auswählen. Eventuell muss man in den GMail-Einstellungen die Option &amp;laquo;Access for less secure apps&amp;raquo; einschalten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Abgesehen von der normalen Frickelei mit &lt;em&gt;Kettle&lt;/em&gt; (die Doku ist aber sehr informativ), lässt sich mit wenig Aufwand eine ganze Prozesskette zum Prüfen von INTERLIS-Dateien auf die Beine stellen. Vorausgesetzt natürlich das &lt;em&gt;ilivalidator&lt;/em&gt;-&lt;strong&gt;Plugin&lt;/strong&gt; hält, was es verspricht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein erster Wurf des Plugins kann man &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-12/kettle-ilivalidator.zip&quot;&gt;hier&lt;/a&gt; herunterladen. Den Quellcode gibt es &lt;a href=&quot;https://git.sogeo.services/stefan/ilivalidator-kettle&quot;&gt;hier&lt;/a&gt;. Die Zip-Datei enthält sowohl das Plugin selbst wie auch die benötigten &lt;em&gt;ilivalidator&lt;/em&gt;-Bibliotheken. Die Zip-Datei kopiert man entpackt im &lt;code&gt;plugins/&lt;/code&gt;-Ordner von &lt;em&gt;Kettle&lt;/em&gt; und schon sollte der &lt;em&gt;ilivalidator&lt;/em&gt;-Step in der Kategorie &lt;code&gt;Validation&lt;/code&gt; von &lt;em&gt;Kettle&lt;/em&gt; sichtbar sein. Die &lt;code&gt;antlr&lt;/code&gt;-Bibliothek von &lt;em&gt;ilivalidator&lt;/em&gt; ist nicht in der Zip-Datei enthalten. Mit dieser gibt es ein Problem, da in &lt;em&gt;Kettle&lt;/em&gt; selbst bereits eine andere Version vorhanden ist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Leider habe erst nach der Copy/Paste-Orgie festgestellt, dass die &lt;a href=&quot;https://help.pentaho.com/Documentation/6.0/0R0/0V0/010&quot;&gt;Plugin-Dokumention&lt;/a&gt; (seit kurzem?) von &lt;em&gt;Kettle&lt;/em&gt; gar nicht so übel ist. Es gibt sogar eine &lt;a href=&quot;https://help.pentaho.com/Documentation/6.0/0R0/0V0/010/035&quot;&gt;Richtlinie&lt;/a&gt;, wie man die Icons designen soll. Das Template-Plugin findet sich im &lt;a href=&quot;https://github.com/pentaho/pdi-sdk-plugins&quot;&gt;Github-Repository&lt;/a&gt;. Ich muss da definitiv nochmals über die Bücher, da mein Copy/Paste-Plugin auf einer sehr alten Kettle-Version basiert.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #11</title>
      <link>http://blog.sogeo.services/blog/2016/07/18/interlis-leicht-gemacht-number-11.html</link>
      <pubDate>Mon, 18 Jul 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/07/18/interlis-leicht-gemacht-number-11.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Getreu dem Motto &amp;laquo;Release early, release often&amp;raquo; gibt es jetzt bereits die zweite Version von &lt;a href=&quot;https://github.com/claeis/ilivalidator&quot;&gt;&lt;em&gt;ilivalidator&lt;/em&gt;&lt;/a&gt; - dem OpenSource INTERLIS-Checker - zum &lt;a href=&quot;https://github.com/claeis/ilivalidator/releases&quot;&gt;Ausprobieren&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um nicht immer die amtliche Vermessung als Testbeispiel zu verwenden, bastle ich mir zuerst ein paar Daten zusammen. Dazu baue ich vorhandene Daten des Kantons Solothurn in das Modell &amp;laquo;&lt;a href=&quot;http://models.geo.admin.ch/BAFU/PlanerischerGewaesserschutz_V1.ili&quot;&gt;Planerischer&lt;/a&gt; &lt;a href=&quot;http://www.bafu.admin.ch/umwelt/12877/15717/15735/index.html?lang=de&quot;&gt;Gewässerschutz&lt;/a&gt;&amp;raquo; des BAFU um.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/topic_gwszonen.png&quot; alt=&quot;UML-Diagramm&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes mache ich mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; einen Schemaimport und lege leere Tabellen an, die ich später mit &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-11/2016-qgis-ili2pg-workshop_v03.pdf&quot;&gt;QGIS-Forms-and-Relations-Magie&lt;/a&gt; abfüllen werde. Ein Datenumbau mit SQL reicht leider nicht, da ich - zum Testen von &lt;em&gt;ilivalidator&lt;/em&gt;  - zusätzliche Daten erfassen will, die in den vorhandenen Daten des Kantons noch nicht vorhanden sind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --dbhost localhost --dbdatabase rosebud2 --dbport 5432 --dbusr stefan --dbpwd ziegler12 --defaultSrsAuth EPSG --defaultSrsCode 21781 --modeldir http://models.geo.admin.ch --models PlanerischerGewaesserschutz_V1 --smart1Inheritance --expandMultilingual --createGeomIdx --createEnumTabs --nameByTopic --strokeArcs --createFkIdx --schemaimport --dbschema plan_gewaesserschutz&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Abfüllen werde ich nicht das ganze Modell, sondern nur das Topic &amp;laquo;GWSZonen&amp;raquo;. Und auch nur die Zonen der &amp;laquo;&lt;a href=&quot;https://www.baerschwil.ch/reglemente?file=tl_files/downloads/reglemente/Schutzzonenreglement%20Wasserbergquellen.pdf&quot;&gt;Stöckli- und Wasserbergquelle&lt;/a&gt;&amp;raquo; in der Gemeinde Bärschwil. Betroffen sind die Klassen &amp;laquo;GWSZone&amp;raquo;, &amp;laquo;Status&amp;raquo; und &amp;laquo;Dokument&amp;raquo;. Zusätzlich müssen einige Assoziationen erfasst werden. Mit &lt;a href=&quot;http://www.qgis.org&quot;&gt;&lt;em&gt;QGIS&lt;/em&gt;&lt;/a&gt; geht das sehr elegant. Neu werden MANDATORY resp. NOT NULL Attribute farblich hervorgehoben:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/qgis01.png&quot; alt=&quot;Datenerfassung in QGIS&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für meinen Geschmack schon sehr viel Farbe. Aber wenigstens fällt es auf&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die vier erfassten Zonen der Quelle sehen so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/qgis02.png&quot; alt=&quot;Zonen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;ili2pg&lt;/em&gt; können die erfassten Daten in eine INTERLIS/XTF-Datei exportiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --dbhost localhost --dbdatabase rosebud2 --dbport 5432 --dbusr stefan --dbpwd ziegler12  --modeldir http://models.geo.admin.ch --models PlanerischerGewaesserschutz_V1 --disableValidation --export --dbschema plan_gewaesserschutz gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;em&gt;xmllint&lt;/em&gt; kann die XML-Datei noch für das/mein Auge schöner formatiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;xmllint --format gewaesserschutz.xtf -o gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die daraus resultierende &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-11/gewaesserschutz.xtf&quot;&gt;INTERLIS/XTF-Datei&lt;/a&gt; kann mit &lt;em&gt;ilivalidator&lt;/em&gt; mit einem einfachen Befehl in der Konsole gegenüber dem konzeptionellen INTERLIS-Modell geprüft werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ilivalidator.jar gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wurden keine Fehler gefunden, erscheinen folgende Zeilen in der Konsole:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/ilivalidator01.png&quot; alt=&quot;ilivalidator ohne Fehler&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In die &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-11/gewaesserschutz_mit_fehler.xtf&quot;&gt;INTERLIS/XTF-Datei&lt;/a&gt; baue ich nun bewusst vier Fehler ein. Und zwar vier Fehler, die &lt;em&gt;ilivalidator&lt;/em&gt; bereits jetzt schon finden sollte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fehlendes MANDATORY-Attribut &amp;laquo;Titel&amp;raquo; in der Klasse &amp;laquo;Dokument&amp;raquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Zu langes Text-Attribut &amp;laquo;OffizielleNr&amp;raquo; in der Klasse &amp;laquo;Dokument&amp;raquo;. Erlaubt sind nur 20 Zeichen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Falscher numerischer Wert für das Attribut &amp;laquo;Gemeinde&amp;raquo; in der Klasse &amp;laquo;Dokument&amp;raquo;. Erlaubt ist eine Zahl zwischen 0 und 9999.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Falscher Aufzähltyp für das Attribut &amp;laquo;Art&amp;raquo; in der Klasse &amp;laquo;GWSZone&amp;raquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein erneuter Aufruf von &lt;em&gt;ilivalidator&lt;/em&gt; liefert in der Konsole folgendes Resultat:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/ilivalidator02.png&quot; alt=&quot;ilivalidator mit Fehler&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es sollten mindestens zwei Dinge auffallen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Es gibt neu in der Konsole vier Zeilen, die mit &lt;code&gt;Error&lt;/code&gt; beginnen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die letzte Zeile endet mit &lt;code&gt;Info: &amp;#8230;&amp;#8203;validation failed&lt;/code&gt; Anstelle von &lt;code&gt;Info: &amp;#8230;&amp;#8203;validation done&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die vier Error-Zeilen geben auch Auskunft über die Art des Fehler und bei welchem Objekt (&lt;code&gt;tid&lt;/code&gt;) der Fehler auftritt. Ebenfalls liefert &lt;em&gt;ilivalidator&lt;/em&gt; die Zeilenummer. Bei XML ist diese aber oftmals nicht wahnsinnig hilfreich. Die Fehlermeldungen sind soweit sehr verständlich.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Meldungen auf der Konsole können auch in eine Logdatei geschrieben werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ilivalidator.jar --log result.log gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es besteht zudem die Möglichkeit die Meldungen in ein bewusst sehr einfach gehaltenes &lt;a href=&quot;https://raw.githubusercontent.com/claeis/ilivalidator/master/docs/IliVErrors.ili&quot;&gt;INTERLIS-Modell&lt;/a&gt; zu schreiben. Das Modell ist auf &amp;laquo;Shapefile-Niveau&amp;raquo;, dh. der Benutzer muss nicht mühsam Daten umbauen, um sie visualisieren zu können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ilivalidator.jar --xtflog result.xtf gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2gpkg/&quot;&gt;&lt;em&gt;ili2gpkg&lt;/em&gt;&lt;/a&gt; könnte jetzt die resultierende Log-XTF-Datei in eine GeoPackage-Datei umgewandelt werden, um die Fehler und Warnungen in QGIS anzeigen zu lassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Manchmal möchte man aber Fehler in der Transfer-Datei tolerieren oder sogar ignorieren. Dazu gibt es bereits jetzt schon die Möglichkeit dies über eine &lt;a href=&quot;https://github.com/toml-lang/toml&quot;&gt;TOML-Datei&lt;/a&gt; zu steuern. In &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-11/gewaesserschutz.toml&quot;&gt;meiner TOML-Konfiguration&lt;/a&gt; schalte ich die type-Prüfung des Attributes &amp;laquo;Art&amp;raquo; in der Klasse &amp;laquo;GWSZone&amp;raquo; aus. Dh. es wird nicht mehr geprüft, ob der Wert des Attributes im Aufzähltyp vorkommt. Zudem stufe ich die MANDATORY-Prüfung des Attributes &amp;laquo;Titel&amp;raquo; in der Klasse &amp;laquo;Dokument&amp;raquo; zu einer Warnung herunter. Der Aufruf mit einer TOML-Konfiguration ist wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ilivalidator.jar --config gewaesserschutz.toml gewaesserschutz.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neu werden nur noch drei Fehler gefunden, wobei ein Fehler nur als Warnung attribuiert ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p11/ilivalidator03.png&quot; alt=&quot;ilivalidator mit TOML&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Steuerung kann auch direkt im Datenmodell mit &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/docs/ilivalidator.rst#interlis-metaattribute&quot;&gt;Metaattributen vorgenommen&lt;/a&gt; werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der &lt;a href=&quot;https://github.com/claeis/ilivalidator/blob/master/docs/ilivalidator.rst&quot;&gt;Anleitung&lt;/a&gt; zu &lt;em&gt;ilivalidator&lt;/em&gt; gibt es noch mehr Tipps &amp;amp; Tricks und ein kleines Beispiel-Datenmodell mit Datensatz.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So geht INTERLIS-Datenprüfung im 21. Jahrhundert.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>MapCache: Disk vs. SQLite</title>
      <link>http://blog.sogeo.services/blog/2016/07/01/mapcache-disk-vs-sqlite.html</link>
      <pubDate>Fri, 1 Jul 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/07/01/mapcache-disk-vs-sqlite.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Historisch betrachtet sind &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;wir&lt;/a&gt; ein &amp;laquo;Ein-Request-ein-Bild-WebGIS-Kanton&amp;raquo;. Was heisst das? Bei all unseren eingesetzten WebGIS-Klienten wurde nach einer Benutzer-Interaktion (also Zoomen, Pannen etc.) ein einziges Bild vom Server zurückgeliefert. Auch wenn das Bild aus mehreren Kartenlayern zusammengesetzt ist. Wie so alles im Leben hat das Vor- und Nachteile. State-of-the-art ist es gefühlt heute nicht mehr. Will man z.B. die Transparenz eines Kartenlayers ändern, löst das einen neuen Request aus und der Server muss ein neues Bild rendern. Er übernimmt eine Aufgabe, die heute locker ein Browser schafft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Einsatz von &lt;a href=&quot;https://github.com/qgis/QGIS-Web-Client&quot;&gt;QGIS-Webclient&lt;/a&gt; kamen dann auch noch Performance-Probleme hinzu. Aus diesem Grund haben wir begonnen die Hintergrundkarten (wie Orthofoto, Basisplan, etc.) zu kacheln und diese Kacheln vorzuhalten (aka &lt;a href=&quot;https://de.wikipedia.org/wiki/Web_Map_Tile_Service&quot;&gt;WMTS&lt;/a&gt;). Als WMTS-Server verwenden wir &lt;a href=&quot;http://mapserver.org/mapcache/index.html&quot;&gt;&lt;em&gt;MapCache&lt;/em&gt;&lt;/a&gt;. &lt;em&gt;MapCache&lt;/em&gt; unterstützt verschiedene &lt;a href=&quot;http://mapserver.org/mapcache/caches.html&quot;&gt;&lt;em&gt;Cache Types&lt;/em&gt;&lt;/a&gt;. Ohne gross zu studieren, haben wir dann das vermeintlich Einfachste gewählt: &lt;a href=&quot;http://mapserver.org/mapcache/caches.html#disk-caches&quot;&gt;&lt;em&gt;Disk caches&lt;/em&gt;&lt;/a&gt;. Dabei werden einfach alle Kacheln direkt auf dem Filesystem gespeichert. Die &lt;a href=&quot;http://mapserver.org/mapcache/caches.html#disk-caches&quot;&gt;Projektdokumentation&lt;/a&gt; macht da eine relativ klare Ansage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;The disk based cache is the simplest cache to configure, and the one with the the fastest access to existing tiles. It is ideal for small tile repositories, but will often cause trouble for sites hosting millions of tiles, as the number of files or directories may rapidly overcome the capabilities of the underlying filesystem.&amp;raquo;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und so ist es dann natürlich auch gekommen. Welches ist das sinnvollste Filesystem? Ok, Rücksprache mit der Informatik-Abteilung und speziell ein Laufwerk erstellen, formatieren und mounten&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als weiterer &lt;em&gt;Cache type&lt;/em&gt; gibt es auch &lt;a href=&quot;http://mapserver.org/mapcache/caches.html#sqlite-caches&quot;&gt;&lt;em&gt;SQLite caches&lt;/em&gt;&lt;/a&gt;. Bei dieser Variante werden sämtliche Kacheln in einer SQLite-Datenbank gespeichert. Was jetzt natürlich auf einen Schlag die ganze Filesystem-Diskussion beendet plus das Deployment vereinfacht: Einfach irgendwo seeden und die SQLite-Datei an den passenden Ort kopieren. Die Kachel wird als &lt;em&gt;Blob&lt;/em&gt; in der Datenbank-Tabelle gespeichert. Nachteilig sind gemäss &lt;a href=&quot;http://mapserver.org/mapcache/caches.html#sqlite-caches&quot;&gt;Dokumentation&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;laquo;The SQLite based caches are a bit slower than the disk based caches, and may have write-locking issues at seed time if a high number of threads all try to insert new tiles concurrently.&amp;raquo;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nicht ganz klar sind mir die &amp;laquo;write-locking issues at seed time&amp;raquo;. Heisst das, dass - falls sie auftreten - Kacheln &amp;laquo;verloren&amp;raquo; gehen oder die Seeding-Performance zusammenbricht? Um das zu klären, müsste man auf der &lt;a href=&quot;http://mapserver.org/community/lists.html&quot;&gt;Mailingliste&lt;/a&gt; nachfragen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In erster Linie geht es mir um die Frage wie gross der Performance-Unterschied der beiden &lt;em&gt;Cache types&lt;/em&gt; ist. Sind &lt;em&gt;Disk caches&lt;/em&gt; so massiv schneller, dass &lt;em&gt;SQLite caches&lt;/em&gt; ein No-go sind? Um diese Frage zu beantworten, habe ich auf meinen &lt;a href=&quot;http://blog.sogeo.services/blog/2016/06/20/qgis-server-vs-mapserver.html&quot;&gt;Testsystem&lt;/a&gt; eine WMS-Karte je einmal mit &lt;em&gt;Disk caches&lt;/em&gt; und &lt;em&gt;SQLite caches&lt;/em&gt; geseedet und anschliessend mit &lt;a href=&quot;http://jmeter.apache.org/&quot;&gt;&lt;em&gt;jmeter&lt;/em&gt;&lt;/a&gt; die Performance gemessen. Dabei habe ich wiederum &lt;a href=&quot;http://mapserver.org/mapcache/services.html#ogc-wms-service&quot;&gt;WMS-Requests&lt;/a&gt; gemacht, die &lt;em&gt;MapCache&lt;/em&gt; aber aus den vorher geseedeten Kacheln zusammensetzt. Das ist zwar ein Zusatzaufwand, der eigentlich nichts mit den &lt;em&gt;Cache types&lt;/em&gt; zu tun hat, aber ich denke, das ist für unsere Anforderungen vernachlässigbar (und an absoluten Zahlen sind wir weniger interessiert).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Seeding selbst scheint bei beiden Typen gleich schnell zu sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die WMS-Requests gibt es wieder altbackene Charts:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/mapcache_disk_vs_sqlite/cwm_req_per_sec.png&quot; alt=&quot;CWM requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/mapcache_disk_vs_sqlite/cwm_max_resp_time.png&quot; alt=&quot;CWM max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessanterweise sind bei mir die &lt;em&gt;SQLite caches&lt;/em&gt; sogar klein wenig schneller. Faszinierend ist der Vergleich zu den nicht-aus-geseedeten-Kacheln-zusammengesetzten Live-WMS-Requests. Als Vergleich habe ich neben dem wirklich direkten Zugriff auf den WMS-Server auch noch &lt;em&gt;MapCache&lt;/em&gt; als Proxy (&lt;code&gt;&amp;lt;full_wms&amp;gt;forward&amp;lt;/full_wms&amp;gt;&lt;/code&gt;) dazwischengeschaltet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/mapcache_disk_vs_sqlite/cwm_req_per_sec_forward_direct.png&quot; alt=&quot;CWM requests per second direct&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>QGIS Server vs. MapServer - Revisited</title>
      <link>http://blog.sogeo.services/blog/2016/06/21/qgis-server-vs-mapserver_revisited.html</link>
      <pubDate>Tue, 21 Jun 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/06/21/qgis-server-vs-mapserver_revisited.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf vielfachen Wunsch hier noch die Resultate mit &lt;em&gt;MapServer&lt;/em&gt; als FCGI. AVWMS und Orthofoto (&amp;laquo;nearest neighbour&amp;raquo;) sind dann schon eine andere Hausnummer. Jedoch zeigt sich dann bei den maximalen Antwortzeiten ein ähnliches Verhalten wie bei &lt;em&gt;QGIS Server&lt;/em&gt;. Der FCGI-Experte würde wahrscheinlich (?) sagen: &amp;laquo;Logisch!&amp;raquo;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;AVWMS:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/avwms_req_per_sec.png&quot; alt=&quot;AVWMS requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/avwms_max_resp_time.png&quot; alt=&quot;AVWMS max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Orthofoto (&amp;laquo;nearest neighbour&amp;raquo;):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/ortho_req_per_sec.png&quot; alt=&quot;Orthofoto (nearest neighour) requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/ortho_max_resp_time.png&quot; alt=&quot;Orthofoto (nearest neighour) max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Orthofoto (&amp;laquo;average&amp;raquo;):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/ortho_resampling_req_per_sec.png&quot; alt=&quot;Orthofoto (average) requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver_revisited/ortho_resampling_max_resp_time.png&quot; alt=&quot;Orthofoto (average) max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>QGIS Server vs. MapServer</title>
      <link>http://blog.sogeo.services/blog/2016/06/20/qgis-server-vs-mapserver.html</link>
      <pubDate>Mon, 20 Jun 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/06/20/qgis-server-vs-mapserver.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;uns&lt;/a&gt; steht ein grösser Umbau der GIS-Infrastruktur im Web an. Da darf natürlich auch die Diskussion über den zukünftigen Kartenserver nicht fehlen. Zurzeit - &amp;laquo;historisch gewachsen&amp;raquo; - setzen wir drei (3) WMS-Server ein: &lt;em&gt;MapServer&lt;/em&gt;, &lt;em&gt;GeoServer&lt;/em&gt; und &lt;em&gt;QGIS Server&lt;/em&gt;. Nach dem Umbau soll es nur einer sein. &lt;em&gt;GeoServer&lt;/em&gt; scheidet als erstes aus. Wir haben damit einfach am wenigsten Erfahrung. Bleiben noch &lt;em&gt;MapServer&lt;/em&gt; und &lt;em&gt;QGIS Server&lt;/em&gt;. &lt;em&gt;MapServer&lt;/em&gt; setzen wir seit Tag 1 ein, haben also reichlich Erfahrung. Bei &lt;em&gt;QGIS Server&lt;/em&gt; schätzen wir z.B. die umfangreichen Funktionen in verschiedenen Bereichen, die allesamt direkt auch im Web verwendbar und vor allem sichtbar werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als eine kleine Hilfe bei der Entscheidungsfindung habe ich kurzerhand ein WMS-Shootout gemacht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Disclaimer: Bis vor vier Tagen habe ich noch nie irgendetwas mit &lt;em&gt;MapServer&lt;/em&gt; gemacht. &lt;em&gt;QGIS&lt;/em&gt; verwende ich seit circa Version 0.7.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Benchmarking-Setup sah wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Server bei server4you.de (i5-4xxx irgendwas) mit SSD.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Daten und WMS-Server auf dem gleichen Server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Benchmarking-Tool: &lt;em&gt;jmeter&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drei Benchmarks:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AVWMS (bestehend aus Bodenbedeckung, Liegenschaften und Liegenschaftsnummern).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Orthofoto (Kantone BL, BS und nördlicher Teil von Kanton SO). Resampling &amp;laquo;nearest neighbour&amp;raquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Orthofoto (dito). Resampling &amp;laquo;average&amp;raquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pro Benchmarks jeweils 1, 2, 4, 8, 16, 32 und 64 parallele Requests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bei allen Tests jeweils drei Durchgänge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;MapServer&lt;/em&gt; (7.0.1) bewusst nur als CGI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;QGIS Server&lt;/em&gt; (master) als FCGI (&lt;code&gt;FcgidMaxProcesses 10&lt;/code&gt;, &lt;code&gt;FcgidMaxProcessesPerClass 10&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alle Einstellungen möglichst out-of-the-box.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Resultate habe ich jeweils in zwei Charts gepackt: Einmal den Throughput (Requests pro Sekunde) und einmal die maximale Antwortzeit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;AVWMS:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/avwms_req_per_sec.png&quot; alt=&quot;AVWMS requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/avwms_max_resp_time.png&quot; alt=&quot;AVWMS max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;QGIS Server&lt;/em&gt; ist in diesem Vergleich schneller. Auffallend ist aber das Hochschnellen der maximalen Antwortzeit wenn mehr als zehn gleichzeitige Requests gemacht werden. Als FCGI-Laie klingt das jetzt noch plausibel, da wir ja in den FCGI-Einstellungen eben nur maximal zehn Prozesse zulassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Orthofoto (&amp;laquo;nearest neighbour&amp;raquo;):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/ortho_req_per_sec.png&quot; alt=&quot;Orthofoto (nearest neighour) requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/ortho_max_resp_time.png&quot; alt=&quot;Orthofoto (nearest neighour) max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;MapServer&lt;/em&gt; ist hier deutlich vor &lt;em&gt;QGIS Server&lt;/em&gt;. Und auch hier wieder die relativ hohen maximalen Antwortzeiten von &lt;em&gt;QGIS Server&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Orthofoto (&amp;laquo;average&amp;raquo;):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/ortho_resampling_req_per_sec.png&quot; alt=&quot;Orthofoto (average) requests per second&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_mapserver/ortho_resampling_max_resp_time.png&quot; alt=&quot;Orthofoto (average) max response&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Überraschung, Überraschung: &lt;em&gt;QGIS Server&lt;/em&gt; liegt hier plötzlich vor &lt;em&gt;MapServer&lt;/em&gt;. Diesen Sachverhalt haben wir früher bereits mal - ohne Benchmarking - festgestellt. Ebenso hat &lt;em&gt;MapServer&lt;/em&gt; hier auch Probleme mit langen Antwortzeiten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einen klaren Gewinner gibt es mit diesem eher simplen Benchmarking nicht. Neben der puren Geschwindigkeit ist für uns auch wichtig, wie pflegeleicht die Software ist. Und da ist (für uns jedenfalls) &lt;em&gt;MapServer&lt;/em&gt;, der auch als purer CGI-Prozess gute Performance liefert, einfacher zu handhaben (&amp;laquo;fire and forget&amp;raquo;). Bei &lt;em&gt;QGIS Server&lt;/em&gt; haben wir bezüglich FCGI unsere Bedenken. Nicht, dass das per se schlecht wäre, nur haben wir da weniger (= kein) Know-How.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weitere Faktoren sind z.B. die kartografischen Möglichkeiten. Da hinkt &lt;em&gt;MapServer&lt;/em&gt; gefühlt Lichtjahre hinterher. Einfache Sachen sind anscheinend &lt;a href=&quot;http://lists.osgeo.org/pipermail/mapserver-users/2016-June/079079.html&quot;&gt;nicht möglich&lt;/a&gt;. Dass &lt;em&gt;QGIS&lt;/em&gt; nativ auch die Ankerpunkte gemäss INTERLIS (&amp;laquo;hali&amp;raquo; und &amp;laquo;vali&amp;raquo;) unterstützt und &lt;em&gt;MapServer&lt;/em&gt; nicht, ist nur noch ein kleines Detail. Handkehrum kann man mit &lt;em&gt;MapServer&lt;/em&gt; garantiert 99% sämtlicher Kartendarstellungen mehr als befriedigend lösen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Entscheide sind noch keine gefallen. Sollten aber bald.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #10</title>
      <link>http://blog.sogeo.services/blog/2016/06/13/interlis-leicht-gemacht-number-10.html</link>
      <pubDate>Mon, 13 Jun 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/06/13/interlis-leicht-gemacht-number-10.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;Ili2pg&lt;/em&gt;&lt;/a&gt; unterstützt neu drei Vererbungsstrategien. Vererbungsstrategien sind keine Erfindung von INTERLIS oder dergleichen, sondern so alt wie das O/R-Mapping selbst. Im Internet gibt es tonnenweise Material zum Nachlesen. Wie so oft, kann Wikipedia die erste &lt;a href=&quot;https://de.wikipedia.org/wiki/Objektrelationale_Abbildung&quot;&gt;Anlaufstelle&lt;/a&gt; sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit der Version &lt;a href=&quot;https://github.com/claeis/ili2db/commit/2cb22eb3fc4f719f27a94089694a5383017b46bb&quot;&gt;2.5.0&lt;/a&gt; unterstützt
&lt;em&gt;ili2pg&lt;/em&gt; zwei Vererbungsstrategien. Vorher kannte &lt;em&gt;ili2pg&lt;/em&gt; &amp;laquo;nur&amp;raquo; die sogenannte NewClass-Strategie. Dabei wird für jede Klasse (abstrakt und konkret) eine Tabelle in der Datenbank angelegt. Die zweite Vererbungsstrategie ist die sogenannte &amp;laquo;smarte&amp;raquo; Vererbung. Sie ist eine Mischung aus verschiedenen Strategien. Ganz neu in der Version &lt;a href=&quot;https://github.com/claeis/ili2db/commit/fb309f2a6b42d71663e78a525d39c65ce291e98d&quot;&gt;3.1.0&lt;/a&gt; ist eine weitere smarte Vererbungsstrategie hinzugekommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der &lt;a href=&quot;https://github.com/claeis/ili2db/blob/master/docs/ili2db.rst&quot;&gt;Dokumentation&lt;/a&gt; von &lt;em&gt;ili2db&lt;/em&gt; sind die Strategien erläutert. Anhand eines Beispieles zeige ich wie das konkret in der Datenbank aussieht. Als Beispiel-Modell verwende ich das gleiche Modell (&lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-10/Buildings_V1.uml&quot;&gt;uml&lt;/a&gt;, &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-10/Buildings_V1.ili&quot;&gt;ili&lt;/a&gt;), das bereits im &lt;a href=&quot;https://bitbucket.org/edigonzales/ili2pg_workshop&quot;&gt;ili2pg-Workshop&lt;/a&gt; verwendet wurde:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/Buildings_V1.png&quot; alt=&quot;UML-Diagramm Modell V1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bei der Klasse &lt;em&gt;Building&lt;/em&gt; handelt es sich um eine abstrakte Klasse. Die beiden Klassen &lt;em&gt;Apartments&lt;em&gt;_building&lt;/em&gt; und &lt;em&gt;Administrative&lt;/em&gt;_building&lt;/em&gt; sind daraus spezialisierte Klassen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst wird das INTERLIS-Modell mit der NewClass-Strategie in der Datenbank abgebildet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --dbhost 10.0.1.10 --dbdatabase rosebud2 --dbusr stefan --dbpwd ziegler12 --noSmartMapping --dbschema buildings_v1_nosmart --schemaimport --modeldir . --models Buildings_V1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Option &lt;code&gt;--noSmartMapping&lt;/code&gt; ist notwendig, weil &lt;em&gt;ili2pg&lt;/em&gt; standardmässig eine smarte Vererbungsstrategie wählt. In der Datenbank wird nun für &lt;strong&gt;jede&lt;/strong&gt; Klasse (sei sie abstrakt oder nicht) &lt;strong&gt;eine&lt;/strong&gt; Tabelle angelegt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/orm_nosmart_01.png&quot; alt=&quot;NewClass-Strategie 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Tabelle &lt;em&gt;apartments_building&lt;/em&gt; ist neben dem Primärschlüssel einzig das zusätzliche Attribut &lt;em&gt;apartments&lt;/em&gt; vorhanden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/orm_nosmart_02.png&quot; alt=&quot;NewClass-Strategie 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Verknüpfung zwischen der Tabelle &lt;em&gt;building&lt;/em&gt; und &lt;em&gt;apartments_building&lt;/em&gt; wird anhand des gleichen Primärschlüsselwertes gemacht. Mit der NewClass-Strategie verteilt sich ein INTERLIS-Objekt auf verschiedene Datenbank-Tabellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn man jetzt die smarte Vererbung mit folgendem Befehl anwendet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --dbhost 10.0.1.10 --dbdatabase rosebud2 --dbusr stefan --dbpwd ziegler12 --smart1Inheritance --dbschema buildings_v1_smart1 --schemaimport --modeldir . --models Buildings_V1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;werden die Klassen wie folgt abgebildet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/orm_smart1_01.png&quot; alt=&quot;Smart Mapping v1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die abstrakte Klasse &lt;em&gt;building&lt;/em&gt; wird nicht mehr in der Datenbank als Tabelle abgebildet. Sämtliche Eigenschaften der abstrakten Klassen sind jetzt als Attribute der spezialisierten Klassen resp. der Tabellen vorhanden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Beispiel-Modell wird nun um eine weitere Klasse erweitert (&lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-10/Buildings_V2.uml&quot;&gt;uml&lt;/a&gt;, &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-10/Buildings_V2.ili&quot;&gt;ili&lt;/a&gt;). Jedes Haus braucht einen oder mehrere Hausmeister:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/Buildings_V2.png&quot; alt=&quot;UML-Diagramm Modell V2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die abstrakte Klasse &lt;em&gt;building&lt;/em&gt; wird in diesem Fall von einer anderen Klasse referenziert. Wird das Modell mit der gleichen Option &lt;code&gt;--smart1Inheritance&lt;/code&gt; abgebildet, ergibt sich folgendes Bild:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/orm_smart1_02.png&quot; alt=&quot;Smart Mapping v1 Janitor&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &amp;laquo;Gebäude&amp;raquo;-Klassen werden jetzt mit einer SuperClass-Strategie abgebildet: sämtliche Informationen sind in einer einzigen &lt;em&gt;building&lt;/em&gt;-Tabelle vorhanden resp. müssen dort erfasst werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit der neusten Variante der Vererbung &lt;code&gt;--smart2Inheritance&lt;/code&gt; kann man aber wieder eine SubClass-Strategie fahren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p10/orm_smart2_01.png&quot; alt=&quot;Smart Mapping v2 Janitor&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum das jetzt alles? Warum diese verschiedenen Varianten? Der Benutzer soll eben wählen können, welche Strategie er für die Abbildung seines INTERLIS-Modells in der relationalen Datenbank wählt. Geht es um Datenerfassung in einer GIS-Anwendung, wählt er unter Umständen eine andere Variante als jemand, der bestehende Daten in ein anderes INTERLIS-Modell umbauen muss. Ein Anderer wiederum muss möglichst schnell Daten exportieren können. Für diesen Einsatzzweck sollte man wahrscheinlich die Variante mit möglichst wenig teuren Datenbankoperationen wählen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wichtig ist vorallem, dass der Anwender weiss, wie die Strategien funktionieren und wie &lt;em&gt;ili2pg&lt;/em&gt; die INTERLIS-Modelle in der Datenbank abgebildet.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #9</title>
      <link>http://blog.sogeo.services/blog/2016/05/30/interlis-leicht-gemacht-number-9.html</link>
      <pubDate>Mon, 30 May 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/05/30/interlis-leicht-gemacht-number-9.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie oft haben wir uns bereits einen Open-Source-INTERLIS-Checker gewünscht? Jahre darüber diskutiert wie es doch schön wäre einen mehr oder weniger plattformunabhängigen Checker zu haben, der auch noch frei verfügbar ist. Reichen würde uns zuerst auch ein &lt;a href=&quot;https://en.wikipedia.org/wiki/No_frills&quot;&gt;&amp;laquo;no-frills&amp;raquo;&lt;/a&gt; Checker. Also keine Mega-Super-Zusatzfunktionen, sondern in erster Priorität die Prüfung einer INTERLIS-Transferdatei (.xtf / .itf) gegenüber dem INTERLIS-Modell (.ili).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Solche Prüfungen werden bei &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;uns&lt;/a&gt; vermehrt eingesetzt und sind für die Qualitätsprüfung schlicht unverzichtbar. Eingesetzt werden solche Prüfungen z.B. bei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;den wöchenlichen Datenlieferungen der amtlichen Vermessung der Nachführungsgeometer an den Kanton.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;den AVGBS-Lieferungen der Nachführungsgeometer an das Grundbuch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;der Erfassung der Nutzungsplanung durch die Planungs- und Ingenieurbüros.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Neben der Plattformunabhängigkeit und freien Verfügbarkeit wünschten wir auch Folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Einbindung als Programmbibliothek soll möglich sein.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export der gefundenen Fehler in eine INTERLIS-Error-Datei und Log-Datei.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Einfaches GUI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;lokalisierbar (im Sinne einer Mehrsprachigkeit)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Erweiterbar um zusätzliche, eigene Tests / Bedingungen&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit den Grundanforderungen (Programmbibliothek, plattformunabhängig, Open Source) ist auch die Trennung zwischen (Weiter-)Entwicklung der eigentlichen Prüfsoftware und eines Check-Services einfach möglich. Mit der rosaroten Brille sehe ich z.B. bereits eine moderne M2M-Schnittstelle und eine saubere Benutzerverwaltung.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die pure Prüfung einer Transferdatei auf der Kommandozeile wäre circa so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ilichecker.jar --errXtf error.xtf --models SO_Nutzungsplanung_2016-02-03 2601_nplso.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie wenig es braucht, um einen kleinen Web-Service auf die Beine zu stellen, habe ich bereits &lt;a href=&quot;http://blog.sogeo.services/blog/2016/02/11/interlis-leicht-gemacht-number-7.html&quot;&gt;hier&lt;/a&gt; gezeigt. Ändern würde sich für den Checker nicht viel: einzig ein paar Zeilen Code und eine andere Klasse verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Soviel zum Wunschkonzert. Manchmal geht es dann aber schneller als gedacht und was lange währt wird hoffentlich auch gut. Mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; und &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2gpkg/&quot;&gt;&lt;em&gt;ili2gpkg&lt;/em&gt;&lt;/a&gt; haben der &lt;a href=&quot;http://geo.gl.ch&quot;&gt;Kanton Glarus&lt;/a&gt; und der &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;Kanton Solothurn&lt;/a&gt; in den letzten Jahren INTERLIS-Werkzeuge weiterentwickeln lassen, die sich hervorragend für den Formatumbau von/nach INTERLIS eignen (Stichwort &amp;laquo;generisches Schnittstellenwerkzeug&amp;raquo;). Diesen Schwung wollten wir mitnehmen und im Bereich der Datenprüfung etwas auf die Beine stellen. Dazu haben wir ein Konzept mit Spezifikation einer möglichen Prüfsoftware geschrieben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Spezifikation kann am Besten mit einem Beispiel-Modell illustriert werden (resp. das Beispiel-Modell ist die Spezifikation):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;INTERLIS 2.3;

CONTRACTED MODEL Beispiel1
  AT &quot;mailto:ceis@localhost&quot;
  VERSION &quot;2016-03-29&quot; =

  DOMAIN
    LKoord = COORD 2480000.00 .. 2850000.00, 1060000.00 .. 1320000.00,
      ROTATION 2 -&amp;gt; 1;


  TOPIC Bodenbedeckung =

    CLASS GebaeudeArt =
      Art : MANDATORY TEXT*6; !! R2.1, R2.2
    END GebaeudeArt;

    CLASS BoFlaechen =
      Art : MANDATORY ( !! R2.1, R2.2
        Gebaeude,
        befestigt,
        humusiert,
        Gewaesser,
        bestockt,
        vegetationslos);
      Form : MANDATORY AREA WITH (STRAIGHTS, ARCS) VERTEX LKoord
        WITHOUT OVERLAPS &amp;gt; 0.10; !! R2.1, R2.2
    END BoFlaechen;

    FUNCTION checkGebaeudeVersicherungsSystem(str : TEXT):BOOLEAN;

    CLASS Gebaeude =
      Art : MANDATORY (
        Wohn,
        Industrie,
        andere); !! R2.1, R2.2
      AndereArt : TEXT*6; !! R2.1, R2.2
      AssNr : MANDATORY TEXT*6; !! R2.1, R2.2
      UNIQUE AssNr;                                    !! R2.3
      EXISTENCE CONSTRAINT AndereArt REQUIRED IN GebaeudeArt:Art;    !! R2.4
      MANDATORY CONSTRAINT Art!=#andere OR DEFINED(AndereArt);       !! R2.5
      MANDATORY CONSTRAINT INTERLIS.len(AssNr)&amp;gt;=4;     !! R2.6
      MANDATORY CONSTRAINT checkGebaeudeVersicherungsSystem(AssNr);  !! R2.7
    END Gebaeude;

    ASSOCIATION GebaeudeFlaeche =
      Gebaeude -- {0..*} Gebaeude;                     !! R2.8, R2.9
      Flaeche -- {1} BoFlaechen;
    END GebaeudeFlaeche;

    VIEW IndustrieGebaeude                             !! R2.10
    	PROJECTION OF Gebaeude;
    	WHERE Gebaeude-&amp;gt;Art==#Industrie;
    =
      ALL OF Gebaeude;
      MANDATORY CONSTRAINT INTERLIS.len(AssNr)==6;
    END IndustrieGebaeude;

  END Bodenbedeckung;

END Beispiel1.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.1 / API&lt;/strong&gt;: Es stehen JAVA-Klassen und -Methoden zur Verfügung, welche die Prüfung der einzelnen INTERLIS-Objekte ermlicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.2 / error.xtf&lt;/strong&gt;: Das Prüfresultat wird in eine &lt;em&gt;error.xtf&lt;/em&gt;-Datei geschrieben. Die Datei entspricht einem durch den Checker fixen, definierten INTERLIS-Modell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.3 /Kommandozeile&lt;/strong&gt;: Es steht auf der Kommandozeile ein Standalone-Programm zur Verfügung: &lt;code&gt;java -jar ilichecker.jar &amp;#8230;&amp;#8203;&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.4 / GUI&lt;/strong&gt;: Einfaches GUI mit allen Möglichkeiten, die die Kommandozeile bietet (aber ohne Editor für Konfigurationsdateien).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.5 / Warnings per Checks&lt;/strong&gt;: Einzelne Checks (Attributekardinalität R2.1) lassen sich ausschalten und erscheinen dann als Warnings oder gar nicht im &lt;em&gt;error.xtf&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.6 / Warnings per Constraints&lt;/strong&gt;: Einzelne Constraints lassen sich ausschalten und erscheinen dann als Warnings oder gar nicht im &lt;em&gt;error.xtf&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.7 / eigene Fehlermeldungen&lt;/strong&gt;: Es lassen sich spezifische Fehlermeldungen zu einzelnen Constraints definieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.8 / Zusätzliche Constraints&lt;/strong&gt;: Zu einem bestehenden Modell lassen sich zusätzliche Constraints definieren (ausserhalb des Modells; via einfache VIEWs oder eigene Syntax; wird während der Realisierung definiert).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.9 / GUI und Fehlermeldungen lokalisierbar&lt;/strong&gt;: Die Fehlermeldungen und das GUI sind lokallisierbar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.10 / Daten lesen via IoxReader&lt;/strong&gt;: Der Checker soll zum Lesen der Daten ausschliesslich das Interface &lt;em&gt;IoxReader&lt;/em&gt; benutzen, so dass andere Formate einfach ergänzt werden können.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R1.11 /ILIGML, XTF, ITF&lt;/strong&gt;: Der Checker soll die aktuellen INTERLIS-Formate ITF, XTF und ILIGML unterstützen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.1 / Kardinalitaet von Attributen&lt;/strong&gt;:
Die Kardinalität von Attributen (&lt;code&gt;MANDATORY&lt;/code&gt;, &lt;code&gt;OPTIONAL&lt;/code&gt;, &lt;code&gt;{0..*}&lt;/code&gt; bei &lt;code&gt;BAG/LIST&lt;/code&gt;) wird geprüft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.2 / Datentyp von Attributen&lt;/strong&gt;:
Der Datentyp von Attributen wird geprüft (z.B. &lt;code&gt;0.0 .. 10.0&lt;/code&gt;) aber ohne die Zielklasse bei Referenzattributen (Teil von R2.8)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.3 / UniquenessConstraint&lt;/strong&gt;:
Eindeutigkeitsbedingung gemäss INTERLIS-Referenzhandbuch werden geprüft&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.4 / ExistenceConstraint&lt;/strong&gt;:
Existenzbedingung gemäss INTERLIS-Referenzhandbuch werden geprüft&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.5 / MandatoryConstraint, PlausibilityConstraint und SetContraint (ohne Funktionen)&lt;/strong&gt;:
inkl. &lt;code&gt;DEFINED&lt;/code&gt;, &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt;, &lt;code&gt;()&lt;/code&gt;, aber ohne Funktionen gemäss Modell INTERLIS (Anhang A des Referenzhandbuches)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.6 / Constraint mit Funktionen gemäss Anhang des INTERLIS Referenzhandbuchs&lt;/strong&gt;:
Funktionen gemäss Modell INTERLIS (Anhang A des Referenzhandbuches) werden geprüft&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.7 / Constraint mit eigenen Funktionen&lt;/strong&gt;:
Eigene INTERLIS-Funktionen können via einen einfachen Plugin-Mechanismus (die Funktion selbst muss in JAVA implementiert sein und kann via Checker-API auf die Daten zugreifen) hinzugefügt werden&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.8 / Zielklasse in ASSOCATION und in Referenzattributen&lt;/strong&gt;:
Es wird geprüft, ob das Zielobjekt existiert, und ob die Klasse des Objekts der Zielklasse gemäss Rolle oder Referenzattribut entspricht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.9 / Kardinalität in ASSOCIATION&lt;/strong&gt;:
Es wird geprüft, ob die Anzahl der in Beziehung stehenden Objekte den Rollendefinitionen entsprechen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;R2.10 / VIEWs&lt;/strong&gt;:
Als Basis für komplexe Constraints lassen sich VIEWs definieren. Diese werden durch den Checker auch ausgewertet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und nun zum wirklich guten Teil: Aufgrund einer glücklichen Fügung ist die Finanzierung des grössten Teils gesichert. Neben den Kantonen Glarus und Solothurn wird ein signifikanter Teil der Entwicklungskosten durch ein privates Ingenieurbüro übernommen. Einzig der ILIGML-Reader und R2.10 (VIEWs) können zum jetzigen Zeitpunkt noch nicht realisiert werden. Wer noch etwas beisteuern will, ist also gerne willkommen (auch in Zukunft). Ende Jahr sollte eine erste Version vollständig (bis auf ILIGML und VIEWs) vorhanden sein. Im September ist ein Zwischenrelease mit abgespecktem Funktionsumfang vorgesehen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ich denke, dass der Open-Source-INTERLIS-Checker viele Anwender finden und begeistern wird. Schon allein aufgrund des sehr interessanten Ansatzes wie eigene Tests / Prüfungen umgesetzt werden können (Plugin-Mechanismus und VIEWs) sowie der einfachen Einsetzbarkeit der Prüfsoftware (JAVA-Klasse).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #8</title>
      <link>http://blog.sogeo.services/blog/2016/05/29/interlis-leicht-gemacht-number-8.html</link>
      <pubDate>Sun, 29 May 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/05/29/interlis-leicht-gemacht-number-8.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für PostGIS-Anwender, die den &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/topics/survey/lv95/lv03-lv95.html&quot;&gt;Bezugsrahmenwechsel&lt;/a&gt; noch durchführen müssen, steht ja eine geniale &lt;a href=&quot;http://blog.sogeo.services/blog/2015/10/04/bezugsrahmenwechsel-st-fineltra-in-action.html&quot;&gt;Datenbankfunktion&lt;/a&gt; zur Verfügung, um die Daten wirklich innerhalb der Datenbank transfomieren zu können und nicht ein Drittwerkzeug bemühen zu müssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Transformationsgrundlage zwischen den beiden Bezugsrahmen dient die nationale Dreiecksvermaschung &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/topics/survey/lv95/lv03-lv95/chenyx06.html&quot;&gt;CHENyx06&lt;/a&gt;. Swisstopo stellt dafür eine DLL zur Verfügung. Die &lt;a href=&quot;https://github.com/strk/fineltra/&quot;&gt;PostGIS-Funktion&lt;/a&gt; kann aber nichts mit dieser DLL anfangen, sondern erfordert die Dreiecksdefinitionen in beiden Bezugsrahmen als Polygon in einer einzigen Datenbanktabelle (was nebenbei bemerkt die Funktion enorm flexibel macht). Leider ist die Definition dieser Dreiecksvermaschung nirgends auffindbar. Ich habe einzig eine &lt;a href=&quot;https://www.dropbox.com/s/8mphf6c912ha1z1/chenyx06.sqlite?dl=0&quot;&gt;Spatialite-Datenbank&lt;/a&gt;, die ich aus Shape-Dateien zusammengeschustert habe, die ich vor Jahren bei swisstopo bestellt habe. Wäre die Definition nicht sogar ein &lt;a href=&quot;https://www.admin.ch/opc/de/classified-compilation/20071096/index.html#a4&quot;&gt;Geobasisdatensatz&lt;/a&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Egal. Um aber die Dreiecksdefinition vom Spatialite-Joch zu befreien und den Import in die PostGIS-Datenbank vermeintlich einfacher zu gestalten, habe ich mir kurzerhand ein klitzekleines INTERLIS-Datenmodell zusammengebaut:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p8/uml.png&quot; alt=&quot;UML-Diagramm&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Datenumbau von der bereits bestehenden Datenbanktabelle (&lt;a href=&quot;http://blog.sogeo.services/blog/2015/10/04/bezugsrahmenwechsel-st-fineltra-in-action.html&quot;&gt;resultierend&lt;/a&gt; aus einem &lt;em&gt;ogr2ogr&lt;/em&gt;-Import der Spatialite-Datenbank nach &lt;em&gt;PostGIS&lt;/em&gt;) in die leere mit &lt;em&gt;ili2pg&lt;/em&gt; angelegten Datenbanktabelle ist simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;INSERT INTO av_chenyx06.chenyx06_chenyx06 (nummer, nom, jahr_definiert, jahr_eliminiert, geom_lv03, geom_lv95)
SELECT num, nom, to_date(jahr_def::varchar, &apos;YYYY&apos;), NULL::date AS jahr_elim,
       ST_SnapToGrid(the_geom_lv03, 0.001),
       ST_SnapToGrid(the_geom_lv95, 0.001)
FROM av_chenyx06.chenyx06_triangles;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die UML-Datei gibt es &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-8/SO_CHENyx06_2016-05-27.uml&quot;&gt;hier&lt;/a&gt;, das INTERLIS-Modell  &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-8/SO_CHENyx06_2016-05-27.ili&quot;&gt;hier&lt;/a&gt; und die exportierte INTERLIS-Transferdatei &lt;a href=&quot;http://blog.sogeo.services/data/interlis-leicht-gemacht-number-8/SO_CHENyx06.xtf&quot;&gt;hier&lt;/a&gt;. Es wurde bewusst nur eine Tabelle mit zwei Geometriespalten modelliert, da ja genau diese Konstellation von der ST_Fineltra-Funktion erwartet wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ohne gross zu überlegen, kann man jetzt mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;&lt;em&gt;ili2pg&lt;/em&gt;&lt;/a&gt; die INTERLIS-Datei in die PostgreSQL-Datenbank importieren:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --dbhost localhost --dbport 5432 --dbdatabase rosebud2 --dbusr stefan --dbpwd ziegler12 --defaultSrsAuth EPSG --defaultSrsCode  21781 --createGeomIdx --strokeArcs --createUnique --nameByTopic --dbschema av_chenyx06 --modeldir &quot;http://models.geo.admin.ch;.&quot; --models SO_CHENyx06_20160527 --import SO_CHENyx06.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Da sollten jetzt zwei Optionen ins Auge stechen: &lt;code&gt;--defaultSrsAuth&lt;/code&gt; resp. &lt;code&gt;--defaultSrsCode&lt;/code&gt;. Damit geben wir der Datenbank an, um welches Bezugssystem es sich handelt. In unserem Fall haben wir nun das Problem, das wir zwei unterschiedliche Bezugssysteme in einer einzigen Tabelle im INTERLIS-Modell haben. Dh. &lt;em&gt;ili2pg&lt;/em&gt; importiert beiden Geometrien als LV03:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p8/pgadmin_01.png&quot; alt=&quot;LV95-Geometrie in LV03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch die einzelnen Geometrien selbst sind als LV03 codiert. Folgende Abfrage&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT ST_AsEWK(geom_lv95) FROM av_chenyx06.chenyx06_chenyx06 LIMIT 1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ergibt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&quot;SRID=21781;POLYGON((2619108.994 1266953.473,2624004.381 1265802.492,2619830.525 1263811.084,2619108.994 1266953.473))&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;PostgreSQL/PostGIS&lt;/em&gt; wäre nicht &lt;em&gt;PostgreSQL/PostGIS&lt;/em&gt; wenn die Rettung nicht bloss ein Einzeiler wäre:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;ALTER TABLE av_chenyx06.chenyx06_chenyx06
  ALTER COLUMN geom_lv95 TYPE geometry(Polygon, 2056)
    USING ST_SetSRID(geom_lv95, 2056);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser Einzeiler setzt einerseits die korrekte SRID für die Tabelle als auch für jedes einzelne Feature ohne irgendetwas zu transformieren. Das Resultat der vorherigen Query ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&quot;SRID=2056;POLYGON((2619108.994 1266953.473,2624004.381 1265802.492,2619830.525 1263811.084,2619108.994 1266953.473))&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und die Definition der Tabelle sieht jetzt wie gewünscht aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p8/pgadmin_02.png&quot; alt=&quot;LV95-Geometrie in LV95&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Problem mit den unterschiedlichen Bezugssystemen tauchte natürlich bereits beim Herstellen der INTERLIS-Transferdatei auf. Dabei wurde die leere Tabelle mit &lt;code&gt;--schemaimport&lt;/code&gt; angelegt. Das Vorgehen war anschliessend identisch.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #7</title>
      <link>http://blog.sogeo.services/blog/2016/02/11/interlis-leicht-gemacht-number-7.html</link>
      <pubDate>Thu, 11 Feb 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2016/02/11/interlis-leicht-gemacht-number-7.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit &lt;a href=&quot;https://github.com/claeis/ili2db/commit/85d167e0c4fd491567cd8e8bab3bd9f8e7a85eed&quot;&gt;kurzem&lt;/a&gt; gibt es neu auch &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2gpkg/&quot;&gt;ili2gpkg&lt;/a&gt;. Damit kann aus einer INTERLIS-Transferdatei schnell und ohne eine PostgreSQL-Datenbank am Laufen zu haben eine GeoPackage-Datei erstellt werden. Diese kann dann in &lt;a href=&quot;http://www.qgis.org&quot;&gt;QGIS&lt;/a&gt; oder ArcGIS visualisert und bearbeitet werden. Der Befehl zum Erstellen des GeoPackages ist praktisch identisch dem Befehl zum Importieren in eine PostGIS-Datenbank:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2gpkg.jar --import --nameByTopic --modeldir http://models.geo.admin.ch --models DM01AVCH24D --dbfile av_solothurn.gpkg ch_260100.itf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Prinzip stehen die gleichen Optionen wie bei &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; zur Verfügung. Einige gibt es natürlich nicht, z.B. die DB-Connection-Parameter. Die werden einfach durch den Filenamen-Parameter ersetzt: &lt;code&gt;--dbfile&lt;/code&gt;. Natürlich ist man dabei nicht nur auf das Erstellen (= Lesen von INTERLIS) von GeoPackage-Dateien beschränkt. Man kann ebenso aus GeoPackage-Dateien INTERLIS-Transfer-Dateien erstellen. Dazu in einem späteren Beitrag mehr.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Anwendungsmöglichkeit unter vielen ist z.B. ein kleiner Webdienst, wo man eine INTERLIS-Transferdatei hochladen kann und als Antwort die GeoPackage-Datei bekommt. Klassischerweise würde man hier ein Java-Servlet schreiben. Fürs Prototyping erstelle ich aber ein &lt;a href=&quot;http://docs.groovy-lang.org/latest/html/documentation/servlet-userguide.html&quot;&gt;Groovlet&lt;/a&gt;. Man kann sich das auf dem Web/Servlet-Container so einrichten, dass alle in einem Ordner liegende &lt;code&gt;*.groovy&lt;/code&gt;-Dateien als Servlet resp. eben als Groovlet ausgeführt werden. Zum Rumspielen noch ganz praktisch.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes brauchen wir das Upload-Formular. Dazu reichen ein paar Zeichen Groovy, das uns das benötigte HTML erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;html.html {
    head {
        meta(charset:&apos;utf-8&apos;)
        meta(name:&apos;viewport&apos;, content:&apos;width=device-width, initial-scale=1&apos;)
        meta(&apos;http-equiv&apos;:&apos;x-ua-compatible&apos;, content:&apos;ie=edge&apos;)

        title &apos;ili2gpkg online converter&apos;
        style &apos;&apos;&apos;
            label { display: block; padding: 0.2em; }
        &apos;&apos;&apos;
    }
    body {
        h1 &apos;INTERLIS (ITF/XTF) to GeoPackage Converter&apos;
        form action: &apos;do_ili2gpkg.groovy&apos;, method: &apos;post&apos;, enctype: &apos;multipart/form-data&apos;, {
            label &apos;Reference frame: &apos;, {
                select name: &apos;reference_frame&apos;, {
                    option &apos;LV03&apos;
                    option &apos;LV95&apos;
                }
            }
            label &quot;--strokeArcs&quot;, {
                input type: &apos;checkbox&apos;, checked: &apos;checked&apos;, name: &apos;strokeArcs&apos;, id: &apos;strokeArcs&apos;
            }
            label &quot;--skipPolygonBuilding&quot;, {
                input type: &apos;checkbox&apos;, name: &apos;skipPolygonBuilding&apos;, id: &apos;skipPolygonBuilding&apos;
            }
            label &quot;--nameByTopic&quot;, {
                input type: &apos;checkbox&apos;, checked: &apos;checked&apos;, name: &apos;nameByTopic&apos;, id: &apos;nameByTopic&apos;
            }
            label &apos;File: &apos;, {
                input type: &apos;file&apos;, name: &apos;file&apos;
            }
            input type: &apos;submit&apos;, name: &apos;submit&apos;, value: &apos;Send to server&apos;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das sind ganz schön nach 90er-Jahre aus, aber es fehlt schliesslich auch jegliches Styling:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p7/ili2gpkg_html_form.png&quot; alt=&quot;ili2gpkg upload formular&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Von den unzähligen Programmoptionen lassen wir nur vier zu, die der Anwender via Webformular auswählen kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--defaultSrsCode&lt;/code&gt;: Entweder LV03 oder LV95.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--skipPolygonBuilding&lt;/code&gt;: Falls gewünscht, wird die Flächenbildung für Polygone nicht gemacht und es bleiben &amp;laquo;Spaghetti&amp;raquo;-Daten. Dies kann sehr praktisch für die Verifikation von Datenlieferungen sein. Bei der Flächenbildung &lt;a href=&quot;http://www.sogeo.services/blog/2015/10/03/interlis-leicht-gemacht-number-5.html&quot;&gt;bereinigt&lt;/a&gt; ili2gpkg zulässige Overlaps, um OGC-konforme Geometrien zu erhalten. Um jedoch wirklich die Original-Geometrien zu erhalten und diese bei Grenzfällen besser beurteilen zu können, lohnt es sich manchmal nur die Linien zu betrachten.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--strokeArcs&lt;/code&gt;: Kreisbogen werden segmentiert.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--nameByTopic&lt;/code&gt;: Die Tabellen in der GeoPackage-Datenbank werden nach dem Muster &lt;code&gt;Topicname_Classname&lt;/code&gt; benannt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie man dem Groovy-Skript resp. der HTML-Datei entnehmen kann, ist &lt;code&gt;do_ili2gpkg.groovy&lt;/code&gt; das Groovy-Skript, das beim Senden aufgerufen wird. Dieses übernimmt die eigentliche Umwandlung INTERLIS &amp;#8594; GeoPackage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;@Grapes([
   @GrabResolver(name=&apos;catais.org&apos;, root=&apos;http://www.catais.org/maven/repository/release/&apos;, m2Compatible=&apos;true&apos;),
   @Grab(group=&apos;commons-fileupload&apos;, module=&apos;commons-fileupload&apos;, version=&apos;1.3.1&apos;),
   @Grab(group=&apos;commons-io&apos;, module=&apos;commons-io&apos;, version=&apos;2.4&apos;),
   @Grab(group=&apos;org.xerial&apos;, module=&apos;sqlite-jdbc&apos;, version=&apos;3.8.11.2&apos;),
   @Grab(&apos;ch.interlis:ili2c:4.5.21&apos;),
   @Grab(&apos;ch.interlis:ili2gpkg:3.0.0&apos;),
   //@GrabConfig(systemClassLoader = true)
])

import javax.servlet.http.HttpServletResponse
import javax.servlet.ServletOutputStream
import java.util.logging.Logger
import java.nio.file.Path
import java.nio.file.Files
import org.apache.commons.io.IOUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.fileupload.FileItem
import org.apache.commons.fileupload.util.Streams
import org.apache.commons.fileupload.servlet.ServletFileUpload
import org.apache.commons.fileupload.disk.DiskFileItemFactory
import org.apache.commons.fileupload.FileUploadException
import ch.ehi.ili2db.base.Ili2db
import ch.ehi.ili2db.base.Ili2dbException
import ch.ehi.ili2db.gui.Config
import ch.ehi.ili2db.mapping.NameMapping
import ch.ehi.basics.logging.EhiLogger

Logger logger = Logger.getLogger(&quot;do_ili2gpkg.groovy&quot;)
logger.setUseParentHandlers(true)
logger.info (&quot;Starts at: &quot; + new Date())

if (ServletFileUpload.isMultipartContent(request)) {
    ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory())
    //upload.setSizeMax(52428800) // 50MB
    upload.setSizeMax(2*5242880) // 2*5MB

    List&amp;lt;FileItem&amp;gt; items = null

    try {
        items = upload.parseRequest(request);
    } catch (FileUploadException e) {
        logger.severe e.getMessage()
        response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage())
        return
    }

    for (item in items) {
        if (item.isFormField()) { // &apos;normal&apos; form fields
            String fieldName = item.getFieldName()
            String value = item.getString()

            params[fieldName] = value
            logger.info fieldName.toString()
            logger.info value.toString()
            logger.info item.getClass().toString()

        } else { // files
            String fieldName = item.getFieldName()
            String fileName = FilenameUtils.getName(item.getName())

            if (fileName.equalsIgnoreCase(&quot;&quot;)) {
                // return &apos;bad request&apos; (400) if no file was sent
                String errorMessage = &quot;No file chosen.&quot;
                logger.severe errorMessage
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, errorMessage)
                return
            }

            // get the file as input stream
            InputStream fileContent = item.getInputStream()

            // create random temporary directory
            String tmpDirPrefix = &quot;ili2gpkg_&quot;;
            Path tmpDir = Files.createTempDirectory(tmpDirPrefix);

            // copy input stream into target file
            String targetFileName = fileName
            File targetFile = new File(tmpDir.toString() + File.separator + targetFileName)

            try {
                FileUtils.copyInputStreamToFile(fileContent, targetFile)
                logger.info &quot;Uploaded file: &quot; + targetFile.toString()
                logger.info &quot;Uploaded file size [KB]: &quot; + (int) (targetFile.length() / 1024)
            } catch (java.io.IOException e) {
                FileUtils.deleteDirectory(tmpDir.toFile())

                logger.severe e.getMessage()
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage())
                return
            }

            // create configuration for ili2gpkg
            def config = initConfig(params)

            // Set the name of the geopackage.
            // Same name as input file but with *.gpkg extension instead.
            String gpkgFileName = FilenameUtils.removeExtension(targetFileName) + &quot;.gpkg&quot;
            gpkgFullFileName = tmpDir.toString() + File.separator + gpkgFileName
            config.setDbfile(gpkgFullFileName)
            config.setDburl(&quot;jdbc:sqlite:&quot;+config.getDbfile())
            config.setXtffile(targetFile.toString())

            String fileExtension = FilenameUtils.getExtension(targetFileName)
            System.out.println(fileExtension)
            if (fileExtension.equalsIgnoreCase(&quot;itf&quot;)) {
                config.setItfTranferfile(true)
            }

            // Now create the GeoPackage.
            try {
                //EhiLogger.getInstance().setTraceFilter(false)
                Ili2db.runImport(config, &quot;&quot;)
            } catch (Ili2dbException e) {
                logger.severe e.getMessage()
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage())
                return
            }

            // Send GeoPackage to browser.
            response.setContentType(&quot;application/x-sqlite3&quot;)
            response.setHeader(&quot;Content-Disposition&quot;, &quot;attachment; filename=&quot; + gpkgFileName);
            ServletOutputStream os = response.getOutputStream();
            FileInputStream fis = new FileInputStream(gpkgFullFileName);
            try {
                int buffSize = 1024
                byte[] buffer = new byte[buffSize]
                int len
                while ((len = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, len)
                    os.flush()
                    response.flushBuffer()
                }
            } catch (Exception e) {
                logger.severe e.getMessage()
            }
            finally {
                FileUtils.deleteDirectory(tmpDir.toFile())
            }
        }
    }
}

def initConfig(params) {
    def config = new Config()
    config.setModeldir(&quot;http://models.geo.admin.ch/;http://models.geo.gl.ch/;http://www.catais.org/models&quot;)
    config.setModels(Ili2db.XTF)
    config.setSqlNull(&quot;enable&quot;);
    config.setDefaultSrsAuthority(&quot;EPSG&quot;);
    config.setDefaultSrsCode(&quot;21781&quot;);
    config.setMaxSqlNameLength(Integer.toString(NameMapping.DEFAULT_NAME_LENGTH));
    config.setIdGenerator(ch.ehi.ili2db.base.TableBasedIdGen.class.getName());
    config.setInheritanceTrafo(config.INHERITANCE_TRAFO_SMART1);
    config.setCatalogueRefTrafo(Config.CATALOGUE_REF_TRAFO_COALESCE);
    config.setMultiSurfaceTrafo(Config.MULTISURFACE_TRAFO_COALESCE);
    config.setMultilingualTrafo(Config.MULTILINGUAL_TRAFO_EXPAND);

    config.setGeometryConverter(ch.ehi.ili2gpkg.GpkgColumnConverter.class.getName());
    config.setDdlGenerator(ch.ehi.sqlgen.generator_impl.jdbc.GeneratorGeoPackage.class.getName());
    config.setJdbcDriver(&quot;org.sqlite.JDBC&quot;);
    config.setIdGenerator(ch.ehi.ili2db.base.TableBasedIdGen.class.getName());
    config.setIli2dbCustomStrategy(ch.ehi.ili2gpkg.GpkgMapping.class.getName());
    config.setOneGeomPerTable(true);

    for (param in params) {
        def key = param.key

        if (key.equalsIgnoreCase(&quot;strokeArcs&quot;)) {
            config.setStrokeArcs(config.STROKE_ARCS_ENABLE)
        }

        if (key.equalsIgnoreCase(&quot;nameByTopic&quot;)) {
            config.setNameOptimization(config.NAME_OPTIMIZATION_TOPIC)
        }

        if (key.equalsIgnoreCase(&quot;skipPolygonBuilding&quot;)) {
            config.setDoItfLineTables(true);
            config.setAreaRef(config.AREA_REF_KEEP);
        }

        if (key.equalsIgnoreCase(&quot;reference_frame&quot;)) {
            if (param.value.equalsIgnoreCase(&quot;LV95&quot;)) {
                config.setDefaultSrsCode(&quot;2056&quot;);
            }
        }
    }
    return config
}

logger.info (&quot;Stops at: &quot; + new Date())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 1 - 9&lt;/strong&gt;: Die notwendigen Bibliotheken werden mittels &lt;a href=&quot;http://docs.groovy-lang.org/latest/html/documentation/grape.html&quot;&gt;Grape&lt;/a&gt; einmalig heruntergeladen. Die &lt;code&gt;*.jar&lt;/code&gt;-Dateien landen dann im &lt;code&gt;.groovy/grapes/&lt;/code&gt;-Verzeichnis des Apache-Tomcat-Users und nicht etwa im &lt;code&gt;lib&lt;/code&gt;-Verzeichnis von Apache-Tomcat selbst. Der Befehl &lt;code&gt;@GrabConfig(systemClassLoader = true)&lt;/code&gt; scheint nicht notwendig zu sein, wenn das Skript als Groovlet in Apache Tomcat läuft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 11 - 28&lt;/strong&gt;: Notwendige Imports werden gemacht. Für den File-Upload verwenden wir die Apache-Commons-Bibliotheken. Ab &lt;a href=&quot;http://docs.oracle.com/javaee/6/tutorial/doc/glrbb.html&quot;&gt;Servlet-Spezifikation 3.0&lt;/a&gt; gibt es dafür native Unterstützung. Heruntergebrochen auf ein Groovlet habe ich das aber nicht zum Laufen gebracht. Daher wird hier wieder die old-school-Methode verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 37&lt;/strong&gt;: Die maximale Grösse der Upload-Datei ist auf 10MB beschränkt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 49 - 58&lt;/strong&gt;: &amp;laquo;Normale&amp;raquo; Parameter aus dem HTML-Formular werden in einer &lt;code&gt;Map&lt;/code&gt; gespeichert. Sie werden später für die Konfiguration von ili2gpkg verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 59 - 92&lt;/strong&gt;: Die gelieferte Datei wird entgegengenommen und in einem temporären Verzeichnis gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 99 - 119&lt;/strong&gt;: Hier findet die eigentliche Umwandlung der INTERLIS-Transferdatei in die GeoPackage-Datei statt. Die &lt;code&gt;Config&lt;/code&gt;-Klasse wird mit den übermittelten Parameter aus dem HTML-Formular in einer separaten Methode konfiguriert. Ein INTERLIS-Modell oder ein -Repository kann nicht übermittelt werden. Das Modell wird aus der Transferdatei selber ermittelt (Zeile 148) und in den drei hardcodierten Repositories gesucht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 121 - 140&lt;/strong&gt;: Zu guter Letzt wird die GeoPackage-Datei an den Browser zurückgesendet. Der Benutzer muss sie nur noch speichern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p7/ili2gpkg_save_as.png&quot; alt=&quot;ili2gpkg download&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sieht genauso aus wie wir es von ili2pg gewohnt sind. Nur halt in einer GeoPackage-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p7/ili2gpkg_sqliteman.png&quot; alt=&quot;ili2gpkg sqliteman&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschauen kann man sich das Resultat anschliessend in QGIS:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p7/ili2gpkg_qgis.png&quot; alt=&quot;ili2gpkg qgis&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Achtung&lt;/strong&gt;: ili2gpkg setzt den &lt;em&gt;Layer-Extent&lt;/em&gt; gemäss dem INTERLIS-Modell. Beim CH-Modell der amtlichen Vermessung entspricht das der Bounding-Box der gesamten Schweiz. Wenn man in QGIS &amp;laquo;Zoom to Layer Extent&amp;raquo; wählt, zoomt QGIS auf die gesamte Schweiz. Einerseits verwirrend, andererseits eigentlich korrekt. Aber darüber kann man sich sicher lange unterhalten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In Apache Tomcat sind drei Anpassungen vorzunehmen. Einerseits muss konfiguriert werden, dass alle &lt;code&gt;*.groovy&lt;/code&gt;-Dateien eines Verzeichnisses als Groovlet ausgeführt werden. Dies wird in der &lt;code&gt;web.xml&lt;/code&gt;-Datei gemacht (im Root-Element):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;Groovy&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;groovy.servlet.GroovyServlet&amp;lt;/servlet-class&amp;gt;
&amp;lt;/servlet&amp;gt;

&amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;Groovy&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;*.groovy&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zudem wollen wir dieses Verzeichnis an einem beliebigen Ort im Filesystem haben und nicht unterhalb des Tomcat-Installationsverzeichnisses. Dazu setzen wir einen &amp;laquo;Context Path&amp;raquo; in der Datei &lt;code&gt;server.xml&lt;/code&gt; unter &lt;code&gt;&amp;lt;Host&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;Host name=&quot;localhost&quot;  appBase=&quot;webapps&quot;
      unpackWARs=&quot;true&quot; autoDeploy=&quot;true&quot;&amp;gt;

  &amp;lt;!-- SingleSignOn valve, share authentication between web applications
       Documentation at: /docs/config/valve.html --&amp;gt;
  &amp;lt;!--
  &amp;lt;Valve className=&quot;org.apache.catalina.authenticator.SingleSignOn&quot; /&amp;gt;
  --&amp;gt;

  &amp;lt;!-- Access log processes all example.
       Documentation at: /docs/config/valve.html
       Note: The pattern used is equivalent to using pattern=&quot;common&quot; --&amp;gt;
  &amp;lt;Valve className=&quot;org.apache.catalina.valves.AccessLogValve&quot; directory=&quot;logs&quot;
         prefix=&quot;localhost_access_log&quot; suffix=&quot;.txt&quot;
         pattern=&quot;%h %l %u %t &amp;amp;quot;%r&amp;amp;quot; %s %b&quot; /&amp;gt;

 &amp;lt;!-- Groovlets --&amp;gt;
 &amp;lt;Context path=&quot;/groovy/ili2gpkg&quot; docBase=&quot;/home/stefan/Projekte/ili2gpkg_service/scripts&quot; reloadable=&quot;true&quot;/&amp;gt;

&amp;lt;/Host&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einzig die Zeilen 17 und 18 stammen von mir, alles Andere sind Default-Einstellungen. Der Eintrag bewirkt jetzt, dass der Request &lt;a href=&quot;http://&amp;#8230;&amp;#8203;/groovy/ili2gpkg/mein_skript.groovy&quot; class=&quot;bare&quot;&gt;http://&amp;#8230;&amp;#8203;/groovy/ili2gpkg/mein_skript.groovy&lt;/a&gt; (Attribut &lt;code&gt;path&lt;/code&gt;) im Verzeichnis gemäss dem Attribut &lt;code&gt;docBase&lt;/code&gt; das Groovy-Skript &lt;code&gt;mein_skript.groovy&lt;/code&gt; sucht und, sofern vorhanden, ausführt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als letztes muss noch die &lt;code&gt;groovy-all-x.y.z.jar&lt;/code&gt; in das &lt;code&gt;lib&lt;/code&gt;-Verzeichnis von Apache Tomcat kopiert werden. Dieses Groovy-&amp;laquo;Sorglos&amp;raquo;-Paket liegt dem &lt;a href=&quot;http://groovy-lang.org/download.html&quot;&gt;Download&lt;/a&gt; bei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;del&gt;Ein Live-Beispiel gibt es &lt;a href=&quot; http://www.sogeo.services/groovy/ili2gpkg/ili2gpkg.groovy&quot;&gt;hier&lt;/a&gt;(mspublic / mspublic).&lt;/del&gt; Ein erweitertes Beispiel findet sich &lt;a href=&quot;http://sogeo.services/ili2gpkg&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #6</title>
      <link>http://blog.sogeo.services/blog/2015/11/28/interlis-leicht-gemacht-number-6.html</link>
      <pubDate>Sat, 28 Nov 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/11/28/interlis-leicht-gemacht-number-6.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;a href=&quot;http://www.cadastre.ch/internet/kataster/de/home/av.html&quot;&gt;Eidgenössische Vermessungsdirektion&lt;/a&gt; aggregiert verschiedene Metadaten der amtlichen Vermessung. Dazu zähle ich:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://models.geo.admin.ch/V_D/LowDistortionAreas_LV95_ili2.ili&quot;&gt;Spannungsarme Gebiete&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://models.geo.admin.ch/V_D/AMO_Grafik_LV95_e.ili&quot;&gt;Stand der amtlichen Vermessung&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://models.geo.admin.ch/V_D/AMO_Grafik_LV95_PNF.ili&quot;&gt;Stand der Periodischen Nachführung&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Daten werden von den Kantonen an die V+D geliefert und anschliessend werden sie publiziert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;iframe src=&apos;https://map.geo.admin.ch/embed.html?topic=ech&amp;lang=de&amp;bgLayer=ch.swisstopo.swissimage&amp;layers_opacity=0.75,0.75,0.75&amp;X=249600.00&amp;Y=628300.00&amp;zoom=2&amp;layers=ch.swisstopo-vd.spannungsarme-gebiete,ch.swisstopo-vd.geometa-periodische_nachfuehrung,ch.swisstopo-vd.geometa-standav&amp;layers_visibility=false,true,false&apos; width=&apos;100%&apos; height=&apos;500&apos; frameborder=&apos;0&apos; style=&apos;border:0&apos;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &amp;laquo;Stand der amtlichen Vermessung&amp;raquo; war der erste Metadatensatz, den die Kantone liefern mussten. Das &lt;a href=&quot;http://models.geo.admin.ch/V_D/AMO_Grafik_LV95_e.ili&quot;&gt;dazugehörige INTERLIS-Modell&lt;/a&gt; ist einfach und besteht aus zwei Tabellen: &lt;code&gt;Actual_Status&lt;/code&gt; und &lt;code&gt;Actual_Status_Geometry&lt;/code&gt;. Die Herausforderung zu diesem Zeitpunkt war aber: Wie kriege ich meine Daten in eine INTERLIS-Transferdatei? Die Lösung war dann nicht so prickelnd: Ich schrieb einen 1:1-Prozessor, der mir die Transferdatei erstellt. Ein Geknorze sondergleichen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Heute geht das mit &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; und &lt;a href=&quot;http://www.qgis.org&quot;&gt;QGIS&lt;/a&gt; viel leichter und eleganter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Schema mit Tabellen in PostgreSQL/Postgis anlegen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Daten erfassen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Daten exportieren&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Schritte (1) und (3) übernimmt ili2pg für uns. Die Daten erfassen müssen wir schon noch selber. Mit &lt;a href=&quot;http://www.qgis.org&quot;&gt;QGIS&lt;/a&gt; macht aber auch diese Arbeit Spass und geht leicht von der Hand. QGIS hat sehr mächtige &lt;a href=&quot;http://docs.qgis.org/2.8/en/docs/user_manual/working_with_vector/vector_properties.html#fields-menu&quot;&gt;Formularfunktionen&lt;/a&gt; und man kann sich damit so etwas wie eine &amp;laquo;Fachschale&amp;raquo; zusammenklicken.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den Datensatz &amp;laquo;Stand der Periodischen Nachführung&amp;raquo; soll nun der ganze Prozess exemplarisch durchgespielt werden. Zuerst legen wir mit ili2pg die leeren Tabellen in der Datenbank an:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --schemaimport --dbhost localhost --dbport 5432 --dbdatabase rosebud2 --dbusr stefan --dbpwd ziegler12 --defaultSrsAuth EPSG --defaultSrsCode 2056 --createGeomIdx --createEnumTabs --nameByTopic --strokeArcs --dbschema av_pnf_stand_amo --modeldir http://models.geo.admin.ch --models AMO_Grafik_LV95_PNF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit den Optionen &lt;code&gt;--defaultSrsAuth EPSG&lt;/code&gt; und &lt;code&gt;--defaultSrsCode 2056&lt;/code&gt; wird ili2pg mitgeteilt, dass die Geometrie-Tabellen in der Datenbank im Bezugsrahmen LV95 angelegt werden sollen. &lt;code&gt;--createEnumTabs&lt;/code&gt; erstellt für Aufzähltypen eine eigene Tabelle. Diese Aufzähltyp-Tabellen sind später wichtig für die Arbeit mit QGIS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sieht relativ unspektakulär aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_pgadmin01.png&quot; alt=&quot;ili2pg schemaimport&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es gibt zwei Tabellen im Modell resp. in der Datenbank: &lt;code&gt;pnf_pnf&lt;/code&gt; und &lt;code&gt;pnf_pnf_geometry&lt;/code&gt;. Die erste Tabelle beinhaltet alle Sachattribute, die zweite die Geometrie dazu. Zusätzlich wird in der Datenbank für jeden Aufzähltyp eine Tabelle angelegt. Der Inhalt der Tabelle &lt;code&gt;amo_grafik_lv95_pnf_cantons&lt;/code&gt; sieht - wenig überraschend - so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_pgadmin02.png&quot; alt=&quot;Aufzähltypen Kantone&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Gute an ili2pg ist, dass man nachträglich auch ein paar Veränderungen/Verfeinerungen in den Datenbanktabellen vornehmen darf, die dem Datenerfasser das Leben erleichtern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Modell gibt vor, dass die Kombination der Attribute &lt;code&gt;canton&lt;/code&gt; und &lt;code&gt;id&lt;/code&gt; eindeutig sein muss. In der Datenbanktabelle fügen wir  einen UNIQUE-Constraint hinzu:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;ALTER TABLE av_pnf_stand_amo.pnf_pnf ADD CONSTRAINT pnf_pnf_ili_unique UNIQUE (canton, id);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Des Weiteren erstellen wir zwei Sequenzen, damit der Primary Key in den Tabellen automatisch nachführt wird:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;-- sequence for t_id &quot;pnf_pnf&quot;
CREATE SEQUENCE av_pnf_stand_amo.pnf_t_id_seq;
ALTER TABLE av_pnf_stand_amo.pnf_pnf ALTER COLUMN t_id SET DEFAULT nextval(&apos;av_pnf_stand_amo.pnf_t_id_seq&apos;);
ALTER TABLE av_pnf_stand_amo.pnf_pnf ALTER COLUMN t_id SET NOT NULL;
ALTER SEQUENCE av_pnf_stand_amo.pnf_t_id_seq OWNED BY av_pnf_stand_amo.pnf_pnf.t_id;

-- sequence for t_id &quot;pnf_pnf_geometry&quot;
CREATE SEQUENCE av_pnf_stand_amo.pnf_geometry_t_id_seq;
ALTER TABLE av_pnf_stand_amo.pnf_pnf_geometry ALTER COLUMN t_id SET DEFAULT nextval(&apos;av_pnf_stand_amo.pnf_geometry_t_id_seq&apos;);
ALTER TABLE av_pnf_stand_amo.pnf_pnf_geometry ALTER COLUMN t_id SET NOT NULL;
ALTER SEQUENCE av_pnf_stand_amo.pnf_geometry_t_id_seq OWNED BY av_pnf_stand_amo.pnf_pnf_geometry.t_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Somit wäre Schritt (1) erledigt und wir können die Daten in QGIS erfassen. Für die Datenerfassung in QGIS werden die Tabellen der Aufzähltypen, die eigentlichen Datentabellen sowie ein Datensatz der Gemeindegrenzen benötigt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis01.png&quot; alt=&quot;QGIS Schritt 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuallererst muss die &lt;a href=&quot;http://blog.vitu.ch/10112013-1201/qgis-relations&quot;&gt;Relation&lt;/a&gt; zwischen dem Layer &lt;code&gt;pnf_pnf&lt;/code&gt; und &lt;code&gt;pnf_pnf_geometry&lt;/code&gt; hergestellt werden. Somit weiss QGIS, dass die beiden Tabellen mit einer 1:n-Relation verknüpft sind:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis02.png&quot; alt=&quot;QGIS Schritt 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend basteln wir uns mit den verschiedenen Editwidgets ein schönes Formular für die effiziente Datenerfassung:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis03.png&quot; alt=&quot;QGIS Schritt 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Attribute &lt;code&gt;canton&lt;/code&gt;, &lt;code&gt;practice&lt;/code&gt; und &lt;code&gt;reference_frame&lt;/code&gt; ist das Editwidget vom Typ &amp;laquo;Value Relation&amp;raquo; zu wählen. Als Inputlayer wählen wir die jeweilige Aufzähltyp-Tabelle, z.B. bei &lt;code&gt;cantons&lt;/code&gt; ist der Layer &lt;code&gt;amo_grafik_lv95_pnf_cantons&lt;/code&gt; zu wählen. Für das Attribut &lt;code&gt;year&lt;/code&gt; wählen wir den Typ &amp;laquo;Range&amp;raquo; und wählen den Bereich gemäss INTERLIS-Modell (2000 bis 2100).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anstelle der automatisch generierten Eingabemake, kann man sich mit Drag &apos;n&apos; Drop seine eigene Maske zusammenstöpseln. Wir verwenden diese Möglichkeit, um die 1:n-Relation in dieser Eingabemaske nicht darzustellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis04.png&quot; alt=&quot;QGIS Schritt 4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn wir anschliessend ein Feature im Layer &lt;code&gt;pnf_pnf&lt;/code&gt; erfassen, sieht das Formular wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis05.png&quot; alt=&quot;QGIS Schritt 5&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Beim Attribut &lt;code&gt;cantons&lt;/code&gt; haben wir den Typ &amp;laquo;Value Relation&amp;raquo; gewählt. Standardmässig erscheint bei diesem Typ eine Combobox. Wählt man jedoch die Option &amp;laquo;Use Completer&amp;raquo; erscheint keine Combobox, sondern es werden mögliche Werte aus der Aufzähltyp-Tabelle live vorgeschlagen. Dank den Editwidget-Typen wie &amp;laquo;Value Relation&amp;raquo; oder &amp;laquo;Range&amp;raquo; können so unnötige Tippfehler bei der Datenerfassung vermieden werden resp. es können gar keine nicht vorhandene Werte eingetippt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine vollständig ausgefüllte Eingabemaske:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis06.png&quot; alt=&quot;QGIS Schritt 6&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als nächstes müssen wir zu diesem PNF-Objekt den dazugehörigen Perimeter erfassen. Als Vorarbeit haben wir in QGIS die Relation definiert: Jedes PNF-Objekt kann gemäss INTERLIS-Modell mehrere Perimeter aufweisen. Also eine klassische 1:n-Beziehung. Interessant ist aber die Frage: wie kann ich einen Perimeter (den ich der Tabelle &lt;code&gt;pnf_pnf_geometry&lt;/code&gt; erfassen muss) korrekt einem Objekt der Tabelle &lt;code&gt;pnf_pnf&lt;/code&gt; zuweisen?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dazu wählen wir für den Layer &lt;code&gt;pnf_pnf_geometry&lt;/code&gt; beim Attribut &lt;code&gt;afrom&lt;/code&gt; (entspricht dem Fremdschlüssel) den Widgettyp &amp;laquo;Relation Reference&amp;raquo;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis07.png&quot; alt=&quot;QGIS Schritt 7&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Datenerfassung läuft dann wie folgt ab: Wir kopieren eine oder mehrere Gemeindegrenzen aus dem Layer mit den Gemeindengrenzen in den Layer &lt;code&gt;pnf_pnf_geometry&lt;/code&gt;. Anschliessend erfassen wir dazu die Daten. In diesem Fall ist das nur der Primary Key &lt;code&gt;t_id&lt;/code&gt; (interessiert uns ja nicht, da dieser automatisch vergeben wird) und das &amp;laquo;Beziehungsattribut&amp;raquo; (aka Fremdschlüssel) &lt;code&gt;afrom&lt;/code&gt;. Mit einer Combobox kann man das Objekt des Layers &lt;code&gt;pnf_pnf&lt;/code&gt; auswählen, dem man die Geometrie zuweisen will.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis08.png&quot; alt=&quot;QGIS Schritt 8&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Blöd nur, dass da bloss der Primary Key steht. Damit kann man meistens nichts anfangen. Viel besser wäre es, wenn man ein anderes Attribut darstellen könnte. Kann man. Ändern kann man das bei den Einstellungen im &amp;laquo;Relation Reference&amp;raquo;-Widget bei &amp;laquo;Display expression&amp;raquo;. Standardmässig steht da &lt;code&gt;COALESCE(&quot;t_id&quot;, &apos;&amp;lt;NULL&amp;gt;&apos;)&lt;/code&gt;. Anstelle von &lt;code&gt;t_id&lt;/code&gt; schreibt man das gewünschte Attribut hin. In unserem Fall &lt;code&gt;description&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis10.png&quot; alt=&quot;QGIS Schritt 10&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Möglich sind auch Kombinationen der Attribute resp. alles was &lt;a href=&quot;http://docs.qgis.org/2.8/en/docs/user_manual/working_with_vector/expression.html&quot;&gt;QGIS Expressions&lt;/a&gt; hergibt. Die Zuweisung der Geometrie zu einem Objekt ist jetzt viel einfacher:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis09.png&quot; alt=&quot;QGIS Schritt 9&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Geometrien möchte ich pro Jahr anders einfärben. Dazu müsste das Attribut &lt;code&gt;year&lt;/code&gt; im Layer &lt;code&gt;pnf_pnf_geometry&lt;/code&gt; vorhanden sein. Dieses Attribut ist aber im &amp;laquo;Parent&amp;raquo;-Layer &lt;code&gt;pnf_pnf&lt;/code&gt; vorhanden. Früher hat man sich dann mit einer View o.ä. geholfen. Das ist nicht mehr nötig. Mit der Kombination aus  &lt;a href=&quot;https://docs.qgis.org/2.8/en/docs/user_manual/working_with_vector/field_calculator.html&quot;&gt;Feldrechner&lt;/a&gt; und QGIS Expressions kann man virtuelle Felder mit Attributwerten aus anderen Layern erstellen. Die dazu benötige Expression: &lt;code&gt;attribute(get_feature(&apos;pnf_pnf&apos;,&apos;t_id&apos;,afrom),&apos;year&apos;)&lt;/code&gt;. Die Syntax ist unter Umständen ein klein wenig gewöhnungsbedürftig:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die erste Funktion &lt;code&gt;get_feature&lt;/code&gt; holt sich ein Feature aus einem anderen Layer (hier: &lt;code&gt;pnf_pnf&lt;/code&gt;). Der zweite und dritte Funktionsparameter beschreiben die jeweiligen Attribute der beiden Layer über die gejoined werden soll. Achtung: beim layereigenen Attribut sind keine Quotes notwendig. Die zweite Funktion &lt;code&gt;attribute&lt;/code&gt; extrahiert aus dem gefundenen &amp;laquo;Fremd&amp;raquo;-Feature ein Attribut. Der Parameter ist der Attributsname.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ergebnis dieser Expression in Kombination mit dem Feldrechner ist ein virtuelles Feld im Layer &lt;code&gt;pnf_pnf_geometry&lt;/code&gt;, das ich zum Einfärben oder Beschriften verwenden kann:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_qgis11.png&quot; alt=&quot;QGIS Schritt 11&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sind alle PNF-Objekte und die dazugehörigen Perimeter erfasst, kann mit ili2pg die INTERLIS-Transferdatei erzeugt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --export --dbhost localhost --dbport 5432 --dbdatabase rosebud2 --dbusr stefan --dbpwd ziegler12 --defaultSrsAuth EPSG --defaultSrsCode 2056 --createGeomIdx --createEnumTabs --nameByTopic --strokeArcs --dbschema av_pnf_stand_amo --modeldir http://models.geo.admin.ch --models AMO_Grafik_LV95_PNF stand_pnf_20151118.itf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat ist eine modellkonforme INTERLIS1-Transferdatei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p6/interlis_p6_itf01.png&quot; alt=&quot;ITF Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es hat sich dann leider herausgestellt, dass die Lieferung aber pro PNF-Jahr erfolgen muss, dh. in jedem ITF dürfen nur PNF-Objekte resp. -Perimeter eines Jahres vorhanden sein. Und zusätzlich verwirrend: Das Attribut &lt;code&gt;reference_frame&lt;/code&gt; beschreibt den Bezugsrahmen der erfassten Geometrien und nicht den Bezugsrahmen in dem die Periodische Nachführung durchgeführt worden ist. Nun gut. Was ich definitiv nicht will, ist pro Jahr ein DB-Schema. Aus diesem Grund erstelle ich ein &amp;laquo;export&amp;raquo;-Schema, das ich mit ein paar simplen SQL-Befehlen für jeweils ein Jahr abfülle und anschliessend mit ili2pg exportiere:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;DELETE FROM av_pnf_stand_amo_export.pnf_pnf_geometry;
DELETE FROM av_pnf_stand_amo_export.pnf_pnf;

INSERT INTO av_pnf_stand_amo_export.pnf_pnf (t_id, canton, id, description, practice, reference_frame, year)
SELECT t_id, canton, id, description, practice, reference_frame, year
FROM av_pnf_stand_amo.pnf_pnf
WHERE year = 2015;

INSERT INTO av_pnf_stand_amo_export.pnf_pnf_geometry (t_id, afrom, perimeter)
SELECT g.t_id as t_id, g.afrom, g.perimeter
FROM av_pnf_stand_amo.pnf_pnf as p, av_pnf_stand_amo.pnf_pnf_geometry as g
WHERE p.t_id = g.afrom
AND p.year = 2015;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fazit: Was früher knorzig und mühsam war, geht heute mit den richtigen Werkzeugen ruckzuck und entspannt vonstatten.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>State of WFS</title>
      <link>http://blog.sogeo.services/blog/2015/10/18/state-of-wfs.html</link>
      <pubDate>Sun, 18 Oct 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/10/18/state-of-wfs.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oder wie soll man WFS &amp;laquo;richtig&amp;raquo; nutzen? Eines vorweg: ich weiss es nicht. Ein Direktzugriffsverfahren auf Daten mit Filterfunktionen ist toll und sinnvoll. Aber für mich stellen sich einige Fragen in zwei Themenfeldern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Interaktion zwischen Benutzer und Service&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Umgang mit grossen Datenmengen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Und irgendwie eine Kombination von beidem.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ganze Thematik mit &lt;a href=&quot;http://docs.geoserver.org/stable/en/user/data/app-schema/index.html&quot;&gt;Anwendungsschemen&lt;/a&gt; und &lt;a href=&quot;http://lists.osgeo.org/pipermail/mapserver-dev/2014-June/014087.html&quot;&gt;komplexen Feature&lt;/a&gt; lassen wir mal beiseite und gehen von good old &lt;a href=&quot;https://www.google.ch/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=1&amp;amp;cad=rja&amp;amp;uact=8&amp;amp;ved=0CCEQFjAAahUKEwi17pPNuMzIAhWLVxoKHUvAC0E&amp;amp;url=http%3A%2F%2Fportal.opengeospatial.org%2Ffiles%2F%3Fartifact_id%3D15201&amp;amp;usg=AFQjCNGwftPpYIra3XPRMDwfqb-BGETqyw&amp;amp;sig2=slYt5wNVI48Niy8Ri6TXnw&quot;&gt;OGC SF-0&lt;/a&gt; aus. Das heisst &lt;a href=&quot;http://docs.geoserver.org/stable/en/user/data/app-schema/complex-features.html#simple-features&quot;&gt;eine Tabelle aus der Datenbank wird zu einem WFS-Layer&lt;/a&gt;, der mit einem &lt;code&gt;GetFeature&lt;/code&gt;-Request heruntergeladen werden kann. Solche WFS-Server gibt es wie Sand am Mehr (&lt;a href=&quot;http://geoserver.org/&quot;&gt;GeoServer&lt;/a&gt;, &lt;a href=&quot;http://hub.qgis.org/projects/quantum-gis/wiki/qgis_server_tutorial&quot;&gt;QGIS Server&lt;/a&gt;, &lt;a href=&quot;http://mapserver.org/&quot;&gt;MapServer/TinyOWS&lt;/a&gt;, &lt;a href=&quot;http://www.deegree.org/&quot;&gt;deegree&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Unterstützung in den Desktop-GIS wie z.B. &lt;a href=&quot;http://www.qgis.org&quot;&gt;QGIS&lt;/a&gt; ist auf den ersten Blick nicht allzu übel. Unterstützt wird die Version 1.0.0. Das Einbinden von WFS-Layer ist einfach und ähnlich dem eines Postgis-Layers:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/hgPlv4tUDGE?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Video wird gezeigt, wie die Hoheitsgrenzen des Kantons Solothurn in QGIS geladen werden. &amp;laquo;In QGIS geladen&amp;raquo; bedeutet, dass die Vektordaten komplett vom WFS-Server heruntergeladen werden und in QGIS in einem Memory-Layer gespeichert werden. Wird QGIS geschlossen, werden die Daten nirgends auf dem Computer gespeichert und beim Öffnen des QGIS-Projektes werden die Daten vom WFS-Server &lt;em&gt;erneut&lt;/em&gt; heruntergeladen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Soweit so gut. Die Hoheitsgrenzen werden zügig geladen. Es sind ja auch nur 110 Polygone. Auch das Nicht-Offline-Speichern und erneute Laden beim Öffnen des QGIS-Projektes hat was: So sind die Daten garantiert immer aktuell resp. entsprechen der Aktualität wie sie der Serviceprovider anbietet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Schauen wir uns die Bodenbedeckung der amtlichen Vermessung an. Das sind im Kanton Solothurn knapp 280&apos;000 Polygone:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/Neerm1dweZo?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wird der WFS-Layer genau gleich geladen wie die Hoheitsgrenzen wartet der Benutzer 1.5 Minuten auf die Antwort. Das QGIS Fenster ist blockiert und auf dem Server läuft in diesem Zeitraum ein Prozess mit 100% CPU-Auslastung. Wird z.B. QGIS-Server verwendet, läuft neben der eigentlichen Verarbeitung des WFS-Request (als FCGI-Prozess) auch noch Apache mit 20% CPU-Auslastung. Speichert man das QGIS-Projekt und öffnet es wieder, wird dieser Request wiederholt. Der Benutzer wartet also bei jedem Öffnen des Projektes einige Minuten. Mir noch nicht klar warum das Abfragen von einzelnen Features (mit dem Identify Features Tool) so lange dauert. Eventuell wird beim Memory-Layer kein Spatial-Index erzeugt und daher wirkt die Feature-Abfrage so käsig.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit den Filterfunktionen kann der Problematik der langen Downloadzeit entgegen gewirkt werden. Einerseits kann nach Sachattributen (z.B. Gemeindenummer) gefiltert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/aAcTHfnoKlc?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man darf aber nicht vergessen, dass man ja nur nach Attributen filtern kann, die auch da sind. Fehlt im WFS-Layer das Attribute &lt;code&gt;bfsnr&lt;/code&gt; kann &lt;strong&gt;nicht&lt;/strong&gt; nach einer Gemeinde gefiltert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anderseits kann man geografisch filtern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/XbAWoqFtSVg?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch das geht nicht immer: WFS-Layer ohne Geometrien können &lt;strong&gt;nicht&lt;/strong&gt; geografisch gefiltert werden. An den Haaren herbeigezogen? Ich denke nicht. Denkbar sind Strassennamen oder ähnliches.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;QGIS hat beim Hinzufügen von WFS-Layer eine interessante Option, die sich &amp;laquo;Cache Features&amp;raquo; nennt. Leider funktioniert sie mit der aktuellen QGIS-Version (2.10) nicht mehr. Im GUI ist die Option noch vorhanden aber die Funktion wurde im &lt;a href=&quot;https://github.com/qgis/QGIS/blob/master/src/providers/wfs/qgswfsprovider.cpp#L126&quot;&gt;Quellcode bewusst auskommentiert&lt;/a&gt;. Äh..? Irgendwie ist da sowieso der &lt;a href=&quot;http://lists.osgeo.org/pipermail/qgis-developer/2015-October/039642.html&quot;&gt;Wurm&lt;/a&gt; drin. Die Idee hinter &amp;laquo;Cache Features&amp;raquo; ist eben das oben beschriebene Verhalten, dh. &lt;strong&gt;alle&lt;/strong&gt; Features des WFS-Layer werden in QGIS &amp;laquo;gecached&amp;raquo;/heruntergeladen. Wählt man jetzt diese Option ab (in QGIS 1.8 funktioniert das noch), werden die Features nicht mehr in QGIS gecached, sondern es werden nur die Features heruntergeladen, die dem aktuellen Kartenausschnitt entsprechen. Der Funktion ist sogar ein Mindestmass an Intelligenz eingepflanzt. So werden die Features nicht erneut geladen, wenn der neue Kartenausschnitt (nach Zoomen) komplett innerhalb des vorangegangenen Kartenausschnittes ist. Auf den ersten Blick ist das genau das Verhalten, das man sich eigentlich wünscht. Die Probleme beginnen aber beim Herauszoomen und am Schluss lädt es bei jedem Verschieben der Karte sehr lange, sehr viele Features herunter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/op7eWUm6wCI?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie man aber auch sieht, funktioniert dieser &lt;em&gt;uncached&lt;/em&gt; WFS-Layer bei grossen Massstäben (und dementensprechend wenig Feature, die nachgeladen werden müssen) ziemlich gut. Bei genügend schneller Internetverbindung merkt man das Nachladen praktisch gar nicht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nach all den Beispielen bleiben bei mir immer noch Fragen in den eingangs erwähnten Themenfeldern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Darf das Herunterladen der Daten länger dauern oder soll der Benutzer die gleiche &lt;em&gt;User Experience&lt;/em&gt; wie bei WMS haben? Bei kleinen Datenmengen scheint das ziemlich gut zu funktionieren. Bei grösseren überhaupt nicht mehr. Oder macht es eben nichts, wenn die Daten nicht mehr &lt;em&gt;instant&lt;/em&gt; erscheinen, sondern es etliche Sekunden oder Minuten geht bis man wieder weiterarbeiten kann? Darf es Ziel der ganzen Übung sein die Daten einmalig herunterzuladen und dann lokal physisch zu speichern (als Shapefile o.ä.)? Ganz quer ist dieser Gedanke nicht: GML ist ja kein Produktionsformat, sondern ein Datenaustauschformat. Aber dann verliert man die Vorteile eines Direktzugriffsverfahrens.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Klar, es gibt &lt;a href=&quot;http://www.opengeospatial.org/standards/filter&quot;&gt;Filter&lt;/a&gt;. Darf man aber erwarten, dass der Benutzer &lt;em&gt;immer&lt;/em&gt; vorgängig die richtigen Filter kennt und auch einstellt? Zudem kann nur nach etwas gefiltert werden, was auch in den Daten vorhanden ist (&amp;laquo;Ich will Daten der Gemeinde XY, kann aber keine Gemeindenummer beim Filter auswählen.&amp;laquo;). Einmal Filter nicht gesetzt und schon dauert ein Download sehr lange und generiert Last.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Last: Die grossen WFS-Requests erzeugen eine hohe Last auf dem Server. Ich bin zwar keine Server-Admin-Guru aber ich glaube nicht, dass man von externen und unter Umständen unbekannten Leuten den Server unbewusst (Filter vergessen?) so einfach unter Volllast gesetzt bekommen will. Für GeoServer gibt es ein &lt;a href=&quot;http://docs.geoserver.org/stable/en/user/extensions/controlflow/index.html&quot;&gt;Control Flow Modul&lt;/a&gt;, das die OWS-Requests detailliert kontrollieren kann. Bei QGIS-Server (FCGI-Process) kann/man/will man das wahrscheinlich teilweise direkt in den FCGI-Einstellungen regeln. Häufig gibt es auch die Möglichkeit auf der Serverseite die maximale Anzahl der Features zu limitieren, die auf eine Anfrage eines Klienten zurückgeschickt werden. Bis zur WFS-Version 2.0.0 wurde diese Anzahl dem Klienten nicht bekannt gegeben. Der Klient wusste also nicht, ob er alle angeforderten Features bekommen hat oder ob der Server die maximale Anzahl der auszuliefernden Features bereits erreicht hat. In WFS 2.0.0 wird diese Anzahl als &lt;code&gt;CountDefault&lt;/code&gt; im &lt;code&gt;GetCapabilities&lt;/code&gt;-Dokument stehen. In Kombinination mit &lt;a href=&quot;http://gis.stackexchange.com/questions/86755/how-to-use-paging-in-a-wfs-query&quot;&gt;Paging&lt;/a&gt; &lt;a href=&quot;https://trac.osgeo.org/mapserver/ticket/2799&quot;&gt;wären&lt;/a&gt; so zumindest sehr grosse Downloads möglich mit der Sicherheit wirklich auch alle Features zu bekommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ist WFS also nur etwas für kleine Datenmengen? Oder aber hat jemand Antworten auf die Frage: Wie soll man WFS &amp;laquo;richtig&amp;raquo; nutzen?&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Bezugsrahmenwechsel: ST_Fineltra in Action</title>
      <link>http://blog.sogeo.services/blog/2015/10/04/bezugsrahmenwechsel-st-fineltra-in-action.html</link>
      <pubDate>Sun, 4 Oct 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/10/04/bezugsrahmenwechsel-st-fineltra-in-action.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sandro Santilli hat eine &lt;a href=&quot;https://github.com/strk/fineltra)&quot;&gt;erste Version&lt;/a&gt; der &lt;a href=&quot;http://sogeo.ch/blog/2015/09/17/bezugsrahmenwechsel-postgis-to-the-rescue/&quot;&gt;ST_Fineltra Funktion&lt;/a&gt; für PostGIS zum Testen freigegeben. Um die Funktion verwenden zu können, sind zwei Schritte notwendig:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Installieren der neuen Funktion&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Importieren der Dreiecksvermaschung in die Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Installieren der Funktion funktioniert unter Ubuntu 14.04 problemlos. Hat man sowieso bereits die üblichen &amp;laquo;GIS-Tools&amp;raquo; (PostgreSQL/Postgis, gdal/ogr etc.) installiert, fehlt anscheinend nur das dev-Paket von &lt;em&gt;liblwgeom&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;sudo apt-get install liblwgeom-dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend kann gemäss der Installationsanleitung im &lt;a href=&quot;https://github.com/strk/fineltra/blob/master/README.md&quot;&gt;README&lt;/a&gt; vorgegangen werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;git clone https://github.com/strk/fineltra.git
cd fineltra
./autogen.sh
./configure
make
sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Funktion ist eine PostgreSQL-Extension und kann für die gewünschte Datenbank aktiviert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;sudo -u postgres psql -d xanadu2 -c &quot;CREATE EXTENSION fineltra;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der zweite Schritt ist der Import der Dreiecksvermaschung. Eine Spatialite-Datei mit einer Tabelle mit sämtlichen Dreiecken in beiden Bezugsrahmen gibts &lt;a href=&quot;https://drive.google.com/uc?export=download&amp;amp;id=0B6Qb5JteUzxLRUtwLVhWVkdpdDQ&quot;&gt;hier&lt;/a&gt;. Vor Jahren habe ich diese aus den Shapedateien von Swisstopo erstellt. Der Import in die Datenbank ist ein einfacher ogr2ogr-Befehl. Leider scheint das mit einer Version kleiner 2.0 nicht wirklich gut zu funktionieren, weil die Tabelle zwei Geometriespalten aufweist. Bei mir werden nur die Sachattribute importiert. Mit ogr2ogr &amp;gt;= 2.0 reicht aber folgender Befehl:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;ogr2ogr -f &quot;PostgreSQL&quot; PG:&quot;dbname=&apos;xanadu2&apos; host=&apos;localhost&apos; port=&apos;5432&apos; user=&apos;stefan&apos; password=&apos;ziegler12&apos;&quot; chenyx06.sqlite chenyx06 -lco SCHEMA=av_chenyx06 -nln chenyx06_triangles&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Tabelle mit den Dreiecken &lt;strong&gt;muss&lt;/strong&gt; beide Bezugsrahmen beinhalten. Es ist &lt;strong&gt;nicht&lt;/strong&gt; möglich die Funktion &lt;code&gt;ST_Fineltra&lt;/code&gt; mit Dreiecksgeometrien aus verschiedenen Tabelle zu bedienen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Schema &lt;code&gt;av_chenyx06&lt;/code&gt; muss bereits existieren. Ansonsten muss es manuell angelegt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;sudo -u postgres psql -d xanadu2 -c &quot;CREATE SCHEMA av_chenyx06 AUTHORIZATION stefan;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Es ist unbedingt darauf zu achten, dass die Geometrietabellen einen Geometrieindex aufweisen. Bei mir werden diese beim Import mit ogr2ogr automatisch erzeugt. Falls dies nicht der Fall ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;sudo -u postgres psql -d xanadu2 -c &quot;CREATE INDEX ON av_chenyx06.chenyx06_triangles USING GiST (the_geom_lv03);&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat das geklappt, kann es ans Testen gehen. Die Syntax ist sowohl im &lt;a href=&quot;https://github.com/strk/fineltra/blob/master/README.md&quot;&gt;README&lt;/a&gt; wie auch im &lt;a href=&quot;http://sogeo.ch/blog/2015/09/17/bezugsrahmenwechsel-postgis-to-the-rescue/&quot;&gt;vorangegangen Beitrag&lt;/a&gt; erläutert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_st_fineltra_in_action/pgadmin_600_200.png&quot; alt=&quot;st_fineltra bsp 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ok, das ist jetzt noch keine Wissenschaft. Aber es scheint zu funktionieren. Die erste Frage muss aber sein: &amp;laquo;Stimmt die Transformation überhaupt?&amp;raquo; Um dies zu beantworten, vergleichen wir die Resultate aus zwei unabhängigen Transformationen. Einmal transformieren wir einen Testdatensatz mit der neuen &lt;code&gt;ST_Fineltra&lt;/code&gt;-Funktion und einmal mit einem &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/en/home/products/software/products/m2m/lv03tolv95.html&quot;&gt;offiziellen Transformationsdienst&lt;/a&gt; der Swisstopo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diesen Vergleich können wir auch innerhalb der Datenbank machen. Dazu verwenden wir die &lt;a href=&quot;https://github.com/pramsey/pgsql-http&quot;&gt;PostgreSQL-Extension &lt;code&gt;http&lt;/code&gt;&lt;/a&gt;. Mit dieser Extension wird PostgreSQL zu einem HTTP-Client. Bevor man das jetzt produktiv wirklich nutzen will, sollte man unbedingt das &lt;a href=&quot;https://github.com/pramsey/pgsql-http/blob/master/README.md#why-this-is-a-bad-idea&quot;&gt;Kapitel &amp;laquo;Why This is a Bad Idea&amp;raquo;&lt;/a&gt; im README lesen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Installation ist simpel:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;git clone https://github.com/pramsey/pgsql-http.git
cd pgsql-http
make
sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und anschliessend:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;sudo -u postgres psql -d xanadu2 -c &quot;CREATE EXTENSION http;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Testaufruf zeigt folgendes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_st_fineltra_in_action/http_lv03tolv95.png&quot; alt=&quot;http test 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit den &lt;a href=&quot;http://www.postgresql.org/docs/9.3/static/functions-json.html&quot;&gt;JSON-Funktionen&lt;/a&gt; von PostgreSQL kann man einfach auf die einzelnen Rückgabewerte zugreifen. Als Testdatensatz wählen wir die Fixpunkte aus der amtlichen Vermessung. Weil jetzt für jeden Fixpunkt ein HTTP-GET-Request gemacht wird, dauert das ein Weilchen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH fineltra AS (
  SELECT nummer, nbident,
    ST_X(wkb_geometry) as x_lv03, ST_Y(wkb_geometry) as y_lv03,
    ST_X((ST_Fineltra(wkb_geometry, &apos;av_chenyx06.chenyx06_triangles&apos;, &apos;the_geom_lv03&apos;, &apos;the_geom_lv95&apos;))) as x_lv95_pg,
    ST_Y((ST_Fineltra(wkb_geometry, &apos;av_chenyx06.chenyx06_triangles&apos;, &apos;the_geom_lv03&apos;, &apos;the_geom_lv95&apos;))) as y_lv95_pg
  FROM av_avdpool_test.fixpunktekategorie__lfp
  --WHERE fid = 3515717
  LIMIT 100
),
swisstopo AS (
  SELECT nummer, nbident, x_lv95_pg, y_lv95_pg,
   cast(content::json-&amp;gt;&amp;gt;&apos;easting&apos; as double precision) as x_lv95_lt,
   cast(content::json-&amp;gt;&amp;gt;&apos;northing&apos; as double precision) as y_lv95_lt
  FROM fineltra as f, http_get(&apos;http://geodesy.geo.admin.ch/reframe/lv03tolv95?easting=&apos; || f.x_lv03 || &apos;&amp;amp;northing=&apos; || f.y_lv03 || &apos;&amp;amp;format=json&apos;)
)
SELECT nummer, nbident, (x_lv95_pg - x_lv95_lt)*1000 as x_diff_mm, (y_lv95_pg - y_lv95_lt)*1000 as y_diff_mm
FROM swisstopo;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sieht vielversprechend aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_st_fineltra_in_action/http_lv03tolv95_diff.png&quot; alt=&quot;swisstopo vs. st_fineltra&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Differenzen bewegen sich also in einem völlig irrelevanten Bereich. Um sicher zu sein, müssen auch noch weitere Geometrietypen (Linien und Polygone) überprüft werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt soll die Performanz getestet werden. Dazu werden alle Tabellen der amtlichen Vermessung des Kantons Solothurn in einem MOpublic-ähnlichen Datenmodell in die Datenbank importiert. Eine GeoPackage-Datei des gesamten Kantons kann &lt;a href=&quot;http://www.catais.org/geodaten/ch/so/agi/av/mopublic/gpkg/lv03/d/kanton.gpkg&quot;&gt;hier&lt;/a&gt; heruntergeladen und mit folgendem ogr2ogr-Befehl in die Datenbank importiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;ogr2ogr -f &quot;PostgreSQL&quot; PG:&quot;dbname=&apos;xanadu2&apos; host=&apos;localhost&apos; port=&apos;5432&apos; user=&apos;stefan&apos; password=&apos;ziegler12&apos;&quot; -lco SCHEMA=av_avdpool_test kanton.gpkg&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In das Schema &lt;code&gt;av_avdpool_test&lt;/code&gt; wurden 33 Tabellen importiert. Alle im Bezugrahmen LV03:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_st_fineltra_in_action/pgadmin_mopublic_lv03_yellow.png&quot; alt=&quot;mopublic lv03&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In einem Groovy-Skript reicht jetzt eigentlich eine einzige For-Schleife. Aus der View &lt;code&gt;geometry_columns&lt;/code&gt; werden alle zu transformierenden Tabellen identifiziert und diese transformiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;@Grapes([
   @Grab(&apos;org.postgresql:postgresql:9.4-1201-jdbc41&apos;),
   @GrabConfig(systemClassLoader = true)
])

import groovy.sql.*

def dbhost = &quot;localhost&quot;
def dbport = &quot;5432&quot;
def dbdatabase = &quot;xanadu2&quot;
def dbusr = &quot;stefan&quot;
def dbpwd = &quot;ziegler12&quot;
def dbschema = &quot;av_avdpool_test&quot;

def dburl = &quot;jdbc:postgresql://${dbhost}:${dbport}/${dbdatabase}?user=${dbusr}&amp;amp;password=${dbpwd}&quot;

def query = &quot;SELECT f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type &quot; +
  &quot; FROM geometry_columns &quot; +
  &quot; WHERE f_table_schema = &apos;$dbschema&apos;&quot; +
  &quot; AND srid = 21781;&quot;

def sql = Sql.newInstance(dburl)

def startTime = Calendar.instance.time
def endTime
println &quot;Start: ${startTime}.&quot;

sql.withTransaction {
  sql.eachRow(query) {row -&amp;gt;
    def tableName = row.f_table_name
    def geomColumn = row.f_geometry_column

    def geomType = row.type
    if (row.coord_dimension == 3) {
      geomType += &apos;Z&apos;
    }

    def alterQuery = &quot;ALTER TABLE ${Sql.expand(dbschema)}.${Sql.expand(tableName)}&quot; +
      &quot; ALTER COLUMN ${Sql.expand(geomColumn)} TYPE geometry(${Sql.expand(geomType)},2056)&quot; +
      &quot; USING ST_Fineltra(${Sql.expand(geomColumn)}, &apos;av_chenyx06.chenyx06_triangles&apos;, &apos;the_geom_lv03&apos;, &apos;the_geom_lv95&apos;);&quot;

    println &quot;--- $dbschema.$tableName ---&quot;
    sql.execute(alterQuery)

    endTime = Calendar.instance.time
    println &quot;Elapsed time: ${(endTime.time - startTime.time)} ms&quot;

  }
}

endTime = Calendar.instance.time
println &quot;End: ${endTime}.&quot;
println &quot;Total elapsed time: ${(endTime.time - startTime.time)} ms&quot;

sql.connection.close()
sql.close()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Transformation einer Datenbanktabelle ist die Query in den Zeilen 38 - 40. Dies ist der einfachst mögliche Fall. Gibt es aber &lt;code&gt;Rules&lt;/code&gt; und/oder &lt;code&gt;Triggers&lt;/code&gt; etc. in der Tabelle, müssen diese wahrscheinlich vorher ausgeschaltet und nach der Transformation eingeschaltet werden. Das genaue Vorgehen muss geprüft werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die gleichen 33 Tabellen liegen nun im Bezugsrahmen LV95 vor:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_st_fineltra_in_action/pgadmin_mopublic_lv95_yellow.png&quot; alt=&quot;mopublic lv03 nach transformation&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Geschwindigkeit ist verblüffend: Für sämtliche Tabellen braucht das Skript bloss circa 140 Sekunden. Die Transformation der  Bodenbedeckung mit circa 280&apos;000 Polygonen dauert nur 25 Sekunden (Ubuntu 14.04 in einer VirtualBox auf einem 2jährigen iMac mit SSD).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #5</title>
      <link>http://blog.sogeo.services/blog/2015/10/03/interlis-leicht-gemacht-number-5.html</link>
      <pubDate>Sat, 3 Oct 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/10/03/interlis-leicht-gemacht-number-5.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Overlaps in INTERLIS. Eine leidige Geschichte. Im &lt;a href=&quot;http://www.interlis.ch/interlis2/docs23/ili2-refman_2006-04-13_d.pdf&quot;&gt;Referenzhandbuch&lt;/a&gt; ist auf den Seiten 49 und 50 beschrieben was erlaubt ist. Fairerweise muss man erwähnen, dass das Verbieten von Overlaps im INTERLIS-Modell das Problem aber trotzdem nicht lösen würde. Möglich blieben Overlaps (aka Self-Intersections) aus rein numerischen Gründen. Hier die erläuternde Skizze aus dem Handbuch ((c) by KOGIS, CH-3084-Wabern, &lt;a href=&quot;http://www.kogis.ch&quot;&gt;www.kogis.ch&lt;/a&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/ili_handbuch_overlaps.png&quot; alt=&quot;INTERLIS Overlaps Referenzhandbuch&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Problem mit Overlaps ist, dass Geometrien mit solchen im OGC Simple Feature Universum &lt;a href=&quot;http://www.postgis.net/docs/ST_IsValid.html&quot;&gt;nicht gültig&lt;/a&gt; sind und beim Prozessieren für nicht zuverlässige Resultate sorgen oder auch zu gar keinen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Solche Overlaps sind unter anderem häufig in der amtlichen Vermessung bei Einlenkern anzutreffen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/einlenker_gbplan.png&quot; alt=&quot;Einlenker mit Overlap Overview&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Soweit sieht das ganz gut aus. Um das Ganze übersichtlicher zu gestalten, lassen wir alles weg, was nicht Liegenchaften sind und markieren die Stützpunkte. Die grünen Quadrate kennzeichnen die Linienstützpunkte &lt;code&gt;LIPT&lt;/code&gt; und die blauen Kreise die Bogenstützpunkte &lt;code&gt;ARCP&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/einlenker_intersection_circle.png&quot; alt=&quot;Einlenker mit Overlap Overview - no frills&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sieht eigentlich immer noch gut aus. Zoomt man jetzt aber beim rechten Einlenker (roter Kreis) stark rein, wird die Self-Intersection sichtbar. Eine der beiden Linien ist rot eingefärbt, um das Kreuzen (= Self-Intersection = Overlap) der Linien besser sichtbar zu machen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/einlenker_intersection_zoom.png&quot; alt=&quot;Einlenker mit Overlap Zoom&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie ging man bis anhin mit diesem Problem um? Eine Variante ist die einzelnen Linienstücke während des INTERLIS-Importes neu zu verknoten. Dort wo sich die Linien kreuzen wird ein neuer Stützpunkt gerechnet und anschliessend wird polygoniert. Dabei entsteht ein klitzekleines Polygon. Für den Umgang mit diesem Polygon gibts wiederum ein paar Varianten:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Es wird gelöscht. Unschön, da jetzt eine Lücke im Datensatz entsteht (bei Areas).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es wird dem &amp;laquo;Ursprungs&amp;raquo;-Polygon zugewiesen. Unschön, da jetzt ein Multipolygon entsteht resp. entstehen muss. Das INTERLIS-Datenmodell sieht aber unter Umständen keine Multipolygone vor (Constraints in der Datenbank lassen grüssen).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es wird dem Nachbarpolygon mit der längsten gemeinsamen Kanten zugewiesen. Unschön, da jetzt der Endpunkt des &amp;laquo;Ursprungs&amp;raquo;-Polygons nicht mehr am &amp;laquo;Ursprungs&amp;raquo;-Endpunkt zu liegen kommt. In der amtlichen Vermessung würde das bedeuten, dass auf diesem Stützpunkt des Polygons kein Grenzpunkt liegt obwohl doch eigentlich einer da sein sollte.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der neusten Version von &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; wurde eine bessere Variante implementiert. Um die Topologie (wenn man das so nennen darf) resp. die Nachbarschaftsbeziehungen zwischen den Polygonen besser einzuhalten wird auf dem Kreisbogen in genügend grossem Abstand (abhängig vom erlaubten OVERLAP-Wert im INTERLIS-Datenmodell) von der tangential zulaufenden Gerade ein neuer Stützpunkt gerechnet. Von diesem neuen Stützpunkt wird anschliessend ein kleines Geradenstück bis zum Endpunkt gezogen. Vielleicht sagt ein Bild mehr als 1&apos;000 komplizierte Worte:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/einlenker_ili2pg_zoom.png&quot; alt=&quot;Einlenker bereinigt zoom&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Rausgezoomt wird die neue Situation nochmals deutlicher sichtbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p5/einlenker_ili2pg.png&quot; alt=&quot;Einlenker bereinigt Overview&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Teil dieses Weiterentwicklungspaketes von ili2pg war ebenfalls die Unterstützung der Flächenbildung unter Beibehaltung der Kreisbogengeometrien. Diese werden neu nicht mehr segmentiert, sondern das Resultat eines INTERLIS1-Importes in die Postgis-Datenbank sind Geometrien vom Typ &lt;code&gt;COMPOUNDCURVE&lt;/code&gt; und &lt;code&gt;CURVEPOLYGON&lt;/code&gt;. Mit dem Programmparameter &lt;code&gt;--strokeArcs&lt;/code&gt; können Kreisbogen weiterhin segmentiert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Diese Weiterentwicklungen wurden finanziell unterstützt durch die Kantone &lt;a href=&quot;http://www.be.ch/agi&quot;&gt;Bern&lt;/a&gt;, &lt;a href=&quot;http://geo.gl.ch/&quot;&gt;Glarus&lt;/a&gt; und &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;Solothurn&lt;/a&gt; und durch die Firma &lt;a href=&quot;http://www.eisenhutinformatik.ch/&quot;&gt;Eisenhut Informatik AG&lt;/a&gt; ausgeführt. Besten Dank!&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Bezugsrahmenwechsel: PostGIS to the rescue</title>
      <link>http://blog.sogeo.services/blog/2015/09/17/bezugsrahmenwechsel-postgis-to-the-rescue.html</link>
      <pubDate>Thu, 17 Sep 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/09/17/bezugsrahmenwechsel-postgis-to-the-rescue.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gute Mitarbeiter mit guten Ideen. Was dabei rauskommt? Die PostgreSQL-Funktion &lt;code&gt;ST_Fineltra&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie wahrscheinlich viele andere Kantone auch wollten wir mit &lt;a href=&quot;http://www.safe.com/&quot;&gt;FME&lt;/a&gt; und dem &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/software/products/reframe_fme.html&quot;&gt;REFRAME-Plugin&lt;/a&gt; die Daten in unserer PostgreSQL-Datenbank transformieren. Das funktioniert und ist genügend schnell. So richtig glücklich wurden wir aber nicht damit:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Warum sollen wir Daten mit einer Drittapplikation ausserhalb der Datenbank transformieren, um sie dann wieder in der Datenbank zu speichern? Wenn ich mit &lt;code&gt;ST_Transform&lt;/code&gt; Daten in ein anderes Koordinatensystem transformiere, muss ich das ja auch nicht machen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In einer Übergangsphase müssen wir wohl oder über die Flexibilität und Fähigkeit besitzen Daten in beiden Bezugsrahmen anbieten und verwalten zu können. Für diese Aufgaben will/kann ich nicht immer gleich FME anschmeissen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wir hatten mit FME Probleme bei Tabellen, die mehrere Geometrieattribute aufweisen. War alles machbar, wirkte aber hakelig.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Warum also nicht ein Funktion à la &lt;code&gt;ST_Transform&lt;/code&gt;, die es ermöglicht die Daten mit der offiziellen &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/topics/survey/lv95/lv03-lv95/chenyx06.html&quot;&gt;Dreiecksvermaschung&lt;/a&gt; zu transformieren? &lt;code&gt;ST_Fineltra&lt;/code&gt; war geboren. Ende Oktober sollte die Funktion als &lt;a href=&quot;http://www.postgresql.org/docs/9.4/static/extend-extensions.html&quot;&gt;PostgreSQL-Extension&lt;/a&gt; verfügbar sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Bodenbedeckung der amtlichen Vermessung mit &lt;code&gt;ST_Fineltra&lt;/code&gt; transformieren?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;UPDATE bodenbedeckung_boflaeche
  SET geometrie_lv95 = ST_Fineltra(geometrie_lv03, &apos;chenyx06&apos;, &apos;geom_lv03&apos;, &apos;geom_lv95&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;geometrie&lt;/strong&gt;: Die zu transformierende Geometrie.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;chenyx06&lt;/strong&gt;: Der Namen der Tabelle mit der Dreiecksvermaschung. Es wird also möglich sein die Funktion mit eigenen Dreiecksvermaschungen (z.B. für lokale Entzerrungen) zu verwenden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;geom_lv03&lt;/strong&gt;: Attributname der Dreiecksdefinitionen (in der Dreiecksvermaschungstabelle) im Ausgangsbezugsrahmen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;geom_lv95&lt;/strong&gt;: Attributname der Dreiecksdefinitionen (in der Dreiecksvermaschungstabelle) im Zielbezugsrahmen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die UPDATE-Query hat den Schönheitsfehler, dass es jetzt zwei Geometrieattribute in der Tabelle gibt. Dies wollen wir vermeiden und die LV03-Geometrie in eine LV95-Geometrie transformieren. Mit folgender schicken Query ist das kein Problem:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;ALTER TABLE bodenbedeckung_boflaeche
  ALTER COLUMN geometrie TYPE geometry(Polygon,2056)
    USING ST_Fineltra(geometrie, &apos;chenyx06&apos;, &apos;geom_lv03&apos;, &apos;geom_lv95&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In der Datenbanksicht &lt;code&gt;geometry_columns&lt;/code&gt; sind alle notwendigen Informationen dazu vorhanden. Mit einem kleinen Skript und einer For-Schleife lassen sich jetzt alle Tabellen transformieren. In PostGIS &amp;lt; 2.0 ist &lt;code&gt;geometry_columns&lt;/code&gt; keine Sicht, sondern eine Tabelle, die manuell nachgeführt werden muss. Entweder ist die sauber und vollständig nachführt oder man muss sich die Informationen aus den verschiedenen &lt;code&gt;pg_*&lt;/code&gt;-Tabellen zusammen suchen, was die Komplexität des Skriptes natürlich erhöht. Ebenfalls muss an anfällige Triggers und Rules gedacht werden. Diese sollten vor der Transformation ausgeschaltet und anschliessend wieder eingeschaltet werden. Informationen dazu können ebenfalls aus den &lt;code&gt;pg_*&lt;/code&gt;-Tabellen geholt werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #4</title>
      <link>http://blog.sogeo.services/blog/2015/08/30/interlis-leicht-gemacht-number-4.html</link>
      <pubDate>Sun, 30 Aug 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/08/30/interlis-leicht-gemacht-number-4.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wir sind momentan dabei im Kanton Solothurn die &lt;a href=&quot;http://models.geo.admin.ch/BJ/KS3-20060703.ili&quot;&gt;AVGBS&lt;/a&gt; einzuführen. In einem Pilotprojekt sollen verschiedene Geschäftsfälle durchgespielt werden. Dafür müssen in der amtlichen Vermessung die Grundstücke das Attribut &amp;laquo;EGRIS_EGRID&amp;raquo; führen. Im Grundbuch wird der EGRID bereits geführt. Eine Liste aller Grundstücke (inkl. EGRID) kann vom Grundbuch als CSV-Datei exportiert werden. Die Frage lautet also nun: Wie kommen die EGRID aus dem Grundbuch in die amtliche Vermessung beim Nachführungsgeometer?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Variante ist das manuelle Abfüllen des EGRID im AV-Erfassungssystem. Bei fast 2000 Grundstücken in der Pilotgemeinde macht das wenig Spass, ist fehleranfällig und wird ewig dauern. Mmmmh, es geht hier doch um AVGBS. Warum erstellen wir nicht eine AVGBS-Datei, die der Nachführungsgeometer in seinem System einlesen kann? Gesagt, getan. Mit einem kleinen &lt;a href=&quot;http://www.groovy-lang.org/&quot;&gt;Groovy&lt;/a&gt;-Skript und &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; wird das TOPIC &lt;code&gt;Eigentumsverhaeltnis&lt;/code&gt; mit der CLASS &lt;code&gt;Grundstueck&lt;/code&gt; erstellt, abgefüllt und anschliessend in einer INTERLIS-Datei dem Nachführungsgeometer zur Verfügung gestellt. Dieser kann die INTERLIS-Datei einlesen und der EGRID wird den Grundstücken in seinem System zugewiesen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der komplette Prozess kann in vier Schritte unterteilt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Erstellen der INTERLIS-Modellstruktur in der Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Importieren der CSV-Datei mit den EGRID in die Datenbank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Abfüllen der in Schritt (1) erstellten Tabellen mit den benötigten Informationen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exportieren der INTERLIS-Datei&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;@Grapes([
   @GrabResolver(name=&apos;catais.org&apos;, root=&apos;http://www.catais.org/maven/repository/release/&apos;, m2Compatible=&apos;true&apos;),
   @Grab(&apos;org.postgresql:postgresql:9.4-1201-jdbc41&apos;),
   @Grab(&apos;ch.interlis:ili2c:4.5.12&apos;),
   @Grab(&apos;ch.interlis:ili2pg:2.1.4&apos;),
   @GrabConfig(systemClassLoader = true)
])

import ch.ehi.ili2db.base.Ili2db
import ch.ehi.ili2db.base.Ili2dbException
import ch.ehi.ili2db.gui.Config
import ch.ehi.ili2pg.converter.PostgisGeometryConverter
import ch.ehi.sqlgen.generator_impl.jdbc.GeneratorPostgresql
import groovy.sql.*

def csv = &quot;/Users/stefan/tmp/gb_egridexport150730_2407.csv&quot;
def numberOfCsvRows = 3717
def municipality = &quot;oensingen&quot;
def fosnr = 2407

def dbhost = &quot;localhost&quot;
def dbport = &quot;5432&quot;
def dbdatabase = &quot;xanadu2&quot;
def dbusr = &quot;stefan&quot;
def dbpwd = &quot;ziegler12&quot;
def dbschema = &quot;av_egrid&quot;
def modelName = &quot;GB2AV&quot;

def dburl = &quot;jdbc:postgresql://${dbhost}:${dbport}/${dbdatabase}?user=${dbusr}&amp;amp;password=${dbpwd}&quot;

/*
* 1. Create empty database tables with ili2db.
*/
def query = &quot;DROP SCHEMA IF EXISTS ${Sql.expand(dbschema)} CASCADE;&quot;
def sql = Sql.newInstance(dburl)
sql.execute(query)

def config = new Config()
config.setDbhost(dbhost)
config.setDbdatabase(dbdatabase)
config.setDbport(dbport)
config.setDbusr(dbusr)
config.setDbpwd(dbpwd)
config.setDbschema(dbschema)
config.setDburl(dburl)

config.setModels(modelName);
config.setModeldir(&quot;http://models.geo.admin.ch/&quot;);

config.setGeometryConverter(PostgisGeometryConverter.class.getName())
config.setDdlGenerator(GeneratorPostgresql.class.getName())
config.setJdbcDriver(&quot;org.postgresql.Driver&quot;)

config.setNameOptimization(&quot;topic&quot;)
config.setMaxSqlNameLength(&quot;60&quot;)
config.setSqlNull(&quot;enable&quot;);

config.setDefaultSrsAuthority(&quot;EPSG&quot;)
config.setDefaultSrsCode(&quot;21781&quot;)

Ili2db.runSchemaImport(config, &quot;&quot;)

/*
* 2. Create foreign table from CSV.
*/
query = &quot;&quot;&quot;\
DROP FOREIGN TABLE IF EXISTS ${Sql.expand(dbschema)}.${Sql.expand(municipality)};

CREATE FOREIGN TABLE ${Sql.expand(dbschema)}.${Sql.expand(municipality)}  (
 bfsnr integer,
 kreisnr integer,
 grundstuecknummer varchar,
 grundstuecknummerzusatz varchar,
 grundstuecknummer3 varchar,
 grundstuecknummer4 varchar,
 egrid varchar(14)
) SERVER file_fdw_server
OPTIONS (format &apos;csv&apos;, header &apos;true&apos;,
         filename &apos;${Sql.expand(csv)}&apos;,
         delimiter &apos;,&apos;, null &apos;&apos;);
&quot;&quot;&quot;
sql.execute(query)

// Check number of rows in foreign table.
query = &quot;SELECT count(*) FROM ${Sql.expand(dbschema)}.${Sql.expand(municipality)};&quot;
assert sql.firstRow(query).count == numberOfCsvRows

/*
* 3. Assign EGRID values to cadastral data and insert data into ili2db tables.
*/
query = &quot;&quot;&quot;\
DELETE FROM ${Sql.expand(dbschema)}.eigentumsverhaeltnis_grundstueck;
DELETE FROM ${Sql.expand(dbschema)}.gb2av_grundstuecknummer;

WITH av AS (
 SELECT g.ogc_fid as av_id, g.nbident, g.nummer as av_nummer, l.flaechenmass, l.geometrie, l.gem_bfs, l.lieferdatum
 FROM av_avdpool_ch.liegenschaften_grundstueck as g, av_avdpool_ch.liegenschaften_liegenschaft as l
 WHERE g.gem_bfs = ${Sql.expand(fosnr)}
 AND l.gem_bfs = ${Sql.expand(fosnr)}
 AND g.tid = l.liegenschaft_von
),
gb AS (
 SELECT row_number() OVER () as gb_id, bfsnr, grundstuecknummer as gb_nummer, egrid
 FROM ${Sql.expand(dbschema)}.${Sql.expand(municipality)}
 WHERE grundstuecknummerzusatz IS NULL
),
eigentumsverhaeltnis_grundstueck AS (
 INSERT INTO ${Sql.expand(dbschema)}.eigentumsverhaeltnis_grundstueck (t_id, art)
 SELECT gb_id, 0::integer as art
 FROM av, gb
 WHERE av.av_nummer = gb.gb_nummer
)
INSERT INTO ${Sql.expand(dbschema)}.gb2av_grundstuecknummer(t_id, t_seq, egrid, nummer, gb2aveigntmsvrhltnis_grundstueck_nummer)
SELECT (gb_id+1000000) as t_id, 0::integer as t_seq, gb.egrid, gb.gb_nummer, gb_id
FROM av, gb
WHERE av.av_nummer = gb.gb_nummer;
&quot;&quot;&quot;
sql.execute(query)

// Check if we were able to assign an EGRID to all Liegenschaften from cadastral
// survyeing. If this is true the number of objects in all three tables is equal.
query = &quot;SELECT count(*) FROM av_avdpool_ch.liegenschaften_liegenschaft WHERE gem_bfs = ${Sql.expand(fosnr)};&quot;
def numberOfLiegenschaften = sql.firstRow(query).count

query = &quot;SELECT count(*) FROM ${Sql.expand(dbschema)}.eigentumsverhaeltnis_grundstueck;&quot;
def numberOfEigentumsverhaeltnisGrundstueck = sql.firstRow(query).count

query = &quot;SELECT count(*) FROM ${Sql.expand(dbschema)}.gb2av_grundstuecknummer;&quot;
def numberOfGrundstuecknummer = sql.firstRow(query).count

assert numberOfLiegenschaften == numberOfEigentumsverhaeltnisGrundstueck
assert numberOfLiegenschaften == numberOfGrundstuecknummer

// Close database connection.
sql.connection.close()
sql.close()

/*
* 4. Export data to an INTERLIS/XTF file.
*/
config.setXtffile(&quot;/Users/stefan/tmp/egrid_${municipality}.xtf&quot;)
Ili2db.runExport(config, &quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 34 - 61&lt;/strong&gt;: Im ersten Schritt wird das Schema, in dem die INTERLIS-Modellstruktur angelegt wird, gelöscht (falls es existiert). Anschliessend werden mit ili2pg die leeren Tabellen in der Datenbank angelegt. Mehr Informationen zu den Konfigurationsparametern gibts &lt;a href=&quot;http://sogeo.ch/blog/2015/06/09/interlis-leicht-gemacht-p2/&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 66 - 86&lt;/strong&gt;: Die Daten aus der CSV-Datei müssen in die Datenbank importiert werden, um anschliessend Abfragen durchführen zu können. Ein eleganter Weg die Daten zu importieren, ist die Verwendung eines &lt;a href=&quot;http://www.postgresql.org/docs/9.4/static/file-fdw.html&quot;&gt;Foreign Data Wrappers&lt;/a&gt; für Textdateien. Die Kenntnis über die Struktur (also die Spalten der CSV-Datei und das verwendete Trennzeichen) reicht, um mit einem &lt;code&gt;CREATE FOREIGN TABLE&lt;/code&gt; die Daten zu &quot;importieren&quot;. Mit &lt;code&gt;assert&lt;/code&gt; wird geprüft, ob auch wirklich alles importiert wurde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 91 - 136&lt;/strong&gt;: Anschliessend können mit SQL-Befehlen die AV-Daten mit den importierten Grundbuchdaten verknüpft werden und das gewünschte Ergebnis in die passenden Tabellen (aus Schritt 1) gespeichert werden. Verwendet werden &lt;a href=&quot;http://www.postgresql.org/docs/9.4/static/queries-with.html&quot;&gt;Common Table Expressions&lt;/a&gt;. Somit fallen die x-fach verschachtelten Subqueries weg.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus dem TOPIC &lt;code&gt;Eigentumsverhaeltnis&lt;/code&gt; interessiert eigentlich nur die CLASS &lt;code&gt;Grundstueck&lt;/code&gt;. Die Klasse verwendet jedoch eine STRUCTURE, die in der relationalen Datenbank in einer weiteren Tabelle abgebildet wird. So müssen Daten in zwei Tabellen geschrieben werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auch hier überprüfen wir wieder auf Vollständigkeit: In beiden Tabellen, in die wir Daten geschrieben haben, müssen genau gleich viele Objekte vorhanden sein, wie in der Ausgangstabelle (Liegenschaften der amtlichen Vermessung).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die selbständigen und dauerenden Rechte fehlen in der Abfrage, können aber genau gleich behandelt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 141 - 142&lt;/strong&gt;: Zu guter Letzt exportieren wird unsere Arbeit in eine INTERLIS/XTF-Datei.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Log-Informationen des Exportprozesses sehen schon mal gut aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p4/ili2pg_export_log.png&quot; alt=&quot;ili2pg export log&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein kurzer Blick in die INTERLIS/XTF-Datei zeigt das gewünschte Resultat:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p4/egrid_xtf.png&quot; alt=&quot;egrid xtf&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #3</title>
      <link>http://blog.sogeo.services/blog/2015/08/09/interlis-leicht-gemacht-number-3.html</link>
      <pubDate>Sun, 9 Aug 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/08/09/interlis-leicht-gemacht-number-3.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das GeoIG resp. die &lt;a href=&quot;https://www.admin.ch/opc/de/classified-compilation/20071088/index.html#a34&quot;&gt;GeoIV sieht Downloaddienste&lt;/a&gt; für Geobasisdaten vor. Kurzum heisst das, dass diese Geobasisdaten &lt;em&gt;dienstebasiert&lt;/em&gt; und &lt;em&gt;modellkonform&lt;/em&gt; zum Download bereitgestellt werden. Modellkonform bedeutet - sehr einfach ausgedrückt - entweder INTERLIS/XTF oder &lt;a href=&quot;http://www.ech.ch/vechweb/page?p=dossier&amp;amp;documentNumber=eCH-0118&quot;&gt;INTERLIS/GML&lt;/a&gt;. INTERLIS/GML hat den Vorteil, dass man es, im Gegensatz zu INTERLIS/XTF, sinnvoll mittels WFS bereitstellen kann. Dienstebasiert bedeutet, dass nicht bloss ein einfacher Downloadlink publiziert wird, sondern dass zusätzlich zum Link eine Serviceschicht darüber gestülpt wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gedanken über das modellkonforme Bereitstellen von Geodaten hat sich &lt;a href=&quot;http://inspire.ec.europa.eu/&quot;&gt;INSPIRE&lt;/a&gt; auch schon gemacht. Als Lösungen für Downloaddienste werden zwei Alternativen &lt;a href=&quot;http://inspire.ec.europa.eu/documents/Network_Services/Technical_Guidance_Download_Services_v3.1.pdf&quot;&gt;vorgeschlagen&lt;/a&gt;: WFS und Atom + OpenSearch (AtOS). Informationen zu Atom + OpenSearch findet man vor allem in &lt;a href=&quot;http://www.weichand.de/masterarbeit/Masterarbeit_Weichand.pdf&quot;&gt;zwei&lt;/a&gt; &lt;a href=&quot;https://www.geoportal.nrw.de/application-informationen/inspire/dokumente/images/Masterthesis_Rohrbach.pdf&quot;&gt;Masterarbeiten&lt;/a&gt;. Bei Atom + OpenSearch können die (Geo)daten dateibasiert und vordefiniert heruntergeladen werden. Um dem Servicegedanken gerecht zu werden und nicht bloss stupide HTTP-Links zu publizieren, muss noch eine Schicht drüber gelegt werden. Diese pflanzt dem Ganzen ein wenig Intelligenz ein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Setzt man sich mit der modellkonformen Bereitstellung von Geodaten auseinander, darf man zum Schluss kommen, dass die Atom + OpenSearch Lösung einfacher und effizienter für eine GDI umsetzbar ist. Zudem schlägt man zwei Fliegen mit einer Klappe:  neben modellkonformen Daten können ebenfalls nicht-modellkonforme Daten und Rasterdaten mittels Atom + OpenSearch bereitgestellt werden können. WFS kann natürlich trotzdem eingesetzt werden. In diesem Fall aber nicht für die modellkonforme Bereitstellung. Die gerade eben diskutierten Datenflüsse lassen sich grob wie folgt darstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p3/gdi.png&quot; alt=&quot;GDI Datenbereitstellung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Schema zeigt die beiden Schritte (1) &lt;em&gt;Datenumbau&lt;/em&gt; und (2) &lt;em&gt;Formatumbau&lt;/em&gt;. Der Datenumbau kann z.B. mit einem ETL-Werkzeug oder auch mit SQL-Befehlen durchgeführt werden. Für den Formatumbau eignet sich ja hervorragend &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt;. Der Formatumbau bei der Variante WFS übernimmt der WFS-Server selbst. Nun könnte man zum Schluss kommen, dass die Variante AtOS weniger aktuell als die WFS-Variante ist: Der WFS greift live auf die Daten des MGDM-Topfs zu wohingegen bei AtOS die Datei z.B. einmal pro Tag mit ili2pg physisch produziert wird. Nun, dem ist nicht so. Eine elegante Lösung ist der Einsatz der ili2pg-Bibliotheken als &lt;a href=&quot;https://de.wikipedia.org/wiki/Servlet&quot;&gt;Servlet&lt;/a&gt;. Damit kann der Formatumbau erst beim Aufruf der URL, z.B. &lt;code&gt;&lt;a href=&quot;http://www.example.com/2583_schoenenwerd.xtf&quot; class=&quot;bare&quot;&gt;http://www.example.com/2583_schoenenwerd.xtf&lt;/a&gt;&lt;/code&gt;, ausgeführt werden. Als &lt;em&gt;Proof of Concept&lt;/em&gt; soll die amtliche Vermessung der Gemeinde Schönenwerd im Datenmodell &lt;a href=&quot;http://www.cadastre.ch/internet/kataster/de/home/manuel-av/service/mopublic.html&quot;&gt;MOpublic&lt;/a&gt; mittels Servlet erzeugt werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes brauchen wir einen Servlet-Container. Anstelle von Java kann man auch - um ein paar Zeichen zu sparen - Groovy einsetzen und sich mit  &lt;a href=&quot;http://www.eclipse.org/jetty/&quot;&gt;Jetty&lt;/a&gt; was zusammenbasteln:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;#!/usr/bin/env groovy

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.*
import groovy.servlet.*

@Grab(group=&apos;org.eclipse.jetty.aggregate&apos;, module=&apos;jetty-all&apos;, version=&apos;9.2.10.v20150310&apos;)
def startJetty() {
    def server = new Server(8080)
    def handler = new ServletContextHandler(ServletContextHandler.SESSIONS)
    handler.contextPath = &apos;/&apos;
    handler.resourceBase = &apos;.&apos;
    handler.addServlet(IliExport, &apos;/*&apos;)

    server.handler = handler
    server.start()
    server.join()
}

println &quot;Starting Jetty, press Ctrl+C to stop.&quot;
startJetty()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant sind wahrscheinlich folgende Zeilen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 7&lt;/strong&gt;: Groovy hat mit &lt;a href=&quot;http://docs.groovy-lang.org/latest/html/documentation/grape.html&quot;&gt;Grape&lt;/a&gt; ein effizientes &lt;em&gt;Dependency Management&lt;/em&gt;. Fehlt die gewünschte Bibliothek auf dem System, wird sie einmalig heruntergeladen und gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 13&lt;/strong&gt;: Hier teilen wir dem Server mit, dass bei allen Requests das Servlet &lt;code&gt;IliExport&lt;/code&gt; aufgerufen werden soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Formatumbau/Export-Servlet sieht dann wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;@GrabConfig(systemClassLoader=true)
@GrabResolver(name=&apos;catais.org&apos;, root=&apos;http://www.catais.org/maven/repository/release/&apos;, m2Compatible=&apos;true&apos;)
@Grab(group=&apos;org.postgresql&apos;, module=&apos;postgresql&apos;, version=&apos;9.4-1201-jdbc41&apos;)
@Grab(group=&apos;ch.interlis&apos;, module=&apos;ili2c&apos;, version=&apos;4.5.12&apos;)
@Grab(group=&apos;ch.interlis&apos;, module=&apos;ili2pg&apos;, version=&apos;2.1.4&apos;)

import javax.servlet.http.*
import javax.servlet.*
import groovy.servlet.ServletCategory

import ch.ehi.ili2db.base.Ili2db
import ch.ehi.ili2db.base.Ili2dbException
import ch.ehi.ili2db.gui.Config
import ch.ehi.ili2pg.converter.PostgisGeometryConverter
import ch.ehi.sqlgen.generator_impl.jdbc.GeneratorPostgresql

class IliExport extends HttpServlet {
    def application

    void init(ServletConfig config) {
        super.init(config)
        application = config.servletContext
    }

    void doGet(HttpServletRequest request, HttpServletResponse response) {
        def requestedFileName = request.getRequestURI() - &apos;/&apos;

        def mapping = [
        &apos;2583_schoenenwerd.xtf&apos;: [&apos;modelname&apos;:&apos;MOpublic03_ili2_v13&apos;, &apos;dbschema&apos;:&apos;av_mopublic_export&apos;],
        &apos;2583_schoenenwerd.gml&apos;: [&apos;modelname&apos;:&apos;MOpublic03_ili2_v13&apos;, &apos;dbschema&apos;:&apos;av_mopublic_export&apos;]
        ]
        def params = mapping.get(requestedFileName)
        def modelName = params[&apos;modelname&apos;]
        def dbschema = params[&apos;dbschema&apos;]

        def config = new Config()
        config.setDbhost(&quot;localhost&quot;)
        config.setDbdatabase(&quot;xanadu2&quot;)
        config.setDbport(&quot;5432&quot;)
        config.setDbusr(&quot;stefan&quot;)
        config.setDbpwd(&quot;ziegler12&quot;)
        config.setDbschema(dbschema)
        config.setDburl(&quot;jdbc:postgresql://localhost:5432/xanadu2&quot;)

        config.setModels(modelName);
        config.setModeldir(&quot;http://models.geo.admin.ch/&quot;);

        config.setGeometryConverter(PostgisGeometryConverter.class.getName())
        config.setDdlGenerator(GeneratorPostgresql.class.getName())
        config.setJdbcDriver(&quot;org.postgresql.Driver&quot;)

        config.setNameOptimization(&quot;topic&quot;)
        config.setMaxSqlNameLength(&quot;60&quot;)
        config.setStrokeArcs(&quot;enable&quot;)
        config.setSqlNull(&quot;enable&quot;);
        config.setValue(&quot;ch.ehi.sqlgen.createGeomIndex&quot;, &quot;True&quot;);

        config.setDefaultSrsAuthority(&quot;EPSG&quot;)
        config.setDefaultSrsCode(&quot;21781&quot;)

        def tmpDir = System.getProperty(&apos;java.io.tmpdir&apos;)
        def fileName = tmpDir + File.separator + requestedFileName

        config.setXtffile(fileName)

        Ili2db.runExport(config, &quot;&quot;)

        ServletOutputStream os = response.getOutputStream();
        FileInputStream fis = new FileInputStream(fileName);
        try {
            int buffSize = 1024;
            byte[] buffer = new byte[buffSize];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                os.write(buffer, 0, len);
                os.flush();
                response.flushBuffer();
            }
        } finally {
            os.close();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 1 -2&lt;/strong&gt;: Weil JDBC-Treiber anders geladen werden, müssen wir Grape speziell konfigurieren. Brauchen wir spezielle Mavenrepositories müssen wir diese ebenfalls angeben (Zeile 2).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 26 - 34&lt;/strong&gt;: Sämtliche Requests werden auf dieses Servlet umgeleitet (siehe oben). Der URL-Aufruf entspricht ja eigentlich einem Download einer statischen Datei (INTERLIS/XTF-Datei der Gemeinde Schönenwerd), dh. der Request sieht in unserem Fall wie folgt aus: &lt;code&gt;&lt;a href=&quot;http://localhost:8080/2583_schoenenwerd.xtf&quot; class=&quot;bare&quot;&gt;http://localhost:8080/2583_schoenenwerd.xtf&lt;/a&gt;&lt;/code&gt;. Die dazugehörigen Daten liegen in der Datenbank in einem Schema. In welchem Schema steht aber nicht in der URL, sondern das müssen wir mittels Mapping herausfinden. In diesem einfachen Proof of Concept stehen die benötigten Informationen in einer &lt;code&gt;Map&lt;/code&gt;. Anstelle der &lt;code&gt;Map&lt;/code&gt; ist natürlich auch eine Meta-DB o.ä. vorstellbar. Neben des Speicherortes (Datenbankschema) müssen wir noch den Interlis-Modellnamen kennen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 36 - 66&lt;/strong&gt;: Die INTERLIS/XTF-Datei wird erzeugt und in ein temporäres Verzeichnis geschrieben (= Formatumbau).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 68 - 81&lt;/strong&gt;: Die gerade eben erzeugte Datei wird an den Klienten gestreamt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Um den Browser nicht zu arg zu belasten (bei 25 MB XML) verwenden wir cURL in der Konsole, um die Datei herunterzuladen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;curl http://localhost:8080/2583_schoenenwerd.xtf | xmllint --format -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;code&gt;xmllint&lt;/code&gt; wird das heruntergeladene XML noch schön formatiert. Der Formatumbau dauert für die Gemeinde Schönenwerd circa fünf Sekunden und das Resultat kann sich sehen lassen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p3/xtf.png&quot; alt=&quot;XTF Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Vorteil von ili2pg ist, dass es neben INTERLIS/XTF auch INTERLIS/GML exportieren kann. Der Anwender muss nur &lt;strong&gt;drei&lt;/strong&gt; Buchstaben ändern (.xtf &amp;#8594; .gml):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;curl http://localhost:8080/2583_schoenenwerd.gml | xmllint --format -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wiederum fünf Sekunden später erfreuen wir uns über die GML-Datei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p3/gml.png&quot; alt=&quot;GML Output&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Je nach Situation (Komplexität und Methode) könnte man auch den Datenumbau (1) gerade beim Aufruf erledigen. In unserem Fall wird der Datenumbau mit SQL-Befehlen gemacht und dauert weniger als eine Sekunde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eleganter wäre sicher auch wenn man auf das Zwischenspeichern in einer temporären Datei verzichten könnte und ili2pg direkt zum Klienten streamen könnte. Dann muss man auch nicht mehr den Knorz in den Zeilen 68 - 81 durchführen. Soweit ich das verstehe, dürfte das möglich sein, da die darunterliegende Klasse &lt;a href=&quot;http://www.eisenhutinformatik.ch/iox-ili/javadocs/ch/interlis/iom_j/xtf/XtfWriter.html&quot;&gt;XtfWriter&lt;/a&gt; dies bereits unterstützt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und sollte aus ili2pg irgendwann einmal ein ili2GeoPackage werden, können auch Nicht-PostgreSQL-Anwender diese Bibliotheken verwenden. Man muss bloss den Datenumbau aus dem proprietären System nach GeoPackage machen und den Rest übernimmt ili2GeoPackage&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Bezugsrahmenwechsel: Transformation von Lidardaten</title>
      <link>http://blog.sogeo.services/blog/2015/08/24/bezugsrahmenwechsel-transformation-von-lidardaten.html</link>
      <pubDate>Wed, 24 Jun 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/08/24/bezugsrahmenwechsel-transformation-von-lidardaten.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Frühjahr 2014 liess der Kanton Solothurn eine LiDAR-Befliegung über das &lt;a href=&quot;http://www.sogis1.so.ch/map/lidar&quot;&gt;gesamte Kantonsgebiet&lt;/a&gt; durchführen. Die Daten wurden, wie von uns gewünscht, im Bezugsrahmen LV03 geliefert. Nicht die sinnvollste Entscheidung&amp;#8230;&amp;#8203; Der Bezugsrahmenwechsel steht definitiv vor der Tür und falls wir wollen, dass die Rohdaten in Zukunft noch verwendet werden, müssen wir wohl oder übel auch die 1&apos;066 LAZ-Dateien transformieren.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für die Transformation stehen - je nach Genauigkeit der Daten - verschiedene Transformationsmethoden zur Verfügung. Eine davon ist die Transformation mittels NTv2-Gitter. Anstelle der Dreiecke wird hier ein regelmässiges Gitter verwendet. Die Differenz zwischen der Transformation mittels Dreiecksvermaschung und NTv2-Gitter ist im Kanton Solothurn sehr klein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_lidar/ntv2_vs_chenyx06.jpg&quot; alt=&quot;NTv2-Genauigkeit Kanton Solothurn&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Abbildung zeigt die Differenzen eines 100 m x 100 m Rasters. In drei sehr kleinen Gebieten wird die maximale Abweichung von 25 mm erreicht. Der Mittelwert ist 1.3 mm, der Median bloss 0.6 mm. Die Genauigkeit der LiDAR-Daten liegt im einstelligen bis tiefen zweistelligen Zentimeterbereich. Für die Transformation genügt das NTv2-Gitter also vollkommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eine Bedingung an die transformierten Daten ist, dass sie ebenfalls wieder in Quadratkilometer-Kacheln mit &amp;laquo;schönen&amp;raquo; Boundingbox-Koordinaten vorliegen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_lidar/kachel.png&quot; alt=&quot;Kacheleinteilung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Bild zeigt exemplarisch vier Kacheln. Im linken Teil sind diese vier Kacheln im Bezugsrahmen LV03 gezeichnet. Im rechten Teil (Bezugsrahmen LV95) sind die transformierten Kacheln grau gezeichnet. Diese &amp;laquo;hässlichen&amp;raquo; Koordinaten sind aber für die Kacheleinteilung nicht erwünscht, sondern die &amp;laquo;schönen&amp;raquo; runden Koordinaten. Das bedeutet, dass die graue Kachel mit dem Gebäude neu in vier Kacheln zu liegen kommt. Anders formuliert: Das Gebäude liegt in einer neuen Kachel, die Daten aus vier alten Kacheln beinhaltet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Software muss also etwas Ähnliches wie GDAL mit seinem &lt;a href=&quot;http://www.gdal.org/drv_vrt.html&quot;&gt;vrt-Treiber&lt;/a&gt; anbieten. Dann kann man  genau gleich vorgehen wie bereits mit den &lt;a href=&quot;http://sogeo.ch/blog/2014/11/15/bezugsrahmenwechsel-transformation-von-rasterdaten-number-1/&quot;&gt;Orthofotos&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.pdal.io/&quot;&gt;PDAL&lt;/a&gt; ist ein relativ neues Projekt. Es ist eine Bibliothek für &quot;translating and manipulating point cloud data&quot;. Was GDAL für 2D-Pixel-Daten heute ist, will PDAL für multidimensionale Punkte werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst muss ein Tileindex für sämtliche Kacheln &lt;a href=&quot;http://www.pdal.io/apps.html#tindex-command&quot;&gt;erzeugt&lt;/a&gt; werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;find /home/stefan/mr_candie_nas/Geodaten/ch/so/agi/hoehen/2014/lidar/ -maxdepth 1 -iname &quot;*.laz&quot; | pdal tindex tileindex.gpkg -f &quot;GPKG&quot; --a_srs EPSG:21781 --t_srs EPSG:21781 --fast_boundary --stdin --lyr_name tileindex&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Befehl sucht alle Dateien mit der Endung &lt;code&gt;*.laz&lt;/code&gt; und schreibt die Boundingbox in eine GeoPackage-Datei. Mit der Option &lt;code&gt;--a_srs EPSG:21781&lt;/code&gt; wird den LiDAR-Daten ein Koordinatensystem zugewiesen. Ob die LiDAR-Daten bereits mit einem Koordinatensystem versehen sind, lässt sich mit dem &lt;a href=&quot;http://www.pdal.io/apps.html#info-command&quot;&gt;&lt;code&gt;pdal info&lt;/code&gt;-Befehl&lt;/a&gt; herausfinden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;pdal info --metadata LAS_592228.laz&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;liefert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;filename&quot;: &quot;LAS_592228.laz&quot;,
  &quot;metadata&quot;:
  {
    &quot;comp_spatialreference&quot;: &quot;&quot;,
    &quot;compressed&quot;: true,
    &quot;count&quot;: 14347366,
    &quot;creation_doy&quot;: 312,
    &quot;creation_year&quot;: 2014,
    &quot;dataformat_id&quot;: 1,
    &quot;dataoffset&quot;: 329,
    &quot;filesource_id&quot;: 0,
    &quot;global_encoding&quot;: &quot;AAA=&quot;,
    &quot;header_size&quot;: 227,
    &quot;major_version&quot;: 1,
    &quot;maxx&quot;: 592999.98999999999,
    &quot;maxy&quot;: 228999.98999999999,
    &quot;maxz&quot;: 2134.52,
    &quot;minor_version&quot;: 2,
    &quot;minx&quot;: 592000,
    &quot;miny&quot;: 228000,
    &quot;minz&quot;: 918.08000000000004,
    &quot;offset_x&quot;: -0,
    &quot;offset_y&quot;: -0,
    &quot;offset_z&quot;: -0,
    &quot;project_id&quot;: &quot;00000000-0000-0000-0000-000000000000&quot;,
    &quot;scale_x&quot;: 0.01,
    &quot;scale_y&quot;: 0.01,
    &quot;scale_z&quot;: 0.01,
    &quot;software_id&quot;: &quot;TerraScan&quot;,
    &quot;spatialreference&quot;: &quot;&quot;,
    &quot;system_id&quot;: &quot;&quot;
  },
  &quot;pdal_version&quot;: &quot;1.0.0.b1 (git-version: 1dce22)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat die Datei bereits ein Koordinatensystem, sieht der Output so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &quot;filename&quot;: &quot;LAS_592228.laz&quot;,
  &quot;metadata&quot;:
  {
    &quot;comp_spatialreference&quot;: &quot;PROJCS[\&quot;CH1903 / LV03\&quot;,GEOGCS[\&quot;CH1903\&quot;,DATUM[\&quot;CH1903\&quot;,SPHEROID[\&quot;Bessel 1841\&quot;,6377397.155,299.1528128,AUTHORITY[\&quot;EPSG\&quot;,\&quot;7004\&quot;]],TOWGS84[674.4,15.1,405.3,0,0,0,0],AUTHORITY[\&quot;EPSG\&quot;,\&quot;6149\&quot;]],PRIMEM[\&quot;Greenwich\&quot;,0,AUTHORITY[\&quot;EPSG\&quot;,\&quot;8901\&quot;]],UNIT[\&quot;degree\&quot;,0.0174532925199433,AUTHORITY[\&quot;EPSG\&quot;,\&quot;9122\&quot;]],AUTHORITY[\&quot;EPSG\&quot;,\&quot;4149\&quot;]],PROJECTION[\&quot;Hotine_Oblique_Mercator_Azimuth_Center\&quot;],PARAMETER[\&quot;latitude_of_center\&quot;,46.95240555555556],PARAMETER[\&quot;longitude_of_center\&quot;,7.439583333333333],PARAMETER[\&quot;azimuth\&quot;,90],PARAMETER[\&quot;rectified_grid_angle\&quot;,90],PARAMETER[\&quot;scale_factor\&quot;,1],PARAMETER[\&quot;false_easting\&quot;,600000],PARAMETER[\&quot;false_northing\&quot;,200000],UNIT[\&quot;metre\&quot;,1,AUTHORITY[\&quot;EPSG\&quot;,\&quot;9001\&quot;]],AXIS[\&quot;Y\&quot;,EAST],AXIS[\&quot;X\&quot;,NORTH],AUTHORITY[\&quot;EPSG\&quot;,\&quot;21781\&quot;]]&quot;,
    &quot;compressed&quot;: true,
    &quot;count&quot;: 14347366,
    &quot;creation_doy&quot;: 224,
    &quot;creation_year&quot;: 2015,
    &quot;dataformat_id&quot;: 3,
    &quot;dataoffset&quot;: 2181,
    &quot;filesource_id&quot;: 0,
    &quot;global_encoding&quot;: &quot;AAA=&quot;,
    &quot;header_size&quot;: 227,
    &quot;major_version&quot;: 1,
    &quot;maxx&quot;: 592999.98999999999,
    &quot;maxy&quot;: 228999.98999999999,
    &quot;maxz&quot;: 2134.52,
    &quot;minor_version&quot;: 2,
    &quot;minx&quot;: 592000,
    &quot;miny&quot;: 228000,
    &quot;minz&quot;: 918.08000000000004,
    &quot;offset_x&quot;: 0,
    &quot;offset_y&quot;: 0,
    &quot;offset_z&quot;: 0,
    &quot;project_id&quot;: &quot;00000000-0000-0000-0000-000000000000&quot;,
    &quot;scale_x&quot;: 0.01,
    &quot;scale_y&quot;: 0.01,
    &quot;scale_z&quot;: 0.01,
    &quot;software_id&quot;: &quot;PDAL 1.0.0.b1 (e412bd)&quot;,
    &quot;spatialreference&quot;: &quot;PROJCS[\&quot;CH1903 / LV03\&quot;,GEOGCS[\&quot;CH1903\&quot;,DATUM[\&quot;CH1903\&quot;,SPHEROID[\&quot;Bessel 1841\&quot;,6377397.155,299.1528128,AUTHORITY[\&quot;EPSG\&quot;,\&quot;7004\&quot;]],TOWGS84[674.4,15.1,405.3,0,0,0,0],AUTHORITY[\&quot;EPSG\&quot;,\&quot;6149\&quot;]],PRIMEM[\&quot;Greenwich\&quot;,0,AUTHORITY[\&quot;EPSG\&quot;,\&quot;8901\&quot;]],UNIT[\&quot;degree\&quot;,0.0174532925199433,AUTHORITY[\&quot;EPSG\&quot;,\&quot;9122\&quot;]],AUTHORITY[\&quot;EPSG\&quot;,\&quot;4149\&quot;]],PROJECTION[\&quot;Hotine_Oblique_Mercator_Azimuth_Center\&quot;],PARAMETER[\&quot;latitude_of_center\&quot;,46.95240555555556],PARAMETER[\&quot;longitude_of_center\&quot;,7.439583333333333],PARAMETER[\&quot;azimuth\&quot;,90],PARAMETER[\&quot;rectified_grid_angle\&quot;,90],PARAMETER[\&quot;scale_factor\&quot;,1],PARAMETER[\&quot;false_easting\&quot;,600000],PARAMETER[\&quot;false_northing\&quot;,200000],UNIT[\&quot;metre\&quot;,1,AUTHORITY[\&quot;EPSG\&quot;,\&quot;9001\&quot;]],AXIS[\&quot;Y\&quot;,EAST],AXIS[\&quot;X\&quot;,NORTH],AUTHORITY[\&quot;EPSG\&quot;,\&quot;21781\&quot;]]&quot;,
    &quot;system_id&quot;: &quot;PDAL&quot;
  },
  &quot;pdal_version&quot;: &quot;1.0.0.b1 (git-version: 1dce22)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Option &lt;code&gt;--t_srs&lt;/code&gt; beschreibt das Koordinatensystem des zu erzeugenden Tileindexes. Mit &lt;code&gt;--fast_boundary&lt;/code&gt; wird die Boundingbox nicht aus den Daten selbst eruiert, sondern es werden die Koordinaten aus den Metadaten verwenden. Und ja, es ist massiv schneller.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anschliessend an die Erstellung des Tileindexes, kann mittels Pythonskript, einer For-Schleife und dreier PDAL-Befehlen das gewünschte Resultat berechnet werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/python
# -*- coding: utf-8 -*-
import os.path

from osgeo import ogr, osr
import os
import sys

ogr.UseExceptions()

S_SRS = &quot;+proj=somerc +lat_0=46.952405555555555N +lon_0=7.439583333333333E +ellps=bessel +x_0=600000 +y_0=200000 +towgs84=674.374,15.056,405.346 +units=m +units=m +k_0=1 +nadgrids=./chenyx06/chenyx06a.gsb&quot;
T_SRS = &quot;+proj=somerc +lat_0=46.952405555555555N +lon_0=7.439583333333333E +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +nadgrids=@null&quot;

LAYERNAME = &quot;tileindex&quot;
TEMPDIR = &quot;/tmp/&quot;

TILEINDEX = &quot;/home/stefan/tmp/lidar/tileindex.gpkg&quot;
OUTDIR = &quot;/home/stefan/tmp/&quot;

BUFFER = 2
SCALE = 0.01

gpkg = ogr.Open(TILEINDEX)
lyr = gpkg.GetLayerByName(LAYERNAME)

for feat in lyr:
    cmd = &quot;rm &quot; + os.path.join(TEMPDIR, &quot;*.las&quot;)
    os.system(cmd)

    cmd = &quot;rm &quot; + os.path.join(TEMPDIR, &quot;*.laz&quot;)
    os.system(cmd)

    filename = feat.GetField(&quot;location&quot;)
    print &quot;*** &quot; + os.path.basename(filename) + &quot; ***&quot;

    env = feat.GetGeometryRef().GetEnvelope()
    min_x = int(env[0] + 0.001)
    min_y = int(env[2] + 0.001)
    max_x = int(env[1] + 0.001)
    max_y = int(env[3] + 0.001)

    # Create new file name.
    filename_lv95 = &quot;LAS_&quot; + str(min_x/1000 + 2000) + &quot;_&quot; + str(min_y/1000 + 1000) + &quot;.laz&quot;
    filename_lv95 = os.path.join(OUTDIR, filename_lv95)

    # Get a slightly larger tile. Two meters buffer is enough. We crop afterwards.
    # Reason: 620&apos;000 -&amp;gt; 2&apos;620&apos;000.65
    bounds = &quot;([&quot;+str(min_x-BUFFER)+&quot;, &quot;+str(max_x+BUFFER)+&quot;], [&quot;+str(min_y-BUFFER)+&quot;, &quot;+str(max_y+BUFFER)+&quot;])&quot;
    cmd = &apos;pdal tindex --merge &apos; + TILEINDEX + &apos; --lyr_name tileindex --bounds &quot;&apos; + str(bounds) + &apos;&quot; --t_srs EPSG:21781 &apos; + os.path.join(TEMPDIR, &apos;tmp.las&apos;)
    os.system(cmd)

    # Now change reference frame with ntv2.
    cmd = &apos;pdal translate --a_srs &quot;&apos;+S_SRS+&apos;&quot; --t_srs &quot;&apos;+T_SRS+&apos;&quot; -i &apos; + os.path.join(TEMPDIR, &apos;tmp.las&apos;) + &apos; -o &apos; + os.path.join(TEMPDIR, &apos;tmp_lv95.las&apos;)
    os.system(cmd)

    # Set better/nicer EPSG:2056. The ntv2 transformation lacks of datum name.
    # Crop to nice bbbox.
    bounds = &quot;([&quot;+str(min_x+2000000)+&quot;, &quot;+str(max_x+2000000)+&quot;], [&quot;+str(min_y+1000000)+&quot;, &quot;+str(max_y+1000000)+&quot;])&quot;
    cmd = &apos;pdal translate --a_srs EPSG:2056 --t_srs EPSG:2056 --writers.las.format=&quot;1&quot; --bounds &quot;&apos; + bounds + &apos;&quot; -z -i &apos; + os.path.join(TEMPDIR, &apos;tmp_lv95.las&apos;) + &apos; -o &apos; + os.path.join(OUTDIR, filename_lv95)
    os.system(cmd)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Grundprinzip resp. -vorgehen steht bereits &lt;a href=&quot;http://sogeo.ch/blog/2014/11/15/bezugsrahmenwechsel-transformation-von-rasterdaten-number-1/&quot;&gt;hier&lt;/a&gt;. Interessant sind bloss die drei PDAL-Aufrufe:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 48 - 50&lt;/strong&gt;: Es wird aus dem Tileindex eine etwas (&lt;code&gt;BUFFER&lt;/code&gt; = 2 Meter) grössere Kachel ausgeschnitten. Es muss nur soviel mehr sein, dass wir anschliessend an die Transformation garantiert &amp;laquo;schöne&amp;raquo; LV95-Koordinaten ausschneiden können. Mit &lt;code&gt;pdal tindex&lt;/code&gt; und der Option &lt;code&gt;--merge&lt;/code&gt; können Daten, die in einem Tileindex vorliegen, zusammengefügt werden. Zusätzlich gibt es die Option &lt;code&gt;--bounds&lt;/code&gt; mit der nur ein ganz bestimmtes Rechteck gemerged resp. ausgeschnitten werden kann. Leider - so wie ich es verstanden habe - werden jeweils zuerst sämtliche vom Rechteck betroffene Kacheln komplett zusammengefügt und erst anschliessend ausgeschnitten. Das schlägt sich einerseits in der Geschwindigkeit und andererseits im RAM-Verbrauch nieder. In Normalfall sind ja neun Kacheln betroffen, die zuerst zusammengefügt werden müssen. Das braucht circa 9 GB RAM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 53 - 54&lt;/strong&gt;: Nach dem Zusammenfügen und Ausschneiden können die Daten mit dem NTv2-Grid transformiert werden. Dazu verwendet man den Befehl &lt;code&gt;pdal translate&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 58 - 59&lt;/strong&gt;: Zu guter Letzt müssen die transformierten Daten auf die &amp;laquo;schönen&amp;raquo; Koordinaten zurechtgeschnitten werden. Zudem kann in diesem Schritt das Koordinatensystem in den Metadaten sauber gesetzt werden (&lt;code&gt;--a_srs EPSG:2056&lt;/code&gt; und &lt;code&gt;--t_srs EPSG:2056&lt;/code&gt;). Sonst stehen da die hässlichen PROJ4-Strings der NTv2-Transformation drin. Für das Zuschneiden kann der Befehl &lt;code&gt;pdal translate&lt;/code&gt; mit der Option &lt;code&gt;--bounds&lt;/code&gt; verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sind jetzt 1&apos;066 LiDAR-Kacheln mit &amp;laquo;schönen&amp;raquo; Boundingbox-Koordinaten in LV95.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ganze Rechnerei dauert aber ein wenig. Liegen die Daten lokal auf der SSD vor, dauert das Prozedere für eine Kachel circa 150 Sekunden. Mein Setup war aufgrund des Platzbedarfs leider ziemlich übel: Die Daten liegen auf einem NAS, das via WLAN an den Computer angebunden ist. Gerechnet wird in einer virtuellen Maschine, in der das NAS mit &lt;code&gt;sshfs&lt;/code&gt; gemounted ist. So rechnete das fast 4 Tage vor sich hin. Also fast doppelt so lange.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hätte man jedoch genügend RAM, könnte man den Prozess leicht parallelisieren. Man muss bloss das Skript x-mal anstossen und die For-Schleife dürfte nicht den gleichen Tileindex als Input haben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Prozess selbst konnte leicht optimert werden, indem beim Zwischenschritt nicht mit der Option &lt;code&gt;-c&lt;/code&gt; komprimiert wurde. Ebenfalls gewinnbringend wirkt sich der Einsatz von &lt;code&gt;--bounds&lt;/code&gt; anstelle &lt;code&gt;--polygon&lt;/code&gt; aus. Will man also bloss ein Rechteck ausscheiden und nicht ein beliebiges Polygon reicht &lt;code&gt;--bounds&lt;/code&gt; allemal und &lt;code&gt;pdal&lt;/code&gt; muss in diesem Fall keine teuren &lt;a href=&quot;http://trac.osgeo.org/geos/&quot;&gt;GEOS&lt;/a&gt;-Operationen verwenden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #2</title>
      <link>http://blog.sogeo.services/blog/2015/06/09/interlis-leicht-gemacht-p2.html</link>
      <pubDate>Tue, 9 Jun 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/06/09/interlis-leicht-gemacht-p2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im &lt;a href=&quot;http://sogeo.ch/blog/2015/05/09/interlis-leicht-gemacht-number-1/&quot;&gt;letzten Beitrag&lt;/a&gt; habe ich gezeigt, wie man einfach und effizient mit einem Kommandozeilenbefehl und dem Java-Tool &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt; aus INTERLIS-Modellen eine Datenbankstruktur anlegen kann.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Schöne an Java und an ili2pg ist, dass man diese Funktionalität jetzt auch in eigenen Java-Code und dementsprechend in einen eigenen Importprozess einbinden kann. Eventuell müssen ja vorgängig Daten bearbeiten werden oder nach dem Import müssen weitere Prozessierungen vorgenommen werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wie das geht? Ganz einfach: Einzig die drei Jar-Dateien &lt;code&gt;ili2c.jar&lt;/code&gt;, &lt;code&gt;ili2pg.jar&lt;/code&gt; und &lt;code&gt;postgresql-9.1-901.jdbc4.jar&lt;/code&gt; müssen im Klassenpfad sein. Mit einer IDE seiner/ihrer Wahl kein Problem. Das kann man sich sogar zusammenklicken.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein minimales Java-Programm zum Importieren einer ITF-Datei sieht wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;package org.catais.interlis;

import ch.ehi.ili2db.base.Ili2db;
import ch.ehi.ili2db.base.Ili2dbException;
import ch.ehi.ili2db.gui.Config;
import ch.ehi.ili2pg.converter.PostgisGeometryConverter;
import ch.ehi.sqlgen.generator_impl.jdbc.GeneratorPostgresql;


public class Ili2pgTest {

	public static void main(String[] args) {

        Config config = new Config();
        config.setDbdatabase(&quot;xanadu2&quot;);
        config.setDbhost(&quot;localhost&quot;);
        config.setDbport(&quot;5432&quot;);
        config.setDbusr(&quot;stefan&quot;);
        config.setDbpwd(&quot;ziegler12&quot;);
        config.setDbschema(&quot;test5&quot;);
        config.setModels(&quot;DM01AVCH24LV95D&quot;);
        config.setModeldir(&quot;/home/stefan/Downloads/&quot;);

        config.setGeometryConverter(PostgisGeometryConverter.class.getName());
        config.setDdlGenerator(GeneratorPostgresql.class.getName());
        config.setJdbcDriver(&quot;org.postgresql.Driver&quot;);

        config.setNameOptimization(&quot;topic&quot;);
        config.setMaxSqlNameLength(&quot;60&quot;);

        config.setDefaultSrsAuthority(&quot;EPSG&quot;);
        config.setDefaultSrsCode(&quot;2056&quot;);

        config.setXtffile(&quot;/home/stefan/Downloads/ch_lv95_254900.itf&quot;);

        String dburl = &quot;jdbc:postgresql://&quot; + config.getDbhost() + &quot;:&quot; + config.getDbport() + &quot;/&quot; + config.getDbdatabase();
        config.setDburl(dburl);

        try {
            Ili2db ili2pg = new Ili2db();
            ili2pg.runImport(config, &quot;&quot;);
        } catch (Ili2dbException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Man benötigt lediglich zwei Klassen: eine Konfigurationsklasse &lt;code&gt;Config&lt;/code&gt; und die eigentliche Importklasse &lt;code&gt;Ili2db&lt;/code&gt;. Mittels der Konfigurationsklasse steuert man verschiedene Parameter &lt;em&gt;und&lt;/em&gt; in welchen Datenbanktyp importiert werden soll. Dies ist nötig, um gewisse Unterschiede zwischen den Datenbanktypen abzufangen resp. anders zu behandeln.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 1 - 22&lt;/strong&gt;: Diese Zeilen sollten eigentlich selbsterklärend sein.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 24 - 26&lt;/strong&gt;: Hier wird der Konfigurationsklasse mitgeteilt, dass es sich bei der Datenbank um PostgreSQL/Postgis handelt. Dies hat Auswirkungen auf die Konvertierung der Geometrie und auf das Erstellen der SQL-Befehle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 28 - 29&lt;/strong&gt;: Mit der Methode &lt;code&gt;setNameOptimization(&quot;topic&quot;)&lt;/code&gt; werden die Datenbanktabellennamen zusammengesetzt aus Topic- und Klassennamen (verbunden mit einem Untertrich): &lt;code&gt;topic_class&lt;/code&gt;. Mit der Methode &lt;code&gt;setMaxSqlNameLength(&quot;60&quot;)&lt;/code&gt; wird die maximale Länge der SQL-Namen auf 60 Zeichen gesetzt. Das Setzen der maximalen Länge ist wichtig, da der Prozess sonst abbricht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 31 - 32&lt;/strong&gt;: Wird was anderes als LV03 (EPSG:21781) importiert, muss das hier mittels EPSG-Code definiert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 34&lt;/strong&gt;: Hier wird die zu importierende Interlis-Datei angegeben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 36 - 37&lt;/strong&gt;: &lt;del&gt;Warum das manuelle Zusammensetzen der Datenbankurl noch nötig ist, ist mir nicht ganz klar. Eigentlich kennt die Konfigurationsklasse bereits alle angaben, die dazu benötig werden.&lt;/del&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 40 - 41&lt;/strong&gt;: Zu guter Letzt kann die Importklasse instanziiert werden und der Importprozess kann gestartet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Falls keine Fehlermeldungen in der Konsole erscheinen, sollten die Daten erfolgreich importiert worden sein:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p2/pgadmin3.png&quot; alt=&quot;pgadmin3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Interlis leicht gemacht #1</title>
      <link>http://blog.sogeo.services/blog/2015/05/09/interlis-leicht-gemacht-number-1.html</link>
      <pubDate>Sat, 9 May 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2015/05/09/interlis-leicht-gemacht-number-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Im Kanton Solothurn wird gegenwärtig die &lt;a href=&quot;http://www.cadastre.ch/internet/gb/de/home/egris/doc/definitionen_und_schnittstellen.html&quot;&gt;AVGBS&lt;/a&gt; eingeführt. Die Gebäudeadressen sollen nicht von den einzelnen Nachführungsgeometern an das Grundbuch geliefert werden, sondern durch das &lt;a href=&quot;http://www.agi.so.ch&quot;&gt;Amt für Geoinformation&lt;/a&gt;. Die Daten der amtlichen Vermessung - aus denen die Lieferung erstellt wird - liegen wochenaktuell als Kopie zentral in einer PostgreSQL/PostGIS-Datenbank beim Amt für Geoinformation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Datenmodell &lt;a href=&quot;http://models.geo.admin.ch/BJ/KS3-20060703.ili&quot;&gt;GB2AV&lt;/a&gt; (aka &quot;Kleine Schnittstelle&quot;), das die auszutauschenden Daten zwischen dem Grundbuch und der amtlichen Vermessung beschreibt, ist in INTERLIS 2.2 geschrieben. Uiuiui, INTERLIS 2&amp;#8230;&amp;#8203; Vererbung und so. Noch nie wirklich was mit gemacht. Bisweilen immer nur INTERLIS 1. Anstatt nur Trockenübungen, ergibt sich hier und jetzt die Gelegenheit etwas Praktisches mit INTERLIS 2 zu machen. Wie kommen jetzt also die Gebäudeadressen aus unserer zentralen Datenbank ins Grundbuch? Oder anders gefragt: Wie erstelle ich diese INTERLIS 2-Transferdatei?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Erstellen der INTERLIS 2-Transferdatei besteht aus zwei wesentlichen Schritten: &lt;em&gt;Datenumbau&lt;/em&gt; und &lt;em&gt;Formatumbau&lt;/em&gt;. Zuerst müssen die Gebäudeadressen so umgebaut werden, damit sie dem AVGBS-Modell entsprechen. Das passiert in der Datenbank. Der zweite Schritt ist dann nur noch ein Export aus der Datenbank in die INTERLIS 2-Transferdatei. Also ein Formatumbau vom Datenbankformat in das INTERLIS 2 Austauschformat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Datenumbau ist ein Umbau der Daten vom Modell der amtlichen Vermessung in das AVGBS-Modell in der Datenbank. Das leere AVGBS-Modell (also die verschiedenen Tabellen) in der Postgis-Datenbank erzeugt mir die Software &lt;a href=&quot;http://www.eisenhutinformatik.ch/interlis/ili2pg/&quot;&gt;ili2pg&lt;/a&gt;. Die Software wird zum jetzigen Zeitpunkt weiterentwickelt: Dokumentation (Programmoptionen, Abbildungsregeln von Klassen, Vererbungsstrategie), Umgang mit Kreisbogen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Folgender Aufruf erzeugt ein Datenbankschema mit leeren Tabellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -Xms128m -Xmx2048m -jar ili2pg.jar --schemaimport --dbhost localhost --dbport 5432 --dbdatabase xanadu --dbschema av_avgbs --dbusr stefan --dbpwd ziegler12 --models GB2AV --modeldir ./ --nameByTopic&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--schemaimport&lt;/code&gt;: Es werden keine Daten importiert sondern lediglich leere Tabellen erzeugt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--models&lt;/code&gt;: Das zu verwendende Interlismodell (oder mehrere Interlismodelle).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--modeldir&lt;/code&gt;: Verzeichnisse, wo die Interlismodelle liegen (oder Repository-URL).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--nameByTopic&lt;/code&gt;: Die Tabellen werden nach dem Schema &lt;code&gt;Topicname_Klassenname&lt;/code&gt; erzeugt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p1/schemaimport.png&quot; alt=&quot;ili2pg schemaimport&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aus dem objektorientierten Interlismodell wurden nach gewissen Regeln in der relationalen Datenbank Tabellen erzeugt. Das Verständnis für die Abbildung des Modelles in der Datenbank braucht vielleicht ein wenig Übung. Aber hat man das UML-Diagramm, das Interlismodell und die Tabellen in der Datenbank vor Augen, lernt man das relativ schnell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für den eigentlichen Datenumbau - also das Abfüllen der vorher erstellen, leeren Tabellen mit den in der Datenbank vorhandenen Daten der amtlichen Vermessung - verwende ich &lt;a href=&quot;http://community.pentaho.com/projects/data-integration/&quot;&gt;Kettle&lt;/a&gt;. Leider scheint das Projekt &lt;a href=&quot;http://geokettle.org/&quot;&gt;GeoKettle&lt;/a&gt; nicht mehr aktiv zu sein. Da bei diesem Datenumbau keine Geometrien umher geschoben werden müssen, kann man auch das &quot;normale&quot; Kettle verwenden. Exemplarisch eine Transformation:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/interlis_leicht_gemacht_p1/kettle.png&quot; alt=&quot;Kettle&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In dieser Kettle-Transformation werden sämtliche Gebäude aus den Daten der amtlichen Vermessung in die Tabelle/Klasse &lt;code&gt;Gebaeude&lt;/code&gt; des Topics &lt;code&gt;Grundstuecksbeschrieb&lt;/code&gt; geschrieben. Der nötige Verschnitt &quot;Gebäude - Liegenschaft&quot; für die Association &lt;code&gt;GrundstueckGebaeude&lt;/code&gt; (ebenfalls als Tabelle in der Datenbank abgebildet) wird in der Datenbank gerechnet. Analog wird der Datenumbau für die anderen Klassen und Strukturen gemacht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wenn jetzt alle benötigen Daten in die neuen Tabellen umgebaut wurden, kann der Formatumbau ausgelöst werden. Beim Formatumbau hilft mir wiederum die Software &lt;em&gt;ili2pg&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;java -jar ili2pg.jar --export --dbhost localhost --dbport 5432 --dbdatabase xanadu --dbschema av_avgbs --dbusr stefan --dbpwd ziegler12 --models GB2AV grundstuecksbeschrieb.xtf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein Verzeichnis, wo sich das Interlismodell befindet, muss man nicht mehr angeben, da das Modell in einer zusätzlichen Tabelle beim Schemaimport mit importiert wurde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Ergebnis des Exportes ist eine INTERLIS 2-Transferdatei. Beim Überprüfen stellt man fest, dass die Daten aber zweimal vorhanden sind: einmal im Topic &lt;code&gt;Grundstuecksbeschrieb&lt;/code&gt; und ein zweites Mal im Topic &lt;code&gt;Mutationstabelle&lt;/code&gt;. Das Topic &lt;code&gt;Mutationstabelle&lt;/code&gt; erbt sämtliche Klassen des Topics &lt;code&gt;Grundstuecksbeschrieb&lt;/code&gt;. Beim Export weiss &lt;em&gt;ili2pg&lt;/em&gt; nicht zu welchen Topic die Daten gehören und schreibt sie bei beiden. Dieses Verhalten muss in einer Weiterentwicklung geändert werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieses Vorgehen eignet sich auch hervorragend für die Umsetzung von minimalen Geodatenmodellen im Rahmen des &lt;a href=&quot;https://www.admin.ch/opc/de/classified-compilation/20050726/index.html&quot;&gt;GeoIG&lt;/a&gt;, wie Peter Staub in seinem &lt;a href=&quot;http://www.gl.ch/documents/Whitepaper_UmsetzungMGDM.pdf&quot;&gt;Whitepaper&lt;/a&gt; eindrücklich zeigt.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>QGIS Server: VRT vs. GeoPackage Raster</title>
      <link>http://blog.sogeo.services/blog/2014/12/25/qgis-server-vrt-vs-geopackage.html</link>
      <pubDate>Thu, 25 Dec 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/12/25/qgis-server-vrt-vs-geopackage.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Seit &lt;a href=&quot;http://osgeo-org.1560.x6.nabble.com/gdal-dev-GDAL-GeoPackage-raster-support-td5177342.html&quot;&gt;kurzem&lt;/a&gt; unterstützt GDAL &lt;a href=&quot;http://www.gdal.org/drv_geopackage_raster.html&quot;&gt;Geopackage Raster&lt;/a&gt;, was ziemlich cool ist. Jetzt man kann man z.B. sämtliche Orthofotos des Kantons Solothurn (1993 bis 2014) in eine &lt;a href=&quot;http://www.catais.org/geodaten/ch/so/kva/orthofoto/orthofoto.gpkg&quot;&gt;85 GB grosse SQLite-Datenbank&lt;/a&gt; packen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und weil mehr oder weniger gilt &amp;laquo;Kann es GDAL, so kann es auch QGIS.&amp;raquo;, soll hier kurz die Performance von QGIS Server anhand eines Orthofoto-WMS verglichen werden. Momentan wird aus vielen einzelnen GeoTIFF-Dateien eine VRT-Datei erstellt und diese in QGIS geladen. Bis zum Massstab 1:20&apos;000. Für kleinere Massstäbe wird ein 5m-Orthofoto verwendet. Ab diesem Massstab sieht man praktisch keinen Unterschied mehr zwischen Originalkacheln (12.5cm) und 5m-Orthofoto. Als Resampling-Methode wird &amp;laquo;average&amp;raquo; mit Faktor 2 verwendet. Insgesamt sind es drei VRT-Dateien sowie drei 5m-Orthofotos (für die Jahre 2012-2014).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zum Vergleich werden zwei Varianten mit Geopackage herangezogen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Pro Jahr eine Geopackage-Datei (zwischen 5 und 10 GB).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Eine einzige Geopackage-Datei mit sämtlichen Orthofotos (inkl. abgeleiteten Produkten, circa 85 GB)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit folgenden Befehlen und Parametern wurden die GPKG-Dateien erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;gdal_translate --config OGR_SQLITE_SYNCHRONOUS OFF -co APPEND_SUBDATASET=YES -co RASTER_TABLE=ch.so.agi.orthofoto.2014.rgb -co TILE_FORMAT=PNG_JPEG -of GPKG /home/stefan/Geodaten/ch/so/kva/orthofoto/2014/rgb/12_5cm/ortho2014rgb.vrt /home/stefan/tmp/orthofoto.gpkg
gdaladdo --config OGR_SQLITE_SYNCHRONOUS OFF -oo TABLE=ch.so.agi.orthofoto.2014.rgb -r average /home/stefan/tmp/orthofoto.gpkg 2 4 8 16 32 64 128 256&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Testsetup war gleich wie bei &lt;a href=&quot;http://sogeo.ch/blog/2014/01/29/qgis-server-vs-qgis-server/&quot;&gt;anderen QGIS Server Performance Tests&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/vrt_vs_gpkg/ortho_vrt_vs_gpkg_bench.png&quot; alt=&quot;GeoPackage-VRT-Benchmark&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anscheinend ist es völlig egal, ob drei einzelne GPKG-Dateien oder eine sehr grosse GPKG-Datei verwendet wird. Der ganz leichte Geschwindigkeitsvorteil von VRT gegenüber GPKG lässt sich beliebig wiederholen. Interessant ist aber die Tatsache, dass bei den GPKG-Varianten beinahe kein average-Resampling notwendig ist/wäre (Geschwindigkeitsfaktor circa 2.5). Da sehen die gerenderten Bilder auch mit &amp;laquo;nearest neighbour&amp;raquo; ganz ordentlich aus. Ganz im Gegensatz dazu die VRT-Variante, die sichtbar an visueller Qualität verliert ohne average-Resampling.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Fun with GeoKettle Episode 2</title>
      <link>http://blog.sogeo.services/blog/2014/11/29/fun-with-geokettle-episode-2.html</link>
      <pubDate>Sat, 29 Nov 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/11/29/fun-with-geokettle-episode-2.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eigentlich geht es hier eher um &lt;a href=&quot;http://postgis.net/docs/RT_reference.html&quot;&gt;PostGIS Raster&lt;/a&gt; als um &lt;a href=&quot;http://geokettle.org/&quot;&gt;GeoKettle&lt;/a&gt;. GeoKettle wird jedoch am Schluss noch für einen kleinen Arbeitsschritt verwendet. Darum sei der Titel erlaubt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Solothurn hat dieses Jahr eine &lt;a href=&quot;http://www.catais.org/geodaten/ch/so/kva/hoehen/2014/&quot;&gt;LiDAR-Befliegung&lt;/a&gt; über sein ganzes Kantonsgebiet durchführen lassen. Neben den Rohdaten wurden abgeleitete Produkte (z.B. DTM, DOM etc.) hergestellt. Als &lt;strong&gt;eine&lt;/strong&gt; Plausibilitätskontrolle sollen die Fixpunkte der amtlichen Vermessung mit dem 50cm-DTM verglichen werden. Möglichkeiten wie man das machen kann, gibts viele. Ich habe mich für PostGIS Raster entschieden, um ein wenig Praxis darin zu bekommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zuerst muss das DTM mit dem Befehl &lt;a href=&quot;http://postgis.net/docs/using_raster_dataman.html#RT_Raster_Loader&quot;&gt;raster2pgsql&lt;/a&gt; in PostGIS importiert werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;raster2pgsql -d -s 21781 -I -C -M -F -r /opt/Geodaten/ch/so/kva/hoehen/2014/dtm/*.tif -t 100x100 av_lidar_2014.dtm | psql -d rosebud2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Befehl werden sämtliche GeoTiff-Dateien im Verzeichnis &lt;code&gt;/opt/Geodaten/ch/so/kva/hoehen/2014/dtm/&lt;/code&gt; in die Tabelle &lt;code&gt;dtm&lt;/code&gt; im Schema &lt;code&gt;av_lidar_2014&lt;/code&gt; importiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-d&lt;/code&gt;: Vorhandene Tabelle wird gelöscht und neu angelegt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-s 21781&lt;/code&gt;: Den Daten wird das Koordinatensystem EPSG:21781 zugewiesen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-I&lt;/code&gt;: Es wird ein Index für die Tabelle erzeugt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-C&lt;/code&gt;: Verschiedene Constraints werden hinzugefügt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-M&lt;/code&gt;: Nach dem Import wird ein &lt;code&gt;vacuum analyze&lt;/code&gt; ausgeführt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt;: Es wird eine Spalte mit dem Dateinamen in der Tabelle hinzugefügt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-r&lt;/code&gt;: Weiterer Constraint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-t&lt;/code&gt;: Die Grösse der Kacheln in welche die Ausgangsdaten geschnitten und in der Datenbank gespeichert werden. Die Grösse der Kacheln ist entscheidend für die &lt;a href=&quot;http://duncanjg.wordpress.com/2013/09/21/effect-of-tile-size-and-data-storage-on-postgis-raster-query-times/&quot;&gt;Ausführungsdauer&lt;/a&gt; der Abfragen, die später gemacht werden. Für unseren Anwendungsfall ist es sinnvoll die Kachelgrösse eher klein zu wählen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das dauert je nach Datenmenge ein paar Minuten und braucht ordentlich Speicherplatz in der Datenbank (DTM und DOM, je 1066 km2):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep2/postgres_size_rosebud2-month.png&quot; alt=&quot;DB size&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das wirklich Schöne an Postgis Raster ist, dass man völlig unkompliziert Vektor- und Rasterdaten gleichzeitig/gemeinsam abfragen kann. Uns interessiert die Höhe aus dem DTM an der Stelle wo ein Fixpunkt der amtlichen Vermessung vorhanden ist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT p.ogc_fid, p.nummer, p.kategorie, p.geometrie, ST_X(p.geometrie) as x, ST_Y(p.geometrie) as y,
   p.hoehe as h_lfp, ST_Value(r.rast, p.geometrie) as h_dtm,
   (ST_Value(r.rast, p.geometrie) - p.hoehe) as diff, p.bfsnr
FROM av_avwmsde_t.cppt as p, av_lidar_2014.dtm as r
WHERE ST_Intersects(r.rast, p.geometrie)
AND p.hoehe IS NOT NULL
AND p.kategorie IN (&apos;LFP1&apos;, &apos;LFP2&apos;, &apos;LFP3&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ganze Magie besteht aus zwei Funktionen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ST_Value(r.rast, p.geometrie)&lt;/code&gt;: Diese Funktion liefert für eine Koordinate (p.geometrie) den Zellenwert des Rasters (r.rast) zurück.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ST_Intersects(r.rast, p.geometrie)&lt;/code&gt;: Funktioniert grundsätzlich gleich wie das Vektorpendant.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alles andere ist Beigemüse.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat wird als Shapedatei und als Exceldatei abgespeichert. Dafür verwenden wir GeoKettle. Die GeoKettle-Transformation sieht dann völlig unspektakulär so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep2/geokettle_diff_dtm_av.png&quot; alt=&quot;GeoKettle Job&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Und das Resultat? Unter Berücksichtigung der Randbedingungen (50cm-DTM, Fixpunkt teilweise unter Schacht etc.) stimmen die resultierenden Differenzen positiv:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep2/diff_dtm_lfp_v2.png&quot; alt=&quot;Differenz Höhe LFP&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Klar gibt es grössere Differenzen (Fixpunkt auf Gebäude oder Kunstbaute, Kirchenspitze etc.). Diese sind aber praktisch alle erklärbar (und wären auch vorgängig filterbar). Ebenso konnten einige wenige Höhenfehler in den Daten der amtlichen Vermessung gefunden werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Bezugsrahmenwechsel: Transformation von Rasterdaten #1</title>
      <link>http://blog.sogeo.services/blog/2014/11/15/bezugsrahmenwechsel-transformation-von-rasterdaten-number-1.html</link>
      <pubDate>Sat, 15 Nov 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/11/15/bezugsrahmenwechsel-transformation-von-rasterdaten-number-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit dem &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/software/products/chenyx06.html&quot;&gt;NTv2-CHENyx06-Datensatz&lt;/a&gt;  lassen sich beliebige Datenformate (Vektor und Raster) transformieren. So unterstützt z.B. &lt;a href=&quot;http://sourcepole.ch/ntv2-transformations-with-qgis&quot;&gt;QGIS&lt;/a&gt; seit der Version 2.2 diese Möglichkeit der Datumstransformation. Die Genauigkeit dieser Methode ist im Kanton Solothurn nur marginal schlechter als die strenge Transformation mit dem FINELTRA-Algorithmus. Für die allermeisten Darstellungen am Bildschirm oder für die Transformation von Rasterdaten reicht die Genauigkeit völlig aus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein NTv2-Datensatz kann in verschiedenen WMS-Servern auch dazu verwendet werden Transformationen on-the-fly durchzuführen (z.B. QGIS Server, &lt;a href=&quot;http://docs.geoserver.org/latest/en/user/advanced/crshandling/coordtransforms.html&quot;&gt;GeoServer&lt;/a&gt;). Will man aber trotzdem die Rasterdaten nicht nur on-the-fly transformieren, sondern sie auch physisch vorliegen haben, kann man das mit einem kleinen Skript und &lt;a href=&quot;http://www.gdal.org&quot;&gt;GDAL&lt;/a&gt; machen. Oftmals reicht für nicht-hochauflösende Rasterdaten z.B. eine Translation um +2 Mio / +1 Mio. Eine andere Variante ist die strenge Transformation der Metadaten (&amp;laquo;Worldfile&amp;raquo;). Für hochauflösende Rasterdaten funktioniert das nur noch bedingt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viele der Rasterdaten liegen in einem nahtlosen Mosaik vor (z.B. Orthofotos). Nach der Transformation sollen weiterhin keine Lücken und Überlappungen zwischen den einzelnen Kacheln sichtbar sein. Um das zu erreichen wird zuerst eine Shapedatei mit den einzelnen Kacheln als Polygon erstellt. Im Kanton Solothurn sind das 1km2-Kacheln:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_raster_p1/kacheleinteilung.png&quot; alt=&quot;Kacheleinteilung&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dieser sogenannte Tileindex wird mit &lt;code&gt;gdaltindex&lt;/code&gt; erstellt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;gdaltindex -write_absolute_path ortho2014.shp *.tif&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dabei werden sämtliche Tiff-Dateien in dem Verzeichnis berücksichtigt. Mit der Option &lt;code&gt;-write_absolute_path&lt;/code&gt; wird im Attribut &lt;em&gt;location&lt;/em&gt; in der Shapedatei &lt;em&gt;ortho2014.shp&lt;/em&gt; der absolute Pfad der Tiff-Dateien gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetzt ist es Zeit für das kleine Pythonskript, dass schlussendlich nichts Anderes macht als für jede dieser Kacheln ein &lt;code&gt;gdal_warp&lt;/code&gt;-Befehl mit passenden Parameter zusammenzustellen und auszuführen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/python
# -*- coding: utf-8 -*-

from osgeo import ogr, osr
import os

S_SRS = &quot;+proj=somerc +lat_0=46.952405555555555N +lon_0=7.439583333333333E +ellps=bessel +x_0=600000 +y_0=200000 +towgs84=674.374,15.056,405.346 +units=m +units=m +k_0=1 +nadgrids=./chenyx06/chenyx06a.gsb&quot;
T_SRS = &quot;+proj=somerc +lat_0=46.952405555555555N +lon_0=7.439583333333333E +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +nadgrids=@null&quot;

ogr.UseExceptions()

shp = ogr.Open(&quot;mosaic/ortho2014.shp&quot;)
layer = shp.GetLayer(0)

for feature in layer:
    infileName = feature.GetField(&apos;location&apos;)
    geom = feature.GetGeometryRef()
    env = geom.GetEnvelope()

    minX = int(env[0] + 0.001 + 2000000)
    minY = int(env[2] + 0.001 + 1000000)
    maxX = int(env[1] + 0.001 + 2000000)
    maxY = int(env[3] + 0.001 + 1000000)

    outfileName = str(minX)[0:4] + str(minY)[0:4] + &quot;_12_5cm.tif&quot;
    outfileName = os.path.join(os.path.dirname(infileName), &quot;lv95&quot;, outfileName)

    cmd = &quot;/usr/local/gdal/gdal-dev/bin/gdalwarp -s_srs \&quot;&quot; + S_SRS + &quot;\&quot; -t_srs \&quot;&quot; + T_SRS + &quot;\&quot; -te &quot;  + str(minX) + &quot; &quot; +  str(minY) + &quot; &quot; +  str(maxX) + &quot; &quot; +  str(maxY)
    cmd += &quot; -tr 0.125 0.125 -wo NUM_THREADS=ALL_CPUS -co &apos;PHOTOMETRIC=RGB&apos; -co &apos;TILED=YES&apos; -co &apos;PROFILE=GeoTIFF&apos;&quot;
    cmd += &quot; -co &apos;INTERLEAVE=PIXEL&apos; -co &apos;COMPRESS=DEFLATE&apos; -co &apos;BLOCKXSIZE=512&apos; -co &apos;BLOCKYSIZE=512&apos;&quot;
    vrt = &quot;/opt/Geodaten/ch/so/kva/orthofoto/2014/rgb/12_5cm/ortho2014rgb.vrt&quot;
    cmd += &quot; -r bilinear &quot; + vrt + &quot; &quot; + outfileName
    os.system(cmd)

    cmd = &quot;/usr/local/gdal/gdal-dev/bin/gdal_edit.py -a_srs EPSG:2056 &quot; + outfileName
    os.system(cmd)

    cmd = &quot;/usr/local/gdal/gdal-dev/bin/gdaladdo -r nearest --config COMPRESS_OVERVIEW DEFLATE --config GDAL_TIFF_OVR_BLOCKSIZE 512 &quot; + outfileName + &quot; 2 4 8 16 32 64 128&quot;
    os.system(cmd)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 4 - 5&lt;/strong&gt;: Benötigte Module werden geladen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 7 - 8&lt;/strong&gt;: Es ist möglich neben EPSG-Codes für Transformation mit GDAL auch eigene proj4-Definitionen als Parameter zur übergeben. Bei der Verwendung von NTv2-Gittern ist das ein bisschen hakelig. Es gibt viele Varianten. Viele davon führen zu falschen Resultaten, zwei davon führen zu richtigen Resultaten. Abhängig davon welcher &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/software/products/chenyx06.html&quot;&gt;NTv2-Datensatz&lt;/a&gt; (&lt;em&gt;chenyx06a.gsb&lt;/em&gt; oder &lt;em&gt;chenyx06etrs.gsb&lt;/em&gt;) verwendet wird. Langer Rede kurzer Sinn: Das hier ist eine Kombination, die korrekt transformiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 12 - 13&lt;/strong&gt;: Die Shapedatei mit der Kacheleinteilung wird geöffnet und ein Layer wird angelegt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 15 -16&lt;/strong&gt;: Die For-Schleife bearbeitet jede Kachel einzeln. Zuerst wird das Attribut &lt;em&gt;location&lt;/em&gt; ausgelesen. Es beinhaltet den kompletten absoluten Pfad der einzelnen Tiff-Datei. Davon verwenden wir aber später nur das Verzeichnis, da wir in ein Unterverzeichnis die resultierenden Tiff-Dateien speichern wollen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 17 - 18&lt;/strong&gt;: Die Geometrie der Kachel wird ausgelesen und die &lt;em&gt;Envelope&lt;/em&gt; (Tupel mit den min/max Koordinatenwerten) berechnet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 20 - 23&lt;/strong&gt;: Aus der &lt;em&gt;Envelope&lt;/em&gt; lesen wir die minimalen X- resp. Y-Werte. Die resultierenden Kacheln sollen wie die Ausgangskacheln &amp;laquo;schöne&amp;raquo; Kilometerkacheln sein. Dazu wird einfach 2 Mio. resp. 1 Mio dazugerechnet. Weil der &lt;code&gt;int&lt;/code&gt;-Befehl abrundet (?), muss den Koordinatenwerten ein kleiner Wert dazu addiert werden, um keine falschen Ganzzahleswerte zu bekommen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 25 - 26&lt;/strong&gt;: Aus den Koordinatenwerten wird der Dateinnamen der neuen Kacheln bestimmt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 28 - 33&lt;/strong&gt;: Hier geschieht die Zauberei. Als Ausgangsdatei wird nicht die einzelne Kacheln verwendet sondern &lt;em&gt;immer&lt;/em&gt; die vrt-Datei (Diese muss u.U. vorgängig noch &lt;a href=&quot;http://www.gdal.org/gdalbuildvrt.html&quot;&gt;erstellt&lt;/a&gt; werden). GDAL sieht jetzt nur eine einzelne, flächendeckende Rasterdatei. Somit muss nur noch der Ausschnitt gewählt werden, der aus dieser einzelnen Rasterdatei ausgeschnitten werden soll. Dieser Ausschnitt wurde ja bereits in den Zeilen 20 - 23 ermittelt. Das Resultat wird wiederum in einer Tiff-Datei gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 35 - 39&lt;/strong&gt;: Anschliessend werden in der neuen GeoTiff-Datei noch korrekte Metadaten zum Koordinatensystem gespeichert und es werden interne Overview gerechnet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat sind einzelne Orthofotokacheln, die sich weder überlappen noch sind Lücken zwischen den Kacheln vorhanden. Die Übergänge sind wie im Ausgangsmaterial nicht sichtbar. Einzig am Perimeterrand sind kleine schwarze Ränder sichtbar. Diese entstehen weil im Ausgangsmaterial in diesem Bereich &lt;em&gt;keine&lt;/em&gt; Daten vorhanden sind. Liegt das Ausgangsmaterial im Bezugsrahmen LV95 vor und wird in den Bezugsrahmen LV03 transformiert, dürfte dieses Phänomen nicht auftreten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/brw_raster_p1/orthorand.png&quot; alt=&quot;Orthofoto Perimeterrand&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Transformationsprozess dauerte für 384 Kacheln circa 1h 45m. Ein Qualitätsverlust aufgrund des Resamplings ist &lt;em&gt;nicht&lt;/em&gt; feststellbar.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Karten rendern mit PyQGIS</title>
      <link>http://blog.sogeo.services/blog/2014/10/19/karten-rendern-mit-pyqgis.html</link>
      <pubDate>Sun, 19 Oct 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/10/19/karten-rendern-mit-pyqgis.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;An der FOSSGIS 2013 wurde von Andreas Schmid &lt;a href=&quot;http://www.fossgis.de/konferenz/2013/programm/attachments/432_fossgis_2013_Schmid_QGIS_Server_Praesentation.pdf&quot;&gt;gezeigt&lt;/a&gt; wie mit QGIS der &lt;a href=&quot;http://www.cadastre.ch/internet/kataster/de/home/services/service/bp.html&quot;&gt;Basisplan der amtlichen Vermessung&lt;/a&gt; erstellt werden kann. Der ganze Systemaufbau ist nicht gerade trivial weil für das Rendern des Kartenbildes &lt;a href=&quot;http://hub.qgis.org/projects/quantum-gis/wiki/QGIS_Server_Tutorial&quot;&gt;QGIS Server&lt;/a&gt; eingesetzt wird. Dies setzt z.B. Apache 2 und ein FCGI-Modul voraus. Das eigentliche Ziel der Übung ist aber nicht einen WMS-Dienst anzubieten, sondern aus Vektordaten ein Rasterkartenwerk zu erstellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Weil bei Projektstart einige Funktionen in den Python-Bindings von QGIS (PyQGIS) fehlten, konnte nicht dieser Weg gewählt werden, sondern es wurde der Umweg über einen WMS-Dienst als Kartenrenderer gewählt. Die fehlenden Funktionen sind jetzt alle vorhanden und es steht einer Vereinfachung des Herstellungsprozesses nichts mehr im Wege.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Vieles zu PyQGIS steht im &lt;a href=&quot;http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/&quot;&gt;Kochbuch&lt;/a&gt;. Einige Details fehlen aber. Darum folgt nachstehend ein komplettes Beispiel, das zeigt wie man mit einem vorbereiteten QGIS-Projekt die Rasterkarten anhand einer Blatteinteilung erstellen kann &lt;strong&gt;ohne&lt;/strong&gt; QGIS manuell starten zu müssen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das vorbereitete QGIS-Projekt ist simpel. Es besteht nur aus den Gemeindegrenzen des Kantons Solothurn:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgsrenderer_p1/qgisproject.png&quot; alt=&quot;QGIS Projekt&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als erstes müssen zwei Dateien erstellt werden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;basisplan.sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;basisplan.py&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;basisplan.sh&lt;/code&gt; macht nichts weiter als den Pfad zu den QGIS-Bibliotheken zu setzen und anschliessend die Pythondatei &lt;code&gt;basisplan.py&lt;/code&gt; aufzurufen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;#!/bin/bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/stefan/Apps/qgis_master/lib
export PYTHONPATH=$PYTHONPATH:/home/stefan/Apps/qgis_master/share/qgis/python

python basisplan.py&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;basisplan.py&lt;/code&gt; enthält den interessanteren Teil:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from qgis.gui import *

import os
import sys

# Aktuelles Verzeichnis
current_dir = os.path.dirname(os.path.realpath(__file__))

# QGIS initialisieren
app = QApplication(sys.argv)
QgsApplication.setPrefixPath(&quot;/home/stefan/Apps/qgis_master&quot;, True)
QgsApplication.initQgis()

# QGIS-Projekt laden
QgsProject.instance().setFileName(os.path.join(current_dir,  &quot;bpav5000sw.qgs&quot;))
if not QgsProject.instance().read():
    sys.exit(&quot;QGIS-Projekt nicht gefunden.&quot;)

# List mit sämtlichen Layer im QGIS-Projekt
lst = []
layerTreeRoot = QgsProject.instance().layerTreeRoot()
for id in layerTreeRoot.findLayerIds():
    node = layerTreeRoot.findLayer(id)
    lst.append(id)

# Layer mit Blatteinteilung laden
layer_name =  &quot;blatteinteilung&quot;
vlayer = QgsVectorLayer(os.path.join(current_dir, &quot;basisplan.gpkg&quot;) + &quot;|layername=blatteinteilung&quot;, &quot;Blatteinteilung&quot;, &quot;ogr&quot;)
if not vlayer.isValid():
    sys.exit(&quot;Blatteinteilung konnte nicht geladen werden.&quot;)

# Rasterkarten erstellen
iter = vlayer.getFeatures()
for feature in iter:
    idx = vlayer.fieldNameIndex(&apos;nummer&apos;)
    nummer = feature.attributes()[idx].toString()

    # Ausschnitt und Grösse der Karte berechnen
    dpi = 508
    scale = 5000

    geom = feature.geometry()
    p1 = geom.vertexAt(0)
    p2 = geom.vertexAt(2)

    rect = QgsRectangle(p1, p2)

    dx = rect.width()
    dy = rect.height()

    width = (dx/scale) / 0.0254 * dpi
    height = (dy/scale) / 0.0254 * dpi

    # Einstellungen für Kartenrenderer
    mapSettings = QgsMapSettings()
    mapSettings.setMapUnits(0)
    mapSettings.setExtent(rect)
    mapSettings.setOutputDpi(dpi)
    mapSettings.setOutputSize(QSize(width, height))
    mapSettings.setLayers(lst)
    mapSettings.setFlags(QgsMapSettings.DrawLabeling)

    # Karte zeichnen
    img = QImage(QSize(width, height), QImage.Format_Mono)
    img.setDotsPerMeterX(dpi / 25.4 * 1000)
    img.setDotsPerMeterY(dpi / 25.4 * 1000)

    p = QPainter()
    p.begin(img)

    mapRenderer = QgsMapRendererCustomPainterJob(mapSettings, p)
    mapRenderer.start()
    mapRenderer.waitForFinished()

    p.end()

    img.save(os.path.join(&quot;/tmp&quot;, &quot;bpav&quot; + str(scale) + &quot;_&quot; + str(nummer) + str(&quot;.png&quot;)), &quot;png&quot;)

# Layer mit Blatteinteilung löschen
del vlayer

# QGIS schliessen
QgsApplication.exitQgis()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 2 - 8&lt;/strong&gt;: Module werden geladen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 11&lt;/strong&gt;: Bei mir funktionieren verschiedene QGIS-Methoden nicht korrekt mit relativen Pfaden, wenn diese direkt reingeschrieben werden (z.B. &lt;code&gt;&quot;./bpav5000sw.qgs&quot;&lt;/code&gt; für das zu ladende QGIS-Projekt). Wird aber zuerst das aktuelle Verzeichnis (in dem das Skript läuft) ermittelt und dieses für die relativen Pfade verwendet, funktionierts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 14 - 16&lt;/strong&gt;: Zuerst wird eine Qt-Applikation und anschliessend QGIS initialisiert. Der Pfad in Zeile 15 zeigt auf die lokale  QGIS-Installation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 19 - 21&lt;/strong&gt;: Hier wird das vorbereitete QGIS-Projekt geladen. Falls es nicht gefunden wird, ist der Rückgabewert der Methode &lt;code&gt;QgsProject.instance().read()&lt;/code&gt; &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 24 - 28&lt;/strong&gt;: Für das Rendern der Karte müssen zuerst sämtliche Layer des QGIS-Projekts in eine Liste geschrieben werden. Sollen nur die sichtbaren Layer gerendert werden, kann man diese mit &lt;code&gt;node.isVisible()&lt;/code&gt; ermitteln.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 31 - 34&lt;/strong&gt;: Die Blatteinteilung wird als QGIS-Layer zusätzlich geladen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 37ff&lt;/strong&gt;: Für jedes Feature des vorhin geladenen Layers mit der Blatteinteilung wird nun die Rasterkarte erstellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 42 - 56&lt;/strong&gt;: Aufgrund des Massstabes, Auflösung (DPI) und Ausdehnung des Kartenblattes wird die Grösse (Pixelanzahl) und die Boundingbox der zu erstellenden Rasterkarte berechnet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 59 - 65&lt;/strong&gt;: Die oben ermittelnden Werte werden in einer Settings-Klasse gespeichert, die später der Kartenrenderer verwenden wird. Interessant ist die Zeile 65. Hier können verschiedene Flags angegeben werden. Mit &lt;code&gt;QgsMapSettings.DrawLabeling&lt;/code&gt; wird dem Renderer mitgeteilt, dass die Labels gezeichnet werden sollen. Ohne das Flag &lt;code&gt;QgsMapSettings.Antialiasing&lt;/code&gt; werden die Karten ohne &lt;a href=&quot;http://de.wikipedia.org/wiki/Antialiasing_\(Computergrafik\)&quot;&gt;Antialiasing&lt;/a&gt; gezeichnet. Dies ist insbesondere für schwarz-weisse
Rasterkarten sinnvoll, da dann 1-Bit-Karten möglich sind. Die Grösse ist um ein vielfaches kleiner als bei RGB-Bildern und die Karte lässt sich mit jeder beliebigen Farbe einfärben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mehrere Flags werden mittels Pipe-Zeichen aneinandergereiht, z.B. mit Antialiasing: &lt;code&gt;mapSettings.setFlags(QgsMapSettings.Antialiasing | QgsMapSettings.DrawLabeling)&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 68 - 81&lt;/strong&gt;: Als nächstes muss ein &lt;code&gt;QImage&lt;/code&gt;-Objekt mit der passenden Grösse, Format und Auflösung erstellt werden. Für unseren Fall eignet sich das Format &lt;code&gt;QImage.Format_Mono&lt;/code&gt; bestens, denn es erstellt das gewünschte 1-Bit-Rasterbild. Für Farbbilder eignet sich z.B. &lt;code&gt;QImage.Format_RGB32&lt;/code&gt;. Alle möglichen Formate sind &lt;a href=&quot;http://qt-project.org/doc/qt-4.8/qimage.html#Format-enum&quot;&gt;hier&lt;/a&gt; beschrieben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kartenrenderer wird auf Zeile 76 gestartet. Die darauffolgende Zeile ist wichtig, da sonst - ohne auf das Ende zu warten - das &lt;em&gt;unfertige&lt;/em&gt; Bild physisch auf die Festplatte geschrieben wird.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nachdem das Bild fertig gerendert ist, wird es an einem gewünschten Ort gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 84&lt;/strong&gt;: Ohne das explizite Löschen des zusätzlich geladenen Layers verabschiedet sich mein Skript mit einem &lt;code&gt;Segmentation fault&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 87&lt;/strong&gt;: QGIS wird geschlossen und alle verwendeten Layer werden gelöscht (siehe Kommentar zu Zeile 84).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Renderzeit eines Kartenblattes ist abhängig von der Anzahl der Layer, der Komplexität der Symbologie und der Grösse (Pixelanzahl). Am schnellsten sind schwarz-weisse Karten, die ohne Antialiasing gezeichnet werden. Farbige Karten sind ebenfalls schneller ohne als mit Antialiasing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die hier vorgestellte Lösung kann den Herstellungsprozess des Basisplanes der amtlichen Vermessung vereinfachen, da kein WMS-Dienst verwendet wird. Zudem eignet sich diese Lösung wegen der mächtigen Symbologiemöglichkeiten und Flexibilität von QGIS ebenfalls für das Herstellen von Rasterkarten jeglicher Art.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das QGIS-Beispielprojekt inkl. Shell- und Pythonskript gibt es &lt;a href=&quot;../../../data/karten-rendern-mit-pyqgis/qgisrenderer1.zip&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>NDVI Orthofotos</title>
      <link>http://blog.sogeo.services/blog/2014/09/22/ndvi-orthofotos.html</link>
      <pubDate>Mon, 22 Sep 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/09/22/ndvi-orthofotos.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Kanton Solothurn lässt jedes Jahr ein Drittel seiner Fläche neu befliegen und lässt daraus neue &lt;a href=&quot;http://www.sogis.ch/daten&quot;&gt;Orthofotos&lt;/a&gt; erstellen. Als Endprodukt werden GeoTIFF mit den vier Kanälen &lt;em&gt;Rot&lt;/em&gt;, &lt;em&gt;Grün&lt;/em&gt;, &lt;em&gt;Blau&lt;/em&gt; und &lt;em&gt;nahes Infrarot&lt;/em&gt; ausgeliefert. Daraus werden anschliessend zwei abgeleitete Produkte erstellt: ein RGB-Orthofoto und ein FCIR-Orthofoto. &quot;FCIR&quot; steht für &quot;false-colour infrared&quot; und besteht aus den Kanälen &lt;em&gt;nahes Infrarot&lt;/em&gt;, &lt;em&gt;Rot&lt;/em&gt; und &lt;em&gt;Grün&lt;/em&gt; und sieht dann in etwa so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/fcir.jpg&quot; alt=&quot;FCIR Beispiel&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hilfreich kann diese Kombination zum Beispiel für Umweltanalysen sein. Unser Fokus lag jedoch in der besseren Identifizierbarkeit von Abgrenzungen zwischen Objekten unterschiedlicher Bodenbeschaffenheit (z.B. Wasser - nicht Wasser oder Strasse - Acker/Wiese).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Auf dem &lt;a href=&quot;https://www.mapbox.com/blog/&quot;&gt;Mapbox-Blog&lt;/a&gt; sind verschiedene, interessante Beiträge was man mit Satellitenbildern und Orthofotos und den verschiedenen Kanälen anstellen kann. So auch ein &lt;a href=&quot;https://www.mapbox.com/blog/processing-rapideye-imagery/&quot;&gt;Beitrag&lt;/a&gt; zu &lt;a href=&quot;http://en.wikipedia.org/wiki/Normalized_Difference_Vegetation_Index&quot;&gt;NDVI&lt;/a&gt;. Sieht cool aus, will ich auch. Netterweise stehen auch gleich die GDAL- und ImageMagick-Befehle im Blog. Daraus lässt sich leicht für unsere Daten ein Skript erstellen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;#!/bin/bash

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/gdal/gdal-dev/lib
export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python2.6/dist-packages/GDAL-2.0.0-py2.6-linux-x86_64.egg

for FILE in /opt/Geodaten/ch/so/kva/orthofoto/2014/cir/12_5cm/*.tif
do
  BASENAME=$(basename $FILE .tif)
  OUTFILE=/home/stefan/Geodaten/NDVI_2014/${BASENAME}.tif
  OUTFILE_PREP=/home/stefan/Geodaten/NDVI_2014/${BASENAME}_prep.tif
  OUTFILE_TMP=/home/stefan/Geodaten/NDVI_2014/${BASENAME}_tmp.tif

  echo &quot;Processing: ${BASENAME}.tif&quot;

  cp $FILE $OUTFILE
  listgeo -tfw $OUTFILE
  rm $OUTFILE

  convert $FILE $OUTFILE_PREP

  convert -monitor $OUTFILE_PREP -fx &apos;(u.r - u.g) / (u.r + u.g + 0.001)&apos; $OUTFILE_TMP

  /usr/local/gdal/gdal-dev/bin/gdal_edit.py -a_srs EPSG:21781 $OUTFILE_TMP
  /usr/local/gdal/gdal-dev/bin/gdal_translate -co TILED=YES -co COMPRESS=LZW $OUTFILE_TMP $OUTFILE
  /usr/local/gdal/gdal-dev/bin/gdaladdo -r cubic --config COMPRESS_OVERVIEW LZW $OUTFILE 2 4 8 16 32 64 128

  rm $OUTFILE_PREP
  rm $OUTFILE_TMP
done&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 3 - 4&lt;/strong&gt;: Sind nicht relevant. Die Zeilen werden gebraucht, um dem Skript mitzuteilen, dass eine bestimmte GDAL-Version verwendet werden soll.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 6&lt;/strong&gt;: Hier beginnt die for-Schleife, die jede einzelne Orthofoto-Kachel verarbeitet. Da schlussendlich nur die Kanäle &lt;em&gt;Rot&lt;/em&gt; und &lt;em&gt;nahes Infrarot&lt;/em&gt; interessieren, kann das FCIR-Orthofoto für die Berechnung verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 8 - 11&lt;/strong&gt;: Parameter für die verschiedenen (temporären) Dateien.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 15 - 17&lt;/strong&gt;: Quick &apos;n&apos; Dirty wird hier ein World-File erstellt. Da mit den ImageMagick-Befehlen die Georeferenzierung verloren geht, muss diese zum Schluss wiederhergestellt werden. Dafür brauchts das World-File.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 19&lt;/strong&gt;: Der später verwendetete &lt;code&gt;-fx&lt;/code&gt; Operator funktioniert am besten mit Dateien, die von ImageMagick selbst erstellt wurden. Aus diesem Grund wird das GeoTIFF mit dem &lt;code&gt;convert&lt;/code&gt; Befehl kopiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile  21&lt;/strong&gt;: Hier passiert die Magie: Mit dem &lt;code&gt;-fx&lt;/code&gt; &lt;a href=&quot;http://www.imagemagick.org/script/fx.php&quot;&gt;Operator&lt;/a&gt; wird der NDVI-Wert für jedes Pixel berechnet. Falls sowohl der &lt;em&gt;nahe Infrarot&lt;/em&gt; (&lt;code&gt;u.r&lt;/code&gt;) und &lt;em&gt;rote&lt;/em&gt; (&lt;code&gt;u.g&lt;/code&gt;) Kanal einen Pixelwert von 0 aufweisen, ist der Nenner ebenfalls 0 und damit der Bruch nicht mehr definiert. Aus diesem Grund wird ein kleiner Wert beim Nenner hinzuaddiert. Dies dürfte auf das Resultat keinen Einfluss haben. Schöner wäre es natürlich wenn man das ganze mit einem if-else-Konstrukt behandeln würde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 23 - 25&lt;/strong&gt;: Anschliessend wird die Georeferenzierung wiederhergestellt und die internen Overviews gerechnet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeilen 27 - 29&lt;/strong&gt;: Zu guter Letzt werden noch die temporären Dateien, die nicht mehr benötigt werden, gelöscht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die ganze Berechnung dauert für circa 450 1km2 Kacheln (8000 x 8000 Pixel) rund 10 Stunden. Der Flaschenhals ist der &lt;code&gt;-fx&lt;/code&gt; Operator obwohl dieser sämtliche Kerne des Prozessors verwendet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/ndvi_htop.png&quot; alt=&quot;htop cpu&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Resultat dieser CPU-Orgie sieht dann wie folgt aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/ndvi_01.jpg&quot; alt=&quot;NDVI Resultat&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Soweit so gut. Nicht wirklich spektakulär aber in etwa das was man aufgrund des Beispiels im  &lt;a href=&quot;https://www.mapbox.com/blog/processing-rapideye-imagery/&quot;&gt;Mapbox-Blog&lt;/a&gt; erwarten durfte. Augenfällig ist aber die klare Unterscheidung zwischen Strassen (keine Vegetation) und der landwirtschaftlich genutzten Flächen oder des Waldes. Für gewisse Fragestellungen (z.B. Periodische Nachführung, Erfassung von Waldstrassen) in der amtlichen Vermessung kann man sich das jetzt zunutze machen. Unter gewissen Umständen ist im normalen RGB-Orthofoto und FCIR-Orthofoto kein Strassenverlauf mehr zu erkennen. Auf dem NDVI-Bild ist ein Verlauf aber immer noch (mehr oder weniger gut) sichtbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/rgb_02.jpg&quot; alt=&quot;RGB Bsp. 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/ndvi_02.jpg&quot; alt=&quot;NDVI Bsp. 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/rgb_03.jpg&quot; alt=&quot;RGB Bsp. 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/ndvi_03.jpg&quot; alt=&quot;NDVI Bsp. 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/rgb_04.jpg&quot; alt=&quot;RGB Bsp. 4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/ndvi_orthofotos/ndvi_04.jpg&quot; alt=&quot;NDVI Bsp. 4&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;del&gt;Die Beispiele zum Vergleichen gibts &lt;a href=&quot;http://s.geo.admin.ch/5fc9390e46&quot;&gt;hier&lt;/a&gt;, &lt;a href =&quot;http://s.geo.admin.ch/5fc93ad569&quot;&gt;hier&lt;/a&gt; und &lt;a href=&quot;http://s.geo.admin.ch/5fc93bb5bb&quot;&gt;hier&lt;/a&gt;.&lt;/del&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Pimp your Orthophoto</title>
      <link>http://blog.sogeo.services/blog/2014/09/22/pimp-your-orthofoto.html</link>
      <pubDate>Mon, 22 Sep 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/09/22/pimp-your-orthofoto.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Werden Luftbilder zu unterschiedlichen Zeitpunkten geflogen, sind die daraus resultierenden Orthofotos farblich unterschiedlich. Was aber nicht mehr nachzuvollziehen ist, sind Unterschiede wie hier in den FCIR-Orthofotos der Jahre 2012 und 2013:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/pimp_your_orthophotos/pimp_vorher_gross.jpg&quot; alt=&quot;FCIR vorher gross&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/pimp_your_orthophotos/pimp_vorher_klein.jpg&quot; alt=&quot;FCIR vorher klein&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Orthofotos auf der linken Seite (2012) wurden zwar bei voller Belaubung geflogen jedoch sind die Farbunterschiede zu gross und die Orthofotos auf der rechten Seite (2013) scheinen einen Blaustich zu haben. Was machen?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Analyse der Bildhistogramme zeigt, dass insbesondere der rote Kanal (resp. das nahe Infrarot) sehr unterschiedlich ist und einen Shift zwischen den beiden Jahren aufweist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hat man nur ein Bild kann man mit Gimp (o.ä.) dran schrauben bis es passt. Hat man aber 467 Kacheln macht das weniger Spass. Mit &lt;a href=&quot;http://www.imagemagick.org/&quot;&gt;ImageMagick&lt;/a&gt; geht das aber wunderbar:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;convert -channel R -gamma 2 $INPUT $OUTPUT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Befehl wird eine Gammakorrektur im roten Kanal durchgeführt. Mit ein wenig probieren, findet man schnell einen passenden Wert. Das Resultat sieht so aus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/pimp_your_orthophotos/pimp_nachher_gross.jpg&quot; alt=&quot;FCIR nachher gross&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/pimp_your_orthophotos/pimp_nachher_klein.jpg&quot; alt=&quot;FCIR nachher klein&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Blauschleier ist nicht mehr so extrem wie vorher. Trotzdem könnte man die Farben sicher noch besser anpassen. &lt;del&gt;Den WMS mit den angepassten Orthofotos gibts &lt;a href=&quot;http://www.catais.org/wms/orthofoto?REQUEST=GetCapabilities&amp;SERVICE=WMS&quot;&gt;hier&lt;/a&gt;.&lt;/del&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>GeoAdmin API Plugin für QGIS</title>
      <link>http://blog.sogeo.services/blog/2014/08/24/geoadmin-api-plugin-fur-qgis.html</link>
      <pubDate>Sun, 24 Aug 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/08/24/geoadmin-api-plugin-fur-qgis.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Swisstopo stellt eine &lt;a href=&quot;http://www.geo.admin.ch/internet/geoportal/de/home/services/geoservices/display_services/api_services.html&quot;&gt;Programmierschnittstelle (API)&lt;/a&gt; zur Verfügung mit der man Webseiten mit Swisstopo-Karten verschönern kann. Diese API ist sauber &lt;a href=&quot;http://api3.geo.admin.ch/index.html&quot;&gt;dokumentiert&lt;/a&gt; und neben der eigentlichen Javascript-API stehen ebenfalls auch &lt;a href=&quot;http://api3.geo.admin.ch/services/sdiservices.html&quot;&gt;REST-Schnittstellen&lt;/a&gt; online.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interessant ist vor allem der &lt;a href=&quot;http://api3.geo.admin.ch/services/sdiservices.html#search&quot;&gt;Suchdienst&lt;/a&gt;, mit dem man neben Adressen und administrativen Einheiten (Kantone, Bezirke) auch Grundstücke suchen kann. Folgender Request liefert Informationen zum Grundstück Nr. 2585 in Solothurn:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Solothurn%202585&amp;amp;origins=parcel&amp;amp;type=locations&quot;&gt;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Solothurn%202585&amp;amp;origins=parcel&amp;amp;type=locations&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Leider darf die Adresssuche nur von der Bundesverwaltung gebraucht werden. Sucht man nach einer bestimmten Adressen, wird zwar eine Antwort geliefert, diese beinhaltet aber keine Georeferenzierung (kein &lt;code&gt;geom_st_box2d&lt;/code&gt; Attribut):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Solothurn%20Werkhofstrasse%2017&amp;amp;origins=address&amp;amp;type=locations&quot;&gt;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Solothurn%20Werkhofstrasse%2017&amp;amp;origins=address&amp;amp;type=locations&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ebenfalls gesucht werden können Layer, die in der GeoAdmin API zur Verfügung stehen. Dieser Request liefert Layer zur Thematik &lt;em&gt;Fixpunkte&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Fixpunkte&amp;amp;type=layers&quot;&gt;http://api3.geo.admin.ch/rest/services/api/SearchServer?searchText=Fixpunkte&amp;amp;type=layers&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Zu guter Letzt lassen sich mit dem Suchdienst auch Features suchen, z.B. den Fixpunkt 11277510:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://api3.geo.admin.ch/rest/services/api/SearchServer?features=ch.swisstopo.fixpunkte-lfp1&amp;amp;type=featuresearch&amp;amp;searchText=11277510&quot;&gt;http://api3.geo.admin.ch/rest/services/api/SearchServer?features=ch.swisstopo.fixpunkte-lfp1&amp;amp;type=featuresearch&amp;amp;searchText=11277510&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nicht alle Layer der GeoAdmin API sind &lt;a href=&quot;http://api3.geo.admin.ch/api/faq/index.html#which-layers-are-searchable&quot;&gt;durchsuchbar&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesen REST-Schnittstellen kann ein QGIS-Plugin erstellt werden, das ähnliche Funktionen bietet, wie die Suchfunktion auf &lt;a href=&quot;http://map.geo.admin.ch&quot;&gt;map.geo.admin.ch&lt;/a&gt;. Dh. Lokalisationen können gesucht werden und es kann an den ausgewählten Ort gezoomt werden. Angebotene Layer können gesucht und in QGIS hinzugefügt werden. Features von durchsuchbaren Layer können gesucht werden und ein Popup-Fenster erscheint, falls etwas ausgewählt wurde.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Screenshots:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoadmin_search/gas_parcel.png&quot; alt=&quot;Parzellensuche&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoadmin_search/gas_layer.png&quot; alt=&quot;Layersuche&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoadmin_search/gas_feature.png&quot; alt=&quot;Featuresuche&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Layersuche resp. das anschliessende Hinzufügen des Layers in QGIS ist noch unbefriedigend gelöst. In den Plugineinstellungen lässt sich wählen, ob der Layer als &lt;em&gt;WMS&lt;/em&gt; oder &lt;em&gt;WMTS&lt;/em&gt; hinzugefügt werden soll:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/geoadmin_search/gas_provider.png&quot; alt=&quot;Provider&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Einige der Layer sind nur als WMS, andere nur als WMTS vorhanden. So wird zuerst also versucht den Layer mit dem Provider hinzuzufügen, den man in den Einstellungen gewählt hat. Klappt das nicht, wird der andere Provider gewählt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das führt auch gleich zum Thema/&lt;strong&gt;Problem&lt;/strong&gt; &lt;em&gt;&amp;laquo;Terms of use&amp;raquo;&lt;/em&gt;. Der WMTS-Dienst darf gemäss &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/services/web_services/webaccess.html&quot;&gt;Webseite&lt;/a&gt; nicht in Desktop-Applikationen verwendet werden: &quot;Nur für Websites, kein Desktop-Zugriff&quot;. Wird beim Anfordern der Kachel &lt;strong&gt;kein&lt;/strong&gt; Referer mitgeschickt, verweigert der Server die Auslieferung der Kachel. Zwar kann man sich mit einer Domain gratis &lt;a href=&quot;http://www.geo.admin.ch/internet/geoportal/de/home/services/geoservices/display_services/api_services/order_form.html&quot;&gt;registrieren&lt;/a&gt; aber die Desktop-Applikation hat halt keine Domain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bleibt der WMS-Dienst: Gewisse Layer sind nicht frei verfügbar (z.B. Pixelkarten) und nicht im &amp;laquo;normalen&amp;raquo; &lt;a href=&quot;http://wms.geo.admin.ch/?REQUEST=GetCapabilities&amp;amp;SERVICE=WMS&amp;amp;VERSION=1.0.0&quot;&gt;BGDI-WMS&lt;/a&gt; enthalten. Für diese Dienste gibt es einen zweiten &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/services/web_services/geoservices/swisstopo_wms.html&quot;&gt;WMS-Dienst&lt;/a&gt;. 5000 Megapixel/Jahr sind &lt;a href=&quot;http://www.toposhop.admin.ch/de/shop/products/geoservice/swisstopoWMS&quot;&gt;gratis&lt;/a&gt;. Leider lässt sich mit keiner REST-Schnittstelle exakt eruieren, ob ein Layer als WMS und/oder als WMTS verfügbar ist. So meint das Plugin, dass das Orthofoto (SWISSIMAGE) nur als WMTS-Layer verfügbar ist, da in den &lt;a href=&quot;http://api3.geo.admin.ch/rest/services/api/MapServer?searchText=ch.swisstopo.swissimage&quot;&gt;Metainformation&lt;/a&gt; beim Layer &lt;code&gt;ch.swisstopo.swissimage&lt;/code&gt; das Attribut &lt;code&gt;wmsUrlResource&lt;/code&gt; fehlt. Login und Passwort für den geschützten WMS lassen sich in den Plugineinstellungen speichern. Es macht den Anschein, dass entweder mein Plugin, QGIS oder der passwortgeschützte WMS von Zeit zu Zeit Probleme beim Verbinden verursacht. Erscheint in QGIS die Fehlermeldung &lt;code&gt;Error: Layer is not valid.&lt;/code&gt; lohnt sich ein erneuter Versuch (irgend ein &amp;laquo;Redirect loop detected&amp;raquo;-Problem).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Plugin &lt;em&gt;GeoAdmin Search&lt;/em&gt; liegt im &lt;a href=&quot;http://plugins.qgis.org/plugins/plugins.xml?qgis=2.4&quot;&gt;QGIS Plugin Repository&lt;/a&gt;. Quellcode gibts hier: &lt;a href=&quot;https://bitbucket.org/edigonzales/qgis_geoadminsearch&quot;&gt;https://bitbucket.org/edigonzales/qgis_geoadminsearch&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Vom Webdienst zum Bulkdownload</title>
      <link>http://blog.sogeo.services/blog/2014/06/08/vom-webdienst-zum-bulkdownload.html</link>
      <pubDate>Sun, 8 Jun 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/06/08/vom-webdienst-zum-bulkdownload.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Chris Herwig &lt;a href=&quot;https://www.mapbox.com/blog/trouble-with-geoportals/&quot;&gt;erkennt&lt;/a&gt; drei Hauptgruppen von Open Government Daten Benutzer:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Gelegenheitsuser&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Benutzer, die mittels einer API Zugriff auf die Daten wünschen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;laquo;Bulk&amp;raquo;-Datenuser: Diese User wollen grosse Datenmengen (und auch komplette Datensätze) herunterladen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein möglicher technischer Umsetzungsansatz ist das Verbinden von Gruppe 2 und 3. Der Webdienst von Gruppe 2 wird verwendet, um die vorgefertigten Datensätze für Gruppe 3 herzustellen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anlass für das Rumspielen war ebenfalls das neue Geodatenformat &lt;a href=&quot;http://sourcepole.ch/assets/2013/6/13/fossgis13_geopackage.pdf&quot;&gt;&lt;em&gt;Geopackage&lt;/em&gt;&lt;/a&gt;, das seit &lt;a href=&quot;http://trac.osgeo.org/gdal/wiki/Release/1.11.0-News&quot;&gt;Version 1.11&lt;/a&gt; in GDAL/OGR unterstützt wird. Da QGIS ebenfalls GDAL/OGR einsetzt, wird das Datenformat auch in QGIS unterstützt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Als Webdienst wird der &lt;a href=&quot;http://www.catais.org/wfs/mopublic?SERVICE=WFS&amp;amp;REQUEST=GetCapabilities&quot;&gt;MOpublic-WFS&lt;/a&gt; verwendet. Das Herstellen der einzelnen Datensätze/Geopackages übernimmt ein kleines Pythonskript:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import os
import osgeo.gdal
from osgeo import ogr


# VARS
GPKG_DIR = &quot;/opt/Geodaten/ch/so/kva/av/mopublic/gpkg/lv03/d/&quot;

# Enable exceptions
ogr.UseExceptions()

# Get all the layer names
layer_list = []
driver = ogr.GetDriverByName(&apos;WFS&apos;)
ds_mopublic = driver.Open(&quot;WFS:http://www.catais.org/wfs/mopublic?&quot;)

for i in range(0, ds_mopublic.GetLayerCount()):
    layer = ds_mopublic.GetLayerByIndex(i)
    layer_list.append(layer.GetName())

ds_mopublic.Destroy()

# Export new deliveries
ds_lieferungen = driver.Open(&quot;WFS:http://www.catais.org/wfs/av_lieferungen?&quot;)
layer_lieferungen = ds_lieferungen.GetLayerByName(&apos;lieferungen_heute&apos;)

for feature in layer_lieferungen:
    gem_bfs = feature.GetField(&apos;gem_bfs&apos;)

    out = os.path.join(GPKG_DIR, str(gem_bfs) + &quot;.gpkg&quot;)
    out_driver = ogr.GetDriverByName(&quot;GPKG&quot;)

    if os.path.exists(out):
        out_driver.DeleteDataSource(out)

    out_datasource = out_driver.CreateDataSource(out)

    filter = &quot;&amp;amp;FILTER=%3Cogc:Filter%20xmlns:ogc=%22http://www.opengis.net/ogc%22%3E%0A%20%3Cogc:PropertyIsEqualTo%3E%0A%20%20%3Cogc:PropertyName%3Ebfsnr%3C/ogc:PropertyName%3E%0A%20%20%3Cogc:Literal%3E&quot; + str(gem_bfs) + &quot;%3C/ogc:Literal%3E%0A%20%3C/ogc:PropertyIsEqualTo%3E%0A%3C/ogc:Filter%3E%0A&quot;

    for i in range(len(layer_list)):
        wfs_string = &quot;WFS:http://www.catais.org/wfs/mopublic?TYPENAME&quot; + layer_list[i] + filter
        ds_mopublic = driver.Open(wfs_string)
        layer_mopublic = ds_mopublic.GetLayerByName(layer_list[i])
        out_layer = out_datasource.CopyLayer(layer_mopublic, layer_list[i])

        ds_mopublic.Destroy()

ds_lieferungen.Destroy()


# Export whole canton
ds_mopublic = driver.Open(&quot;WFS:http://www.catais.org/wfs/mopublic?&quot;)

out = os.path.join(GPKG_DIR,  &quot;kanton.gpkg&quot;)
out_driver = ogr.GetDriverByName(&quot;GPKG&quot;)

if os.path.exists(out):
    out_driver.DeleteDataSource(out)

out_datasource = out_driver.CreateDataSource(out)

for i in range(len(layer_list)):
    layer_mopublic = ds_mopublic.GetLayerByName(layer_list[i])
    out_layer = out_datasource.CopyLayer(layer_mopublic, layer_list[i])

ds_mopublic.Destroy()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 16 - 24&lt;/strong&gt;: Es werden zuerst alle Layernamen des WFS in eine Liste geschrieben. Diese Layer werden später gemeindeweise angefordert und als GeoPackage gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 27 - 28&lt;/strong&gt;: Ein weiterer WFS liefert die neuen Datenlieferungen (Gemeinden, die heute Nacht geliefert wurden).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 30 - 49&lt;/strong&gt;: Die Layer werden nun gemeindeweise angefordert und pro Gemeinde in eine GeoPackage-Datei gespeichert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OGR hat eine Methode &lt;code&gt;layer.SetAttributeFilter(&quot;gem_bfs = 2549&quot;)&lt;/code&gt;, die Datensätze nach Attribute filtern kann. Dies funktioniert beim OGR-WFS-Treiber entweder auf der Serverseite (bevorzugt) oder auf der Klientenseite. Da QGIS-Server (hier als WFS-Server eingesetzt) in der GetCapabilities-Antwort die &amp;laquo;LogicalOperators&amp;raquo; &lt;a href=&quot;http://osgeo-org.1560.x6.nabble.com/gdal-dev-WFS-driver-and-filtering-td5144594.html&quot;&gt;nicht auflistet&lt;/a&gt;, sie aber versteht, verwendet OGR nur das klientenseitige Filtern. Das hat zur Folge, dass zuerst immer sämtliche Daten vom Server geholt werden müssen. Um dies zu verhindern, kann der Filter direkt in der URL angegeben werden (Zeile 41 und 44/45).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Zeile 55 - 69&lt;/strong&gt;: Nach dem gemeindeweisen Erstellen der GeoPackages wird ein GeoPackage für den ganzen Kanton Solothurn erstellt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;Funktionierts?&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Skript lokal ausgeführt braucht circa 1.5 Stunden für das Erstellen sämtlicher 109 Gemeinden. Das Skript auf dem gleichen Server ausgeführt, auf dem der WFS läuft, dauert länger (circa 2.5 Stunden). Der lokale Rechner hat im Gegensatz zum Server eine SSD. Eventuell kann das ein Flaschenhals sein. Interessanterweise macht die kürzere Downloadzeit das nicht wett.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Laden, Zoomen und Pannen funktioniert in QGIS 2.3 (kompiliert mit GDAL/OGR 2.0.0dev) tadellos und sehr schnell. Einzig das Scrollen in der Attributtabelle ist einiges zäher und käsiger als mit Postgis.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Daten können &lt;a href=&quot;http://www.catais.org/geodaten/ch/so/kva/av/mopublic/gpkg/lv03/d/&quot;&gt;hier&lt;/a&gt; heruntergeladen werden.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Fun with GeoKettle Episode 1</title>
      <link>http://blog.sogeo.services/blog/2014/02/09/fun-with-geokettle-episode-1.html</link>
      <pubDate>Sun, 9 Feb 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/02/09/fun-with-geokettle-episode-1.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/topics/survey/lv95/lv03-lv95.html&quot;&gt;Bezugsrahmenwechsel&lt;/a&gt; steht &lt;a href=&quot;http://www.admin.ch/opc/de/classified-compilation/20071088/index.html#a53&quot;&gt;vor der Tür&lt;/a&gt; und viele Geodaten müssen früher oder später transformiert werden. Swisstopo bietet einen &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/apps/calc/reframe.html&quot;&gt;Webdienst&lt;/a&gt; und ein &lt;a href=&quot;http://www.swisstopo.admin.ch/internet/swisstopo/de/home/products/software/products/reframe_fme.html&quot;&gt;FME-Plugin&lt;/a&gt; für diese Transformation an.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für das ETL-Tool &lt;a href=&quot;http://www.spatialytics.org/projects/geokettle/&quot;&gt;GeoKettle&lt;/a&gt; habe ich ein Plugin geschrieben, das diese Transformation für Vektordaten als &amp;laquo;Transform-Step&amp;raquo; anbietet. Das Plugin kann auf Github heruntergeladen werden: &lt;a href=&quot;https://github.com/edigonzales/geokettle_freeframe_plugin/releases&quot;&gt;FreeFrame.zip&lt;/a&gt;. Die Zip-Datei muss entpackt und in den Ordner &lt;code&gt;plugins/steps&lt;/code&gt; von GeoKettle kopiert werden. Hat die Installation geklappt, erscheint unter Design - Transform ein neuer Step: &amp;laquo;FreeFrame Plugin&amp;raquo; Eine (marginal) ausführlichere Installationsanleitung findet sich &lt;a href=&quot;https://github.com/edigonzales/geokettle_freeframe_plugin&quot;&gt;hier&lt;/a&gt;. Zum Ausprobieren habe ich ein &lt;a href=&quot;http://www.catais.org/tmp/geokettle-2.5.zip&quot;&gt;Sorglos-Paket&lt;/a&gt; zusammengestellt, das aus GeoKettle und dem Plugin selbst besteht. Es wird &lt;strong&gt;nicht&lt;/strong&gt; mit jeder neuen Plugin-Version aktualisiert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Anwendung ist einfach: Der Transformationsschritt kann innerhalb GeoKettle zu einem beliebigen Zeitpunkt angewendet werden, z.B. vor dem Export einer Datenbanktabelle in eine Shapedatei:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep1/postgis2shape.png&quot; alt=&quot;Postgis 2 Shape&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Viel einstellen muss/kann man nicht:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep1/einstellungen.png&quot; alt=&quot;Plugin Einstellungen&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Apply transformation on&lt;/code&gt;: Die gewünschte Geometriespalte, die transformiert werden soll.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Source reference frame&lt;/code&gt;: Quell-Referenzrahmen (LV03 oder LV95)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Target reference frame&lt;/code&gt;: Ziel-Referenzrahmen (LV03 oder LV95)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Triangular transformation network&lt;/code&gt;: Dreiecksvermaschungsdatensatz (CHENyx06)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das Plugin prüft &lt;strong&gt;nicht&lt;/strong&gt;, ob die Geometrien im richtigen Koordinatensystem vorliegen. Wählt man also z.B. als Quell-Referenzrahmen &amp;laquo;LV03&amp;raquo; aus und die Input-Geometrien sind in einem anderen Koordinatensystem (also &lt;strong&gt;nicht&lt;/strong&gt; EPSG:21781) erhält man keine befriedigenden und sinnvollen Resultate. Für vorgängige Koordinatentransformationen kann der &amp;laquo;SRS Transformation&amp;raquo;-Schritt verwendet werden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Liegen die zu transformierenden Koordinaten nicht innerhalb der Dreiecksvermaschung wird keine Transformation durchgeführt und die Koordinaten werden unverändert weitergeleitet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stimmt die Transformation auch? Als kleine &lt;a href=&quot;https://raw2.github.com/edigonzales/geokettle_freeframe_plugin/master/data/verification/vergleich_swisstopo_geokettle.ktr&quot;&gt;GeoKettle-Übung&lt;/a&gt; habe ich Punktkoordinaten verglichen, die ich mit dem swissopo-Dienst und mit GeoKettle transformiert habe:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/fun_with_geokettle_ep1/swisstopo_vs_geokettle.png&quot; alt=&quot;Vergleich swisstopo vs. GeoKettle&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die &lt;a href=&quot;https://github.com/edigonzales/geokettle_freeframe_plugin/blob/master/data/verification/vergleich_swisstopo_geokettle.xls?raw=true&quot;&gt;Resultate&lt;/a&gt; zeigen, dass es ausser Kleinstdifferenzen aus (wahrscheinlich?) nummerischen Gründen keine Unterschiede gibt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Transformationsschritt ist vernünftig schnell: Für das Exportieren der gesamten Bodenbedeckung des Kantons Solothurn (circa 270&apos;000 Polygone) aus einer PostgreSQL/Postgis-Datenbank in eine Shape-Datei benötigt GeoKettle &lt;strong&gt;ohne&lt;/strong&gt; Transformation circa 40 Sekunden; &lt;strong&gt;mit&lt;/strong&gt; Transformation werden knapp 100 Sekunden benötigt. Das Transformieren der gleichen Datenbanktabelle inkl. Speichern  in einer neuen Datenbanktabelle benötigt circa 130 Sekunden. Für den Test wurde ein Hetzner-Server (Intel i7-3770, HDD im Software-Raid 1, Ubuntu 10.04) verwendet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Was bringt die Zukunft:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Einbinden von kantonalen Dreiecksvermaschungen, z.B. &lt;a href=&quot;http://www.lv95.bve.be.ch/lv95_bve/de/index/navi/index/haeufig_gestelltefragen/glossar.html#anker-anchor-10&quot;&gt;BEENyx15&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatische Detektierung der Geometriespalte(n). So wird es möglich sein alle Tabellen einer Datenbank oder eines Datenbankschemas in einem Rutsch zu transformieren.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Der Quellcode des Plugins befindet sich auf &lt;a href=&quot;https://github.com/edigonzales/geokettle_freeframe_plugin&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>QGIS server vs. QGIS server</title>
      <link>http://blog.sogeo.services/blog/2014/01/29/qgis-server-vs-qgis-server.html</link>
      <pubDate>Wed, 29 Jan 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/01/29/qgis-server-vs-qgis-server.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;QGIS Server ist schnell. Das zeigt die &lt;a href=&quot;http://blog.sourcepole.ch/assets/2013/6/17/fossgis_2013_performanceoptimierte_wms_dienste.pdf&quot;&gt;Präsentation&lt;/a&gt; an der FOSSGIS 2013. Ich wurde aber das Gefühl nie los, dass sich die Performance verschlechtert, falls Layer kaskadiert werden. Was verstehe ich unter &amp;laquo;kaskadiert&amp;raquo;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Häufig benutzte Grundlagen- resp. Hintergrundkarten werden einmalig erstellt und über &lt;strong&gt;eine&lt;/strong&gt; Schnittstelle (WMS) zur Verfügung gestellt. Dies ist vor allem praktisch, falls die Grundlagenkarte aus vielen einzelnen Layern und Datensätzen besteht.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für insgesamt fünf Hintergrundkarten habe ich mit JMeter Lasttests durchgeführt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AV-WMS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Plan für das Grundbuch&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Basisplan&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Orthofoto&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kombination aus Orthofoto und Basisplan resp. Plan für das Grundbuch&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dabei wurden drei Ansätze (= drei QGIS-Projekte = drei WMS-Dienste) verfolgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;direkt&lt;/em&gt;: Layer werden in QGIS direkt geladen, z.B. VRT-Layer für Orthofotos, Postgis für Vektordaten.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;embedded&lt;/em&gt;: Layer werden via QGIS-Projekt (Variante 1) geladen (&amp;laquo;Embed Layers and Groups&amp;#8230;&amp;#8203;&amp;raquo;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;wms&lt;/em&gt;: Layer wird von Variante 1 als WMS in QGIS-Projekt geladen (= Kaskadieren).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Die Testbedingungen sind wie folgt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hetzner-Server:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Intel i7-3770 (4 Cores, 8 Threads, 3.4 - 3.9 GHz)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;16 GB RAM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2 x 3 TB HDD (7200 rpm, Software-RAID 1)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu 10.04&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PostgreSQL 8.4 / Postgis 2.0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;QGIS enterprise 13.03b&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JMeter, Datenbank und QGIS auf dem gleichen Server&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Exemplarisch das Resultat des AV-WMS:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/qgisserver_vs_qgisserver/avwms_bench.png&quot; alt=&quot;AVWMS Benchmark&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Embedded Layer und Gruppen sind gleichauf mit direkt geladenen Datensätzen. Massiv hingegen ist der Einbruch unter Last bei kaskadierten Layern. Warum der Einbruch so massiv ist, entzieht sich meiner Kenntnis. Zu empfehlen sind daher kaskadierende Layer nur beschränkt. Die bessere Lösung ist das direkte Einbinden der Datensätze oder &amp;laquo;eingebettete&amp;raquo; Datensätze.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Für alle anderen Hintergrundkarten zeigt sich exakt das gleiche Bild. Sämtliche Resultate und einige Beispielbilder gibts &lt;a href=&quot;http://edigonzales.github.io/qgisserver_vs_qgisserver/&quot;&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Smoothe Höhenkurven</title>
      <link>http://blog.sogeo.services/blog/2014/01/03/smoothe-hoehenkurven.html</link>
      <pubDate>Fri, 3 Jan 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">blog/2014/01/03/smoothe-hoehenkurven.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit &lt;a href=&quot;http://www.gdal.org/gdal_contour.html&quot;&gt;gdal_contour&lt;/a&gt; steht ein Programm zur Verfügung, das aus einem digitalen Terrainmodell Höhenkurven berechnet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;gdal_contour -a elev dtm110733.tif contour.shp -i 10.0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mit diesem Befehl werden Höhenkurven mit einer Äquidistanz von 10 Metern erzeugt und in der Shapedatei  &lt;code&gt;contour.shp&lt;/code&gt; gespeichert. Zusätzlich wird die Höhe in das Attribut &lt;code&gt;elev&lt;/code&gt; geschrieben.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ohne jegliches Zutun wirken die Höhenkurven aber unruhig und es sind viele &amp;laquo;Kleinst&amp;raquo;-Höhenkurven vorhanden:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/smoothe-hoehenkurven/contour_5000.png&quot; alt=&quot;Höhenkurven mit gdal_contour&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ein schöneres Kartenbild ergibt sich durch vorgängiges Prozessieren des digitalen Terrainmodells. Mit einem Shell-Skript wird das Terrainmodell zehn Mal mit einer bestimmten Resamplingmethode umprojiziert. Das Umprojizieren ist in diesem Fall aber belanglos, da a) nichts umprojiziert wird und b) nur das Verschmieren der Rasterwerte durch das Resampling interessiert:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight linenums&quot;&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;#!/bin/bash

cp $1 tmp_$1

for i in {1..10}
do
  gdalwarp -co COMPRESS=PACKBITS -overwrite -r cubicspline tmp_$1 smooth_$1
  cp smooth_$1 tmp_$1
done

rm contour_smooth.*
gdal_contour -a elev smooth_$1 contour_smooth.shp -i 10.0

ogr2ogr -overwrite -dialect SQLITE contour_smooth_reduced.shp contour_smooth.shp -sql &quot;SELECT * FROM  contour_smooth WHERE ST_Length(GEOMETRY) &amp;gt; 100&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Das 10-fache Resampling findet in der for-Schleife statt. Anschliessend werden die Höhenkurven erzeugt. Der letzte Befehl löscht alle Höhenkurven, deren Länge kleiner 100 Meter ist. Dieser Befehl funktioniert erst mit gdal &amp;ge; 1.10.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Et voilà:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock text-center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../../../../images/smoothe-hoehenkurven/contour_smooth_reduced_5000.png&quot; alt=&quot;Smoothe Höhenkurven&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>

  </channel> 
</rss>
