Zum Inhalt

Lua und CadnaA

CadnaA-Lua-Skripte sind vollständige Lua-Skripte. Die Interaktion zwischen dem Skript und CadnaA wird durch das globale CadnaA cna Objekt vermittelt. Dieses Objekt ist nur zugänglich, wenn die Skript-Datei aus CadnaA heraus ausgeführt wird.

CadnaA-Anweisungen

CadnaA-Lua bietet eine Reihe von Anweisungen für Aufgaben wie Laden, Speichern und Berechnung von Projekt-Dateien im Raster oder an Immissionspunkten sowie einiges mehr (z.B. Neuberechnung des Umgriffs des Projekts, usw.). Diese Anweisungen sind alle in den sog. cna-Namespace (Gültigkeitsbereich des Bezeichners) eingebunden, d.h. diese sind in Lua zugänglich über die Anweisung cna.INSTRUCTION, z.B.:

cna.calc_grid()
cna.save_grid("test.cnr")
-- print out a string of characters to the dialog
cna.print("message to console")

Objektzugriff

Neben diesen allgemeinen Anweisungen sind die CadnaA-Objekte auch in CadnaA-Lua zugänglich. Genauer gesagt, sind die CadnaA-Objekttabellen direkt zugänglich und da die CadnaA-Objekte Bestandteile dieser Tabellen sind, sind diese genauso durch die CadnaA-Tabellenfunktionen zugänglich (wie z.B. first_by_id("id") und all("id")).

Die CadnaA-Tabellenobjekte können im Namespace cna.tables gefunden werden, z. B.

cna.tables.pt_src   -- table of point sources
cna.tables.imm      -- table of receivers

Je Tabelle gibt es zwei Identifier (z.B. pt_src bzw. point_source), siehe Tabelle. Die folgenden Beispiele zeigen wie mit CadnaA-Lua auf Projektdateien zugegriffen und CadnaA-Objekte manipuliert werden.

Beispiel 1: Iteration über jedes Objekt innerhalb einer CadnaA-Table

In folgenden Quellcode-Auszug werden Schalldruckpegel LP1 von Immissionspunkten aus der Tabelle Immissionspunkt gelesen und diejenigen, die größer als 70 dB sind, in das Ausgabefenster geschrieben.

Beispiel

Pfad: LUA_1.lua

local level = ""
local r
for r in cna.tables.imm:all() do
    if r.LP1 > 70 then
        level = level .. r.LP1 .. " dB"
    end
end
cna.print('levels > 70 are: ' .. level)

Der Ausdruck tables.imm: all () ist der "Iterator" für die Objekte der Tabelle cna.tables.imm. Iteratoren machen Objekte nacheinander zugänglich unter Verwendung von Schleifen, wie in diesem Fall mit der Schleife for .. in .. do. Auf diese Weise wird sichergestellt, dass jedes Element der Tabelle durchlaufen wird. Die Variable r stellt einen Verweis auf eine CadnaA-Objekt dar (man könnte auch sagen: Diese bezieht sich auf eine Zeile in der CadnaA-Tabelle cna.tables.imm).

r ist ein "Verweis" auf das CadnaA-Objekt „Immissionspunkt“. Dies bedeutet, dass Manipulationen an r direkt auf das "echte" CadnaA-Objekt angewendet werden. Durch die Verwendung der Punkt-Notation r.ATTRIBUTE können die Objektattribute gelesen oder verändert werden. Die Attributbezeichnungen sind im CadnaA-Handbuch "Attribute, Variablen und Schlüsselwörter" dokumentiert.

Beispiel 2: Automatische Rasterberechnung

Das folgende Skript fügt eine Punktquelle q in das Projekt ein und verschiebt dieses an eine neue Position, um anschließend das Raster zu berechnen. Dieser Prozess wird 400-mal wiederholt und bei jedem Schritt wird die Punktquelle 25 m in Richtung der x-Achse verschoben. Es resultieren 400 nummerierten Rasterdateien, die zeigen, wie sich der Schalldruckpegel ändert, wenn die Punktquelle über die Distanz von 1 km bewegt wurde.

Hinweis

Der Befehl Raster|Speichern überschreibt ggf. schon bestehende Raster-Dateien (*. cnr) ohne Warnung.

Beispiel

Pfad: LUA_2.lua

local x
local q = cna.tables.pt_src:append()
q.LW_LI = 100
for x=0,1000,25 do -- x in steps of 25 m
    q.X = 100 + x   -- set position
    cna.calc_grid()   
    -- save grid (filename contains x-pos)
    cna.save_grid("grid_" .. x .. ".cnr")
    -- saves to the directory containing the LUA-file
