Schlagwort-Archive: Map

Visualisierung von Geodaten in RapidMiner Server

(English version)

Update 2020-02: GIS-Funktionalität ist jetzt als Erweiterung verfügbar.

Nach der Artikelserie über GIS in RapidMiner Studio (1234) geht es nun darum, wie die erhaltenen Ergebnisse visualisiert werden können. In Studio sind die Möglichkeiten dafür ja ziemlich eingeschränkt: Punkt-Daten können noch halbwegs als Scatterplots angezeigt werden, aber für Linien und Flächen gibt es keine guten Methoden.

RapidMiner Server bietet aber mit den Webapps die Möglichkeit einer flexiblen Visualisierung durch die Einbindung von JavaScript.

Einbindung von GeoTools

Für die nachfolgende Vorgehensweise ist es nicht notwendig, den Server ähnlich wie Studio mit Geo-Libraries auszustatten. Wenn man jedoch die gleichen GIS-Funktionen wie in Studio verwenden will, kann es sinnvoll sein.

Ausgehend vom eingerichteten geoscript-Verzeichnis wie im ersten Teil der Anleitung werden die Jar-Bibliotheken aus diesem Verzeichnis in die EAR-Datei des Servers kopiert. Man braucht dazu ein Zip-Werkzeug, ich habe den Midnight Commander verwendet.

  1. RapidMiner Server beenden
  2. rapidminer-server/standalone/deployments/rapidminer-server-X.Y.Z.ear zur Sicherheit anderswo hinkopieren
  3. Aus dem lib/-Verzeichnis die alte groovy-X.jar löschen und die neue aus der Studio-Installation hineinkopieren
  4. Alle Jar-Dateien aus dem geoscript-Ordner der Studio-Installation auch in lib/ kopieren. Wenn eine Datei schon vorhanden ist, muß sie nicht überschrieben werden.
  5. RapidMiner Server starten.

Danach sollten alle Prozesse mit GIS-Verarbeitung aus Studio auch am Server funktionieren.

Visualisierung in Webapps

Meine Wahl fiel auf die Leaflet-Library, da sie Open Source und gut dokumentiert ist. Da wir in RapidMiner keinen eigenen GIS-Datentyp haben und die bisherigen Prozesse die Geodaten als WKT (Well Known Text)  verarbeiten, brauchen wir noch die Mapbox-Omnivore-Library. Diese konvertiert WKT-Daten in GeoJSON, das bevorzugte Format von Leaflet.

Vor der Erstellung des Webapps bauen wir einen Prozess in Studio, der die gewünschten Daten ausgibt. Ein Beispielprozess könnte vom Wiener Open-Data-Server die Bezirksgrenzen als CSV und Bevölkerungsstatistiken holen. Die Bezirke werden über ein gemeinsames Feld (NUTS-Id) verknüpft. Der Output des Prozesses ist eine Tabelle mit den Geodaten des Bezirks, ihrer Fläche, der Gesamtbevölkerung und der Bevölkerungsdichte. Für die Bevölkerungsdichte errechnen wir mit Generate Attributes die Anzahl der Personen pro Quadratkilometer und klassifizieren sie, indem wir verschiedenen Wertbereichen HTML-Farben in der #AABBCC-Notation zuweisen. Hier ist die eigene Kreativität gefragt.

Der Prozess wird auf den Server gelegt. Unter Processes/Services legen wir eine neue Eintragung an und nennen sie z. B. ViennaDistrictPopDensitySvc. Wir wählen als Datenquelle den vorhin angelegten Prozess und als Output Format JSON. Es ist sinnvoll, dieses Webservice als anonym/öffentlich aufzusetzen, um zusätzliche Paßworteingaben zu vermeiden.

In der neuen Webapplikation erzeugen wir eine Komponente vom Typ Text, und schalten „Use graphical editor“ ab. Danach geben wir den HTML- und JavaScript-Code ein.

Infrastruktur

<div id="map">
</div>

<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css"/>
<script src="https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js"></script>

<style type="text/css">
 #map {
 height: 650px;
}
</style>

