Processwire - Leaflet Map Marker Modul

Aus Wikizone
Wechseln zu: Navigation, Suche
Todo: 
Artikel überarbeiten (Beispiel Magnetic Music mit überarbeitetem Modul)
Neuer Modulentwickler -> testen ob die neue Version gut funktioniert oder lieber eigene 
Optionen für Karte was - wo? Evtl. viel über das Hauptskript MarkupLeafletMap.js einstellen statt zu viel über $options. Dokumentieren was wo. Evtl. Karteneinstellungen über js und Moduleinstellungen über options. Andererseits Einstellmöglichkeiten die man Steuern möchte über $options.

Siehe auch[Bearbeiten]

https://processwire.com/talk/topic/9745-module-leaflet-map/?page=5 (Forum)
https://github.com/gmclelland/FieldtypeLeafletMapMarker/blob/PW3/InputfieldLeafletMapMarker.module (Fork)
https://leafletjs.com/ (Leaflet Hauptseite)
https://leaflet-extras.github.io/leaflet-providers/preview/index.html (Karten Provider)
https://github.com/Leaflet/Leaflet.markercluster (Doku für Cluster Modul)
https://github.com/leaflet-extras/leaflet-providers

Überblick[Bearbeiten]

Wird aus Zeitgründen vom Entwickler nicht sehr aktiv weitergepflegt. Allerdings gibt es ordentlichen Support im Forum und ein paar Forks auf Github.

Man kann für spezielle Anforderungen auch nur das Feld im Backend nutzen und Leaflet im Frontend selbst aufsetzen bzw. eigene Skripte nutzen. Das JavaScript für die Frontendausgabe im Modul findet man in:

MarkupLeafletMap.js (JavaScript für Frontend-Ausgabe)
MarkupLeafletMap.module (Inline JS für Frontend ) 

Quickstart[Bearbeiten]

  • Benötigt die Konfiguration von $additionalHeaderData. Das ist einfach ein String der in _init.php angelegt und im Header eingebunden wird. Damit lassen sich Zusätzliche Daten per Template rendern.
  • Feldname im Backend ist hier map_leaflet
  • Achtung Bug (V2.8.1): Immer den Geocoder auf der Seite benutzen. Die Standarddaten aus der Feldkonfiguration werden nicht richtig übernommen (Adressfeld) und es gibt Ausgabefehler.

Beispiel