end

Die Schleife im Detail: Nach der Variablendeklaration, mit der x-Position (x) und q (Punktquellen) wird der Zähler in Schritten von 25 von 0 bis 1000 erhöht. q ist ein Verweis auf ein neues CadnaA-Objekt. CadnaA-Objekte können durch Anhängen an die CadnaA-Tabelle mit der Methode cna.tables.*:append() erstellt werden. Bei jedem Iterationsschritt wird die X-Attribut der Punktquelle verändert und das Raster wird berechnet und gespeichert. Um mehrere Ausgabedateien zu erzeugen, wird der Dateiname durch den Ausdruck "grid_" .. x .. ".cnr" gebildet.

Um die Ergebnisraster in ein bestimmtes Verzeichnis zu speichern, ist die Anweisung cna.save_grid um den Dateipfad zu ergänzen gemäß:

cna.save_grid("D:/CADNA_A/LUA/grid_" .. x .. ".cnr")

Als Pfadtrenner ist der Forward-Slash zu verwenden, da Lua den Backward-Slash (mit nachfolg. Buchstaben) als Steuerzeichen interpretiert.

Beispiel 3: Lissajous-Figuren

Das folgende Beispiel zeigt, wie mehrere Objekte erzeugt werden können, um anschließend deren Attribute zu manipulieren. Das Ergebnis dieses Skripts ist eine Reihe von Punktquellen, die in der Form von Lissajous-Figuren angeordnet sind. Dieses Beispiel kann ganz einfach abgewandelt werden, um andere Formen für andere Zwecke zu erzeugen (z. B. enthält das math Paket einen Zufallszahlengenerator, der verwendet werden kann, um zufällig verteilte Quellen zu erzeugen, usw.).

Beispiel

Pfad: LUA_3.lua

require('math')
local N = 10000
for x = 1,N do
    local q = cna.tables.pt_src:append()
    -- q is the new source
    local f = 2*math.pi / N
    q.X = 50 + 250*math.sin(x/f) 
    q.Y = 500 + 250*math.cos(x/f*3)
    q.ID = "source" .. x  -- assign an ID
    -- assign level in dB
    q.LW_LI = 90 + math.random() * 20 - 10 
    -- create and assign a memo text variable
    -- "objectindex" with content x
    q.memo_var["objektindex"] =  x 
    q.memo_var.objektindex =  x -- alternative way of referencing the variable
end

In diesem Beispiel werden negative x-Koordinaten erzeugt. Daher soll der Umgriff des Projektes angepasst werden mit:

cna.limit.calc()

Die Lua Mathematik-Bibliothek ist der namespace math und bietet grundlegende mathematische Operationen für Trigonometrie und andere (math.sqrt, math.sqrt, math.abs, math.exp, math.log, math.log10 usw., siehe die Lua Dokumentation für Details).

Der Zugang zu Textvariablen ist mit dem memo_var Feld möglich. Textvariablen sind verfügbar als Attribute memo_var.

Beispiel 4: Iterieren über aktive Objekte

In Beispiel 1 wurde gezeigt, wie man alle Objekte einer Tabelle durchläuft. Das folgende Beispiel zeigt, wie Iterationen über Teilmengen einer Objekttabelle erfolgen, und zwar: 1. nur über die aktivierten Objekte und 2. nur über Objekte mit einem bestimmten ID.

Beispiel

Pfad: LUA_4_1.lua

local L = ""
local r
-- just looking for active receivers
for r in cna.tables.imm:all{active_only=true} do
    if r.LP1 > 70 then
        L= L .. r.LP1 .. " dB"
    end
end
cna.print("L > 70: " .. L)

Beispiel

Pfad: LUA_4_2.lua

local L = ""
local r
-- looking for actice rcvrs AND with ID=R_01
for r in cna.tables.imm:all{active_only=true, id="R_01"} do
    if r.LP1 > 70 then
        L = L .. r.LP1 .. " dB"
    end
end
cna.print("L > 70: " .. L)

Beispiel 5: Iteration über ein Gruppe im ObjectTree - Pattern Matching

