Alte Version abgeändert auf neue Datebank /-Struktur).
This commit is contained in:
129
docs/MIGRATION_LOG.md
Normal file
129
docs/MIGRATION_LOG.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Migration zu neuer Datenbank-Struktur
|
||||
|
||||
**Datum:** 27. März 2026
|
||||
|
||||
## Ziel
|
||||
Migration von Multi-Sensor-DB (PM + THP + Radiation) zu fokussierter DB (nur Radiation + THP)
|
||||
|
||||
## Änderungen
|
||||
|
||||
### Entfernt:
|
||||
- ❌ PM (Feinstaub) Sensoren komplett
|
||||
- ❌ `problemsensors` Collection
|
||||
- ❌ `mapdata` Collection (ersetzt durch `properties.values`)
|
||||
- ❌ Separate `data_<sid>` Collections
|
||||
|
||||
### Neu:
|
||||
- ✅ Zentrale `radioactivity_sensors` Collection
|
||||
- ✅ Zentrale `thp_sensors` Collection
|
||||
- ✅ Aktuelle Werte in `properties.values`
|
||||
- ✅ Name als Array mit Historie
|
||||
- ✅ Vereinfachte Location-Struktur
|
||||
|
||||
### Beibehalten (auskommentiert):
|
||||
- 💤 AKW-Funktionalität (für spätere Verwendung)
|
||||
|
||||
## Bearbeitete Dateien
|
||||
|
||||
### ✅ Abgeschlossen
|
||||
- ✅ **routes/fsdata.js**
|
||||
- Radiation: `data_<sid>` → `radioactivity_sensors`
|
||||
- THP: `data_<sid>` → `thp_sensors`
|
||||
- properties.name als Array handhaben
|
||||
- PM-Funktionen auskommentiert (movAvgSDSWeek, calcMinMaxAvgSDS, isPM, getStatistics)
|
||||
|
||||
- ✅ **routes/utilities.js**
|
||||
- PM-Teile aus calcMovingAverage entfernt
|
||||
- Unterstützung für neue `values` Struktur hinzugefügt
|
||||
- `datetime.$date` Format-Handling
|
||||
|
||||
- ✅ **routes/apidata.js**
|
||||
- getAPIdata(): Type-basierte Collection-Auswahl (radioactivity_sensors/thp_sensors)
|
||||
- getAPIprops(): name Array, location.country statt address
|
||||
- getApiCities(): PM-Filter entfernt, type-basiert
|
||||
- PM-Funktionen auskommentiert (getAPIalldata, isPM)
|
||||
|
||||
- ✅ **routes/mapdata.js**
|
||||
- getaktdata(): properties Collection statt mapdata
|
||||
- properties.values für aktuelle Werte
|
||||
- location.loc statt location
|
||||
- indoor als Number (0/1)
|
||||
- getRegionSensors(): PM-Filter entfernt
|
||||
- AKW-Funktionen auskommentiert (getakwdata)
|
||||
|
||||
- ✅ **docs/MongoDB_Struktur.md**
|
||||
- THP-Struktur dokumentiert
|
||||
- Neue Collections beschrieben
|
||||
- Migrations-Anleitungen hinzugefügt
|
||||
|
||||
## Wichtige Änderungen im Detail
|
||||
|
||||
### 1. Collections
|
||||
- ❌ `data_<sid>` (tausende separate Collections)
|
||||
- ✅ `radioactivity_sensors` (eine zentrale Collection)
|
||||
- ✅ `thp_sensors` (eine zentrale Collection)
|
||||
- ❌ `mapdata` Collection
|
||||
- ✅ Aktuelle Werte jetzt in `properties.values`
|
||||
|
||||
### 2. Properties-Struktur
|
||||
- `name`: String → Array von {name, since}
|
||||
- `location.address`: Objekt → nur `location.country`: String
|
||||
- `indoor`: Boolean → Number (0/1)
|
||||
- Neu: `location.id`, `location.exact_loc`, `location.since`
|
||||
- Neu: `type: "radioactivity"` zur Typ-Identifikation
|
||||
- Neu: `values` Objekt mit aktuellen Messwerten
|
||||
|
||||
### 3. Zeitstempel
|
||||
- Alt: `datetime: Date`
|
||||
- Neu: `datetime: { $date: Date }`
|
||||
- Beide Formate werden jetzt unterstützt
|
||||
|
||||
### 4. Query-Änderungen
|
||||
- Sensor-ID-Filter: `sensorid: sid` statt Collection-Name
|
||||
- Zeitstempel-Filter: `'datetime.$date'` statt `datetime`
|
||||
- Messwerte-Zugriff: `values.counts_per_minute` statt `counts_per_minute`
|
||||
- Location-Zugriff: `location.loc` statt `location` (für GeoJSON)
|
||||
|
||||
### 5. Indizes (müssen erstellt werden!)
|
||||
```javascript
|
||||
db.radioactivity_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
|
||||
db.radioactivity_sensors.createIndex({ "datetime.$date": -1 });
|
||||
db.thp_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
|
||||
db.thp_sensors.createIndex({ "datetime.$date": -1 });
|
||||
db.properties.createIndex({ type: 1 });
|
||||
db.properties.createIndex({ "location.loc": "2dsphere" });
|
||||
```
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. **Datenbank-Verbindung**: Environment-Variablen für neue DB setzen
|
||||
2. **Indizes erstellen**: Obige Indizes in MongoDB anlegen
|
||||
3. **Testen**: Alle Funktionen durchTesten
|
||||
4. **AKW-Features**: Bei Bedarf auskommentierte AKW-Funktionen reaktivieren
|
||||
|
||||
## Bekannte Einschränkungen
|
||||
|
||||
- `othersensors` Array in properties wird möglicherweise nicht mehr gefüllt
|
||||
- Stadt-Namen (Stadt-basierte Queries) funktionieren möglicherweise nicht mehr
|
||||
- PM-bezogene API-Endpunkte geben Fehler zurück
|
||||
- AKW-Funktionalität ist auskommentiert
|
||||
|
||||
## Bugfixes nach Migration
|
||||
|
||||
### 27.03.2026 - Frontend Name-Handling
|
||||
**Problem:** `Uncaught TypeError: data.name.startsWith is not a function`
|
||||
- Frontend erwartete name als String, erhielt aber Array
|
||||
**Lösung:**
|
||||
- routes/fsdata.js: getSensorProperties konvertiert name-Array zu String für Frontend
|
||||
- public/js/global.js: holAddress auf neue location.country Struktur angepasst
|
||||
|
||||
### 27.03.2026 - MongoDB datetime.$date Query-Fehler
|
||||
**Problem:** `MongoServerError: FieldPath field names may not start with '$'`
|
||||
- MongoDB interpretiert `$date` in `'datetime.$date'` als Operator, nicht als Feldname
|
||||
**Lösung:**
|
||||
- Alle Queries auf direktes `datetime` Feld umgestellt (BSON Date)
|
||||
- Betroffene Funktionen:
|
||||
- routes/fsdata.js: readRadiationMovingAverage, readRadiationAverages, readClimateAverages
|
||||
- routes/apidata.js: getAPIdataSensor
|
||||
- utilities.js bleibt unverändert (behandelt beide Formate bei Datenverarbeitung)
|
||||
**Hinweis:** Datenbank muss datetime als BSON Date speichern, nicht als verschachteltes Objekt
|
||||
856
docs/MongoDB_Struktur.md
Normal file
856
docs/MongoDB_Struktur.md
Normal file
@@ -0,0 +1,856 @@
|
||||
# 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:
|
||||
```javascript
|
||||
{
|
||||
_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_<sid>** (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):**
|
||||
```javascript
|
||||
{
|
||||
datetime: Date, // Zeitstempel der Messung
|
||||
P1: Number, // PM10 Wert (Feinstaub < 10μm)
|
||||
P2: Number, // PM2.5 Wert (Feinstaub < 2.5μm)
|
||||
}
|
||||
```
|
||||
|
||||
**Klima-Sensoren (THP):**
|
||||
```javascript
|
||||
{
|
||||
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):**
|
||||
```javascript
|
||||
{
|
||||
datetime: Date, // Zeitstempel der Messung
|
||||
counts_per_minute: Number // CPM (Counts per Minute)
|
||||
}
|
||||
```
|
||||
|
||||
#### Verwendung:
|
||||
- Zeitbereich-Abfragen mit `datetime` Filter
|
||||
- Aggregation für Durchschnittswerte über Zeiträume
|
||||
- Sortiert nach `datetime`
|
||||
|
||||
---
|
||||
|
||||
### 3. **values**
|
||||
Vorberechnete/aggregierte Tageswerte für schnelleren API-Zugriff.
|
||||
|
||||
#### Struktur:
|
||||
```javascript
|
||||
{
|
||||
_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:
|
||||
```javascript
|
||||
{
|
||||
_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 `$geoWithin` für Karten-Bounds
|
||||
- Filter nach Sensor-Namen (z.B. `/Radiation/`)
|
||||
- Anzeige aktueller Werte auf der Karte
|
||||
|
||||
---
|
||||
|
||||
### 5. **problemsensors**
|
||||
Sensoren mit erkannten Problemen/Anomalien.
|
||||
|
||||
#### Struktur:
|
||||
```javascript
|
||||
{
|
||||
_id: Number, // Sensor-ID (0 = Metadaten-Eintrag mit Textbeschreibungen)
|
||||
problemNr: Number, // Problem-Typ-Nummer
|
||||
// ... weitere problem-spezifische Felder
|
||||
}
|
||||
```
|
||||
|
||||
#### Besonderheit:
|
||||
- `_id: 0` enthält Textbeschreibungen der Problem-Typen
|
||||
- Bulk-Updates mit `bulkWrite()` und `upsert: true`
|
||||
|
||||
---
|
||||
|
||||
### 6. **akws**
|
||||
Atomkraftwerke-Daten.
|
||||
|
||||
#### Struktur:
|
||||
```javascript
|
||||
{
|
||||
_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:
|
||||
```javascript
|
||||
{
|
||||
_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-Abfragen
|
||||
- `mapdata.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:
|
||||
```javascript
|
||||
db.collection('properties').findOne({_id: sensorId})
|
||||
```
|
||||
|
||||
### 2. Messdaten für Zeitraum:
|
||||
```javascript
|
||||
db.collection('data_' + sensorId).find({
|
||||
datetime: {
|
||||
$gte: new Date(start),
|
||||
$lt: new Date(end)
|
||||
}
|
||||
}).sort({datetime: 1}).toArray()
|
||||
```
|
||||
|
||||
### 3. Aggregierte Durchschnittswerte:
|
||||
```javascript
|
||||
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:
|
||||
```javascript
|
||||
db.collection('mapdata').find({
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$box: [[west, south], [east, north]]
|
||||
}
|
||||
},
|
||||
name: /Radiation/
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Sensoren innerhalb Polygon:
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
1. **Dynamische Collections**: Für jeden neuen Sensor wird automatisch eine `data_<sid>` Collection angelegt
|
||||
2. **Location-Array**: Das neueste Location-Objekt ist immer am Ende des Arrays (`location[location.length-1]`)
|
||||
3. **Zeitstempel**: Alle Zeitstempel sind als JavaScript Date-Objekte gespeichert
|
||||
4. **GeoJSON**: Koordinaten sind im Format `[longitude, latitude]` (nicht lat/lon!)
|
||||
5. **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:
|
||||
```javascript
|
||||
{
|
||||
_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:
|
||||
```javascript
|
||||
{
|
||||
_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:
|
||||
```javascript
|
||||
{
|
||||
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:
|
||||
```javascript
|
||||
{
|
||||
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:
|
||||
```javascript
|
||||
// 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:
|
||||
```javascript
|
||||
{
|
||||
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:
|
||||
```javascript
|
||||
{
|
||||
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:
|
||||
```javascript
|
||||
// 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:**
|
||||
```javascript
|
||||
name: "Radiation Si22G"
|
||||
```
|
||||
|
||||
**Neu:**
|
||||
```javascript
|
||||
name: [
|
||||
{
|
||||
name: "Radiation Si22G",
|
||||
since: { $date: "2024-06-20T10:13:12.970Z" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Zugriff auf aktuellen Namen:**
|
||||
```javascript
|
||||
const currentName = sensor.name[sensor.name.length - 1].name;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Location-Feld
|
||||
**Alt:**
|
||||
```javascript
|
||||
location: [
|
||||
{
|
||||
loc: { type: "Point", coordinates: [lon, lat] },
|
||||
altitude: 62,
|
||||
address: {
|
||||
country: "AU",
|
||||
plz: "2000",
|
||||
city: "Sydney"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Neu:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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
|
||||
```javascript
|
||||
db.collection('mapdata').findOne({_id: sensorId})
|
||||
```
|
||||
|
||||
**Neu:** Direkt in `properties.values`
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
if (sensor.indoor) { ... } // Boolean
|
||||
```
|
||||
|
||||
**Neu:**
|
||||
```javascript
|
||||
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:
|
||||
```javascript
|
||||
const cpm = (counts / sample_time_ms) * 60000;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migrations-Checkliste
|
||||
|
||||
### Code-Anpassungen erforderlich:
|
||||
|
||||
#### Datenbank-Zugriffe:
|
||||
- [ ] **Messdaten-Collection**: Von `db.collection('data_' + sid)` zu `db.collection('radioactivity_sensors')`
|
||||
- [ ] **Sensor-Filter**: Query um `sensorid: sid` erweitern (statt Collection-Name)
|
||||
- [ ] **Indizes**: `sensorid` und `datetime` Index auf `radioactivity_sensors` erstellen
|
||||
- [ ] **Zeitstempel-Feld**: `datetime.$date` statt nur `datetime` beim Lesen beachten
|
||||
|
||||
#### Metadaten-Zugriffe:
|
||||
- [ ] **Name-Zugriff**: Von `sensor.name` zu `sensor.name[sensor.name.length-1].name`
|
||||
- [ ] **Country-Zugriff**: Von `sensor.location[i].address.country` zu `sensor.location[i].country`
|
||||
- [ ] **Indoor-Check**: Von Boolean zu Number-Vergleich (`=== 1`)
|
||||
- [ ] **Aktuelle Werte**: Von `mapdata` Collection zu `properties.values`
|
||||
- [ ] **Address-Felder**: Entfernen von `plz`, `city`, `street` (nur noch `country`)
|
||||
|
||||
#### Neue Felder:
|
||||
- [ ] **Messwerte**: `values.counts_per_minute` statt direktem `counts_per_minute`
|
||||
- [ ] **Neue Metriken**: `hv_pulses`, `counts`, `sample_time_ms` verarbeiten
|
||||
- [ ] **Location-ID**: Neues Feld `location.id` berücksichtigen
|
||||
- [ ] **exact_loc**: Neues Feld für Positionsgenauigkeit
|
||||
- [ ] **Zeitstempel-Format**: `$date`-Wrapper bei MongoDB-Exporten beachten
|
||||
|
||||
#### Performance-Optimierungen:
|
||||
- [ ] **Compound Index**: `{sensorid: 1, datetime: 1}` auf `radioactivity_sensors`
|
||||
- [ ] **Projection**: Nur benötigte Felder aus `values` laden
|
||||
- [ ] **Query-Limits**: Pagination bei großen Zeiträumen über mehrere Sensoren
|
||||
|
||||
### Migrations-Beispiele:
|
||||
|
||||
#### Messdaten abrufen:
|
||||
|
||||
**Alt:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
// 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:
|
||||
|
||||
1. **Collection-Zugriff für Messdaten**
|
||||
- ❌ Alt: `db.collection('data_' + sid)`
|
||||
- ✅ Neu: `db.collection('radioactivity_sensors')` mit `{sensorid: sid}`
|
||||
|
||||
2. **Messwerte-Zugriff**
|
||||
- ❌ Alt: `doc.counts_per_minute`
|
||||
- ✅ Neu: `doc.values.counts_per_minute`
|
||||
|
||||
3. **Name-Zugriff**
|
||||
- ❌ Alt: `sensor.name`
|
||||
- ✅ Neu: `sensor.name[sensor.name.length-1].name`
|
||||
|
||||
4. **Adress-Zugriff**
|
||||
- ❌ Alt: `sensor.location[i].address.country`
|
||||
- ✅ Neu: `sensor.location[i].country`
|
||||
|
||||
### 🟡 Wichtig - Sollte beachtet werden:
|
||||
|
||||
5. **Indoor-Flag**: `indoor === 1` statt `indoor === true`
|
||||
6. **Zeitstempel**: `datetime.$date` statt `datetime` bei Exporten
|
||||
7. **Aktuelle Werte**: `properties.values` statt `mapdata` Collection
|
||||
8. **Index**: Compound Index auf `{sensorid: 1, "datetime.$date": 1}` essentiell!
|
||||
|
||||
### 🟢 Optional - Neue Features:
|
||||
|
||||
9. **Neue Metriken**: `hv_pulses`, `counts`, `sample_time_ms` verfügbar
|
||||
10. **Location-Historie**: `location[].since` zeigt Gültigkeit
|
||||
11. **Positionsgenauigkeit**: `exact_loc` (0=ungefähr, 1=exakt)
|
||||
12. **Location-ID**: `location[].id` für eindeutige Identifikation
|
||||
|
||||
---
|
||||
|
||||
**Stand der Dokumentation:** 27. März 2026
|
||||
45
docs/info.md
Normal file
45
docs/info.md
Normal file
@@ -0,0 +1,45 @@
|
||||
#Infos zur Karte
|
||||
|
||||
|
||||
###Allgemein
|
||||
Auf der Karte wird jede Zählstation mit dem Radioaktivitäts-Symbol angezeigt. Die Farbe des Symbols ändert sich mit der Zählrate, der Zusammenhang ist rechts oben in der Legende dargestellt.
|
||||
Hat der Sensor seit mind. 1 Stunde keine Daten mehr gesendet, so wird er dunkelgrau eingefärbt.
|
||||
Die Karte ist standardmäßig auf Stuttgart ausgerichtet. Wenn der Aufruf der Webseite mit einer Sensornummer erfolgt, so wird die Karte auf diesen Sensor zentriert (z.B. für den Sensor Nr. 34188 ist der Aufruf dann: <https://multigeiger.citysensor.de/34188>).
|
||||
|
||||
###Bedienelemente
|
||||
* Die Karte kann mit dem Mausrad oder den beiden Knöpfen **+/-** in der linken obere Ecke ein- und ausgezoomed werden.
|
||||
* Mit gedrückter Maustaste läßt sich die Karte verschieben.
|
||||
|
||||
Über der Karte befindet sich die Navigationsleiste mit folgenden Knöpfen/Eingabefeldern:
|
||||
|
||||
* **Zählrohre**
|
||||
Damit wird umgeschaltet, ob **alle** Sensoren oder nur die Sensoren, die ein **Si22G**-Zählrohr haben, angezeigt werden.
|
||||
* **Ort oder Sensornummer suchen**
|
||||
In dieses Eingabefeld kann ein Ort oder eine Sensornummer eingegeben werden. Die Karte wird dann darauf zentriert.
|
||||
* **Info**
|
||||
Es erscheint diese Info-Seite.
|
||||
|
||||
Ein Klick auf das Atom-Symbol bringt eine Info-Tafel zur Anzeige. Auf dieser Tafel stehen folgende Informationen:
|
||||
|
||||
* Sensor-Nummer
|
||||
* Typ des Zählrohres
|
||||
* Adresse des Sensors (falls in der Datenbank vorhanden)
|
||||
* Aktueller Messwert in cpm (Impulse pro Minute) und µSv/h (Micro-Sievert pro Stunde)
|
||||
* Link **Grafik anzeigen**
|
||||
|
||||
###Grafik
|
||||
Durch Klick auf **Grafik anzeigen** öffnet sich ein Fenster, das den Verlauf der letzten 24 Stunden anzeigt.
|
||||
Die angezeigten Werte sind jeweils der Mittelwert über 10min.
|
||||
Über die Knöpfe **-24h -12h +12h +24h** kann der Zeitstrahl um die jeweilige Anzahl an Stunden verschoben werden.
|
||||
Der Knopf **live** schaltet wieder auf den Live-Mode um, d.h. es werden wieder die Daten der letzten 24 Stunden angezeigt.
|
||||
Über den Knopf **7d** kann der Verlauf einer ganzen Woche betrachtet werden.
|
||||
Auch hier kann der Zeitstrahl verschoben werden, und zwar um 3 bzw. 7 Tage.
|
||||
Außerdem kann über den **Einstellung**-Knopf festgelegt werden, über welchen Zeitraum die Werte gemittelt werden
|
||||
und ob eine gleitende oder statische Mittelwertbildung durchgeführt weden soll.
|
||||
Über den Knopf **30d** wird die Darstellung der Tagesmittelwerte jeden Tages der letzten 30 Tage als Balken-Diagramm angezeigt.
|
||||
Durch Klick auf den Knopf **Ende** wird die Grafik verlassen.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user