$map = wire('modules')->get('MarkupLeafletMap');
$additionalHeaderData = $map->getLeafletMapHeaderLines();
$mapMarkup = $map->render($page, 'map_leaflet ,array('markerColour' => 'green'));

$content .= '
  <div class="content_bottom">
    <!-- Button GM -->
    <div class="gmroute" style="z-index: 30001;position: relative;height: 24px;right: 20px;top: 40px;">
      <a class="gmroute-link" href="https://www.google.de/maps/dir//Lange+Str.+9+D-72829+Engstingen" target="_blank" style="float: right;">Route planen</a>
    </div>
    <!-- Map -->
    <div class="col span_12">'.$mapMarkup.'</div>
  </div>';

Quickstart und wichtige Dateien[Bearbeiten]

Todo -> für aktualisierung von leaflet notwendige Änderungen dokumentieren.

Quickstart[Bearbeiten]

Head Tag - Skript Snippets für Ausgabe[Bearbeiten]

Wichtigste Skripte Stand 6/2024

<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/MarkerCluster.Custom.css" />

<!-- Scripts supporting the use of Leaflet.js -->
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/MarkupLeafletMapCustom.js"></script>

<!-- Leaflet Extensions (Plugins) -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/css/leaflet.awesome-markers.css" />
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/js/leaflet.awesome-markers.min.js"></script>

<link rel="stylesheet" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.css" type="text/css">
<script src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.js"></script>

Custom Mainskript für viele Optionen[Bearbeiten]

Skript funktioniert mit den aktuellen Settings von oben. Für GestureHandling, Awesome Markers, Multiple Markers, Clustering, Fit to Bounds.

MarkupLeafletMapCustom.js

/**
 * ProcessWire Leaflet Map Markup (JS)
 *
 * Renders maps for the FieldtypeLeafletMapMarker module
 *
 * Javascript Usage:
 * =================
 * var map = new jsMarkupLeafletMap();
 * map.setOption('any-leaflet-maps-option', 'value');
 * map.setOption('zoom', 12); // example
 *
 * // init(container ID, latitude, longitude):
 * map.init('#map-div', 26.0936823, -77.5332796);
 *
 * // addMarker(latitude, longitude, optional URL, optional URL to icon file):
 * map.addMarker(26.0936823, -77.5332796, 'en.wikipedia.org/wiki/Castaway_Cay', '');
 * map.addMarker(...you may have as many of these as you want...);
 *
 * // optionally fit the map to the bounds of the markers you added
 * map.fitToMarkers();
 *
 */

function jsMarkupLeafletMap() {

    this.map = null;
    this.markers = [];
    this.numMarkers = 0;

    this.options = {
        zoom: 10,
        center: null,
        scrollWheelZoom: true,
    };

    this._currentURL = '';
    this.init = function(mapID, lat, lng, provider) {
        if(lat != 0) this.map = L.map(mapID, {
            center: [lat, lng], 
            zoom: this.options.zoom, 
            scrollWheelZoom: this.options.scrollWheelZoom,
            gestureHandling: true,
            // touchZoom:false,
            tap:false
        } );
        L.tileLayer.provider(provider).addTo(this.map);
    }

    this.setOption = function(key, value) {
        this.options[key] = value;
    }

    var markers = new L.MarkerClusterGroup({polygonOptions: {color: 'teal', weight: 1, opacity: .39, lineJoin: 'round'}});
    var marker = '';

    this.addMarker = function(lat, lng, url, title, extra) {
        if(lat == 0.0) return;

        var latLng = L.latLng(lat, lng);

        var markerOptions = {
            linkURL: '',
            title: title
        };

        marker = L.marker(latLng, markerOptions);
        markers.addLayer(marker);
        this.map.addLayer(markers);
        this.markers[this.numMarkers] = marker;
        this.numMarkers++;

        if(url.length > 0) {
            marker.linkURL = url;
            if (extra.length > 0) {
                extra = '<br />' + extra;
            }
            marker.bindPopup("<b><a href='" + marker.linkURL + "'>" + title + "</a></b>" + extra);
        }
    }


    this.addMarkerIcon = function(icon, lat, lng, url, title, extra) {
        if(lat == 0.0) return;

        var latLng = L.latLng(lat, lng);

        marker = L.marker(latLng, {icon: icon, linkURL: '', title: title});
        markers.addLayer(marker);
        this.map.addLayer(markers);
        this.markers[this.numMarkers] = marker;
        this.numMarkers++;

        if(url.length > 0) {
            marker.linkURL = url;

            if (extra.length > 0) {
                extra = '<br />' + extra;
            }
            marker.bindPopup("<b><a href='" + marker.linkURL + "'>" + title + "</a></b>" + extra);
        }
    }


    this.fitToMarkers = function() {
        var map = this.map;
        var mg = [];
        for(var i = 0; i < this.numMarkers; i++) {
            mg.push(this.markers[i].getLatLng());
        }

        map.fitBounds(mg);
    }


    this.setMaxZoom = function() {
        var map = this.map;
        map.setZoom(this.options['zoom']);
    }
}

Wo finde ich was[Bearbeiten]

Karte einbinden und wichtige Modul-Optionen[Bearbeiten]

Template Datei (z.B. my-map.php)

JavaScript für Kartendarstellung und Verarbeitung generell.[Bearbeiten]

Wird über dein Templateskript eingebunden.

site/modules/FieldtypeLeafletMapMarker/MarkupLeafletMap.js (oder eigenes). Hier kann man auch eigene JavaScript Funktionen definieren.

JavaScript Inline Script[Bearbeiten]

Erstellt die Instanz deiner Karte und verarbeitet Marker (z.T. mit Funktionen aus der MarkupLeafletMap.js).

Gesture Handling[Bearbeiten]

<link rel="stylesheet" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.css" type="text/css">
<script src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.js"></script>

Daten manuell auslesen und verarbeiten[Bearbeiten]

Einfaches Beispiel das auch bei AJAX Seiten funktioniert und einen Button zur Google Maps Wegbeschreibung enthält.

// If AJAX put this in _init.php else uncomment next two lines
// $map = wire('modules')->get('MarkupLeafletMap');
// $additionalHeaderData = $map->getLeafletMapHeaderLines();

$myAdress = $page->map_leaflet->address;	// outputs the address you entered
$myLat = $page->map_leaflet->lat; 		// outputs the latitude
$myLng = $page->map_leaflet->lng; 		// outputs the longitude
$myZoom = $page->map_leaflet->zoom;		// outputs the zoom level

$mapMarkup = "
<div id='mleafletmap1'style='height:400px;'></div>
<script>
var mleafletmap1 = new jsMarkupLeafletMap();
 mleafletmap1.setOption('zoom', $myZoom);
  mleafletmap1.init('mleafletmap1', $myLat, $myLng, 'OpenStreetMap.Mapnik');
  var default_marker_icon = L.AwesomeMarkers.icon({ icon: 'home', iconColor: 'white', prefix: 'fa', markerColor: 'darkblue' });
  mleafletmap1.addMarkerIcon(default_marker_icon, $myLat, $myLng, '/kontakt/', 'Kontakt', '');
</script>
";

$content .= '
<div class="row">
  <div class="gmroute" style="z-index: 30001;position: relative;height: 24px;right: 20px;top: 40px;">
    <a class="gmroute-link" href="https://www.google.de/maps/dir//Meine+Str.+9+D-72829+Engstingen" target="_blank" style="float: right;">Route planen</a>
  </div>
  <div class="col-sm-12" style="height:400px;">'
    .$mapMarkup.'
  </div>
</div>

Geocoder[Bearbeiten]

Das Modul verwendet an zwei Stellen einen Geocoder.

Am wichtigsten ist er wenn man ein Feld definiert hat. In der Karte gibt es eine kleine Lupe mit der man nach Adressen suchen kann. Dies wird realisiert über die JavaScript Datei:

Control.Geocoder.js

Sie enthält Code für verschiedene Geocoding Dienste. Standard ist Nominatim (von OpenStreetMap). Eventuell muss man diesen Code ab und zu anpassen, wenn sich die APIs der Anbieter ändern.

Die zweite Stelle ist die Eingabe der Default Adresse, wenn man ein Feld definiert. Er befindet sich in der Datei

LeafletMapMarker.js

Hier wird eine Klasse LeafletMapMarker definiert, die eine Funktion geocode() besitzt. Diese nutzt den Nominatim (OpenStreetMap) Dienst. Die Funktion läßt sich geschickt für eigene Zwecke missbrauchen. Man könnte evtl. beim Speichern einer Seite über einen Hook eine Geocodierung vornehmen wenn man eine Seite hat die bereits Adressfelder hat.

Marker[Bearbeiten]

Woher kommen die Marker Popup-Daten?[Bearbeiten]

Generell kommt das Inline Script, dass für die Optionen und Marker der Karte zuständig ist aus MarkupLeafletMap.module. In den Optionen wird angegeben woher (aus welchem Feld) die Inhalte für das Marker Popup kommen sollen:

Titel und URL kommen über die Optionen markerLinkField und markerTitleField

$url   = $options['markerLinkField']  ? $page->get($options['markerLinkField'])  : '';
$title = $options['markerTitleField'] ? $page->get($options['markerTitleField']) : '';

Der Inhalt selbst kommt über eine Callback-Funktion die man z.b.so definieren kann:

$options = array(
    'popupFormatter' => function($page) {
        $out[] = "<strong>Contact: $page->phone_number</strong>";
        $out[] = "<img src=\"{$page->image->url}\" width=\"100\" height=\"100\" />"; // ** NB: Use escaped double quotes if HTML attributes needed **
        return implode('<br/>', $out);
    }
);

Erweiterung für Orte auf Referenzen[Bearbeiten]

Beispiele[Bearbeiten]

Siehe Webmynet Cloud für aktuelles Beispiel cloud.webmynet.de (Developer/ProcessWire/Module)

Multiple Markers[Bearbeiten]

Die Render Funktion kann auch mit mehreren Seitenobjekten umgehen und erstellt dann eine Karte mit mehreren Markern.

$items = $pages->find("A SELECTOR THAT GETS YOUR PAGES WITH MARKER FIELDS");
echo $map->render($items, 'YOUR MARKER FIELD');

Multiple Markers von selektierten Seiten[Bearbeiten]

Es sollen Orte (LeafletMapMarker) von Unterseiten geholt werden und aus diesen eine Map mit mehreren Markern generiert werden. Viele Marker auf engem Raum werden geclustert.

Voraussetzungen im Beispiel

  • die Kindseiten werden über ihr template selektiert. Im Beispiel wird das durch ein Textfeld im Parent Template festgelegt (Feldname template_title)
  • der Leaflet code wird nur bei Bedarf geladen. Das geschieht über eine Switch Anweisung im Repeater
  • Leaflet Dateien sind lokal hinterlegt (siehe js und css Anweisungen).

mytemplate.php

$additionalHeaderData .= '
<!-- Styles supporting the use of Leaflet.js -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->templates.'vendors/leaflet/leaflet.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->templates.'styles/MarkerCluster.Custom.css" />

<!-- Scripts supporting the use of Leaflet.js -->
<script type="text/javascript" src="'.$config->urls->templates.'vendors/leaflet/leaflet.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/leaflet.markercluster.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/MarkupLeafletMap.js"></script>

<!-- Extend Leaflet with Awesome.Markers -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers/leaflet.awesome-markers.css" />
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers/leaflet.awesome-markers.min.js"></script>
';

$map = wire('modules')->get('MarkupLeafletMap');
$options = array('markerColour' => 'green');
$mySelector = 'template=event';
$places = $pages->find($mySelector);
$mapMarkup = $map->render($places, 'location' ,$options);

Leaflet Skripte lokal einbinden (keine externen Skripte laden)[Bearbeiten]

Dies sind die benötigten Header Zeilen. leaflet.css und leaflet.js sollte dann entsprechend vorhanden sein. Der Rest kommat aus dem Modul Verzeichnis.

Hinweis: Font Awesome muß ebenfalls vorhanden sein (Bisher - September 2018, die 4er Version).

$additionalHeaderData = '
<!-- Styles supporting the use of Leaflet.js -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->templates.'vendors/leaflet/leaflet.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->templates.'styles/MarkerCluster.Custom.css" />

<!-- Scripts supporting the use of Leaflet.js -->
<script type="text/javascript" src="'.$config->urls->templates.'vendors/leaflet/leaflet.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/leaflet.markercluster.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/MarkupLeafletMap.js"></script>

<!-- Extend Leaflet with Awesome.Markers -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers/leaflet.awesome-markers.css" />
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers/leaflet.awesome-markers.min.js"></script>
';

Scroll Zoom abschalten[Bearbeiten]

Am einfachsten im Modul (Nachteil: Vorsicht beim Update)

Evtl. fürs Frontend könnte man auch im Nachhinein in einem eigenen Skript abschalten über die Funktion:

map.scrollWheelZoom.disable(); // map ist die Mapinstanz. Muß evtl. angepasst werden.

Im Frontend (Fieldtype)[Bearbeiten]

/site/modules/FieldtypeLeafletMapMarker/MarkupLeafletMap.js

In MarkupLeafletMap.js die Option hinzufügen.

this.options = {
        zoom: 10,
        center: null,
        scrollWheelZoom: false, // diese option einfügen
    };

    this._currentURL = '';
    this.init = function(mapID, lat, lng, provider) {
        if(lat != 0) this.map = L.map(mapID, {
          center: [lat, lng],
          zoom: this.options.zoom,
          scrollWheelZoom: this.options.scrollWheelZoom // und dieses wenn nicht vorhanden
        } );
        L.tileLayer.provider(provider).addTo(this.map);
    }

Im Backend[Bearbeiten]

Hier muß mann den Code des Inputfield anpassen. Direkt in den Optionen hat das bei mir nicht funktioniert. Sieht so aus als ob nur der zoom statt alle Optionen gesetzt wird. Daher einfach nach der Instanzerzeugung der karte den ScrollWheelZoom abschalten:

FieldtypeLeafletMapMarker/InputfieldLeafletMapMarker.js

var map = L.map(document.getElementById(mapId)). setView([lat, lng], options.zoom); // nach dem hier
map.scrollWheelZoom.disable(); // dieses einfügen.

Modul Gesture Handling[Bearbeiten]

Leaflet - Gesture Handling
https://elmarquis.github.io/Leaflet.GestureHandling/
Bildet das Verhalten von Google Maps nach:

Desktop

  • Karte Ignoriert Mausrad
  • User wird Informiert mit ctrl+scrool Karte zoomen

Mobile

  • Karte ignoriert Ein Finger Drag
  • User wird über 2 Finger Pan informiert

In ProcessWire einbauen[Bearbeiten]

  • Unterordner 'dist' in 'Leaflet.GestureHandling' umbenennen und im FieldtypeLeafletMapMarker Modul unter assets ablegen
  • anpassen des JS für die Frontendausgabe
/site/modules/FieldtypeLeafletMapMarker/MarkupLeafletMap.js
  • Init Funktion anpassen:
    this.init = function(mapID, lat, lng, provider) {
        if(lat != 0) this.map = L.map(mapID, {
          center: [lat, lng],
          zoom: this.options.zoom,
          scrollWheelZoom: this.options.scrollWheelZoom,
          gestureHandling: true
        } );
        L.tileLayer.provider(provider).addTo(this.map);
    }
  • Einbinden von CSS und JS
<!-- Extend Leaflet with Gesture Handling -->
<link rel="stylesheet" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling/leaflet-gesture-handling.min.css" type="text/css">
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling/leaflet-gesture-handling.min.js"></script>

Allgemein[Bearbeiten]

<div id='map'></div>

<script>
	var cities = new L.LayerGroup();

	L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.').addTo(cities),
	L.marker([39.74, -104.99]).bindPopup('This is Denver, CO.').addTo(cities),
	L.marker([39.73, -104.8]).bindPopup('This is Aurora, CO.').addTo(cities),
	L.marker([39.77, -105.23]).bindPopup('This is Golden, CO.').addTo(cities);


	var mbAttr = 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
			'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
			'Imagery © <a href="http://mapbox.com">Mapbox</a>',
		mbUrl = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw';

	var grayscale   = L.tileLayer(mbUrl, {id: 'mapbox.light', attribution: mbAttr}),
		streets  = L.tileLayer(mbUrl, {id: 'mapbox.streets',   attribution: mbAttr});

	var map = L.map('map', {
		center: [39.73, -104.99],
		zoom: 10,
		layers: [grayscale, cities]
	});

	var baseLayers = {
		"Grayscale": grayscale,
		"Streets": streets
	};

	var overlays = {
		"Cities": cities
	};

	L.control.layers(baseLayers, overlays).addTo(map);
</script>

Popup über URL Parameter öffnen[Bearbeiten]

https://stackoverflow.com/questions/29004617/open-leaflet-marker-using-url-parameter-not-working-now-that-markercluster-is-us

Komplettes Beispiel (Veranstaltungen)[Bearbeiten]

Basisbeispiel

$out = '';

$today = strtotime(date('Y-m-d'));

page()->additionalHeaderData .= '
<!-- Styles supporting the use of Leaflet.js -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/MarkerCluster.Custom.css" />

<!-- Scripts supporting the use of Leaflet.js -->
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/MarkupLeafletMapCustom.js"></script>

<!-- Leaflet Extensions (Plugins) -->
<link rel="stylesheet" type="text/css" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/css/leaflet.awesome-markers.css" />
<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/js/leaflet.awesome-markers.min.js"></script>

<link rel="stylesheet" href="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.css" type="text/css">
<script src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.js"></script>
';

// MAP
$map = wire('modules')->get('MarkupLeafletMap');
$options = array(
  'scrollWheelZoom' => 'false',
  'provider' => 'OPNVKarte',
	'markerColour' => 'green',
	'height' => '460',
  'markerLinkField' => 'url',
  'markerTitleField' => 'title',
  'popupFormatter' => function($page) {
        $out = "<div class=\"popup-body\">{$page->body}</div>";
        //$out[] = "<img src=\"{$page->image->url}\" width=\"100\" height=\"100\" />"; // ** NB: Use escaped double quotes if HTML attributes needed **
        return $out;
    }
);

$selectorParts = array();
$selectorParts[] = "template=event,date_begin>=$today";
// Artistfilter
//bd($page->pr_artist);
if($page->pr_artist){
  $selectorParts[] = 'pr_artist='.$page->pr_artist;
}

$mySelector = implode(',',$selectorParts);
//bd($mySelector);
$events = $pages->find($mySelector);
$mapEvents = new WireArray; 
foreach($events as $key => $event){
  //bd($event->pr_venue->place,'venue');
  if($event->pr_venue->place->lat && $event->pr_venue->place->lng){
    // create a temporary virtual event WireArray to collect all nessecary information
    // Note that WireArray is compatible to PageArray and WireData ist compatible to Page
    $tempEvent = new WireData(); 
    //$tempEvent->template = $templates->get('dummy-event');
    // Hinzufügen von Feldern und Werten
    $tempEvent->set('title', $event->pr_artist->title.' / '.$event->title);
    $tempEvent->set('url', $event->url);
    $tempEvent->set('body', $event->date_begin.'<br>'.$event->pr_venue->title);
    $tempEvent->set('place', $event->pr_venue->place);
    $mapEvents->add($tempEvent);
    //bd($tempEvent,'tempEvent');
  }
}

//bd($mapEvents,'mapEvents');

$mapMarkup = $map->render($mapEvents, 'place' ,$options);
// Dont't forget to include font awesome if not present

$out .= $mapMarkup;

Komplette Veranstaltungssuche mit gekapselten Funktionen[Bearbeiten]

Aufwändiges Beispiel mit Suche, Kartenausgabe und Listenausgabe.

Ausgabetemplate (Field) events_combined.php

<?php namespace ProcessWire;

include_once(wire()->config->paths->templates . 'includes/event-helpers.php');

$out = '';
$mapMarkup = '';
$listMarkup = '';
$filterMarkup = '';

// GET EVENTS
// todo -> input data from filters
$events = getEvents($page); // all future events
$filteredEvents = null;

// FILTER EVENTS IF NEEDED
if ($page->opt_3 && $input->get->count()) {
  $filteredEvents = filterEvents($events, $input, $sanitizer);
}

// FILTERS
if ($page->opt_3){
  $options = array(
    'searchButtonText' => 'Suchen',
    'resetButtonText' => 'Zurücksetzen'
  );
  $filterMarkup = renderEventSearchForm($input, $sanitizer, $events, $options);
  // FREE MEMORY IF POSSIBLE
  if($filteredEvents) {
    $events = $filteredEvents;
    unset($filteredEvents);
  }
}

// MAP
if($page->opt_1){
    // subselection with a custom WireArray for map creation
    $mapEvents = createMapEvents($events);
    
    // include headers
    if(!property_exists($page,'mapHeadersIncluded')){
      page()->set('mapHeadersIncluded', true);
      page()->additionalHeaderData .= getMapHeaders();
    }
    
    // create map object
    $map = wire('modules')->get('MarkupLeafletMap');
    $options = array(
      'scrollWheelZoom' => 'false',
      'provider' => 'OPNVKarte',
      'markerColour' => 'green',
      'height' => '460',
      'markerLinkField' => 'url',
      'markerTitleField' => 'title',
      'popupFormatter' => function($page) {
            $out = "<div class=\"popup-body\">{$page->body}</div>";
            //$out[] = "<img src=\"{$page->image->url}\" width=\"100\" height=\"100\" />"; // ** NB: Use escaped double quotes if HTML attributes needed **
            return $out;
        }
    );

    // render map markup
    $mapMarkup = $map->render($mapEvents, 'place' ,$options);
}

// LIST
$listOptions = array(
  'showArtist' => true,
  'delimiter' => ' // ',
  'presaleTitle' => 'Vorverkauf',
  'presaleLinkDescriptionFallback' => 'Tickets',
  'presaleLinkTitleAttribute' => 'Zum Ticketverkauf hier klicken',
  'waitForPresaleText' => 'Vorverkauf demnächst'
);
if($page->opt_2){
  $listMarkup = renderEventList($events, $listOptions);
}


?>
<div>
<?= $filterMarkup ?>
<?= $mapMarkup ?>
<?= $listMarkup ?>
</div>

Hilfsfunktionen event-helpers.php

<?php namespace ProcessWire;
// Todo 
// delimiter für title
// Vorverkauf Hinweis wenn noch nicht vorhanden

// MAP HELPERS

function getMapHeaders(){
  $out = '';
  $out .= '
      <!-- Styles supporting the use of Leaflet.js -->
      <link rel="stylesheet" type="text/css" href="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.css" />
      <link rel="stylesheet" type="text/css" href="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/MarkerCluster.css" />
      <link rel="stylesheet" type="text/css" href="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-markercluster/MarkerCluster.Custom.css" />
    
      <!-- Scripts supporting the use of Leaflet.js -->
      <script type="text/javascript" src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-1.9.4/leaflet.js"></script>
      <script type="text/javascript" src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster.js"></script>
      <script type="text/javascript" src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>
      <script type="text/javascript" src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/MarkupLeafletMapCustom.js"></script>
    
      <!-- Leaflet Extensions (Plugins) -->
      <link rel="stylesheet" type="text/css" href="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/css/leaflet.awesome-markers.css" />
      <script type="text/javascript" src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-awesome-markers-2.0.2/js/leaflet.awesome-markers.min.js"></script>
    
      <link rel="stylesheet" href="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.css" type="text/css">
      <script src="'.wire()->config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/Leaflet.GestureHandling-1.2.2/leaflet-gesture-handling.min.js"></script>
      ';
  return $out;
}

function getEvents($page,$selector = ''){
  $today = strtotime(date('Y-m-d'));
  $selectorParts = array();
  if(empty($selector)){
    // template
    $selectorParts[] = "template=event";
    // date
    $selectorParts[] = "date_begin>=$today";
    // artist
    if($page->pr_artist){
      $selectorParts[] = 'pr_artist='.$page->pr_artist;
    }
    // sort
    $selectorParts[] = 'sort=date_begin';
    $selector = implode(',',$selectorParts);
    //bd($mySelector);
  }
  $events = pages()->find($selector);

  return $events;
}

/**
 * Create custom events for map including venue-data. 
 * Will return events with localized venues you can use in LeafletMapMarker
 * @param PageArray events
 * @return WireArray mapEvents
 */
function createMapEvents($events){
  $mapEvents = new WireArray; 
  foreach($events as $event){
    if($event->pr_venue->place->lat && $event->pr_venue->place->lng){
      // create a temporary virtual event WireArray to collect all nessecary information
      // Note that WireArray is compatible to PageArray and WireData ist compatible to Page
      $tempEvent = new WireData(); 
      $tempEvent->set('title', $event->pr_artist->title.' / '.$event->title);
      $tempEvent->set('url', $event->url);
      $tempEvent->set('body', $event->date_begin.'<br>'.$event->pr_venue->title);
      $tempEvent->set('place', $event->pr_venue->place);
      $mapEvents->add($tempEvent);
      //bd($tempEvent,'tempEvent');
    }
  }
  return $mapEvents;
}

// LIST HELPERS

/**
 * Render a list of given events
 */
function renderEventList($events, $options = array()){
  $listMarkup = '';
  $defaults = array(
    'showArtist' => true,
    'delimiter' => '//',
    'presaleTitle' => 'Vorverkauf',
    'presaleLinkDescriptionFallback' => 'Ticket',
    'presaleLinkTitleAttribute' => 'Zum Ticketverkauf hier klicken'
  );
  $options = array_merge($defaults, $options);

  foreach ($events as $key => $event) {
    $dateMarkup = '';
    $timeMarkup = '';
    $bodyMarkup = '';
    $organizerMarkup = '';
    $presaleMarkup = '';
    $eventMarkup = '';
    $artistMarkup = '';
    $venueMarkup = '';
    $addressLocality = '';

    $dateMarkup = renderEventDate($event);
    $venueMarkup = renderEventVenueInfo($event);
    $presaleMarkup = renderEventPresale($event, $options);

    // title

    if($options['showArtist']) {
      $eventTitle = $event->pr_artist->title;
      if($venueMarkup) $eventTitle .= $options['delimiter'];
    }

    $eventTitle .= '<span class="location">'.$venueMarkup.'</span>';

    $eventMarkup = '
      <div class="event">
        <div class="headline-event">
          <div class="date">
            '.$dateMarkup.'
          </div>
        <div class="event-title">
          <h3>'.$eventTitle.'</h3>
        </div>
        </div>

        <div class="description">
        <div class="body">
          '.$event->body.'
        </div>
        </div>

        <div class="infoblock">
        <div class="morelink">
          <a class="button-event-more" href="'.$event->url.'">Mehr zur Veranstaltung</a>
        </div>
        '.$presaleMarkup.'
        </div>
    ';
    $listMarkup .= $eventMarkup;
  }

  return $listMarkup;
}

function renderEventDate($event){
  $timeMarkup = '';
  // Date
  $unformattedDate = $event->getUnformatted("date_begin");
  $daynumber = date('N',$unformattedDate);
  $day = date('d', $unformattedDate);
  $monthnumber = date('m', $unformattedDate);
  $year = date('Y', $unformattedDate);
  $dayname = getDayName($daynumber);

  // Time(-span)
  if($event->time_start && $event->time_start != 0){
    $timeMarkup = $event->time_start;
    if($event->time_end) $timeMarkup .= ' - '.$event->time_end;
  }

  // DateTime-Infoblock
  $dateMarkup = '
  <div class="datetime">
    <span class="day">'.$day.'. '.getEventMonthName($monthnumber).' '.$year.'</span><br>
    <span class="dayname">'.$dayname.'</span>, <span class="time">'.$timeMarkup.'</span>
  </div>';

  return $dateMarkup;
}

function renderEventVenueInfo($event, $options = array()){
  $venueInfo = '';
  $defaults = array(
    'type' => 'short' 
  );
  if($event->pr_venue){
    if($event->pr_venue->addressLocality){
      $venueInfo .= $event->pr_venue->addressLocality .= ' - ';
    }
    $venueInfo .= $event->pr_venue->title;
  }
  return $venueInfo;
}

function renderEventPresale($event, $options = array()){
  $presaleMarkup = '';
  $defaults = array(
    'presaleTitle' => 'Vorverkauf',
    'presaleLinkDescriptionFallback' => 'Ticket',
    'presaleLinkTitleAttribute' => 'Zum Ticketverkauf hier klicken',
    'presaleNotAvailableText' => 'Nur Abendkasse',
    'waitForPresale' => 'Vorverkauf startet demnächst'
  );
  $options = array_merge($defaults, $options);
  //bd($event->r_links);
  if($event->r_links && $event->r_links->count){
    $presaleMarkup = '
    <p class="presale"><span class="presale-title">'.$options['presaleTitle'].'<br>';
    foreach ($event->r_links as $link) {
      $linkTitle = $link->title ? $link->title : $options['presaleLinkDescriptionFallback'];
      $presaleMarkup .= '<a class="button-event" href="'.$link->link.'" target="_blank" title="'.$options['presaleLinkTitleAttribute'].'">'.$linkTitle.'</a>&nbsp;';
    }
    $presaleMarkup .= '</p>';
  }else{
    $presaleMarkup = '
    <p class="presale"><span class="presale-title presale-na">'.$options['waitForPresale'].'</span></p>';
  }
  return $presaleMarkup;
}

// SEARCH FORM HELPERS
function renderEventSearchForm($input, $sanitizer, $events, $options=array()) {
  $defaults = array(
    'searchButtonText' => 'Suchen',
    'resetButtonText' => 'Zurücksetzen'
  );
  $options = array_merge($defaults, $options);
  
  //$query = $sanitizer->text($input->get('q'));
  $dateFrom = $input->get('date_from'); // no need to sanitize as it will be used directly in date input
  $dateTo = $input->get('date_to'); // same here
  $selectedArtist = $input->get('artist');

  // Generieren des Formulars
  $form = '<form id="eventSearchForm" action="./" method="get">';
  // $form .= '<label for="q">Event suchen:</label>';
  // $form .= '<input type="text" name="q" id="q" value="' . $query . '">';

  $form .= '<label for="date_from">Datum von:</label>';
  $form .= '<input type="date" name="date_from" id="date_from" value="' . $dateFrom . '">';

  $form .= '<label for="date_to">Datum bis:</label>';
  $form .= '<input type="date" name="date_to" id="date_to" value="' . $dateTo . '">';

  // Aufruf der Dropdown-Funktion
  $form .= renderArtistDropdown($events, $selectedArtist);

  // Hinzufügen des Such- und Zurücksetzen-Buttons
  $form .= '<button type="submit">'.$options['searchButtonText'].'</button>';
  $form .= '<button type="button" onclick="clearForm()">'.$options['resetButtonText'].'</button>';
  $form .= '</form>';

  // JavaScript zum Leeren des Formulars
  $form .= '
      <script>
          function clearForm() {
              var form = document.getElementById("eventSearchForm");
              form.reset();
              var inputs = form.querySelectorAll("input[type=text], input[type=date], select");
              for (var i = 0; i < inputs.length; i++) {
                  inputs[i].value = "";
              }
          }
      </script>
  ';

  return $form;
}



/**
 * Filtert ein Array mit Events und wendet daraus den Formularinput an.
 * Sinnvoll solange die Anzahl der $events nicht zu groß wird.
 * Große Eventanzahl sollte über die Datenbank abgefragt werden (Performance testen).
 */
function filterEvents(PageArray $events, $input, $sanitizer) {
  //bd($events);
  $filteredEvents = null;
  // Suchparameter einlesen und bereinigen
  //$query = $sanitizer->text($input->get('q'));
  $dateFrom = $sanitizer->date($input->get('date_from'));
  $dateTo = $sanitizer->date($input->get('date_to'));
  $artist = $sanitizer->text($input->get('artist'));

  // Basis-Suche im PageArray vorbereiten
  $selector = "template=event";

  // if($query) {
  //     $selector .= ", title|body|description%=$query";
  // }

  if($dateFrom) {
      $selector .= ", date_begin>=$dateFrom";
  }

  if($dateTo) {
      $selector .= ", date_begin<=$dateTo";
  }

  if($artist) {
      $selector .= ", pr_artist.name=$artist";
  }

  // Filterung des PageArray
  //bd($selector,'selector');
  $filteredEvents = $events->find($selector);
  //bd($filteredEvents);
  return $filteredEvents;
}

/**
 * Rendert ein Dropdown mit Künstlern aus einer Liste von Events
 * Nur sinnvoll solange die Anzahl der Events nicht zu groß ist 
 * (Performance testen)
 */
function renderArtistDropdown($events, $selectedArtist) {
  // Erstellen eines Arrays zur Speicherung der Künstler
  $artists = [];

  // Durchlaufen der Veranstaltungen und Sammeln der Künstler
  foreach($events as $event) {
      if ($event->pr_artist) {
          $artist = $event->pr_artist;
          if (!array_key_exists($artist->name, $artists)) {
              $artists[$artist->name] = $artist->title;
          }
      }
  }

  // Generieren des Dropdown-Felds
  $dropdown = '<label for="artist">Künstler:</label>';
  $dropdown .= '<select name="artist" id="artist">';
  $dropdown .= '<option value="">-- Wählen Sie einen Künstler --</option>';

  foreach ($artists as $name => $title) {
      $selected = ($name == $selectedArtist) ? ' selected' : '';
      $dropdown .= '<option value="' . $name . '"' . $selected . '>' . $title . '</option>';
  }

  $dropdown .= '</select>';

  return $dropdown;
}


// HELPER UTILITIES
function getEventDayName($daynumber){
	if($daynumber < 1 || $daynumber > 7) $daynumber = 1;
	$days = array('Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag');
	return $days[$daynumber-1];
}

function getEventMonthName($monthnumber){
	if($monthnumber < 1 || $monthnumber > 12) $monthnumber = 1;
	$days = array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember');
	return $days[$monthnumber-1];
}

Weitere Kartenanbieter[Bearbeiten]

Um andere Kartenprovider zu nutzen gibt es das Plugin leaflet-providers

https://github.com/leaflet-extras/leaflet-providers

Das ist standardmäßig im Ordner

modules/FieldtypeLeafletMapMarker/assets/leaflet-providers

Man benötigt nur die leaflet-providers.js Datei. Im Prinzip ist die Datei eine Liste von verschiedenen Anbietern und dazu notwendigen Einstellungen.

Einbindung in die Karte[Bearbeiten]

Wichtig ist dass das JavaScript vor leaflet.js geladen wird:

<script type="text/javascript" src="'.$config->urls->siteModules.'FieldtypeLeafletMapMarker/assets/leaflet-providers/leaflet-providers.js"></script>

Man gibt die gewünschte Karte beim Einbinden des Moduls in ein Template über die Option provider an.

"provider" => 'OpenStreetMap.Mapnik', // Standardkarte

So lassen sich auch andere Anbieter nutzen:

"provider" => 'Stadia.AlidadeSmooth', // Achtung Stadia geht nur wenn man sich registriert

Funktionsweise[Bearbeiten]

In der init Funtion wird

// ...
Stadia: {
			url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png',
			options: {
				maxZoom: 20,
				attribution: '&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'
			},
			variants: {
				AlidadeSmooth: {
					url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png'
				},
				AlidadeSmoothDark: {
					url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png'
				},
				OSMBright: {
					url: 'https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}{r}.png'
				},
				Outdoors: {
					url: 'https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.png'
				}
			}
		},
// ...

Customization Examples[Bearbeiten]

Marker direkt verlinken (kein PopUp)[Bearbeiten]

var marker = L.marker([52, 12], {});
marker.url = 'www.google.com'

marker.on('click', function(){
window.location = (this.url);
});

Probleme beheben[Bearbeiten]

Feld zeigt an Error Geocoding[Bearbeiten]

File Cache komplett löschen (assets/cache/...)