24 KiB
MongoDB Datenbankstruktur - Geiger_WEB_26
Übersicht
Datenbankname: allsensors (aus MONGOBASE Environment-Variable)
Die Datenbank enthält Messdaten von Strahlungs-, Feinstaub- und Klimasensoren.
Collections
1. properties
Speichert die Eigenschaften und Metadaten aller Sensoren.
Struktur:
{
_id: Number, // Sensor-ID (z.B. 140, 1234)
name: String, // Sensor-Typ/Name
// Beispiele:
// - Feinstaub: "SDS011", "PMS7003", "PMS3003", "PMS5003", "HPM", "SDS021", "PPD42NS"
// - Strahlung: "Radiation <Type>" (z.B. "Radiation SBM-20", "Radiation Si22G")
// - Klima: andere Typen für Temp/Hum/Press
date_since: Date, // Startdatum des Sensors
last_seen: Date, // Letzter Kontakt mit dem Sensor
location: [ // Array mit Standort-Historie (neuester = letzter Eintrag)
{
loc: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude] // [lon, lat] Format!
},
altitude: Number, // Höhe über NN in Metern
address: { // Adressinformationen
country: String, // Ländercode (z.B. "DE", "AT")
plz: String, // Postleitzahl
city: String, // Stadt
street: String // Straße (optional)
}
}
],
indoor: Boolean, // true = Innenraum-Sensor
othersensors: [ // Zugehörige Sensoren (für Multi-Sensor-Geräte)
{
id: Number, // oder direkt: Number (ältere Einträge)
name: String
}
]
}
Verwendung:
- Abruf per Sensor-ID:
db.collection('properties').findOne({_id: sensorId}) - Geo-Abfragen für Karten-Bounds
- Filtern nach Sensor-Typ (PM, Radiation, etc.)
2. data_ (Dynamische Collections)
Für jeden Sensor existiert eine eigene Collection mit seinen Messdaten.
Format: data_140, data_1234, etc.
Struktur (abhängig vom Sensor-Typ):
Feinstaub-Sensoren (PM):
{
datetime: Date, // Zeitstempel der Messung
P1: Number, // PM10 Wert (Feinstaub < 10μm)
P2: Number, // PM2.5 Wert (Feinstaub < 2.5μm)
}
Klima-Sensoren (THP):
{
datetime: Date, // Zeitstempel der Messung
temperature: Number, // Temperatur in °C
humidity: Number, // Luftfeuchtigkeit in %
pressure: Number, // Luftdruck in Pa
pressure_at_sealevel: Number // Normierter Luftdruck auf Meereshöhe
}
Strahlungs-Sensoren (Radiation):
{
datetime: Date, // Zeitstempel der Messung
counts_per_minute: Number // CPM (Counts per Minute)
}
Verwendung:
- Zeitbereich-Abfragen mit
datetimeFilter - Aggregation für Durchschnittswerte über Zeiträume
- Sortiert nach
datetime
3. values
Vorberechnete/aggregierte Tageswerte für schnelleren API-Zugriff.
Struktur:
{
_id: String, // Format: "<sid>_YYYYMMDD" (z.B. "140_20180602")
values: [ // Array mit Messwerten des Tages
{
datetime: Date,
P1: Number, // oder andere Sensor-spezifische Felder
P2: Number,
// ... weitere Felder je nach Sensor-Typ
}
]
}
Verwendung:
- Schneller Zugriff auf Tagesdaten
- Mehrere Tage werden durch mehrere findOne-Calls abgerufen
4. mapdata
Aktuelle Sensordaten für die Karten-Visualisierung.
Struktur:
{
_id: Number, // Sensor-ID
name: String, // Sensor-Name (z.B. "Radiation SBM-20")
location: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude]
},
indoor: Boolean, // Innen-/Außensensor
values: { // Aktuellster Messwert
datetime: Date,
counts_per_minute: Number, // oder P1, P2, temperature, etc.
// ... je nach Sensor-Typ
}
}
Verwendung:
- Geo-Abfragen mit
$geoWithinfür Karten-Bounds - Filter nach Sensor-Namen (z.B.
/Radiation/) - Anzeige aktueller Werte auf der Karte
5. problemsensors
Sensoren mit erkannten Problemen/Anomalien.
Struktur:
{
_id: Number, // Sensor-ID (0 = Metadaten-Eintrag mit Textbeschreibungen)
problemNr: Number, // Problem-Typ-Nummer
// ... weitere problem-spezifische Felder
}
Besonderheit:
_id: 0enthält Textbeschreibungen der Problem-Typen- Bulk-Updates mit
bulkWrite()undupsert: true
6. akws
Atomkraftwerke-Daten.
Struktur:
{
_id: Mixed,
Name: String, // Name des AKW
lat: Number, // Breitengrad
lon: Number, // Längengrad
Status: String, // "aktiv" oder Stillgelegt-Status
Baujahr: String, // Baujahr
Stillgeleg: String, // Stilllegungsjahr (falls zutreffend)
Wiki_Link: String // Wikipedia-Link
}
7. th1_akws
Erweiterte AKW-Daten (vermutlich aus Wikidata).
Struktur:
{
_id: Mixed,
name: String, // Name
geo: String, // Format: "POINT(lon lat)"
types: String, // "Nuclear power plant" oder andere
item: String, // Wikidata/Wikipedia-Link
itemServiceentry: String, // Inbetriebnahme-Datum
itemServiceretirement: String // Stilllegungs-Datum
}
Indizes und Performance
GeoJSON-Indizes:
properties.location.0.loc- 2dsphere Index für Geo-Abfragenmapdata.location- 2dsphere Index für Kartenansicht
Zeitstempel-Indizes:
data_<sid>.datetime- Index für schnelle Zeitbereich-Abfragen- Sortierung erfolgt meistens nach
datetime
Wichtige Queries
1. Sensor-Eigenschaften abrufen:
db.collection('properties').findOne({_id: sensorId})
2. Messdaten für Zeitraum:
db.collection('data_' + sensorId).find({
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
}).sort({datetime: 1}).toArray()
3. Aggregierte Durchschnittswerte:
db.collection('data_' + sensorId).aggregate([
{$sort: {datetime: 1}},
{$match: {datetime: {$gte: startDate, $lt: endDate}}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, zeitinterval]}
]
}
},
cpmAvg: {$avg: '$counts_per_minute'},
count: {$sum: 1}
}
},
{$sort: {_id: 1}}
])
4. Geo-Abfrage für Karten-Bereich:
db.collection('mapdata').find({
location: {
$geoWithin: {
$box: [[west, south], [east, north]]
}
},
name: /Radiation/
})
5. Sensoren innerhalb Polygon:
db.collection('properties').find({
'location.0.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [polygonCoords]
}
}
}
})
Sensor-Typen
Feinstaub (PM):
- SDS011, PMS7003, PMS3003, PMS5003, HPM, SDS021, PPD42NS
- Felder:
P1(PM10),P2(PM2.5)
Strahlung (Radiation):
- Typen: SBM-20, SBM-19, Si22G
- Feld:
counts_per_minute(CPM) - Umrechnungsfaktoren in µSv/h:
- SBM-20: 1/2.47/60
- SBM-19: 1/9.81888/60
- Si22G: 0.081438/60
Klima (THP):
- Felder:
temperature,humidity,pressure - Druckberechnung auf Meereshöhe mit Formel aus BMP180 Datenblatt
Verbindung
const MONGO_URL = 'mongodb://rexfueAdmin:D6grTasE56@207.180.224.98:20019/?authSource=admin'
const MONGOBASE = 'allsensors'
MongoClient.connect(MONGO_URL, {useNewUrlParser: true, useUnifiedTopology: true})
.then(client => {
const db = client.db(MONGOBASE);
// ... Verwendung der Datenbank
})
Notizen
- Dynamische Collections: Für jeden neuen Sensor wird automatisch eine
data_<sid>Collection angelegt - Location-Array: Das neueste Location-Objekt ist immer am Ende des Arrays (
location[location.length-1]) - Zeitstempel: Alle Zeitstempel sind als JavaScript Date-Objekte gespeichert
- GeoJSON: Koordinaten sind im Format
[longitude, latitude](nicht lat/lon!) - Offline-Sensoren:
-
2 Stunden offline:
cpm = -1 -
7 Tage offline:
cpm = -2 - Aktiv: tatsächlicher CPM-Wert
-
NEUE DATENBANKSTRUKTUR (Geplant für zukünftige Verwendung)
Übersicht
Die neue MongoDB-Struktur fokussiert sich ausschließlich auf Geiger-Sensoren (Radioaktivität) und vereinfacht einige Aspekte der Datenorganisation.
Hauptunterschiede zur aktuellen Struktur
| Aspekt | Alte Struktur | Neue Struktur |
|---|---|---|
| Sensor-Typen | Multi-Sensor (PM, THP, Radiation) | Nur Radioaktivität |
| Messdaten-Collections | Separate data_<sid> pro Sensor |
Eine zentrale radioactivity_sensors |
| Sensor-Name | name: String |
name: Array mit Historie |
| Adresse | Detailliert im location.address |
Nur country im location |
| Aktuelle Werte | Separate mapdata Collection |
Direkt in properties.values |
| Indoor-Flag | indoor: Boolean |
indoor: Number (0/1) |
| Location-ID | Nicht vorhanden | location.id |
| Zeitstempel-Feld | datetime in data_<sid> |
datetime.$date in radioactivity_sensors |
| Sensor-Identifikation | Collection-Name (data_<sid>) |
Feld sensorid |
| Zusätzliche Felder | - | hv_pulses, counts, sample_time_ms |
Collections (Neue Struktur)
1. properties (Neue Struktur)
Enthält sowohl Metadaten als auch aktuelle Messwerte der Sensoren.
Struktur:
{
_id: Number, // Sensor-ID
type: String, // Sensor-Typ: "radioactivity"
name: [ // Name-Historie als Array
{
name: String, // z.B. "Radiation Si22G", "Radiation SBM-20"
since: { // Gültig seit
$date: Date
}
}
],
location: [ // Location-Historie als Array
{
loc: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude]
},
id: Number, // Location-ID (neu)
altitude: Number, // Höhe über NN in Metern
since: { // Gültig seit
$date: Date
},
exact_loc: Number, // 0 = ungefähr, 1 = exakt
indoor: Number, // 0 = outdoor, 1 = indoor
country: String // ISO Ländercode (z.B. "AU", "DE")
}
],
values: { // Aktuellste Messwerte (neu in properties!)
counts_per_minute: Number, // CPM (Counts per Minute)
hv_pulses: Number, // Hochspannungs-Pulse
counts: Number, // Gesamt-Counts in der Messperiode
sample_time_ms: Number, // Messzeit in Millisekunden
timestamp: { // Zeitstempel der Messung
$date: Date
}
}
}
Beispiel:
{
_id: 12345,
type: "radioactivity",
name: [
{
name: "Radiation Si22G",
since: { $date: "2024-06-20T10:13:12.970Z" }
}
],
location: [
{
loc: {
type: "Point",
coordinates: [151.032, -33.792]
},
id: 70638,
altitude: 62,
since: { $date: "2024-06-20T10:13:12.970Z" },
exact_loc: 0,
indoor: 0,
country: "AU"
}
],
values: {
counts_per_minute: 61,
hv_pulses: 44,
counts: 154,
sample_time_ms: 151052,
timestamp: { $date: "2026-03-27T13:18:15.000Z" }
}
}
2. radioactivity_sensors (Zentrale Messdaten-Collection)
WICHTIG: Anders als in der alten Struktur gibt es keine separaten data_<sid> Collections mehr!
Alle Sensordaten werden in einer einzigen Collection radioactivity_sensors gespeichert.
Struktur:
{
datetime: { // Zeitstempel der Messung
$date: Date
},
sensorid: Number, // Sensor-ID (Referenz zu properties._id)
values: { // Messwerte
counts_per_minute: Number, // CPM (Counts per Minute)
hv_pulses: Number, // Hochspannungs-Pulse
counts: Number, // Gesamt-Counts in der Messperiode
sample_time_ms: Number // Messzeit in Millisekunden
}
}
Beispiel:
{
datetime: { $date: "2024-06-20T10:56:55.000Z" },
sensorid: 74797,
values: {
counts_per_minute: 46,
hv_pulses: 823,
counts: 114,
sample_time_ms: 146826
}
}
Verwendung:
// Alle Daten für einen Sensor abrufen
db.collection('radioactivity_sensors').find({
sensorid: 74797,
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray()
// Aggregation für Durchschnittswerte
db.collection('radioactivity_sensors').aggregate([
{ $match: { sensorid: 74797, datetime: { $gte: startDate, $lt: endDate } } },
{ $sort: { datetime: 1 } },
{
$group: {
_id: {
$toDate: {
$subtract: [
{ $toLong: '$datetime' },
{ $mod: [{ $toLong: '$datetime' }, zeitinterval] }
]
}
},
cpmAvg: { $avg: '$values.counts_per_minute' },
countsAvg: { $avg: '$values.counts' },
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
])
Vorteile gegenüber alten data_<sid> Collections:
✅ Zentralisiert: Nur eine Collection statt tausende
✅ Einfachere Wartung und Backups
✅ Sensor-übergreifende Queries möglich
✅ Index auf sensorid für schnelle Filterung
Nachteile:
⚠️ Größere Collection → Indizes wichtiger
⚠️ sensorid muss bei jeder Query gefiltert werden
3. thp_sensors (Zentrale Klima-Messdaten-Collection)
Analog zu radioactivity_sensors werden alle Klima-Sensordaten (Temperatur, Luftfeuchtigkeit, Luftdruck) in einer einzigen Collection gespeichert.
Struktur:
{
datetime: { // Zeitstempel der Messung
$date: Date
},
sensorid: Number, // Sensor-ID (Referenz zu properties._id)
values: { // Messwerte
temperature: Number, // Temperatur in °C
pressure: Number, // Luftdruck in Pa
humidity: Number, // Luftfeuchtigkeit in %
pressure_at_sealevel: Number // Normierter Luftdruck auf Meereshöhe in Pa
}
}
Beispiel:
{
datetime: { $date: "2024-06-20T11:02:08.000Z" },
sensorid: 550,
values: {
temperature: 28.42,
pressure: 98326.75,
humidity: 47.15,
pressure_at_sealevel: 101559.71
}
}
Verwendung:
// Alle Daten für einen THP-Sensor abrufen
db.collection('thp_sensors').find({
sensorid: 550,
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray()
// Aggregation für Durchschnittswerte
db.collection('thp_sensors').aggregate([
{ $match: { sensorid: 550, datetime: { $gte: startDate, $lt: endDate } } },
{ $sort: { datetime: 1 } },
{
$group: {
_id: {
$toDate: {
$subtract: [
{ $toLong: '$datetime' },
{ $mod: [{ $toLong: '$datetime' }, zeitinterval] }
]
}
},
tempAvg: { $avg: '$values.temperature' },
humiAvg: { $avg: '$values.humidity' },
pressAvg: { $avg: '$values.pressure_at_sealevel' },
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
])
Wichtige Änderungen für die Migration
1. Name-Feld
Alt:
name: "Radiation Si22G"
Neu:
name: [
{
name: "Radiation Si22G",
since: { $date: "2024-06-20T10:13:12.970Z" }
}
]
Zugriff auf aktuellen Namen:
const currentName = sensor.name[sensor.name.length - 1].name;
2. Location-Feld
Alt:
location: [
{
loc: { type: "Point", coordinates: [lon, lat] },
altitude: 62,
address: {
country: "AU",
plz: "2000",
city: "Sydney"
}
}
]
Neu:
location: [
{
loc: { type: "Point", coordinates: [lon, lat] },
id: 70638,
altitude: 62,
since: { $date: "2024-06-20T10:13:12.970Z" },
exact_loc: 0,
indoor: 0,
country: "AU"
}
]
Zugriff auf aktuelle Location:
const currentLoc = sensor.location[sensor.location.length - 1];
const country = currentLoc.country; // Nicht mehr currentLoc.address.country!
const isIndoor = currentLoc.indoor === 1; // Nicht mehr Boolean!
3. Aktuelle Werte
Alt: Separate mapdata Collection
db.collection('mapdata').findOne({_id: sensorId})
Neu: Direkt in properties.values
const sensor = db.collection('properties').findOne({_id: sensorId});
const currentValue = sensor.values.counts_per_minute;
const lastUpdate = sensor.values.timestamp.$date;
4. Indoor-Flag
Alt:
if (sensor.indoor) { ... } // Boolean
Neu:
if (sensor.location[sensor.location.length - 1].indoor === 1) { ... } // Number!
Neue Messwerte-Felder
hv_pulses (Hochspannungs-Pulse)
Anzahl der Hochspannungspulse, die zur Ansteuerung des Geiger-Müller-Zählrohrs verwendet wurden.
counts (Gesamt-Counts)
Gesamtzahl der registrierten Impulse während der Messperiode.
sample_time_ms (Messzeit)
Dauer der Messperiode in Millisekunden. Wichtig für genaue CPM-Berechnungen:
const cpm = (counts / sample_time_ms) * 60000;
Migrations-Checkliste
Code-Anpassungen erforderlich:
Datenbank-Zugriffe:
- Messdaten-Collection: Von
db.collection('data_' + sid)zudb.collection('radioactivity_sensors') - Sensor-Filter: Query um
sensorid: siderweitern (statt Collection-Name) - Indizes:
sensoridunddatetimeIndex aufradioactivity_sensorserstellen - Zeitstempel-Feld:
datetime.$datestatt nurdatetimebeim Lesen beachten
Metadaten-Zugriffe:
- Name-Zugriff: Von
sensor.namezusensor.name[sensor.name.length-1].name - Country-Zugriff: Von
sensor.location[i].address.countryzusensor.location[i].country - Indoor-Check: Von Boolean zu Number-Vergleich (
=== 1) - Aktuelle Werte: Von
mapdataCollection zuproperties.values - Address-Felder: Entfernen von
plz,city,street(nur nochcountry)
Neue Felder:
- Messwerte:
values.counts_per_minutestatt direktemcounts_per_minute - Neue Metriken:
hv_pulses,counts,sample_time_msverarbeiten - Location-ID: Neues Feld
location.idberücksichtigen - exact_loc: Neues Feld für Positionsgenauigkeit
- Zeitstempel-Format:
$date-Wrapper bei MongoDB-Exporten beachten
Performance-Optimierungen:
- Compound Index:
{sensorid: 1, datetime: 1}aufradioactivity_sensors - Projection: Nur benötigte Felder aus
valuesladen - Query-Limits: Pagination bei großen Zeiträumen über mehrere Sensoren
Migrations-Beispiele:
Messdaten abrufen:
Alt:
const collection = db.collection('data_' + sid);
const docs = await collection.find({
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray();
Neu:
const collection = db.collection('radioactivity_sensors');
const docs = await collection.find({
sensorid: sid,
'datetime.$date': { $gte: new Date(start), $lt: new Date(end) }
}).sort({ 'datetime.$date': 1 }).toArray();
// Werte-Zugriff:
const cpm = docs[0].values.counts_per_minute; // Nicht docs[0].counts_per_minute!
Aggregation:
Alt:
const collection = db.collection('data_' + sid);
const docs = await collection.aggregate([
{ $match: { datetime: { $gte: start, $lt: end } } },
{ $group: {
_id: ...,
cpmAvg: { $avg: '$counts_per_minute' }
}}
]).toArray();
Neu:
const collection = db.collection('radioactivity_sensors');
const docs = await collection.aggregate([
{ $match: {
sensorid: sid,
'datetime.$date': { $gte: start, $lt: end }
}},
{ $group: {
_id: ...,
cpmAvg: { $avg: '$values.counts_per_minute' } // Path in values!
}}
]).toArray();
Vorteile der neuen Struktur:
✅ Einfacher: Keine separate mapdata Collection nötig
✅ Historisch: Name- und Location-Änderungen werden nachvollziehbar
✅ Fokussiert: Nur Radioaktivitätssensoren, keine Multi-Sensor-Logik
✅ Zusätzliche Metriken: Mehr technische Details (HV-Pulse, Sample-Time)
✅ Zentralisiert: Alle Messdaten in einer Collection statt tausenden
✅ Sensor-übergreifend: Queries über mehrere Sensoren gleichzeitig möglich
✅ Wartungsfreundlich: Keine dynamischen Collection-Namen mehr
Nachteile / Herausforderungen:
⚠️ Weniger Details: Keine vollständigen Adressen (Stadt, PLZ, Straße)
⚠️ Breaking Changes: Viele Code-Stellen müssen angepasst werden
⚠️ Boolean → Number: Weniger typsicher für indoor und exact_loc
⚠️ Indizes kritisch: Ohne richtigen Index auf sensorid + datetime langsam
⚠️ Größere Collection: Mehr Daten in einer Collection → Monitoring wichtig
⚠️ Nested Values: Alle Messwerte in values → längere Accesspfade
Empfohlene Indizes für neue Struktur:
// Essenziell für radioactivity_sensors
db.radioactivity_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
db.radioactivity_sensors.createIndex({ "datetime.$date": -1 }); // Für neueste Werte
// Für properties
db.properties.createIndex({ type: 1 });
db.properties.createIndex({ "location.country": 1 });
db.properties.createIndex({ "location.loc": "2dsphere" }); // Geo-Queries
db.properties.createIndex({ "values.timestamp.$date": -1 }); // Aktualitäts-Check
Zusammenfassung: Kritischste Änderungen
🔴 Kritisch - Muss angepasst werden:
-
Collection-Zugriff für Messdaten
- ❌ Alt:
db.collection('data_' + sid) - ✅ Neu:
db.collection('radioactivity_sensors')mit{sensorid: sid}
- ❌ Alt:
-
Messwerte-Zugriff
- ❌ Alt:
doc.counts_per_minute - ✅ Neu:
doc.values.counts_per_minute
- ❌ Alt:
-
Name-Zugriff
- ❌ Alt:
sensor.name - ✅ Neu:
sensor.name[sensor.name.length-1].name
- ❌ Alt:
-
Adress-Zugriff
- ❌ Alt:
sensor.location[i].address.country - ✅ Neu:
sensor.location[i].country
- ❌ Alt:
🟡 Wichtig - Sollte beachtet werden:
- Indoor-Flag:
indoor === 1stattindoor === true - Zeitstempel:
datetime.$datestattdatetimebei Exporten - Aktuelle Werte:
properties.valuesstattmapdataCollection - Index: Compound Index auf
{sensorid: 1, "datetime.$date": 1}essentiell!
🟢 Optional - Neue Features:
- Neue Metriken:
hv_pulses,counts,sample_time_msverfügbar - Location-Historie:
location[].sincezeigt Gültigkeit - Positionsgenauigkeit:
exact_loc(0=ungefähr, 1=exakt) - Location-ID:
location[].idfür eindeutige Identifikation
Stand der Dokumentation: 27. März 2026