Ich halte auch heuer einen Vortrag bei den Linuxwochen Wien, diesmal mit dem Titel „Open Source im Startup”. Es ist eine große Ehre, gleich den ersten Vortrag nach der Eröffnung halten zu dürfen.
Diesmal geht es weniger um mein Kernthema Data Science, sondern um allgemeine IT und den Einsatz verschiedener Open-Source-Lösungen in einem Startup. Ich bin ja „nebenbei“ der CIO (und gleichzeitig die IT-Abteilung) von SCO2T, dem Roller-Sharing in Wien.
Im Vortrag geht es um Technologien, die ich teilweise auch schon hier im Blog vorgestellt habe: PostgreSQL und PostGIS, Foreign Data Wrappers, den Pentaho-Stack, aber auch um neue Themen wie Web-APIs mit PostgREST, Traccar und so weiter. Ich zeige einige Beispiele, wie man als Nicht-Programmierer mit eher dem Bereich Data Science zugehörigen Werkzeugen auch komplexe-IT-Systeme aufbauen kann.
Der Vortrag beginnt am Donnerstag, 4. 5. 2017 um 10 Uhr im Raum F0.01 am FH Technikum Wien, Höchstädtplatz 6.
Auf der Vortrags-Seite im Programm werden auch meine Folien hinterlegt werden, außerdem gibt es dort wie jedes Jahr die Möglichkeit, eine Bewertung des Vortrags abzugeben.
Es gibt wie jedes Jahr spannende Vorträge an allen drei Tagen, ich werde wohl öfters in einem der Hörsäle anzutreffen sein.
In diesem vierten Teil geht es um die Filterung von Datensätzen und die Verbindung mehrerer Datensätze anhand geographischer Kriterien. (Um die Beispiele nachzuvollziehen, muß RapidMiner wie in der Einführung beschrieben um die GeoScript-Libraries ergänzt werden.)
Da kein eingebauter Join-Operator für geographische Kriterien existiert, bauen wir diese Operation nach, indem wir jedes Element der beiden Datensätze miteinander vergleichen und das Ergebnis dann filtern. Der Vergleich wird mit geographischen Operationen durchgeführt.
In den bisherigen Beispielen wurde mit Hilfe des Cartesian-Product-Operators jede Kombination der Datensätze gebildet. Die andere Möglichkeit ist, in einer Schleife alle Elemente eines Datensatzes mit denen des anderen zu vergleichen.
(Dies ist wieder ein Bereich, in dem PostGIS mit geographischen Indizes in der Datenbank eine wesentliche Beschleunigung bietet, die bei wirklich großen Datenmengen auch noch gut funktioniert.)
Einige Funktionen, die uns für die Verbindung von Datensätzen zur Verfügung stehen:
distance: Diese Funktion haben wir bereits kennengelernt. Wenn wir die Distanzen aller Kombinationen bestimmt haben, können wir das Ergebnis filtern, um z. B. jene herauszufiltern, deren Distanz einen bestimmten Wert nicht überschreitet.
intersects: Liefert true, wenn die Objekte sich an mindestens einem Punkt überschneiden.
intersection: Erzeugt die Überschneidung der Objekte als neues geometrisches Objekt. Der Typ des überschneidenden Bereichs orientiert sich an den verglichenen Objekten: Z. B. ist die Überschneidung einer Fläche mit einer Linie wieder eine Linie. Wir können mit dem Ergebnis natürlich weiterarbeiten und z. B. die Fläche oder andere Kennzahlen bestimmen und darauf filtern.
contains: A.contains(B) liefert true, wenn das Objekt A das Objekt B vollständig enthält, also kein Teil von B außerhalb von A liegt.
Bei intersects und intersection ist die Richtung des Aufrufs egal (A.intersects(B) ergibt das gleiche wie B.intersects(A)). Bei contains jedoch nicht: Eine Fläche A kann einen Punkt B enthalten, was im umgekehrten Fall nicht gilt.
Eine häufig verwendete geographische Operation ist das Buffering, das mit der buffer-Funktion realisiert wird. Hierbei wird um das ursprüngliche Objekt (Punkt, Linie, Fläche) eine Fläche erzeugt, deren Grenze die im Funktionsaufruf angegebene Distanz zum Objekt hat. Das Ergebnis ist somit immer eine Fläche. Damit können wir verschiedene Dinge wie Einzugsgebiete von Geschäften, die Reichweite von Funkantennen oder die tatsächliche Fläche einer als Linie mit Breitenangabe angegebenen Straße berechnen. Mit der Berechnung des Buffers werden auch häufig Distanz-Vergleiche mit Hilfe von contains oder intersects durchgeführt.
Die Vorgehensweise in RapidMiner ist diese: zuerst werden in einem Execute-Script-Operator mit Groovy/GeoScript die benötigten Ergebnisse ermittelt (z. B. contains: true/false) und danach die Ergebnismenge mit Filter Examples gefiltert, sodaß nur die Objekte übrigbleiben, auf die das gewünschte Kriterium zutrifft (z. B. contains = true oder distance < 10).
Der Beispielprozess existiert in zwei Varianten: einmal mit Cartesian Product und einmal mit Loop Examples. Die erste Variante ist deutlich schneller, braucht jedoch sehr viel Speicher, weil sie riesige Tabellen anlegen muß. Die zweite Variante braucht viel länger, aber der Speicherverbrauch ist geringer, da keine „multiplizierten“ Datensätze erzeugt werden.
Für manche Operationen wie intersects oder contains ist die Projektion unerheblich (solange beide Geometrien im gleichen Koordinatensystem angegeben sind). Für buffer müssen wir aber eine Ausdehnung angeben, somit ist es wieder zweckmäßig, mit einer Meter-basierten Projektion zu arbeiten. Deswegen transformieren die Beispielprozesse alle Datensätze in die für Österreich geeignete Projektion EPSG:3416.
Drei Datensätze werden vom Wiener Open-Data-Server geholt: Wasserflüsse (Linien), Brücken (Flächen) und Spielplätze (Punkte). Dann sucht der Prozess mit intersects und intersection die Bereiche, in denen die Brücke über Wasser führt. Mit buffer wird der Bereich um die Wasserflüsse markiert, in dem mit contains nach Spielplätzen gesucht wird. Das Ergebnis ist dann eine Liste von Spielplätzen, die nahe an einem Bach oder Fluß liegen.
Damit ist diese Serie über GIS in RapidMiner vorerst abgeschlossen. Die besprochenen Methoden decken schon eine große Anzahl von Aufgaben ab, und mit etwas Kreativität ist noch viel mehr möglich. Ich werde sicherlich noch Anwendungen und Lösungen finden und darüber auch hier berichten. Wenn etwas unklar sein sollte, beantworte ich gerne Fragen: hier in den Kommentaren, im RapidMiner-Forum, oder auch direkt. Ich wünsche viel Erfolg!
GIS in RapidMiner (4) – Geographic Filter and Joins
The fourth part of this series is about filtering and joining example sets on geographic criteria. (RapidMiner needs to be extended with the GeoScript libraries as described in the Introduction for the examples to work.)
There is no built-in Join operator with support for geographic functions. So we reproduce this functionality by comparing each element of both example sets and filter the result. Geographic functions are used for the comparison.
In the examples until now, we used the Cartesian Product operator for building an example set with each combination of examples. The other way is a Loop over each element of example set 1 that compares the one example of the loop with all elements of example set 2.
(This is also an area where PostGIS shines with geographic indexes in the database that improve processing times by a huge factor, even in the case of huge tables.)
Some geographic functions usable for joining or connecting example sets:
distance: We already saw this. After calculating the distance of all combinations, we can filter the result set to only contain those within a certain distance.
intersects: Returns true if the objects have at least one point in common.
intersection: Creates a new geometry with the common parts of both objects. The type of the result depends on the compared objects: e. g. the intersection of an area and a line is again a line. The resulting geometry can be processed further, for example by calculating its area or other measures and filtering on those.
contains: A.contains(B) returns true if object A fully contains B, in other words, no part of B is outside of A.
The order of the objects is not relevant when using intersects and intersection: A.intersects(B) has the same result as B.intersects(A). This is not true for contains: An area A can contain point B but this is not true for the opposite.
Buffering is a popular geographic operation, available in the buffer function. Buffering creates an area around the original object (point, line, area) with a border in a distance specified in the function call. The result is always an area. We can calculate different things with buffering: the service area of shops, the coverage of wireless antennas or the actual area of a street that is specified as a line but has a width attribute. After creating the buffer, less-or-equal distance calculations can be done with contains or intersects.
The following work flow is available in RapidMiner: first, in an Execute Script operator we process the data using geographic functions with Groovy and GeoScript (e. g. contains: true/false); then we filter the result set with Filter Examples to only keep examples with the selected criteria (e. g. contains = true or distance < 10).
There are two variants of the example process: one with Cartesian Product and one with Loop Examples. The first version is much faster but needs a huge amount of memory as it has to create very large tables. The second version takes much longer but uses less memory, as it doesn’t need to process „multiplied“ data sets.
For some operations like intersects oder contains, the projection is not relevant (as long as both geometries use the same coordinate system). But we need to specify the border size in buffer, so it is again better to work with a meter based projection. Therefore, the example processes transform the original geometries to EPSG:3416, a projection suitable for Austria.
The process fetches three data sets from the Vienna Open Data Server: Water flows (lines), Bridges (areas) und playgrounds (points). It then uses intersects and intersection to find areas where the bridge is over water. Using buffer, it marks an area around water flows and uses contains to find playgrounds in that area. The result is a list of playgrounds in the vicinity of streams or rivers.
This concludes the series about GIS in RapidMiner for now. The described methods solve a range of problems, and many more can be solved with some creativity. I will surely find use cases and solutions, and describe them here. If something is not clear, please ask: here in the comments, in the RapidMiner Forum or even directly. I wish you a lot of success!
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.
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.
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.