Dieser Teil holt die Leaflet- und Omnivore-Komponenten und erzeugt ein Objekt, in das die Karte eingefügt werden kann. Im CSS wird die Höhe in Pixeln angegeben (z. B. 650px).

Danach starten wir mit <script language="JavaScript"> einen JavaScript-Block, der am Ende mit </script> geschlossen wird.

Definition der Basiskarte

// Create the map
var map = L.map('map').setView([48.17, 16.4], 11);
// Set up the OSM layer
L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    {
       maxZoom: 18,
       attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors; District data: Open Data Vienna'
    }
).addTo(map);

Hier erzeugen wir das Kartenobjekt mit einer OpenStreetMap-Hintergrundkarte. Die Initialisierungsparameter sind Längen- und Breitengrad der anfänglichen Position der Karte, die Zahl dahinter (11 in diesem Beispiel) die Zoom-Stufe.

Es gibt viele Tile-Server, man sollte die Nutzungsbedingungen prüfen und die Herkunftsangabe (attribution) entsprechend anpassen.

Daten des RapidMiner-Prozesses holen

var Httpreq = new XMLHttpRequest();
Httpreq.open("GET","/api/rest/public/process/ViennaDistrictPopDensitySvc?",false);
Httpreq.send(null);
var mapdata = JSON.parse(Httpreq.responseText);

Dieser Block holt vom lokalen (oder auch einem beliebigen anderen) RapidMiner Server die Daten des Prozesses im JSON-Format und legt sie im mapdata-Objekt ab. Die URL des Webservice kann hier angepaßt werden.

Anzeige der Geo-Objekte

for (i = 0; i < mapdata.length; i++) {
  var district = omnivore.wkt.parse(mapdata[i].SHAPE);
  district.addTo(map)
    .bindPopup(mapdata[i].NAME + "
Population density: " + mapdata[i].POP_DENSITY)
    .setStyle({color: mapdata[i].densityColor, weight: 2, fillOpacity: 0.3});
}

Hier verarbeiten wir die Ergebnisse des Prozesses in einer Schleife. Aus jeder Zeile wird die Form des Bezirks (Attribut SHAPE in den Beispieldaten) mit Hilfe von Omnivore konvertiert, und als neues Objekt zur Karte hinzugefügt.
Mit .setStyle(...) ordnen wir die im Prozess erzeugte Farbabstufung (im Beispiel das densityColor-Attribut) zu.
Als zusätzliche Information erzeugen wir mit .bindPopup(...) noch ein Popup-Fenster, das beim Klick auf einen Bezirk angezeigt wird und zusätzliche Informationen enthält.

Linien-Daten werden ganz ähnlich angezeigt und verarbeitet. Bei Punkt-Daten können verschiedene Marker definiert werden, bei Leaflet gibt es die Anleitung dazu.

Damit ist das Ziel erreicht: RapidMiner Server zeigt eine Karte an, deren Daten (Punkte, Linien oder Flächen) in einen RapidMiner-Prozess verarbeitet wurden.

Bevölkerungsdichte pro Bezirk in Wien
Bevölkerungsdichte pro Bezirk in Wien (Daten: Open Data Wien)

Displaying geographic data in RapidMiner Server

Update 2020-02: GIS functionality is now available in an extension.

After the series of blog posts about GIS in RapidMiner Studio (1234) we’d probably like to visualize our results. The mechanisms in Studio are quite limited: we can create scatter plots from point data but there is no good method for displaying lines and areas.

However, RapidMiner Server offers webapps and powerful visualization using JavaScript.

Using GeoScript in processes

For displaying geographic data it’s not necessary to set up the server with the GeoScript libraries. However, if you want to execute the GIS processes like in studio, it can be a good idea to do so.

Start with the geoscript directory set up in the first part. You’ll need a Zip utility; I used Midnight Commander.

  1. Stop RapidMiner Server
  2. Make a backup copy of rapidminer-server/standalone/deployments/rapidminer-server-X.Y.Z.ear somewhere else
  3. Delete the old groovy-X.jar from the lib/ directory and put in the new one from the Studio installation
  4. Copy all jar files from the geoscript directory of the Studio installation to lib/. Existing files don’t need to be overwritten.
  5. Start RapidMiner Server.

After this process, all your Studio GIS processes should work in the Server.

Map display in webapps

I chose the Leaflet library, as it is open source and well documented. There is no special GIS data type in RapidMiner and the processes in the tutorials used WKT (Well Known Text) until now, so we’ll also need the Mapbox Omnivore library. This converts WKT data to GeoJSON which Leaflet prefers.

Before starting with the web app, we need to build a process in Studio for creating the data. An example process could use the district boundaries as CSV and the population statistics from the Vienna Open Data server. It joins the districts using a common attribute (NUTS id). The output of the process is a table with the district boundaries, their area, the total population and the population density. We also use Generate Attributes to classify the population density with HTML colors (#AABBCC notation). You can get creative here and use the entire functionality of RapidMiner.

The process is saved on the server. We create a new entry in Processes/Services and call it for example ViennaDistrictPopDensitySvc. The data source is the process created before, the output format is JSON. It is a good idea to set up this web service for public anonymous access.

In a new web app we create a Text component and uncheck the „Use graphical editor“ checkbox to enter HTML and JavaScript code.

Infrastructure

<div id="map">
</div>

<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css"/>
<script src="https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js"></script>

<style type="text/css">
#map {
    height: 650px;
}
</style>

This part fetches the Leaflet and Omnivore components and creates a DIV object for the map. We specify the map height in pixels in the CSS block (e. g. 650px).

Then a JavaScript block is started with <script language="JavaScript">. Don’t forget to close the block with </script> at the end.

Setting up the base map

// Create the map
var map = L.map('map').setView([48.17, 16.4], 11);
// Set up the OSM layer
L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    {
       maxZoom: 18,
       attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors; District data: Open Data Vienna'
    }
).addTo(map);

