Skip to content
Grafana Panels Docs

Maps

generateLeafletMap(id, config, useCases) rendert Leaflet-Karten mit Usecase-Layern, Popups, Marker-Icons, Custom Image Overlays und Positionsbearbeitung.

generateLeafletMap(id, config, useCases);
ParameterRequiredTypBeschreibung
idJastringID des HTML-Containers.
configJaobjectKartenkonfiguration.
useCasesJaobject[]Array mit Usecase-Konfigurationen und Grafana-Variablen.

Maps folgen dem gleichen Grundaufbau wie alle Custom Panels: Ein eindeutiger HTML-Container wird angelegt, danach werden config und useCases definiert und an generateLeafletMap() übergeben.

<div id="device_map"></div>
<script>
  var id = 'device_map';

  var config = {
    display: {
      showStreetMap: true,
      showInitLayer: 'Streets',
    },
  };

  var useCases = [
    {
      name: 'Gerät',
      icon: 'Hole_White',
      varGraf: '${mapdata_devices}',
    },
  ];

  generateLeafletMap(id, config, useCases);
</script>
PfadRequiredTypDefaultBeschreibung
general.worldCopyJumpNeinbooleanfalseAktiviert/deaktiviert Weltkartenumsprung bei Erreichen des Randes.
general.doubleClickZoomNeinbooleantrueAktiviert/deaktiviert Zoom bei Doppelklick.
general.zoomSnapNeinnumber1Schrittweite der Zoomstufen.
general.minZoomNeinnumber3Minimalzoom der Layers und Leaflet.
general.maxZoomNeinnumber22Maximalzoom der Layers und Leaflet.
general.useSimpleCrsNeinbooleanfalseNutzt L.CRS.Simple für XY-/Bildkarten statt geografischer CRS.
display.showStreetMapNeinbooleanfalseAktiviert Mapbox Street/Satellite/Combined Base-Layer.
display.showInitLayerNein'Streets' | 'Satellite' | 'Combined'StreetsInitialer Base-Layer, wenn display.showStreetMap aktiv ist.
display.showInitOverlayNein'all' | string | string[]-Initial sichtbare Usecase-Overlays.
display.showSimpleCustomImageNeinbooleanfalseZeigt ein nicht rotiertes Custom Image Overlay.
display.showRotatedCustomImageNeinbooleanfalseZeigt ein rotiertes Custom Image Overlay.
display.imageUrlBedingtstring-URL des Custom Image Overlays. Pflicht bei Custom Image Overlays.
rotation.isRotatedNeinbooleanfalseAktiviert Kartenrotation.
rotation.rotateBearingNeinnumber0Rotationswinkel von 0 bis 360 Grad.
rotation.customRotateImageBoundsBedingt{ topLeft: [number, number]; topRight: [number, number]; bottomLeft: [number, number] }-Drei Punkte für rotiertes Image Overlay.
bounds.hasFixedBoundsNeinbooleanfalseErzwingt feste Kartenbegrenzung.
bounds.fixedBoundsBedingt{ top: number; right: number; bottom: number; left: number }-Feste Bounds. Pflicht bei bounds.hasFixedBounds.
bounds.centerZoomNein{ center: [number, number]; zoom: number }{ center: [48.208, 16.372], zoom: 12 }Startposition und Zoom.
bounds.defaultPosNein[number, number][48.208, 16.372]Fallback-Position für Marker ohne Koordinaten.
bounds.customSimpleImageBoundsBedingt{ top: number; right: number; bottom: number; left: number }-Bounds für einfaches Image Overlay.
interaction.lockScrollWheelZoomNeinbooleanfalseScrollrad-Zoom erst bei Kartenfokus.
interaction.showCoorOnClickNeinbooleanfalseZeigt Koordinaten bei Kartenklick.
interaction.allowEditPositionNeinbooleanfalseMarker per Drag oder Eingabe verschiebbar.
interaction.hideMarkerPositionContainerNeinbooleanfalseVerbirgt Positions-Eingaben.
interaction.serialInputNeinbooleantrueZeigt Seriennummer-Feld in Positionssteuerung.
marker.ignoreAppColorNeinbooleanfalseIgnoriert app_color und nutzt Defaultfarbe.
marker.ignoreAppBlinkingNeinbooleanfalseIgnoriert app_blinking.
marker.showDetailsInPopupNeinbooleantrueZeigt Details-Link im Popup.
marker.sortUsecasesNeinbooleanfalseSortiert Usecases alphabetisch, sofern forcePosition nicht greift.
marker.extraHtmlNein'fieldtester' | 'tracking' | 'beaconTracker' | falsefalseExtraHTML-Variante für Legenden oder Beacon/Tracker-Control.
marker.extraHtmlConfigNeinobject{}Zusatzkonfiguration für marker.extraHtml: 'beaconTracker'.
marker.forceLayerControlNein'default' | 'show' | 'hide'defaultSteuert, ob die Layer-Control aufgeklappt ist.
marker.allowSpiderfyNeinbooleanfalseAktiviert OverlappingMarkerSpiderfier.
marker.showAllMarkerAtDefaultPosNeinbooleanfalseSetzt fehlende Markerpositionen auf bounds.defaultPos.
other.extraZoomOutNeinbooleanfalseZoomt beim Fitten der Bounds eine Stufe heraus.
other.showAllMarkerInboundNeinbooleanfalsePasst die View initial an alle Marker an.