Sie können auch eine Gruppe von Objekten durchlaufen (d.h. nicht die gesamte Objekttabelle. Dazu wird ein Muster eines gültigen Ausdrucks für eine ID-Zeichenkette definiert, die dann mit der Objekt-ID verglichen wird:

Beispiel

Pfad: LUA_5.lua

local L = ""
local r
for r in tables.imm:all<div id="!01!*"/> do
    if r.LP1 > 70 then
        L = L .. r.LP1 .. " dB"
    end
end
cna.print("L > 70: " .. L)

Die Ausdrücke für Muster entsprechen denjenigen, die auch in CadnaA Dialogen verwendet werden können.

Beispiel 6: Zugriff auf Polygone und Polygonpunkte

Verschiedene CadnaA-Objekte weisen Polygonpunkt-Daten auf, z.B. Häuser, Straßen, Hilfs-Polygone usw. CadnaA-Lua gestattet den Zugriff auf diese Polygone mit dem Attribut poly. Wie bei Objekten in CadnaA-Tabellen, ist mit CadnaA-Lua auch die Iteration über Polygonpunkte möglich.

Zudem stehen Befehle zur Vereinfachung der Geometrie (Stich) oder die Berechnung von Flächen und Längen zur Verfügung. Die folgende Funktion zeigt anhand eines Hilfspolygons auxpoly, wie Polygone behandelt werden. Die Datei start.cna enthält ein Polygon mit dem ID „aux1“.

Beispiel

Pfad: LUA_6_1.lua

local auxpoly = cna.tables.div:first_by_id("aux1")     
-- polygon data is available via `poly`
-- Shortcut: pp is declared to be a reference to auxpoly.poly

local pp = auxpoly.poly     
cna.print("Number of polygon vertices: " .. pp:num_vertices())

-- alternative way of putting it:
cna.print("Num. vertices with # operator: " .. #pp)  
cna.print("length 2d " .. pp:length2d())
cna.print("length 3d " .. pp:length3d())
cna.print("area 2d " .. pp:area2d())
cna.print("area 3d " .. pp:area3d())

-- Iteration over all polygon points
local p
for p in pp:all() do
    cna.print(p) -- print out point
    -- manually repeat what cna.print(p) does by
    -- accessing the point data itself
    cna.print(string.format("test [%f,%f,%f; G=%f]", p[1], p[2], p[3], p[4])) 
    -- or
    cna.print(string.format("test [%f,%f,%f; G=%f]", p.x, p.y, p.z, p.g)) 
end
{ignore-vars="true"}

Beispiel

Pfad: LUA_6_2.lua

-- apply 'simplify geometry':
local del_vertices = pp:simplify_geometry(1.0)
cna.print("Number of vertices removed" .. del_vertices)
cna.print("Num. vertices with # operator: " .. #pp)  
cna.print("length 2d " .. pp:length2d())
cna.print("length 3d " .. pp:length3d())
cna.print("area 2d " .. pp:area2d())
cna.print("area 3d " .. pp:area3d())

Beispiel 7: Polygonstücke einfügen und definieren

Der folgende Code erzeugt ein zusätzliches Hilfspolygon und fügt Polygonpunkte ein. Die Punkte sind auf einem Kreis mit einem Radius von 10 m um dem Ursprung (0, 0) angeordnet. Die zweite for-Schleife durchläuft die Polygonpunkte und fügt unter Verwendung eines 5 m Radius weitere Punkte ein. Das Ergebnis ist ein sternförmiges Hilfspolygon.

Beispiel

Pfad: LUA_7.lua

local auxpoly = cna.tables.Div:append()
auxpoly.ID = "test"

local N = 100
local i

-- Append N vertices
for i=1,N do
      -- append and set coordinate
 local p = auxpoly.poly:append() 
 p.x = 10*math.cos(i/N*2*math.pi)
 p.y = 10*math.sin(i/N*2*math.pi)
 p.z = 0
end

-- assertions help to write save code. If the 
-- expression within assert(...) evaluates to false
-- => Lua reports an error
assert(N == #auxpoly.poly)

i=0
for pp in auxpoly.poly:all() do
 i = i + 1
 local p = auxpoly.poly:insert_after(pp)
 p.x = 5*math.cos((i+1)/N*2*math.pi)
 p.y = 5*math.sin((i+1)/N*2*math.pi)
end

Beispiel 8: Lua-gesteuerte Berechnung

Dieses Beispiel soll zeigen, wie die Leistungsfähigkeit von Lua verwendet werden kann, jenseits einer bloßen Sammlung von Befehlen, neue, interessante Datenstrukturen aufzubauen.

Motivation: Es ist bekannt, dass bei einigen Lärm-Berechnungsverfahren an zwei Immissionspunkten, die nahe beieinander liegen, stark verschiedene Schalldruckpegel auftreten können. Der örtliche Pegel weist damit bei diesen Verfahren eine geringere örtliche Präzision als bei anderen Verfahren auf. Dazu soll die verfahrens-bedingte Streuung der Berechnungsergebnisse bei kleinen Änderungen eines Parameters (z.B. der Ortskoordinate x) anhand eines Lua-Skripts untersucht werden. Dazu soll der Immissionspegel an N Immissionspunkten berechnet werden, die sich alle innerhalb eines 1 m Radius um den Original-IP befinden. Aus diesen N Immissionspegeln soll der arithmetische Mittelwert μ und die Standardabweichung σ berechnet werden. Diese beiden Werte, die die Geräuschpegelverteilung kennzeichnen, werden durch die beiden folgenden Gleichungen beschrieben:

Da CadnaA-Lua diese Berechnungen durchführen soll, muss eine Lua-Funktion geschrieben werden:

Beispiel

Pfad: LUA_8.lua

-- mean and stddev of values t = {x1, x2, ...., x_(#t)}
local function calc_mean_stddev(t) 
  local s = 0 -- accumulator for sum
  local i
  for i=1,#t do  
    s = s + t[i]
    end
  local mean = s / #t

  -- calculate stddev
  local sum_sqr =0 -- accumulator for sum of squares
  for i=1,#t do
    sum_sqr = sum_sqr +  math.pow(t[i]-mean,2)
  end
  local stddev =  math.sqrt(1/(#t - 1) * sum_sqr)

  -- return two values 
  return mean, stddev
end

Zunächst wird ein bestimmter Immissionspunkt als Original verwendet. Dazu wird der Immissionspunkt mit der ID "i1" ausgewählt und dieser wird während der Ausführung des Skripts verschoben. Jetzt können wir einfach cna.tables.imm:append()verwenden, um einen neuen Immissionspunkt im Projekt anzulegen. Würde dies mehrfach ausgeführt, so würden ein IP nach dem anderen am Tabellenkopf eingefügt. Um dies zu vermeiden, wird der or Operator in Lua verwendet. In einem Ausdruck a or b, wird a ausgewertet, wenn das Ergebnis des Ausdrucks true ist. Wenn das Ergebnis hingegen false ist, wird der zweite Operand b ausgewertet. Folglich ergibt die folgende Zeile den Immissionspunkt mit dem ID "i1", wenn er existiert, oder es wird einen neuer Immissionspunkt zum Projekt mit der ID "i1" hinzugefügt.

local imm = cna.tables.imm:first_by_id("i1") or cna.tables.imm:append()
imm.ID = "i1"

Jetzt werden einige grundlegende Eigenschaften des Immissionspunktes gesetzt (dies ist ggf. in neuen Projekten zu ändern).

imm.X = 100
imm.Y = 100
imm.H = 4

Wir haben uns für 10 Immissionspunkte im Gebiet entschieden und führen jeweils 10 Immissionspunkt-Variationen an jedem dieser Orte aus. Die folgenden Variablen vereinfachen die spätere Änderung dieser Zahlen:

local N=10 -- Number of mean values 
local M=10 -- Number of values for calculating the mean

Die aktuelle x-Koordinate wird gespeichert, da diese für die Berechnung der x-Positionen der weiteren Immissionspunkte benötigt wird.

local x0 = imm.X

Jetzt wird über Positionen im großen Maßstab (10 Meter Entfernung) iteriert (dies ist die for-Schleife über i) und eine Array data erstellt, das die berechneten Lärmpegel enthalten soll. Dann wird über die lokalen Variationen iteriert (Schleife über j), eine neue x-Koordinate berechnet, die eine lokale Variation innerhalb eines 1 m Abstandes darstellt. Die Berechnung für den jeweiligen Immissionspunkt wird gestartet und der Lärmpegel in das Datenarray geschrieben. Nach dem Berechnen der lokalen Variationen werden die Mittelwerte und die Standardabweichungen der Mittelwerte berechnet und in das log-Fenster geschrieben.

for i=0,10*N-1,10 do
  local j
  local data = {}
  -- local variations within one meter
  for j = 1,M do
    imm.X = x0 - i + j*1/M
    cna.calc_all_imm()
    data[j] = imm.LP1  
  end 
  local x_c = x0 - i + 0.5
  local mean, stddev = calc_mean_stddev(data)
  cna.print(string.format("around %f; mean %f +/- %f",x_c, mean, stddev))
end
{ignore-vars="true"}

Dieses Beispiel könnte in einem einfachen Projekt auch auf Straßen angewandt werden, um - zum Beispiel - die Pegelunterschiede zwischen einer Berechnung nach RLS-90 und nach NMPB08 zu untersuchen.