This creates a map object with an OpenStreetMap background layer. The initialization parameters are latitude and longitude of the initial position, and the zoom level (11 in this example).

There are many tile servers available. Be sure to check the terms of usage and update the attribution appropriately.

Getting data from the RapidMiner process

var Httpreq = new XMLHttpRequest();
Httpreq.open("GET","/api/rest/public/process/ViennaDistrictPopDensitySvc?",false);
Httpreq.send(null);
var mapdata = JSON.parse(Httpreq.responseText);

This part fetches the data in JSON format from the local RapidMiner Server and stores them in the mapdata variable. To refer to another web service, change the URL.

Displaying the objects on the map

for (i = 0; i < mapdata.length; i++) {
  var district = omnivore.wkt.parse(mapdata[i].SHAPE);
  district.addTo(map)
    .bindPopup(mapdata[i].NAME + "
Population density: " + mapdata[i].POP_DENSITY)
    .setStyle({color: mapdata[i].densityColor, weight: 2, fillOpacity: 0.3});
}

Here, a loop processes the process results. The Omnivore function converts the district area (SHAPE attribute in the example data) to a new object on the map.
We assign the color calculated in the process with .setStyle(...) (densityColor attribute in this example).
We also create a popup window with additional information using .bindPopup(...). It will be displayed when the user clicks a district.

Displaying line data is very similar. For displaying point data, you can use different markers. This is described by a Leaflet tutorial.

So we reached our goal: RapidMiner Server displays a map with data (points, lines or areas or even a combination) coming from a RapidMiner process.

Population density by district in Vienna
Population density by district in Vienna (Data: Open Data Vienna)

Kartendarstellung mit Pentaho Community Dashboards

(English version)

Im Pentaho BI Server ist eine sehr flexible Karten-Komponente enthalten, die leider nicht gerade ausführlich dokumentiert ist. Ich möchte hier eine kurze Anleitung für einen erfolgreichen Start geben.

Zuerst braucht man Positionsdaten. Diese liegen entweder als echte Geodaten (z. B. aus einer GIS-Datenbank wie PostGIS) oder als Breiten- und Längengrad (latitude/longitude) vor. Im Dashboard brauchen wir die lat/lon-Darstellung.

Als erstes legen wir im Layout-Panel des Dashboard-Editors einen Bereich für die Karte an, z. B. eine Zeile und darin eine Spalte, in diesem Beispiel mit einer Breite von 10 Elementen (Medium Devices). Diese Spalte benennen wir so, daß die Verbindung zum Map-Element erkennbar ist, z. B. MapDiv.

Datenquelle

Die Datenquelle für die Punkte auf der Karte definieren wir im Datasources Panel. Hier legen wir eine Abfrage namens MapQuery an, die uns die Geokoordinaten liefert. Diese Spalten müssen „Latitude“ und „Longitude“ heißen (die Klein- oder Großschreibung ist egal).

Hat man echte Geodaten in einer GIS-fähigen Datenbank, sind die Punkte eventuell in einer eigenen Spalte zusammengesetzt gespeichert. Das läßt sich leicht in die benötigten Koordinaten aufspalten:


SELECT ST_X(ST_Transform(geo, 4326)) as longitude,
       ST_Y(ST_Transform(geo, 4326)) as latitude
FROM gis_table

In diesem Beispiel ist „geo“ der Name der Geodaten-Spalte. Mit ST_Transform(geo, 4326) konvertiert PostGIS die Koordinaten aus der in der Datenbank verwendeten Projektion in Länge- und Breitengrad. ST_X und ST_Y extrahieren aus dem konvertierten Objekt die einzelnen Koordinaten.

Karten-Komponente

Jetzt kann das Karten-Element im Components Panel angelegt werden: Custom: NewMapComponent.

Als Name wählen wir in diesem Beispiel einfach „Map“. Als Datasource tragen wir den Namen der Datenquelle (MapQuery) ein, und als HtmlObject den Namen des angelegten Bereichs (MapDiv).

Wenn wir das Dashboard speichern und ausführen, stellen wir fest, daß keine Karte angezeigt wird. Das liegt daran, daß die Karten im Gegensatz zu anderen Elementen selbst keinen Platz beanspruchen. Wir müssen also Breite und Höhe des enthaltenden Elements festlegen, entweder im Layout Panel mit Height, oder in einem Stylesheet.

Das Ergebnis der Mühen ist eine Karte, in der die Punkte aus der Datenbankabfrage mit farbigen Standard-Symbolen markiert sind.

Büchereien in Wien - Kartendaten (c) OpenStreetMap contributors
Büchereien in Wien – Kartendaten (c) OpenStreetMap contributors

Standardmäßig zeigt die Karte die ganze Erde an. Meist will man aber eine kleinere Region anzeigen. Das geht mit den Optionen Center Latitude und Center Longitude sowie Default Zoom Level.

Die beiden Center-Koordinaten kann man von der Karte ablesen, wenn man mit der Maus über sie fährt. Der Zoom Level hängt von der Größe der Kartendarstellung und dem darzustellenden Gebiet ab. Für eine Großstadt kann der Zoom ca. 12 betragen, Österreich paßt bei Zoom 7 ganz gut, Deutschland braucht schon Zoom 6.

Anpassung der Marker

Die Standard-Markierungen der Punkte auf der Karte passen nicht zu jeder Anwendung. Es besteht die Möglichkeit, eigene Symbole anzuzeigen: dazu gibt man in der Datenquelle die URL zum Symbol (ganz normale Bilddatei) in der Spalte „marker“ aus. Damit lassen sich zum Beispiel unterschiedliche Zustände der dargestellten Objekte anzeigen.

Popups

Zu jedem Marker können wir Zusatzinformationen anzeigen lassen. Wenn die Datenquelle eine Spalte „popupContents“ enthält, wird deren Inhalt (optional HTML-formatiert)  angezeigt, wenn der Benutzer ein Marker-Symbol anklickt. Mit Popup Width und Popup Height läßt sich die Größe des Popups an den erwarteten Inhalt anpassen.

Beispiel eines formatierten Popups - Kartendaten (c) OpenStreetMap contributors
Beispiel eines formatierten Popups – Kartendaten (c) OpenStreetMap contributors

Mit diesen Optionen läßt sich schon ganz ohne Programmierung viel machen. Mit etwas JavaScript läßt sich noch viel mehr erreichen.

Automatische Aktualisierung der Karteninhalte

Im Gegensatz zu vielen anderen Dashboard-Komponenten bietet NewMapComponent keine Funktion zum automatischen Aktualisieren des Inhalts in festgelegten Abständen. In einigen Fällen möchte man jedoch die Positionen beweglicher Objekte darstellen und die Karte von Zeit zu Zeit automatisch aktualisieren. Glücklicherweise läßt sich das mit ein wenig JavaScript-Code erreichen.

In den Advanced Properties der Karten-Komponente geben wir in der Eigenschaft Post Execution eine Funktion ein:


//Sets up a timer to refresh the map after a minute
function () {
    this.timerId = window.setTimeout(
        function() {
            render_Map.update();
        }
    , 60000);
}

60000 ist die Anzahl der Millisekunden, nach denen der Timer laufen soll. render_Map ist der Name der Karten-Komponente, mit „render_“ davor – so benennt das Community Dashboard Framework das JavaScript-Objekt.

Da diese Funktion immer nach dem Aktualisieren der Karte ausgeführt wird, ist gleich der nächste Timer nach der gleichen Periode aktiviert. Die Timer-ID, die in JavaScript zum Abbrechen des Timer genutzt werden kann, wird dabei im render_Map-Objekt gespeichert.

Es gibt nur ein Problem: Verschiebt der Benutzer den Kartenausschnitt oder zoomt hinein oder hinaus, setzt die Aktualisierung der Karte den Ausschnitt und den Zoom-Wert auf die Standardwerte. Die Karte kehrt also zum Ausgangspunkt zurück. Um das zu vermeiden, können wir vor dem Update noch schnell die aktuelle Sicht abspeichern, wodurch die Karte nicht mehr springt.

Folgende Funktion gehört in die Pre Execution-Eigenschaft:

//Before reloading the map automatically, save the current zoom level and position
function () {
    if (this.timerId != null) {

        //Get current map center and zoom level
        center = this.mapEngine.map.center;
        zoom = this.mapEngine.map.zoom;

        //Transform to WGS84 coordinates
        center.transform(this.mapEngine.map.projection, "EPSG:4326");

        this.defaultZoomLevel = zoom;
        this.centerLongitude = center.lon;
        this.centerLatitude = center.lat;
    }
}

Zuerst überprüfen wir, ob die timerId schon gesetzt ist. Beim ersten Anzeigen der Karte (wenn das Dashboards geöffnet wird) ist das noch nicht der Fall.

Ist die Karte schon fertig dargestellt worden, ist timerId beim nächsten Durchlauf nicht mehr leer, dann können wir also die Position und den Zoom-Level der Karte abfragen und in die Standardeinstellung der Komponente hineinschreiben.

Bei der nächsten automatischen Aktualisierung der Karte tritt zwar ein systembedingtes Flackern auf, aber der vom Benutzer gewählte Bildausschnitt bleibt bestehen.

Die Map-Komponente bietet noch andere Möglichkeiten, so wie das Thema der GIS-Daten ein fast unerschöpfliches ist. Ich werde sicher noch weitere Beiträge in diesem Themenkreis schreiben.

Displaying maps with the Pentaho Dashboard Framework

Pentaho BI Server contains a map component that’s very versatile but unfortunately quite sparsely documented. I’d like to give you a simple introduction into using the component.

First, we need position data. These are either stored in a real geographic database like PostGIS or separated into latitude and longitude. The dashboard map needs the lat/lon form.

First we create an area for the map in the Layout Panel of the Dashboard Editor. This can be a row and a column in it, for example with a width of 10 elements (enter it in the Medium Devices setting). We name this column MapDiv to state that it belongs to the map.

Data source

We need to specify the data source of the points in the Datasources Panel. Here, we enter a query called MapQuery that returns the coordinates. The columns must be called „Latitude“ and „Longitude“ (capitalization doesn’t matter).

Geograpic data in a GIS-capable database stores the points in a single column. It is easy to separate the two coordinates:


SELECT ST_X(ST_Transform(geo, 4326)) as longitude,
       ST_Y(ST_Transform(geo, 4326)) as latitude
FROM gis_table

In this example, „geo“ is the name of the geodata column. In PostGIS, ST_Transform(geo, 4326) converts the coordinates from the geographic reference system used in the database to the classic Earth latitude and longitude values. ST_X and ST_Y extract the longitude and latitude from the compound object.

Map component

Now we create the Map element in the Components Panel by adding a Custom: NewMapComponent.

The name is simply „Map“ in this example. The Datasource is the name of our data source (MapQuery) and the HtmlObject is the name of the created row (MapDiv).

After saving and executing the dashboard, no map is shown. The reason is that the Map component itself doesn’t specify a content height, as other components do. So we need to set the width and height ourselves, either in the Layout Panel (Row Height) or in a stylesheet.

The result is a map that visualizes points from the database query with standard symbols in different colors.

By default, the map shows the entire Earth. We usually want to restrict the displayed area: this is done with the options Center Latitude/Longitude and Default Zoom Level.

The Center coordinates are displayed on the map when moving the mouse over it. The zoom level depends on the size of the map and the area to be shown. For a large city, the zoom can be around 12, Austria fits well with zoom = 7, and Germany requires zoom = 6.

Changing the markers

The default markers of map points aren’t suitable for some requirements. It is possible to use our own symbols: just return the URL of the symbol (a normal image file) in the column called „marker“ in the map data source. This allows us to visualize different object states.

Popups

For each marker we can display additional information in a popup area. If the data source contains a column named „popupContents“, its text (optionally HTML formatted) is displayed when the user clicks on a marker symbol. Popup Width and Popup Height can be changed to match the size of the content to display.

Using these options, we can already do a lot, entirely without programming. A bit of JavaScript can do a lot more.

Periodic updates of the map contents

In contrast to many other dashboard components, the NewMapComponent doesn’t have a setting for periodic updates. Sometimes we have to visualize objects that can move and automatically refresh the map from time to time. Fortunately, only a few lines of simple JavaScript are necessary to achieve this.

We enter the following function in the Advanced Properties of the Map component, in the property Post Execution:


//Sets up a timer to refresh the map after a minute
function () {
    this.timerId = window.setTimeout(
        function() {
            render_Map.update();
        }
    , 60000);
}

60000 is the number of milliseconds after the timer executes the function. The map component is named render_Map in this example: the Community Dashboard Framework always prefixes the specified names with „render_“ to create the JavaScript object in the running dashboard.

This function is executed after each update of the map, so it also sets up the next automatic update after the same period. The timer ID stored in the render_Map object could be used to cancel the timer if desired.

However, there’s one usability problem: If the user moves the map or zooms in, the automatic update resets the map to the values defined in the dashboard properties and the map returns to the initial view. We can avoid this by storing the properties of the current view before the map update, so the map doesn’t jump around anymore.

The following function is entered into the Pre Execution property:

//Before reloading the map automatically, save the current zoom level and position
function () {
    if (this.timerId != null) {

        //Get current map center and zoom level
        center = this.mapEngine.map.center;
        zoom = this.mapEngine.map.zoom;

        //Transform to WGS84 coordinates
        center.transform(this.mapEngine.map.projection, "EPSG:4326");

        this.defaultZoomLevel = zoom;
        this.centerLongitude = center.lon;
        this.centerLatitude = center.lat;
    }
}

The function checks if the timerId is set. If the map is displayed the first time (when opening the dashboard), this is not yet the case.

In a map that is already displayed, the timerId has a value, so we can store the map’s position and zoom level in the properties of the map object.

The map update causes a visible flicker of the dashboard, but at least the map area selected by the user stays as it is.

The map component offers a lot more, and GIS data is an almost infinite topic. This blog post isn’t the last one about maps, that’s for sure.