marker.extraHtml wählt eine optionale Zusatzanzeige. fieldtester und tracking rendern eine Legende unten links. beaconTracker rendert zusätzliche Beacon- und Tracker-Layer sowie ein Bedienfeld rechts unten.

// Keine ExtraHTML-Box
marker: {
  extraHtml: false,
};

// Fieldtester-Legende
marker: {
  extraHtml: 'fieldtester',
};

// Tracking-Legende
marker: {
  extraHtml: 'tracking',
};

// Beacon/Tracker-Control
marker: {
  extraHtml: 'beaconTracker',
  extraHtmlConfig: {
    beaconsVarGraf: '${mapdata_beacon}',
    trackersVarGraf: '${mapdata_tracker}',
    parentDeviceId: '${deviceid_parent}',
    autoUpdate: false,
  },
};

marker.extraHtmlConfig wird aktuell nur von marker.extraHtml: 'beaconTracker' ausgewertet.

PfadRequiredTypDefaultBeschreibung
marker.extraHtmlConfig.beaconsVarGrafJastring''Grafana-Variable mit Beacon-Daten.
marker.extraHtmlConfig.trackersVarGrafJastring''Grafana-Variable mit Tracker-Daten.
marker.extraHtmlConfig.parentDeviceIdNeinstring''Device-ID zum Speichern des aktuellen Base-Layers, falls vorhanden.
marker.extraHtmlConfig.beaconLayerNameNeinstringBeaconsLayer-Name für Beacon-Marker.
marker.extraHtmlConfig.trackerLayerNameNeinstringTrackerLayer-Name für Tracker-Marker.
marker.extraHtmlConfig.beaconIconNeinstringBLE-BeaconMarker-Icon für Beacons.
marker.extraHtmlConfig.trackerIconNeinstringTrackingMarker-Icon für Tracker.
marker.extraHtmlConfig.roundDecimalsNeinnumber3Rundung der gespeicherten XY-Positionen.
marker.extraHtmlConfig.trackerOffsetNeinnumber2900Abstand der Tracker-Marker um den zugeordneten Beacon.
marker.extraHtmlConfig.showInitOverlayNeinbooleantrueZeigt Beacon- und Tracker-Layer initial an.
marker.extraHtmlConfig.autoUpdateNeinbooleanfalseAktiviert automatische Karten-Neuladung.
marker.extraHtmlConfig.followGrafanaRefreshNeinbooleantrueNutzt das Grafana-Refresh-Intervall, wenn intervalMs leer ist.
marker.extraHtmlConfig.intervalMsNeinnumbernullFestes Auto-Update-Intervall in Millisekunden.

Das Legacy-Datenformat wird nur für dieses ExtraHTML geparst.

Beacon-Daten:

deviceid|serial|posy|posx|color|beacon_id|§

Tracker-Daten:

mapAttribute€$€color€$€serial

Das mapAttribute wird über § zerlegt. Pro Segment wird der erste Pipe-Wert als Beacon-ID interpretiert und case-insensitive gegen beacon_id gematcht.

PfadRequiredTypDefaultBeschreibung
useCases[].varGrafJastring-Grafana-Variable mit Mapdaten, getrennt durch §.
useCases[].nameNeinstringGerätUsecase-Name für Layer und Legende.
useCases[].iconNeinstringHole_WhiteMarker-Icon vom Fileserver.
useCases[].showIconInLegendNeinbooleanfalseZeigt Icon in Layer-Legende.
useCases[].forcePositionNeinnumber-Erzwingt Sortierposition in der Legende.
useCases[].popupNein(el) => string-Custom HTML für Marker-Popup.
useCases[].structureNeinstring[]-Benennung zusätzlicher Werte nach den Basisattributen.
useCases[].polygonNeinboolean-Rendert Polyline-Daten statt Marker.
useCases[].options.circleNein(el) => false | LeafletPathOptions & { radius: string | number }-Kreis um Marker. Muss radius in Metern liefern.
useCases[].options.hideMarkerNeinboolean | (el) => boolean-Unterdrückt Marker je Sensor.
useCases[].options.multiplePositionsNein(el) => Array<{ lat: string | number; lng: string | number }>-Zusätzliche Markerpositionen für denselben Sensor.
useCases[].options.multipleDevicesNeinobject-Parst mehrere Devices aus einem $-getrennten Wert.
useCases[].options.multipleDevices.structureJastring[]-Struktur eines einzelnen $-Chunks. Muss Lat/Lng enthalten.
useCases[].options.multipleDevices.keyJastring | ((el) => string)-Datenquelle für den $-getrennten Multiple-Devices-String.

Die Grafana-Variable in useCases[].varGraf muss die Basisattribute in dieser Reihenfolge liefern. Jeder Datensatz endet mit §.

SELECT CONCAT(
  deviceid,
  '|',
  serial,
  '|',
  attributes.value('(/attributes/attribute[@name="latitude"])[1]', 'VARCHAR(21)'),
  '|',
  attributes.value('(/attributes/attribute[@name="longitude"])[1]', 'VARCHAR(21)'),
  '|',
  devicetype,
  '|',
  attributes.value('(/attributes/attribute[@name="last_timestamp"])[1]', 'VARCHAR(23)'),
  'Z',
  '|',
  status,
  '|',
  attributes.value('(/attributes/attribute[@name="offline_status"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="app_color"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="app_blinking"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="is_gps"])[1]', 'FLOAT'),
  '§'
)
FROM RI.Devices
WHERE deviceId = '$deviceid'

Vor dem § können weitere Custom Attributes ausgegeben werden. Diese müssen in useCases[].structure in der gleichen Reihenfolge definiert werden.

SELECT CONCAT(
  deviceid,
  '|',
  serial,
  '|',
  attributes.value('(/attributes/attribute[@name="latitude"])[1]', 'VARCHAR(21)'),
  '|',
  attributes.value('(/attributes/attribute[@name="longitude"])[1]', 'VARCHAR(21)'),
  '|',
  devicetype,
  '|',
  attributes.value('(/attributes/attribute[@name="last_timestamp"])[1]', 'VARCHAR(23)'),
  'Z',
  '|',
  status,
  '|',
  attributes.value('(/attributes/attribute[@name="offline_status"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="app_color"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="app_blinking"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="is_gps"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="installation_place"])[1]', 'VARCHAR(MAX)'),
  '|',
  attributes.value('(/attributes/attribute[@name="humidity_threshold_low"])[1]', 'FLOAT'),
  '|',
  attributes.value('(/attributes/attribute[@name="humidity_threshold_high"])[1]', 'FLOAT'),
  '§'
)
FROM RI.Devices
WHERE deviceId = '$deviceid'
var useCases = [
  {
    icon: 'Current_Metering',
    name: 'Stromzähler PV',
    structure: ['installation_place', 'humidity_threshold_low', 'humidity_threshold_high'],
    popup: (el) => {
      return (
        '<div>Schwellwert Low: ' +
        el.data.humidity_threshold_low +
        '</div><br><div>Schwellwert High: ' +
        el.data.humidity_threshold_high +
        '</div>'
      );
    },
    varGraf: '${mapdata_devices}',
  },
];

Die Runtime erwartet die Basisattribute in dieser Reihenfolge:

  1. deviceid
  2. serial
  3. posY
  4. posX
  5. devicetype
  6. timestamp
  7. status
  8. offline_status
  9. app_color
  10. app_blinking
  11. isGPS

posY ist Latitude, posX ist Longitude. Das SQL-Attribut is_gps wird in der Runtime als isGPS bereitgestellt.

useCases[].popup ist eine Funktion mit el als Parameter. Seriennummer und timestamp werden im Standard-Popup immer angezeigt; darunter wird der HTML-String aus popup(el) angehängt.

Auf diese Keys kann direkt zugegriffen werden:

KeyBeschreibung
el.deviceidDevice-ID vom Marker.
el.serialIOTA-Serial.
el.posYLatitude.
el.posXLongitude.
el.devicetypeIOTA-Devicetype-ID.
el.timestampTimestamp im ISO-Format.
el.status0/1 für disabled/enabled.
el.offline_statusIOTA-Attribut offline_status.
el.app_colorIOTA-Attribut app_color.
el.app_blinkingIOTA-Attribut app_blinking.
el.isGPSIOTA-Attribut is_gps.
el.data.*Alle in useCases[].structure definierten Zusatzattribute.
popup: (el) => {
  return 'Seriennummer: ' + el.serial;
};
structure: ['installation_place'],
popup: (el) => {
  return 'Installationsort: ' + el.data.installation_place;
};
structure: ['installation_place', 'humidity_threshold_low', 'humidity_threshold_high'],
popup: (el) => {
  return (
    '<div>Schwellwert Low: ' +
    el.data.humidity_threshold_low +
    '</div><br><div>Schwellwert High: ' +
    el.data.humidity_threshold_high +
    '</div>'
  );
};
structure: ['input', 'output'],
popup: (el) => {
  var returnString = '';
  if (el.data.input != undefined) {
    returnString += ifEmpty(el.data.input, el.data.input == '1.0' ? 'Eingang: Ein' : 'Eingang: Aus');
  }
  if (el.data.output != undefined) {
    returnString += returnString != '' ? '<br>' : '';
    returnString += ifEmpty(el.data.output, el.data.output == '1.0' ? 'Ausgang: Ein' : 'Ausgang: Aus');
  }
  return returnString != '' ? returnString : 'keine Daten';
};

useCases[].options.circle(el) funktioniert wie popup(el). Die Funktion muss entweder false oder ein Objekt mit radius in Metern zurückgeben. Zusätzlich können Leaflet Path Options wie color, fillColor oder fillOpacity gesetzt werden.

options: {
  circle: (el) => {
    return {
      radius: '20',
    };
  },
};
structure: ['radius'],
options: {
  circle: (el) => {
    return {
      radius: el.data.radius,
      fillOpacity: 0.1,
    };
  },
};
structure: ['tracker_status', 'ble0_distance'],
options: {
  circle: (el) => {
    if (el.data.tracker_status != 'ble') return false;
    return {
      radius: el.data.ble0_distance,
      color: getAppColorHex(el.app_color),
      fillColor: getAppColorHex(el.app_color),
    };
  },
};
var mapConfigMinimal = {
  display: {
    showStreetMap: true,
    showInitLayer: 'Streets',
  },
};

var useCasesMinimal = [
  {
    varGraf: '${minimal_data}',
  },
];

generateLeafletMap('device_map', mapConfigMinimal, useCasesMinimal);
var mapConfigSatellite = {
  general: {
    worldCopyJump: true,
    doubleClickZoom: false,
    zoomSnap: 0.5,
  },
  display: {
    showStreetMap: true,
    showInitLayer: 'Satellite',
    showInitOverlay: ['UseCase A', 'UseCase B'],
  },
  interaction: {
    showCoorOnClick: true,
  },
  marker: {
    showDetailsInPopup: true,
  },
  other: {
    showAllMarkerInbound: true,
  },
};

var useCasesSatellite = [
  { name: 'UseCase A', icon: 'Current_Metering', varGraf: '${usecase_A_data}' },
  { name: 'UseCase B', icon: 'Temperature', varGraf: '${usecase_B_data}' },
];
var mapConfigFixedBounds = {
  bounds: {
    hasFixedBounds: true,
    fixedBounds: { top: 48.3, right: 16.6, bottom: 48.1, left: 16.1 },
    customSimpleImageBounds: { top: 48.3, right: 16.6, bottom: 48.1, left: 16.1 },
  },
  display: {
    showSimpleCustomImage: true,
    imageUrl: 'https://example.com/custom_image.png',
  },
};

var useCasesFixedBounds = [
  {
    varGraf: '${fixed_bounds_data}',
  },
];
var mapConfigRotatedImage = {
  rotation: {
    isRotated: true,
    rotateBearing: 45,
    customRotateImageBounds: {
      topLeft: [48.3, 16.2],
      topRight: [48.3, 16.5],
      bottomLeft: [48.1, 16.2],
    },
  },
  display: {
    showRotatedCustomImage: true,
    imageUrl: 'https://example.com/rotated_image.png',
  },
};

var useCasesRotatedImage = [
  {
    varGraf: '${rotated_image_data}',
  },
];
var mapConfigInteractive = {
  interaction: {
    allowEditPosition: true,
    serialInput: true,
    lockScrollWheelZoom: true,
  },
  marker: {
    allowSpiderfy: true,
    extraHtml: 'fieldtester',
  },
  other: {
    extraZoomOut: true,
    showAllMarkerInbound: true,
  },
};

var useCasesInteractive = [
  {
    name: 'Temperature_Humidity',
    icon: 'Temperature_Humidity',
    popup: (el) => `Seriennummer: ${el.serial}`,
    varGraf: '${interactive_data}',
  },
];
<div id="device_map" style="width: 100%; height: 100%"></div>

<script>
  var generateMap = function () {
    var id = 'device_map';

    var config = {
      general: {
        useSimpleCrs: true,
        doubleClickZoom: false,
        zoomSnap: 1,
        minZoom: -30,
        maxZoom: 18,
      },
      display: {
        showSimpleCustomImage: true,
        imageUrl: 'https://sens-fileserver.xaas-a0a0.cloud/sens/PoC/Rectangle_SENS.svg',
      },
      bounds: {
        centerZoom: { center: [50000, 80000], zoom: -8 },
        customSimpleImageBounds: {
          top: 100000,
          right: 100000,
          bottom: 0,
          left: 0,
        },
      },
      interaction: {
        lockScrollWheelZoom: true,
      },
      marker: {
        extraHtml: 'beaconTracker',
        forceLayerControl: 'show',
        extraHtmlConfig: {
          beaconsVarGraf: '${mapdata_beacon}',
          trackersVarGraf: '${mapdata_tracker}',
          parentDeviceId: '${deviceid_parent}',
          autoUpdate: false,
        },
      },
    };

    var useCases = [];

    generateLeafletMap(id, config, useCases);
  };

  generateMap();
</script>
var useCases = [
  {
    name: 'Tracking',
    icon: 'Tracking',
    structure: ['lat_2', 'lng_2'],
    options: {
      multiplePositions: (el) => [
        {
          lat: el.data.lat_2,
          lng: el.data.lng_2,
        },
      ],
    },
    varGraf: '${mapdata_devices}',
  },
];
var useCases = [
  {
    name: 'Gateways',
    icon: 'Gateway',
    structure: ['gatewayData'],
    options: {
      multipleDevices: {
        structure: ['serial', 'latitude', 'longitude'],
        key: (el) => el.data.gatewayData,
      },
    },
    varGraf: '${mapdata_gateways}',
  },
];
var ifEmpty = (value, string) => {
  if (value != '' && typeof value != 'undefined') {
    return string;
  }
};

app_blinking sollte eine Zahl sein.

WertBedeutung
1slow
2fast
3medium
0 oder Defaultkein Blinken

app_color sollte eine Zahl aus dem gemeinsamen app_color Farbkatalog sein. Die Tabelle mit Namen und Hex-Werten steht in den Grundlagen unter Icons & Farben.

Die Markerfarbe wird so bestimmt:

var determineIconColor = (data, ignoreAppColor) => {
  if (!data.status) return 'Blue';
  if (data.offline_status === 1) return 'Gray';
  if (data.timestamp === '0Z' || data.timestamp === 'Z') return 'Gray';
  if (!ignoreAppColor) {
    return getAppColorName(data.app_color);
  }
  return getAppColorName();
};

Die verfügbaren Map-Marker-Icons stehen in den Grundlagen unter Icons & Farben.