diff --git a/.eslintrc b/.eslintrc
index edc9138d7455e8743114e0a5b5b6647fee4814e0..9af3fc79b55ed079012dfb25a07eb94ca007a8b6 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -19,6 +19,7 @@
     "new-cap": "off",
     "radix": "off",
     "no-jquery/no-ajax-events": "off",
+    "no-case-declarations": "off",
     "no-restricted-syntax": "off",
     "no-bitwise": "off"
   },
diff --git a/geolocation.module b/geolocation.module
index 73066d3aa4f34f069729862de073a916f20ce594..73abfcce5dd69713e1a4229ef32d4fbfe229dfe1 100644
--- a/geolocation.module
+++ b/geolocation.module
@@ -70,7 +70,7 @@ function geolocation_theme(): array {
         'row' => NULL,
       ],
     ],
-    'geolocation_map_geometry' => [
+    'geolocation_map_shape' => [
       'variables' => [
         'attributes' => NULL,
         'children' => NULL,
diff --git a/geolocation.views.inc b/geolocation.views.inc
index 066803cf85fd1a843d451e61f4213fb1a1ad4999..6cfb5e588a5ca4847e47f14ee2a261c55e8dbff2 100644
--- a/geolocation.views.inc
+++ b/geolocation.views.inc
@@ -16,7 +16,7 @@ function geolocation_field_views_data(FieldStorageConfigInterface $field_storage
   $entity_definition = Drupal::entityTypeManager()->getDefinition($field_storage->getTargetEntityTypeId());
 
   // Get the default data from the views module.
-  $data = Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage);
+  $data = \Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage);
 
   $title_short = $help = '';
 
diff --git a/js/Base/GeolocationGeometry.js b/js/Base/GeolocationGeometry.js
new file mode 100644
index 0000000000000000000000000000000000000000..c0ca8e02da00c90f2070646baa1f01090dc2a222
--- /dev/null
+++ b/js/Base/GeolocationGeometry.js
@@ -0,0 +1,14 @@
+/**
+ * @prop {string} type
+ * @prop { number[] | number[][] | number[][][] | number[][][][]} coordinates
+ */
+export class GeolocationGeometry {
+  /**
+   * @param {string} type
+   * @param {array} coordinates
+   */
+  constructor(type, coordinates) {
+    this.type = type;
+    this.coordinates = coordinates;
+  }
+}
diff --git a/js/Base/GeolocationShape.js b/js/Base/GeolocationShape.js
index d638b1808778a873b909b4cbbae4d33be2b5f3f4..74c7cdf2b78390aa765bd1f175391700fc34dc4f 100644
--- a/js/Base/GeolocationShape.js
+++ b/js/Base/GeolocationShape.js
@@ -12,14 +12,6 @@
  * @prop {Number} [fillOpacity]
  */
 
-/**
- * @typedef {Object} GeolocationGeometry
- *
- * @prop {GeolocationCoordinates[]} [points]
- * @prop {Array.<GeolocationCoordinates[]>} [lines]
- * @prop {Array.<GeolocationCoordinates[]>} [polygons]
- */
-
 import { GeolocationCoordinates } from "./GeolocationCoordinates.js";
 import { GeolocationBoundaries } from "./GeolocationBoundaries.js";
 
@@ -72,37 +64,6 @@ export class GeolocationShape {
     }
   }
 
-  /**
-   * @param {Element} metaWrapper
-   *   Element.
-   * @return {GeolocationCoordinates[]}
-   *   Points.
-   */
-  static getPointsByGeoShapeMeta(metaWrapper) {
-    const points = [];
-
-    if (!metaWrapper) {
-      return points;
-    }
-
-    metaWrapper
-      .getAttribute("content")
-      ?.split(" ")
-      .forEach((value) => {
-        const coordinates = value.split(",");
-        if (coordinates.length !== 2) {
-          return;
-        }
-
-        const lat = parseFloat(coordinates[0]);
-        const lon = parseFloat(coordinates[1]);
-
-        points.push(new GeolocationCoordinates(lat, lon));
-      });
-
-    return points;
-  }
-
   getContent() {
     if (!this.content) {
       this.content = this.wrapper?.querySelector(".location-content")?.innerHTML ?? "";
@@ -112,7 +73,7 @@ export class GeolocationShape {
   }
 
   /**
-   * @param {Object} [geometry]
+   * @param {GeolocationGeometry} [geometry]
    *   Geometry.
    * @param {GeolocationShapeSettings} [settings]
    *   Settings.
@@ -169,30 +130,30 @@ export class GeolocationShape {
     switch (this.type) {
       case "line":
       case "polygon":
-        this.geometry.points.forEach((value) => {
-          bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north;
-          bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south;
-          bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east;
-          bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west;
+        this.geometry.coordinates.forEach((value) => {
+          bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north;
+          bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south;
+          bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east;
+          bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west;
         });
         break;
       case "multiline":
-        this.geometry.lines.forEach((line) => {
-          line.points.forEach((value) => {
-            bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north;
-            bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south;
-            bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east;
-            bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west;
+        this.geometry.coordinates.forEach((line) => {
+          line.coordinates.forEach((value) => {
+            bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north;
+            bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south;
+            bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east;
+            bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west;
           });
         });
         break;
       case "multipolygon":
-        this.geometry.polygons.forEach((polygon) => {
-          polygon.points.forEach((value) => {
-            bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north;
-            bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south;
-            bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east;
-            bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west;
+        this.geometry.coordinates.forEach((polygon) => {
+          polygon.coordinates.forEach((value) => {
+            bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north;
+            bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south;
+            bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east;
+            bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west;
           });
         });
         break;
@@ -205,7 +166,11 @@ export class GeolocationShape {
     return new GeolocationBoundaries(bounds);
   }
 
-  remove() {}
+  remove() {
+    this.map.dataLayers.forEach((layer) => {
+      layer.shapeRemoved(this);
+    });
+  }
 
   /**
    * Click handler delegation.
diff --git a/js/Base/GeolocationShapeLine.js b/js/Base/GeolocationShapeLine.js
index 65734e35091410d093868bc7f809d9ccfc55cd71..c9f89029bc1ff302766676cd6949622acdb50cbf 100644
--- a/js/Base/GeolocationShapeLine.js
+++ b/js/Base/GeolocationShapeLine.js
@@ -1,9 +1,7 @@
-import { GeolocationCoordinates } from "./GeolocationCoordinates.js";
 import { GeolocationShape } from "./GeolocationShape.js";
 
 /**
- * @prop {Object} geometry
- * @prop {GeolocationCoordinates[]} geometry.points
+ * @prop {GeolocationGeometry} geometry
  */
 export class GeolocationShapeLine extends GeolocationShape {
   constructor(geometry, settings = {}, map) {
diff --git a/js/Base/GeolocationShapeMultiLine.js b/js/Base/GeolocationShapeMultiLine.js
index 2a7697953d4af83ece18f765131ba8511f11da24..beb1761991cb402c88c1f686ee5e023148b4f5db 100644
--- a/js/Base/GeolocationShapeMultiLine.js
+++ b/js/Base/GeolocationShapeMultiLine.js
@@ -1,9 +1,7 @@
-import { GeolocationCoordinates } from "./GeolocationCoordinates.js";
 import { GeolocationShape } from "./GeolocationShape.js";
 
 /**
- * @prop {Object} geometry
- * @prop {{points: GeolocationCoordinates[]}} geometry.lines
+ * @prop {GeolocationGeometry} geometry
  */
 export class GeolocationShapeMultiLine extends GeolocationShape {
   constructor(geometry, settings = {}, map) {
diff --git a/js/Base/GeolocationShapeMultiPolygon.js b/js/Base/GeolocationShapeMultiPolygon.js
index b41e6591ccc30cd349a10873513da2e4ce43537b..38754cfa9c95cf4e49b0078374052447b2b0dd9b 100644
--- a/js/Base/GeolocationShapeMultiPolygon.js
+++ b/js/Base/GeolocationShapeMultiPolygon.js
@@ -2,8 +2,7 @@ import { GeolocationCoordinates } from "./GeolocationCoordinates.js";
 import { GeolocationShape } from "./GeolocationShape.js";
 
 /**
- * @prop {Object} geometry
- * @prop {{points: GeolocationCoordinates[]}} geometry.polygons
+ * @prop {GeolocationGeometry} geometry
  */
 export class GeolocationShapeMultiPolygon extends GeolocationShape {
   constructor(geometry, settings = {}, map) {
diff --git a/js/Base/GeolocationShapePolygon.js b/js/Base/GeolocationShapePolygon.js
index 481575addad07837c3b179633d3a0184e827a299..fe978c40f3fee99dfe909fec17e2355f24b6c0a8 100644
--- a/js/Base/GeolocationShapePolygon.js
+++ b/js/Base/GeolocationShapePolygon.js
@@ -1,9 +1,7 @@
-import { GeolocationCoordinates } from "./GeolocationCoordinates.js";
 import { GeolocationShape } from "./GeolocationShape.js";
 
 /**
- * @prop {Object} geometry
- * @prop {GeolocationCoordinates[]} geometry.points
+ * @prop {GeolocationGeometry} geometry
  */
 export class GeolocationShapePolygon extends GeolocationShape {
   constructor(geometry, settings = {}, map) {
diff --git a/js/DataLayerProvider/GeolocationDataLayer.js b/js/DataLayerProvider/GeolocationDataLayer.js
index 425bab333eaca24f5f19deda3153e128d9803805..0f2b775575f9b538abfd33d866a0387fa4a55082 100644
--- a/js/DataLayerProvider/GeolocationDataLayer.js
+++ b/js/DataLayerProvider/GeolocationDataLayer.js
@@ -6,7 +6,7 @@ import { GeolocationShape } from "../Base/GeolocationShape.js";
  *
  * @prop {String} import_path
  * @prop {Object} settings
- * @prop {Object.<string, Object>} features
+ * @prop {Map<string, GeolocationLayerFeature>} features
  * @prop {String[]} scripts
  * @prop {String[]} async_scripts
  * @prop {String[]} stylesheets
@@ -28,7 +28,7 @@ export default class GeolocationDataLayer {
   constructor(map, id, layerSettings) {
     this.map = map;
     this.settings = layerSettings.settings;
-    this.features = [];
+    this.features = new Map();
     this.markers = [];
     this.shapes = [];
     this.id = id;
@@ -37,10 +37,12 @@ export default class GeolocationDataLayer {
   /**
    * @param {GeolocationLayerFeatureSettings} layerFeatureSettings
    *   Layer feature settings.
+   * @param {?string} id
+   *   Layer feature ID.
    * @return {Promise<GeolocationLayerFeature>|null}
    *   Loading feature Promise.
    */
-  loadFeature(layerFeatureSettings) {
+  loadFeature(layerFeatureSettings, id = null) {
     if (!layerFeatureSettings.import_path) {
       return null;
     }
@@ -76,7 +78,7 @@ export default class GeolocationDataLayer {
       .then((featureImport) => {
         try {
           const feature = new featureImport.default(layerFeatureSettings.settings, this);
-          this.features.push(feature);
+          this.features.set(id, feature);
 
           return feature;
         } catch (e) {
@@ -93,7 +95,7 @@ export default class GeolocationDataLayer {
     const featureImports = [];
 
     Object.keys(this.settings.features ?? {}).forEach((featureName) => {
-      const featurePromise = this.loadFeature(this.settings.features[featureName]);
+      const featurePromise = this.loadFeature(this.settings.features[featureName], featureName);
 
       if (featurePromise) {
         featureImports.push(featurePromise);
@@ -234,63 +236,10 @@ export default class GeolocationDataLayer {
         fillOpacity: shapeElement.getAttribute("data-fill-opacity") ?? 0.2,
       };
 
-      let geometry = {};
-      const geometryWrapper = shapeElement.querySelector(".geometry");
-      if (!geometryWrapper) {
-        return;
-      }
-
-      let points;
-
-      switch (geometryWrapper.getAttribute("data-type")) {
-        case "line":
-        case "polygon":
-          points = GeolocationShape.getPointsByGeoShapeMeta(geometryWrapper.querySelector('span[typeof="GeoShape"] meta'));
-
-          if (!points) {
-            break;
-          }
-          geometry = {
-            points,
-          };
-          break;
-
-        case "multiline":
-          geometry = {
-            lines: [],
-          };
-          geometryWrapper.querySelectorAll('span[typeof="GeoShape"] meta').forEach((meta) => {
-            points = GeolocationShape.getPointsByGeoShapeMeta(meta);
-            if (!points) {
-              return;
-            }
-            geometry.lines.push({
-              points,
-            });
-          });
-          break;
-
-        case "multipolygon":
-          geometry = {
-            polygons: [],
-          };
-          geometryWrapper.querySelectorAll('span[typeof="GeoShape"] meta').forEach((meta) => {
-            points = GeolocationShape.getPointsByGeoShapeMeta(meta);
-            if (!points) {
-              return;
-            }
-            geometry.polygons.push({
-              points,
-            });
-          });
-          break;
-
-        default:
-          console.error("Unknown shape type cannot be added.");
-      }
+      const geometry = JSON.parse(shapeElement.querySelector(".geometry")?.textContent);
 
       let shape;
-      switch (geometryWrapper.getAttribute("data-type")) {
+      switch (shapeElement.getAttribute("data-geometry-type")) {
         case "line":
           shape = this.map.createShapeLine(geometry, settings);
           break;
diff --git a/js/GeolocationWidgetBroker.js b/js/GeolocationWidgetBroker.js
index 3658bb952c33a3cd20cf91c0e21b17786448c803..7ffb56208755d18624a5eb883c603ce374ceb58d 100644
--- a/js/GeolocationWidgetBroker.js
+++ b/js/GeolocationWidgetBroker.js
@@ -112,4 +112,65 @@ export default class GeolocationWidgetBroker {
       }
     });
   }
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Geometry.
+   * @param {Number} index
+   *   Index.
+   * @param {String} caller
+   *   Calling entity.
+   */
+  geometryAdded(geometry, index, caller) {
+    this.subscribers.forEach((subscriber, id) => {
+      if (id === caller) {
+        return;
+      }
+      try {
+        subscriber.addGeometry(geometry, index, caller);
+      } catch (e) {
+        console.error(e, `Subscriber ${subscriber.id} failed addGeometry: ${e.toString()}`);
+      }
+    });
+  }
+
+  /**
+   * @param {Number} index
+   *   Index.
+   * @param {String} caller
+   *   Caller.
+   */
+  geometryRemoved(index, caller) {
+    this.subscribers.forEach((subscriber, id) => {
+      if (id === caller) {
+        return;
+      }
+      try {
+        subscriber.removeGeometry(index, caller);
+      } catch (e) {
+        console.error(e, `Subscriber ${subscriber.id} failed removeGeometry: ${e.toString()}`);
+      }
+    });
+  }
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Geometry.
+   * @param {Number} index
+   *   Index.
+   * @param {String} caller
+   *   Caller.
+   */
+  geometryAltered(geometry, index, caller) {
+    this.subscribers.forEach((subscriber, id) => {
+      if (id === caller) {
+        return;
+      }
+      try {
+        subscriber.alterGeometry(geometry, index, caller);
+      } catch (e) {
+        console.error(e, `Subscriber ${subscriber.id} failed alterGeometry: ${e.toString()}`);
+      }
+    });
+  }
 }
diff --git a/js/MapFeature/GeolocationFieldWidgetMapConnector.js b/js/MapFeature/GeolocationFieldWidgetMapConnector.js
index 6e88bf3fd9e3cfe8d24726caeda288c029caa4de..463308eae358c84e88a0be4aa82ae35b87603584 100644
--- a/js/MapFeature/GeolocationFieldWidgetMapConnector.js
+++ b/js/MapFeature/GeolocationFieldWidgetMapConnector.js
@@ -1,9 +1,16 @@
+/**
+ * @typedef {Object} GeolocationFieldWidgetMapConnectorSettings
+ *
+ * @prop {int} cardinality
+ * @prop {string} field_type
+ */
+
 import { GeolocationMapFeature } from "./GeolocationMapFeature.js";
 
 /**
  * @prop {WidgetSubscriberBase} subscriber
- * @prop {Object} settings
- * @prop {int} settings.cardinality
+ *
+ * @prop {GeolocationFieldWidgetMapConnectorSettings} settings
  */
 export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFeature {
   setWidgetSubscriber(subscriber) {
@@ -79,7 +86,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
     this.map.dataLayers.get("default").markerAdded(marker);
     delete marker.geolocationWidgetIgnore;
 
-    this.map.fitMapToMarkers();
+    this.map.fitMapToElements();
 
     return marker;
   }
@@ -113,7 +120,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
     marker.update(coordinates, settings ?? {});
     delete marker.geolocationWidgetIgnore;
 
-    this.map.fitMapToMarkers();
+    this.map.fitMapToElements();
 
     return marker;
   }
@@ -124,15 +131,13 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
     marker.geolocationWidgetIgnore = true;
     marker.remove();
 
-    this.map.fitMapToMarkers();
+    this.map.fitMapToElements();
   }
 
   onClick(coordinates) {
     super.onClick(coordinates);
 
-    const numberOfMarkers = this.map.dataLayers.get("default").markers.length;
-
-    if (this.settings.cardinality > numberOfMarkers || this.settings.cardinality === -1) {
+    if (this.settings.cardinality > this.map.dataLayers.get("default").markers.length || this.settings.cardinality === -1) {
       let newIndex = 0;
       this.map.dataLayers.get("default").markers.forEach((marker) => {
         const markerIndex = this.getIndexByMarker(marker) ?? 0;
@@ -158,7 +163,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
       const warning = document.createElement("div");
       warning.innerHTML = `<p>${Drupal.t("Maximum number of locations reached.")}</p>`;
       Drupal.dialog(warning, {
-        title: Drupal.t("Address synchronization"),
+        title: Drupal.t("Synchronization"),
       }).showModal();
     } else {
       const marker = this.getMarkerByIndex(0);
@@ -174,7 +179,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
 
     if (marker.geolocationWidgetIgnore ?? false) return;
 
-    this.subscriber.coordinatesAdded(marker.coordinates, this.getIndexByMarker(marker) ?? 0);
+    this.subscriber?.coordinatesAdded(marker.coordinates, this.getIndexByMarker(marker) ?? 0);
   }
 
   onMarkerClicked(marker) {
@@ -189,7 +194,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
 
     if (marker.geolocationWidgetIgnore ?? false) return;
 
-    this.subscriber.coordinatesAltered(marker.coordinates, this.getIndexByMarker(marker));
+    this.subscriber?.coordinatesAltered(marker.coordinates, this.getIndexByMarker(marker));
   }
 
   onMarkerRemove(marker) {
@@ -197,6 +202,6 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe
 
     if (marker.geolocationWidgetIgnore ?? false) return;
 
-    this.subscriber.coordinatesRemoved(this.getIndexByMarker(marker));
+    this.subscriber?.coordinatesRemoved(this.getIndexByMarker(marker));
   }
 }
diff --git a/js/MapFeature/GeolocationMapFeature.js b/js/MapFeature/GeolocationMapFeature.js
index 7faab9a7abccea66269b5ff389859309ec422bb2..654ca08b1cf5b8fe845c8c09f714c5e2ad17ea1a 100644
--- a/js/MapFeature/GeolocationMapFeature.js
+++ b/js/MapFeature/GeolocationMapFeature.js
@@ -18,6 +18,8 @@ export class GeolocationMapFeature {
   /**
    * @constructor
    *
+   * Called when map is initialized, but no map content is loaded yet.
+   *
    * @param {GeolocationMapFeatureSettings} settings
    *   Settings.
    * @param {GeolocationMapBase} map
@@ -52,6 +54,9 @@ export class GeolocationMapFeature {
    */
   onContextClick(coordinates) {}
 
+  /**
+   * Called when map content is fully loaded.
+   */
   onMapReady() {}
 
   onMapIdle() {}
diff --git a/js/MapProvider/GeolocationMapBase.js b/js/MapProvider/GeolocationMapBase.js
index 9a0f8833086c37693fc84884aaa13bc9dd175043..5b66591e57ee710a32b9e884ce568b2b7b581ef3 100644
--- a/js/MapProvider/GeolocationMapBase.js
+++ b/js/MapProvider/GeolocationMapBase.js
@@ -50,7 +50,7 @@ import { GeolocationShape } from "../Base/GeolocationShape.js";
  * @prop {HTMLElement} container
  * @prop {Map<String, GeolocationDataLayer>} dataLayers
  * @prop {Map<String, Object>} tileLayers
- * @prop {GeolocationMapFeature[]} features
+ * @prop {Map<String, GeolocationMapFeature>} features
  * @prop {GeolocationMapCenterBase[]} mapCenter
  */
 export class GeolocationMapBase {
@@ -64,7 +64,7 @@ export class GeolocationMapBase {
       throw new Error("Geolocation - Map container not found");
     }
 
-    this.features = [];
+    this.features = new Map();
     this.mapCenter = [];
     this.dataLayers = new Map();
     this.tileLayers = new Map();
@@ -127,10 +127,12 @@ export class GeolocationMapBase {
   /**
    * @param {GeolocationMapFeatureSettings} featureSettings
    *   Feature settings.
+   * @param {?string} id
+   *   Feature ID.
    * @return {Promise<GeolocationMapFeature>|null}
    *   Loaded feature.
    */
-  loadFeature(featureSettings) {
+  loadFeature(featureSettings, id = null) {
     if (!featureSettings.import_path) {
       return null;
     }
@@ -157,7 +159,7 @@ export class GeolocationMapBase {
       .then((featureImport) => {
         try {
           const feature = new featureImport.default(featureSettings.settings, this);
-          this.features.push(feature);
+          this.features.set(id, feature);
 
           return feature;
         } catch (e) {
@@ -173,7 +175,7 @@ export class GeolocationMapBase {
     const featureImports = [];
 
     Object.keys(this.settings.features ?? {}).forEach((featureName) => {
-      const featurePromise = this.loadFeature(this.settings.features[featureName]);
+      const featurePromise = this.loadFeature(this.settings.features[featureName], featureName);
 
       if (featurePromise) {
         featureImports.push(featurePromise);
diff --git a/js/WidgetSubscriber/FieldWidgetBase.js b/js/WidgetSubscriber/FieldWidgetBase.js
index 42a32eeee7f7c9ee9df2f0725c3a9f98e7bfdf74..8956e14b65916d106a3ba3aaac27b977994362de 100644
--- a/js/WidgetSubscriber/FieldWidgetBase.js
+++ b/js/WidgetSubscriber/FieldWidgetBase.js
@@ -12,6 +12,8 @@ import { WidgetSubscriberBase } from "./WidgetSubscriberBase.js";
 import { GeolocationCoordinates } from "../Base/GeolocationCoordinates.js";
 
 /**
+ * @abstract
+ *
  * @prop {GeolocationWidgetBroker} broker
  * @prop {Object} settings
  */
@@ -137,7 +139,7 @@ export class FieldWidgetBase extends WidgetSubscriberBase {
    */
   getAllInputElements(returnElements = false) {
     const map = new Map();
-    const elements = this.form.querySelectorAll(".geolocation-widget-input");
+    const elements = this.form.querySelectorAll(this.getElementSelector());
 
     if (returnElements) {
       return elements;
@@ -163,6 +165,10 @@ export class FieldWidgetBase extends WidgetSubscriberBase {
     return parseInt(element.getAttribute("data-geolocation-widget-index"));
   }
 
+  getElementSelector() {
+    return ".geolocation-widget-input";
+  }
+
   getElementSelectorByIndex(index) {
     return `[data-geolocation-widget-index='${index.toString()}']`;
   }
@@ -208,8 +214,18 @@ export class FieldWidgetBase extends WidgetSubscriberBase {
     return promise;
   }
 
+  /**
+   * @param {GeolocationCoordinates} coordinates
+   * @param {Element} element
+   */
   setCoordinatesByElement(coordinates, element) {}
 
+  /**
+   * @param {GeolocationGeometry} geometry
+   * @param {Element} element
+   */
+  setGeometryByElement(geometry, element) {}
+
   /**
    * @param {Element} element
    *   Element.
@@ -221,6 +237,17 @@ export class FieldWidgetBase extends WidgetSubscriberBase {
     return null;
   }
 
+  /**
+   * @param {Element} element
+   *   Element.
+   *
+   * @return {Promise<GeolocationGeometry>}
+   *   Coordinates.
+   */
+  getGeometryByElement(element) {
+    return null;
+  }
+
   reorder(newOrder, source) {
     super.reorder(newOrder, source);
 
@@ -292,4 +319,28 @@ export class FieldWidgetBase extends WidgetSubscriberBase {
       this.setCoordinatesByElement(coordinates, element);
     });
   }
+
+  addGeometry(geometry, index, source) {
+    super.addGeometry(geometry, index, source);
+
+    this.getElementByIndex(index).then((element) => {
+      this.setGeometryByElement(geometry, element);
+    });
+  }
+
+  removeGeometry(index, source) {
+    super.removeGeometry(index, source);
+
+    this.getElementByIndex(index).then((element) => {
+      this.setGeometryByElement(null, element);
+    });
+  }
+
+  alterGeometry(geometry, index, source) {
+    super.alterGeometry(geometry, index, source);
+
+    this.getElementByIndex(index).then((element) => {
+      this.setGeometryByElement(geometry, element);
+    });
+  }
 }
diff --git a/js/WidgetSubscriber/GeolocationFieldMapWidget.js b/js/WidgetSubscriber/GeolocationFieldMapWidget.js
index e326f4cb804309b740f6099c8354101341e691d8..94b6fff27560871eccca7447db1e47a87e9b4789 100644
--- a/js/WidgetSubscriber/GeolocationFieldMapWidget.js
+++ b/js/WidgetSubscriber/GeolocationFieldMapWidget.js
@@ -13,18 +13,16 @@ export default class GeolocationFieldMapWidget extends WidgetSubscriberBase {
       Drupal.geolocation.maps.getMap(settings.mapId).then((map) => {
         this.map = map;
 
-        this.settings.featureSettings.settings = this.settings.featureSettings.settings ?? {};
-        this.settings.featureSettings.settings.cardinality = this.settings.featureSettings.settings.cardinality ?? this.settings.cardinality;
-
-        this.map.loadFeature(this.settings.featureSettings).then(
-          /** @param {GeolocationFieldWidgetMapConnector} feature Feature */ (feature) => {
-            this.mapFeature = feature;
-            if (typeof this.mapFeature.setWidgetSubscriber === "function") {
-              this.mapFeature.setWidgetSubscriber(this);
-            }
-            resolve(feature);
+        this.map.features.forEach((feature, id) => {
+          if (this.settings.feature_id !== id) {
+            return;
           }
-        );
+          this.mapFeature = feature;
+          if (typeof this.mapFeature.setWidgetSubscriber === "function") {
+            this.mapFeature.setWidgetSubscriber(this);
+          }
+          resolve(feature);
+        });
       });
     });
   }
diff --git a/js/WidgetSubscriber/GeolocationFieldWidget.js b/js/WidgetSubscriber/GeolocationFieldWidget.js
index 0ce4c59f14aab93c981b478ea5c28e84557948fc..d867157b6c64f29cdb218bc81d0acc39093df9bd 100644
--- a/js/WidgetSubscriber/GeolocationFieldWidget.js
+++ b/js/WidgetSubscriber/GeolocationFieldWidget.js
@@ -36,7 +36,7 @@ export default class GeolocationFieldWidget extends FieldWidgetBase {
   }
 
   getElementSelectorByIndex(index) {
-    return `.geolocation-widget-input[data-geolocation-widget-index='${index.toString()}']`;
+    return `${this.getElementSelector()}[data-geolocation-widget-index='${index.toString()}']`;
   }
 
   getCoordinatesByElement(element) {
diff --git a/js/WidgetSubscriber/WidgetSubscriberBase.js b/js/WidgetSubscriber/WidgetSubscriberBase.js
index e130639d3511eb9eb6ca825ee08b1a7616fa1061..01e687886c0ee222241c4f43bda02ff32a5ffba1 100644
--- a/js/WidgetSubscriber/WidgetSubscriberBase.js
+++ b/js/WidgetSubscriber/WidgetSubscriberBase.js
@@ -3,6 +3,8 @@
  */
 
 /**
+ * @abstract
+ *
  * @prop {String} id
  * @prop {Object} settings
  * @prop {int} settings.cardinality
@@ -51,4 +53,32 @@ export class WidgetSubscriberBase {
    *   Source.
    */
   alterCoordinates(coordinates, index, source) {}
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Shape.
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  addGeometry(geometry, index, source) {}
+
+  /**
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  removeGeometry(index, source) {}
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Shape.
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  alterGeometry(geometry, index, source) {}
 }
diff --git a/modules/geolocation_geometry/geolocation_geometry.views.inc b/modules/geolocation_geometry/geolocation_geometry.views.inc
index 5a50618b4d76d405bc2805c654ca128acc422265..1f41caf651fdfd0b03ac6a20e386868805fb5dc6 100644
--- a/modules/geolocation_geometry/geolocation_geometry.views.inc
+++ b/modules/geolocation_geometry/geolocation_geometry.views.inc
@@ -55,7 +55,7 @@ function geolocation_geometry_field_views_data(FieldStorageConfigInterface $fiel
   }
 
   // Get the default data from the views module.
-  $data = Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage);
+  $data = \Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage);
 
   $args = ['@field_name' => $field_storage->getName()];
 
diff --git a/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..945a100c922d3c2ce2ae227be5a9a362d58c76c3
--- /dev/null
+++ b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js
@@ -0,0 +1,83 @@
+import GeolocationFieldMapWidget from "../../../../js/WidgetSubscriber/GeolocationFieldMapWidget.js";
+
+/**
+ * @prop {GeolocationMapBase} map
+ * @prop {GeolocationMapFeature} mapFeature
+ */
+export default class GeolocationGeometryFieldMapWidget extends GeolocationFieldMapWidget {
+  /**
+   * @return {Promise<GeolocationMapFeature>}
+   */
+  getMapFeature() {
+    return super.getMapFeature();
+  }
+
+  onClick() {}
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Shape.
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  addGeometry(geometry, index, source) {
+    super.addGeometry(geometry, index, source);
+
+    switch (geometry.type) {
+      case "Point":
+      case "MultiPoint":
+        if (this.broker.settings.cardinality > 0 && this.map.dataLayers.get("default").markers.length >= this.broker.settings.cardinality) {
+          console.error(Drupal.t(`Maximum number of entries reached. Cardinality set to ${this.broker.settings.cardinality}`));
+          return;
+        }
+        break;
+
+      default:
+        if (this.broker.settings.cardinality > 0 && this.map.dataLayers.get("default").shapes.length >= this.broker.settings.cardinality) {
+          console.error(Drupal.t(`Maximum number of entries reached. Cardinality set to ${this.broker.settings.cardinality}`));
+          return;
+        }
+    }
+    this.getMapFeature().then((feature) => feature.addShapeSilently(index, geometry));
+  }
+
+  /**
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  removeGeometry(index, source) {
+    super.removeGeometry(index, source);
+
+    this.getMapFeature().then((feature) => feature.removeShapeSilently(index));
+  }
+
+  /**
+   * @param {GeolocationGeometry} geometry
+   *   Shape.
+   * @param {Number} index
+   *   Index.
+   * @param {String} source
+   *   Source.
+   */
+  alterGeometry(geometry, index, source) {
+    super.alterGeometry(geometry, index, source);
+
+    this.getMapFeature().then((feature) => feature.updateShapeSilently(index, geometry));
+  }
+
+  geometryAltered(geometry, index) {
+    this.broker.geometryAltered(geometry, index, this.id);
+  }
+
+  geometryAdded(geometry, index) {
+    this.broker.geometryAdded(geometry, index, this.id);
+  }
+
+  geometryRemoved(index) {
+    this.broker.geometryRemoved(index, this.id);
+  }
+}
diff --git a/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..86b4df1f4aed0625bf5a82fe5cc7e6c648992c08
--- /dev/null
+++ b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js
@@ -0,0 +1,77 @@
+/**
+ * @name GeolocationWidgetSettings
+ *
+ * @prop {String} id
+ * @prop {String} type
+ * @prop {String} fieldName
+ * @prop {String} cardinality
+ */
+
+import { FieldWidgetBase } from "../../../../js/WidgetSubscriber/FieldWidgetBase.js";
+import { GeolocationGeometry } from "../../../../js/Base/GeolocationGeometry.js";
+
+export default class GeolocationGeometryFieldWidget extends FieldWidgetBase {
+  getElementSelector() {
+    return ".geolocation-geometry-widget-geojson-input";
+  }
+
+  onFormChange(element, index) {
+    this.getGeometryByElement(element)
+      .then((newGeometry) => {
+        if (!newGeometry) {
+          this.broker.geometryRemoved(index, this.id);
+        } else {
+          this.broker.geometryAltered(newGeometry, index, this.id);
+        }
+      })
+      .catch(() => {
+        this.broker.geometryRemoved(index, this.id);
+      });
+  }
+
+  setGeometryByElement(geometry, element) {
+    if (geometry) {
+      element.value = JSON.stringify(geometry);
+      element.setAttribute("data-geolocation-current-value", JSON.stringify(geometry));
+    } else {
+      element.value = "";
+      element.setAttribute("data-geolocation-current-value", "");
+    }
+  }
+
+  getElementSelectorByIndex(index) {
+    return `.geolocation-geometry-widget-geojson-input[data-geolocation-widget-index='${index.toString()}']`;
+  }
+
+  getGeometryByElement(element) {
+    let geometry;
+
+    try {
+      geometry = JSON.parse(element.value);
+    } catch (e) {
+      return Promise.reject(new Error(`GeolocationGeometryFieldWidget: Cannot get geometry by element due to JSON error. ${e.toString()}`));
+    }
+
+    if (!geometry.type || !geometry.coordinates) {
+      return Promise.reject(new Error("GeolocationGeometryFieldWidget: Cannot get geometry as type or coordinates are missing."));
+    }
+
+    return Promise.resolve(new GeolocationGeometry(geometry.type, geometry.coordinates));
+  }
+
+  alterGeometry(geometry, index, source) {
+    this.getElementByIndex(index).then((element) => {
+      this.getGeometryByElement(element)
+        .then((currentGeometry) => {
+          if (currentGeometry === geometry) {
+            return;
+          }
+
+          super.alterGeometry(geometry, index, source);
+        })
+        .catch(() => {
+          super.alterGeometry(geometry, index, source);
+        });
+    });
+  }
+}
diff --git a/modules/geolocation_geometry/src/GeometryFormat/WKT.php b/modules/geolocation_geometry/src/GeometryFormat/WKT.php
index fe318e0482b51e950751b2906dafdf0e3a2086de..fd60c19b0cb8b8e1297f3e758a63857d95780bf6 100644
--- a/modules/geolocation_geometry/src/GeometryFormat/WKT.php
+++ b/modules/geolocation_geometry/src/GeometryFormat/WKT.php
@@ -82,11 +82,12 @@ class WKT implements GeometryFormatInterface { // phpcs:ignore
       case 'Polygon':
       case 'MultiPolygon':
         $components = [];
+        /** @var string $subvalue */
         foreach (preg_split('/\)\s*,\s*\(/', $value) as $subvalue) {
-          if ($subvalue[0] ?? FALSE == '(') {
+          if (($subvalue[0] ?? FALSE) === '(') {
             $subvalue = substr($subvalue, 1);
           }
-          if ($subvalue[strlen($subvalue) - 1] ?? FALSE == ')') {
+          if (($subvalue[strlen($subvalue) - 1] ?? FALSE) == ')') {
             $subvalue = substr($subvalue, 0, -1);
           }
 
diff --git a/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php b/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php
index b37d59a798d8592b600f544dc00952ad9c1a01e5..a7b5cad665f9fb8a447e057a38311af19d0e137c 100644
--- a/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php
+++ b/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php
@@ -12,6 +12,8 @@ use Drupal\Core\TypedData\MapDataDefinition;
  * Class Geolocation Geometry Base.
  *
  * @package Drupal\geolocation_geometry\Plugin\Field\FieldType
+ *
+ * @property string $geojson
  */
 abstract class GeolocationGeometryBase extends FieldItemBase {
 
@@ -134,6 +136,7 @@ abstract class GeolocationGeometryBase extends FieldItemBase {
    *   Coordinates.
    */
   protected static function getRandomCoordinates(?array $reference_point = NULL, float $range = 5): array {
+    // @todo Update to Gemeotry!
     if ($reference_point) {
       return [
         'latitude' => rand(
diff --git a/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php b/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..41a46fcb82af76a469b1229e9867bd577d7f8fc9
--- /dev/null
+++ b/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\geolocation_geometry\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationMapWidgetBase;
+use Drupal\geolocation_geometry\Plugin\geolocation\DataProvider\GeolocationGeometry;
+
+/**
+ * Plugin implementation of the 'geolocation_map' widget.
+ */
+abstract class GeolocationGeometryMapWidget extends GeolocationMapWidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
+    $element['#type'] = 'container';
+    $element['#attributes'] = [
+      'data-geometry-type' => str_replace('geolocation_geometry_', '', $this->fieldDefinition->getType()),
+      'class' => [
+        str_replace('_', '-', $this->getPluginId()) . '-geojson',
+      ],
+    ];
+
+    $element['geojson'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('GeoJSON'),
+      '#default_value' => $items[$delta]->geojson ?? NULL,
+      '#empty_value' => '',
+      '#required' => $element['#required'],
+      '#attributes' => [
+        'class' => [
+          'geolocation-geometry-widget-geojson-input',
+        ],
+      ],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL): array {
+    $element = parent::form($items, $form, $form_state, $get_delta);
+
+    $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], [
+      'drupalSettings' => [
+        'geolocation' => [
+          'widgetSettings' => [
+            $element['#attributes']['id'] => [
+              'widgetSubscribers' => [
+                'geolocation_geometry_field' => [
+                  'import_path' => base_path() . $this->moduleHandler->getModule('geolocation_geometry')->getPath() . '/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js',
+                  'settings' => [
+                    'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+                    'field_name' => $this->fieldDefinition->getName(),
+                    'field_type' => $this->fieldDefinition->getType(),
+                  ],
+                ],
+                'geolocation_geometry_map' => [
+                  'import_path' => base_path() . $this->moduleHandler->getModule('geolocation_geometry')->getPath() . '/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js',
+                  'settings' => [
+                    'mapId' => $element['map']['#id'],
+                    'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+                    'field_name' => $this->fieldDefinition->getName(),
+                    'field_type' => $this->fieldDefinition->getType(),
+                    'feature_id' => $this->getWidgetFeatureId(),
+                  ],
+                ],
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+
+    /**
+     * @var Integer $index
+     * @var \Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryLinestring|\Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryPolygon|\Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryPoint $item
+     */
+    foreach ($items as $index => $item) {
+      if ($item->isEmpty()) {
+        continue;
+      }
+
+      if (!json_validate($item->get('geojson')->getValue())) {
+        continue;
+      }
+
+      $element['map']['locations']['location-' . $index] = GeolocationGeometry::getRenderedElementByGeoJSON(json_decode($item->get('geojson')->getValue()));
+      $element['map']['locations']['location-' . $index]['#title'] = ($index + 1);
+      $element['map']['locations']['location-' . $index]['#label'] = ($index + 1);
+      $element['map']['locations']['location-' . $index]['#attributes'] = [
+        'data-geolocation-widget-index' => $index,
+      ];
+    }
+
+    return $element;
+  }
+
+}
diff --git a/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php b/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php
index cef20673b80d2338fd4b4e05d8332fe34e11664e..6c92d197f852c5a845771036895d238e260487f3 100644
--- a/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php
+++ b/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php
@@ -31,10 +31,10 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
 
     $settings['stroke_color'] = '#FF0044';
     $settings['stroke_width'] = 1;
-    $settings['stroke_opacity'] = 0.8;
+    $settings['stroke_opacity'] = 0.9;
 
     $settings['fill_color'] = '#0033FF';
-    $settings['fill_opacity'] = 0.1;
+    $settings['fill_opacity'] = 0.2;
 
     return $settings;
 
@@ -178,13 +178,13 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
   public function getShapesFromItem(FieldItemInterface $fieldItem): array {
     $settings = $this->getSettings();
 
-    $geometries = [];
+    $shapes = [];
 
-    foreach ($this->getShapesFromGeoJson($fieldItem->get('geojson')->getString()) as $shapeElement) {
-      $geometries[] = self::getRenderedElementByGeoJSON($shapeElement, $settings);
+    foreach ($this->getShapeGeometriesFromGeoJson($fieldItem->get('geojson')->getString()) as $geometry) {
+      $shapes[] = self::getRenderedElementByGeoJSON($geometry, $settings);
     }
 
-    return $geometries;
+    return $shapes;
   }
 
   /**
@@ -193,13 +193,13 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
   public function getLocationsFromItem(FieldItemInterface $fieldItem): array {
     $settings = $this->getSettings();
 
-    $positions = [];
+    $locations = [];
 
-    foreach ($this->getLocationsFromGeoJson($fieldItem->get('geojson')->getString()) as $location) {
-      $positions[] = self::getRenderedElementByGeoJSON($location, $settings);
+    foreach ($this->getLocationGeometriesFromGeoJson($fieldItem->get('geojson')->getString()) as $geometry) {
+      $locations[] = self::getRenderedElementByGeoJSON($geometry, $settings);
     }
 
-    return $positions;
+    return $locations;
   }
 
   /**
@@ -259,17 +259,10 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
         return $container;
 
       case 'Polygon':
-        $geometry = [
-          'type' => 'polygon',
-          'points' => [],
-        ];
-        foreach ($geojson->coordinates[0] as $coordinate) {
-          $geometry['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]];
-        }
-
         return [
-          '#type' => 'geolocation_map_geometry',
-          '#geometry' => $geometry,
+          '#type' => 'geolocation_map_shape',
+          '#geometry' => json_encode($geojson),
+          '#geometry_type' => 'polygon',
           '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'],
           '#stroke_width' => (int) $settings['stroke_width'],
           '#stroke_opacity' => (float) $settings['stroke_opacity'],
@@ -278,23 +271,9 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
         ];
 
       case 'MultiPolygon':
-        $geometry = [
-          'type' => 'multipolygon',
-          'polygons' => [],
-        ];
-        foreach ($geojson->coordinates as $current_polygon) {
-          $polygon = [
-            'type' => 'polygon',
-            'points' => [],
-          ];
-          foreach ($current_polygon[0] as $coordinate) {
-            $polygon['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]];
-          }
-          $geometry['polygons'][] = $polygon;
-        }
         return [
-          '#type' => 'geolocation_map_geometry',
-          '#geometry' => $geometry,
+          '#type' => 'geolocation_map_shape',
+          '#geometry' => json_encode($geojson),
           '#geometry_type' => 'multipolygon',
           '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'],
           '#stroke_width' => (int) $settings['stroke_width'],
@@ -304,40 +283,20 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
         ];
 
       case 'LineString':
-        $geometry = [
-          'type' => 'line',
-          'points' => [],
-        ];
-        foreach ($geojson->coordinates as $coordinate) {
-          $geometry['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]];
-        }
-
         return [
-          '#type' => 'geolocation_map_geometry',
-          '#$geometry' => $geometry,
+          '#type' => 'geolocation_map_shape',
+          '#geometry' => json_encode($geojson),
+          '#geometry_type' => 'line',
           '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'],
           '#stroke_width' => (int) $settings['stroke_width'],
           '#stroke_opacity' => (float) $settings['stroke_opacity'],
         ];
 
       case 'MultiLineString':
-        $geometry = [
-          'type' => 'multiline',
-          'lines' => [],
-        ];
-        foreach ($geojson->coordinates as $current_line) {
-          $line = [
-            'type' => 'line',
-            'points' => [],
-          ];
-          foreach ($current_line as $coordinate) {
-            $line['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]];
-          }
-          $geometry['lines'][] = $line;
-        }
         return [
-          '#type' => 'geolocation_map_geometry',
-          '#geometry' => $geometry,
+          '#type' => 'geolocation_map_shape',
+          '#geometry' => json_encode($geojson),
+          '#geometry_type' => 'multiline',
           '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'],
           '#stroke_width' => (int) $settings['stroke_width'],
           '#stroke_opacity' => (float) $settings['stroke_opacity'],
@@ -373,8 +332,8 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
    * @return array
    *   Shapes.
    */
-  protected function getShapesFromGeoJson(string $geoJson): array {
-    $shapes = [];
+  protected function getShapeGeometriesFromGeoJson(string $geoJson): array {
+    $geometries = [];
 
     $json = json_decode($geoJson);
 
@@ -394,21 +353,21 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
           if (empty($entry->features)) {
             continue 2;
           }
-          $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features)));
+          $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features)));
           break;
 
         case 'Feature':
           if (empty($entry->geometry)) {
             continue 2;
           }
-          $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry)));
+          $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry)));
           break;
 
         case 'GeometryCollection':
           if (empty($entry->geometries)) {
             continue 2;
           }
-          $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries)));
+          $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries)));
           break;
 
         case 'MultiPolygon':
@@ -418,12 +377,12 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
           if (empty($entry->coordinates)) {
             continue 2;
           }
-          $shapes[] = $entry;
+          $geometries[] = $entry;
           break;
       }
     }
 
-    return $shapes;
+    return $geometries;
   }
 
   /**
@@ -435,8 +394,8 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
    * @return array
    *   Locations.
    */
-  protected function getLocationsFromGeoJson(string $geoJson): array {
-    $locations = [];
+  protected function getLocationGeometriesFromGeoJson(string $geoJson): array {
+    $geometries = [];
 
     $json = json_decode($geoJson);
 
@@ -456,21 +415,21 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
           if (empty($entry->features)) {
             continue 2;
           }
-          $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features)));
+          $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features)));
           break;
 
         case 'Feature':
           if (empty($entry->geometry)) {
             continue 2;
           }
-          $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry)));
+          $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry)));
           break;
 
         case 'GeometryCollection':
           if (empty($entry->geometries)) {
             continue 2;
           }
-          $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries)));
+          $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries)));
           break;
 
         case 'MultiPoint':
@@ -478,12 +437,12 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf
           if (empty($entry->coordinates)) {
             continue 2;
           }
-          $locations[] = $entry;
+          $geometries[] = $entry;
           break;
       }
     }
 
-    return $locations;
+    return $geometries;
   }
 
 }
diff --git a/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php b/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php
index d8d6a153688251e59c9dfa3f63ea527d45f4f027..2049417a0d939d00e0ac19a802ff0869210462aa 100644
--- a/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php
+++ b/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php
@@ -33,7 +33,8 @@ class GeoProximityField extends ProximityField {
 
     // Get a placeholder for this query and save the field_alias for it.
     // Remove the initial ':' from the placeholder and avoid collision with
-    // original field name.
+    // the original field name.
+    // @phpstan-ignore-next-line
     $this->field_alias = $query->addField(NULL, $expression, substr($this->placeholder(), 1));
   }
 
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php
index 4b7ac0da538bf3c58187d1e43eb27ed19f74bccc..4b2c587f2f12fc676f0d3499a89cdacd7da07e60 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php
@@ -19,7 +19,7 @@ class GeolocationContains extends JoinPluginBase implements JoinPluginInterface
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php
index fe520ee60cb001c0e0b0e1e8cb0f944d8310bdea..619aa0e41b98bbdf2673addbc608deea40da885a 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php
@@ -19,7 +19,7 @@ class GeolocationGeometryContains extends JoinPluginBase implements JoinPluginIn
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php
index 705121eee019b34e776a26c971479079ddc1714d..9be11817e94fb527856e7f4527d9b23e726ad4af 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php
@@ -19,7 +19,7 @@ class GeolocationGeometryIntersects extends JoinPluginBase implements JoinPlugin
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php
index e4ec5cca989fa86982a538d06aa632ab23106e39..0f47728c8fa41e191919d78b937215288c6660da 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php
@@ -19,7 +19,7 @@ class GeolocationGeometryWithin extends JoinPluginBase implements JoinPluginInte
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php
index 2475022862a226ed66282182b15dd8f895c778cb..0a5cca09452e309151b07eb1355bfbfd6febb050 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php
@@ -19,7 +19,7 @@ class GeolocationIntersects extends JoinPluginBase implements JoinPluginInterfac
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php
index e9740a6248e7a6b2d7269a1edc686077556493f8..67d2b3c63e51ac29381bc216d388d4d0641b19ca 100644
--- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php
+++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php
@@ -19,7 +19,7 @@ class GeolocationWithin extends JoinPluginBase implements JoinPluginInterface {
    *
    * @param \Drupal\Core\Database\Query\SelectInterface $select_query
    *   Select query.
-   * @param array $table
+   * @param string|array $table
    *   Table data.
    * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query
    *   View query.
diff --git a/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml b/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml
index 1cf7efcb354da923c67b82dc8e26a7c76f39f8fe..a77d081bc70c6ddcbe49da03b672cfcd907e8d03 100644
--- a/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml
+++ b/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml
@@ -2,10 +2,3 @@ geolocation_google_maps.loader:
   version: 4.x
   js:
     js/geolocation-google-maps-loader.js: {}
-
-widget.google_maps.geojson:
-  version: 4.x
-  js:
-    js/geolocation-geometry-widget-google-maps.js: {}
-  dependencies:
-    - geolocation/geolocation.map
diff --git a/modules/geolocation_google_maps/js/GoogleShapeLine.js b/modules/geolocation_google_maps/js/GoogleShapeLine.js
index f5b7c6aaeb1928365e5974091d42cfeba39d633b..e22b72dfc949acadfc92588d5cb1f455470d23fb 100644
--- a/modules/geolocation_google_maps/js/GoogleShapeLine.js
+++ b/modules/geolocation_google_maps/js/GoogleShapeLine.js
@@ -4,24 +4,30 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.
 
 /**
  * @prop {GoogleMaps} map
+ *
+ * @mixes GoogleShapeTrait
  */
 export class GoogleShapeLine extends GeolocationShapeLine {
   constructor(geometry, settings = {}, map) {
     super(geometry, settings, map);
 
-    this.googleShapeTrait = new GoogleShapeTrait();
+    Object.assign(this, GoogleShapeTrait);
 
     this.googleShapes = [];
 
     const line = new google.maps.Polyline({
-      path: geometry.points,
+      path: [
+        geometry.coordinates.map((value) => {
+          return { lat: value[1], lng: value[0] };
+        }),
+      ],
       strokeColor: this.strokeColor,
       strokeOpacity: this.strokeOpacity,
       strokeWeight: this.strokeWidth,
     });
 
     if (this.title) {
-      this.googleShapeTrait.setTitle(line, this.title, this.map);
+      this.setTitle(line, this.title, this.map);
     }
 
     line.addListener("click", (event) => {
@@ -35,7 +41,7 @@ export class GoogleShapeLine extends GeolocationShapeLine {
 
   remove() {
     this.googleShapes.forEach((googleShape) => {
-      googleShape.remove();
+      googleShape.setMap();
     });
 
     super.remove();
diff --git a/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js b/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js
index 348c22c6d9da02dc753d621f7968e654e5e0716f..d6b5b1225c112d85ada8c6a33f930b0ee00d630b 100644
--- a/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js
+++ b/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js
@@ -4,24 +4,30 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.
 
 /**
  * @prop {GoogleMaps} map
+ *
+ * @mixes GoogleShapeTrait
  */
 export class GoogleShapeMultiLine extends GeolocationShapeMultiLine {
   constructor(geometry, settings = {}, map) {
     super(geometry, settings, map);
 
-    this.googleShapeTrait = new GoogleShapeTrait();
+    Object.assign(this, GoogleShapeTrait);
 
     this.googleShapes = [];
     this.geometry.lines.forEach((lineGeometry) => {
       const line = new google.maps.Polyline({
-        path: lineGeometry.points,
+        path: [
+          lineGeometry.coordinates.map((value) => {
+            return { lat: value[1], lng: value[0] };
+          }),
+        ],
         strokeColor: this.strokeColor,
         strokeOpacity: this.strokeOpacity,
         strokeWeight: this.strokeWidth,
       });
 
       if (this.title) {
-        this.googleShapeTrait.setTitle(line, this.title, this.map);
+        this.setTitle(line, this.title, this.map);
       }
 
       line.addListener("click", (event) => {
@@ -36,7 +42,7 @@ export class GoogleShapeMultiLine extends GeolocationShapeMultiLine {
 
   remove() {
     this.googleShapes.forEach((googleShape) => {
-      googleShape.remove();
+      googleShape.setMap();
     });
 
     super.remove();
diff --git a/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js b/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js
index 9d3db0d3b4d38e5f3f0c11314117ad2b19dd8cae..ef3454d618478f8fd35e7048632c9fa772017b16 100644
--- a/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js
+++ b/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js
@@ -4,17 +4,23 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.
 
 /**
  * @prop {GoogleMaps} map
+ *
+ * @mixes GoogleShapeTrait
  */
 export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon {
   constructor(geometry, settings = {}, map) {
     super(geometry, settings, map);
 
-    this.googleShapeTrait = new GoogleShapeTrait();
+    Object.assign(this, GoogleShapeTrait);
 
     this.googleShapes = [];
-    this.geometry.polygons.forEach((polygonGeometry) => {
+    this.geometry.coordinates.forEach((polygonGeometry) => {
       const polygon = new google.maps.Polygon({
-        paths: polygonGeometry.points,
+        paths: [
+          polygonGeometry.coordinates[0].map((value) => {
+            return { lat: value[1], lng: value[0] };
+          }),
+        ],
         strokeColor: this.strokeColor,
         strokeOpacity: this.strokeOpacity,
         strokeWeight: this.strokeWidth,
@@ -22,7 +28,7 @@ export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon {
         fillOpacity: this.fillOpacity,
       });
       if (this.title) {
-        this.googleShapeTrait.setTitle(polygon, this.title, this.map);
+        this.setTitle(polygon, this.title, this.map);
       }
 
       polygon.addListener("click", (event) => {
@@ -37,7 +43,7 @@ export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon {
 
   remove() {
     this.googleShapes.forEach((googleShape) => {
-      googleShape.remove();
+      googleShape.setMap();
     });
 
     super.remove();
diff --git a/modules/geolocation_google_maps/js/GoogleShapePolygon.js b/modules/geolocation_google_maps/js/GoogleShapePolygon.js
index 288c421a42d4000e75a3006577633abf14b99be5..b17d8c919323a781ad7ab1625b818a061046601a 100644
--- a/modules/geolocation_google_maps/js/GoogleShapePolygon.js
+++ b/modules/geolocation_google_maps/js/GoogleShapePolygon.js
@@ -4,17 +4,24 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.
 
 /**
  * @prop {GoogleMaps} map
+ * @prop {Array} googleShapes
+ *
+ * @mixes GoogleShapeTrait
  */
 export class GoogleShapePolygon extends GeolocationShapePolygon {
   constructor(geometry, settings = {}, map) {
     super(geometry, settings, map);
 
-    this.googleShapeTrait = new GoogleShapeTrait();
+    Object.assign(this, GoogleShapeTrait);
 
     this.googleShapes = [];
 
     const polygon = new google.maps.Polygon({
-      paths: geometry.points,
+      paths: [
+        geometry.coordinates[0].map((value) => {
+          return { lat: value[1], lng: value[0] };
+        }),
+      ],
       strokeColor: this.strokeColor,
       strokeOpacity: this.strokeOpacity,
       strokeWeight: this.strokeWidth,
@@ -23,7 +30,7 @@ export class GoogleShapePolygon extends GeolocationShapePolygon {
     });
 
     if (this.title) {
-      this.googleShapeTrait.setTitle(polygon, this.title, this.map);
+      this.setTitle(polygon, this.title, this.map);
     }
 
     polygon.addListener("click", (event) => {
@@ -37,7 +44,7 @@ export class GoogleShapePolygon extends GeolocationShapePolygon {
 
   remove() {
     this.googleShapes.forEach((googleShape) => {
-      googleShape.remove();
+      googleShape.setMap();
     });
 
     super.remove();
diff --git a/modules/geolocation_google_maps/js/GoogleShapeTrait.js b/modules/geolocation_google_maps/js/GoogleShapeTrait.js
index fbe695c2e0b4d1b55b7006d6305bae77d539e021..cb1b08fccb97a176783ae318f641a42a92c1465a 100644
--- a/modules/geolocation_google_maps/js/GoogleShapeTrait.js
+++ b/modules/geolocation_google_maps/js/GoogleShapeTrait.js
@@ -1,4 +1,11 @@
-export class GoogleShapeTrait {
+import { GeolocationGeometry } from "../../../js/Base/GeolocationGeometry.js";
+
+/**
+ * Google Shape support functions.
+ *
+ * @mixin
+ */
+export const GoogleShapeTrait = {
   /**
    * @param {google.maps.MVCObject} shape
    * @param {String} title
@@ -19,5 +26,46 @@ export class GoogleShapeTrait {
     google.maps.event.addListener(shape, "mouseout", () => {
       infoWindow.close();
     });
-  }
-}
+  },
+
+  /**
+   *
+   * @param {google.maps.Polyline|google.maps.Polygon|google.maps.Rectangle} googleShape
+   */
+  updateByGoogleShape(googleShape) {
+    if (googleShape instanceof google.maps.Rectangle) {
+      this.geometry = new GeolocationGeometry("Polygon", [
+        [
+          [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getNorthEast().lat()],
+          [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getSouthWest().lat()],
+          [googleShape.getBounds().getNorthEast().lng(), googleShape.getBounds().getSouthWest().lat()],
+          [googleShape.getBounds().getNorthEast().lng(), googleShape.getBounds().getNorthEast().lat()],
+          [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getNorthEast().lat()],
+        ],
+      ]);
+    } else if (googleShape instanceof google.maps.Polyline) {
+      const coordinates = [];
+      googleShape.getPath().forEach((coordinate) => {
+        coordinates.push([coordinate.lng(), coordinate.lat()]);
+      });
+      this.geometry = new GeolocationGeometry("Polyline", coordinates);
+    } else if (googleShape instanceof google.maps.Polygon) {
+      const coordinates = [];
+      googleShape.getPaths().forEach((path) => {
+        let first = null;
+        path.forEach((coordinate) => {
+          if (first === null) {
+            first = coordinate;
+          }
+          coordinates.push([coordinate.lng(), coordinate.lat()]);
+        });
+        coordinates.push([first.lng(), first.lat()]);
+      });
+      this.geometry = new GeolocationGeometry("Polygon", [coordinates]);
+    } else {
+      return false;
+    }
+
+    this.googleShapes.push(googleShape);
+  },
+};
diff --git a/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js b/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js
new file mode 100644
index 0000000000000000000000000000000000000000..e155e51ed5470e109a268c05ffdaffd3e95cb7b4
--- /dev/null
+++ b/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js
@@ -0,0 +1,310 @@
+import { GoogleMapFeature } from "./GoogleMapFeature.js";
+import { GeolocationCoordinates } from "../../../../js/Base/GeolocationCoordinates.js";
+
+/**
+ * @prop {google.maps.drawing.DrawingManager} drawingManager
+ */
+export default class GoogleGeometryWidgetMapConnector extends GoogleMapFeature {
+  constructor(settings, map) {
+    super(settings, map);
+
+    let drawingModes;
+
+    switch (this.settings.field_type.replace("geolocation_geometry_", "")) {
+      case "multiline":
+      case "line":
+        drawingModes = [google.maps.drawing.OverlayType.POLYLINE];
+        break;
+
+      case "multipolygon":
+      case "polygon":
+        drawingModes = [google.maps.drawing.OverlayType.RECTANGLE, google.maps.drawing.OverlayType.POLYGON];
+        break;
+
+      case "multipoint":
+      case "point":
+        drawingModes = [google.maps.drawing.OverlayType.MARKER];
+        break;
+
+      default:
+        drawingModes = [google.maps.drawing.OverlayType.MARKER, google.maps.drawing.OverlayType.POLYLINE, google.maps.drawing.OverlayType.POLYGON, google.maps.drawing.OverlayType.RECTANGLE];
+        break;
+    }
+
+    this.drawingManager = new google.maps.drawing.DrawingManager({
+      drawingMode: null,
+      drawingControl: true,
+      drawingControlOptions: {
+        position: google.maps.ControlPosition.TOP_CENTER,
+        drawingModes,
+      },
+    });
+
+    this.drawingManager.setMap(this.map.googleMap);
+  }
+
+  onMapReady() {
+    google.maps.event.addListener(this.drawingManager, "overlaycomplete", (event) => {
+      let currentElementCount = 0;
+      let newIndex = 0;
+
+      switch (this.settings.field_type.replace("geolocation_geometry_", "")) {
+        case "point":
+        case "multipoint":
+          this.map.dataLayers.get("default").markers.forEach((element) => {
+            currentElementCount++;
+            const currentElementIndex = this.getIndexByShape(element);
+            if (currentElementIndex === null) {
+              return;
+            }
+            if (currentElementIndex >= newIndex) {
+              newIndex = currentElementIndex + 1;
+            }
+          });
+          break;
+
+        default:
+          this.map.dataLayers.get("default").shapes.forEach((element) => {
+            currentElementCount++;
+            const currentElementIndex = this.getIndexByShape(element);
+            if (currentElementIndex === null) {
+              return;
+            }
+            if (currentElementIndex >= newIndex) {
+              newIndex = currentElementIndex + 1;
+            }
+          });
+      }
+
+      if (this.settings.cardinality <= currentElementCount && this.settings.cardinality !== -1) {
+        const warning = document.createElement("div");
+        warning.innerHTML = `<p>${Drupal.t("Maximum number of element reached.")}</p>`;
+        Drupal.dialog(warning, {
+          title: Drupal.t("Synchronization"),
+        }).showModal();
+        return;
+      }
+
+      event.overlay.setEditable(true);
+
+      switch (event.type) {
+        case google.maps.drawing.OverlayType.MARKER:
+          break;
+
+        case google.maps.drawing.OverlayType.POLYLINE:
+          break;
+
+        case google.maps.drawing.OverlayType.POLYGON:
+        case google.maps.drawing.OverlayType.RECTANGLE:
+          const polygon = this.map.createShapePolygon({ type: "Polygon", coordinates: [[]] }, {});
+          this.setIndexByShape(polygon, newIndex);
+          polygon.updateByGoogleShape(event.overlay);
+
+          this.map.dataLayers.get("default").shapeAdded(polygon);
+          break;
+      }
+    });
+
+    return Promise.resolve();
+  }
+
+  setWidgetSubscriber(subscriber) {
+    this.subscriber = subscriber;
+  }
+
+  /**
+   * @param {GeolocationShape} shape
+   *   Marker.
+   *
+   * @return {int|null}
+   *   Index.
+   */
+  getIndexByShape(shape) {
+    return Number(shape.wrapper.dataset.geolocationWidgetIndex ?? 0);
+  }
+
+  /**
+   * @param {int} index
+   *   Index.
+   *
+   * @return {GeolocationShape|null}
+   *   Shape.
+   */
+  getShapeByIndex(index) {
+    let returnValue = null;
+    this.map.dataLayers.get("default").shapes.forEach((shape) => {
+      if (index === this.getIndexByShape(shape)) {
+        returnValue = shape;
+      }
+    });
+
+    return returnValue;
+  }
+
+  /**
+   * @param {GeolocationShape} shape
+   *   Marker.
+   * @param {int|false} index
+   *   Index.
+   */
+  setIndexByShape(shape, index = false) {
+    if (index === false) {
+      delete shape.wrapper.dataset.geolocationWidgetIndex;
+    } else {
+      shape.wrapper.dataset.geolocationWidgetIndex = index.toString();
+    }
+  }
+
+  /**
+   * @param {Number} index
+   *   Index.
+   *
+   * @return {String}
+   *   Title.
+   */
+  getShapeTitle(index) {
+    return `${index + 1}`;
+  }
+
+  addShapeSilently(index, geometry) {
+    let shape;
+
+    switch (geometry.type) {
+      case "Point":
+        const coordinates = new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0]);
+        const marker = this.map.createMarker(coordinates, {
+          title: this.getMarkerTitle(index, coordinates),
+          label: index + 1,
+          draggable: true,
+        });
+
+        this.setIndexByMarker(marker, index);
+
+        marker.geolocationWidgetIgnore = true;
+        this.map.dataLayers.get("default").markerAdded(marker);
+        delete marker.geolocationWidgetIgnore;
+
+        this.map.fitMapToElements();
+
+        return marker;
+
+      case "LineString":
+        shape = this.map.createShapeLine(geometry, {
+          title: this.getShapeTitle(index),
+          label: index + 1,
+          draggable: true,
+        });
+        break;
+
+      case "Polygon":
+        shape = this.map.createShapePolygon(geometry, {
+          title: this.getShapeTitle(index),
+          label: index + 1,
+          draggable: true,
+        });
+        break;
+    }
+
+    if (!shape) return;
+
+    this.setIndexByShape(shape, index);
+
+    shape.geolocationWidgetIgnore = true;
+    this.map.dataLayers.get("default").shapeAdded(shape);
+    delete shape.geolocationWidgetIgnore;
+
+    this.map.fitMapToElements();
+
+    return shape;
+  }
+
+  reorderSilently(newOrder) {
+    this.map.dataLayers.get("default").markers.forEach((marker) => {
+      const oldIndex = this.getIndexByMarker(marker);
+      const newIndex = newOrder.indexOf(oldIndex);
+
+      marker.geolocationWidgetIgnore = true;
+      marker.update(null, {
+        title: this.getShapeTitle(newIndex),
+        label: newIndex + 1,
+      });
+      delete marker.geolocationWidgetIgnore;
+
+      this.setIndexByMarker(marker, newIndex);
+    });
+  }
+
+  updateShapeSilently(index, geometry, settings = null) {
+    const shape = this.getShapeByIndex(index);
+
+    if (!shape) return this.addShapeSilently(index, geometry);
+
+    if (shape.geometry === geometry && !settings) {
+      return;
+    }
+
+    shape.geolocationWidgetIgnore = true;
+    shape.update(geometry, settings ?? {});
+    delete shape.geolocationWidgetIgnore;
+
+    this.map.fitMapToElements();
+
+    return shape;
+  }
+
+  removeShapeSilently(index) {
+    const shape = this.getShapeByIndex(index);
+
+    shape.geolocationWidgetIgnore = true;
+    shape.remove();
+
+    this.map.fitMapToElements();
+  }
+
+  onShapeAdded(shape) {
+    super.onShapeAdded(shape);
+
+    shape.googleShapes.forEach((googleShape) => {
+      if (!googleShape.getEditable()) {
+        googleShape.setEditable(true);
+      }
+    });
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryAdded(shape.geometry, this.getIndexByShape(shape) ?? 0);
+  }
+
+  onShapeClicked(shape, coordinates) {
+    super.onShapeClicked(shape, coordinates);
+    let editing = false;
+    shape.googleShapes.forEach((googleShape) => {
+      if (googleShape.getEditable()) {
+        editing = true;
+      }
+    });
+
+    if (editing) {
+      return;
+    }
+
+    // Will trigger onShapeRemove and notify broker.
+    shape.remove();
+  }
+
+  onShapeUpdated(shape) {
+    super.onShapeUpdated(shape);
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryAltered(shape.geometry, this.getIndexByShape(shape));
+  }
+
+  onShapeRemove(shape) {
+    super.onShapeRemove(shape);
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryRemoved(this.getIndexByShape(shape));
+  }
+}
diff --git a/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js b/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js
index 7e290d51dab7cd5f1bfa217d735a4bc0fd18e367..d8fa2288b30973d1e96fb255e139140db39cd157 100644
--- a/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js
+++ b/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js
@@ -265,18 +265,30 @@ export default class GoogleMaps extends GeolocationMapBase {
     return new GoogleCircle(center, radius, this, settings);
   }
 
+  /**
+   * @return {GoogleShapeLine}
+   */
   createShapeLine(geometry, settings) {
     return new GoogleShapeLine(geometry, settings, this);
   }
 
+  /**
+   * @return {GoogleShapePolygon}
+   */
   createShapePolygon(geometry, settings) {
     return new GoogleShapePolygon(geometry, settings, this);
   }
 
+  /**
+   * @return {GoogleShapeMultiLine}
+   */
   createShapeMultiLine(geometry, settings) {
     return new GoogleShapeMultiLine(geometry, settings, this);
   }
 
+  /**
+   * @return {GoogleShapeMultiPolygon}
+   */
   createShapeMultiPolygon(geometry, settings) {
     return new GoogleShapeMultiPolygon(geometry, settings, this);
   }
diff --git a/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js b/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js
deleted file mode 100644
index eebfd33160bad6ec5f77b261eeca0e7a454955d0..0000000000000000000000000000000000000000
--- a/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * @file
- * Javascript for the geolocation geometry Google Maps widget.
- */
-
-/**
- * @typedef {Object} GoogleGeojsonData
- *
- * @property {Object[]} features
- */
-
-(function (Drupal) {
-  /**
-   * Google maps GeoJSON widget.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Widget.
-   */
-  Drupal.behaviors.geolocationGeometryWidgetGoogleMaps = {
-    attach: (context) => {
-      context.querySelectorAll(".geolocation-geometry-widget-google-maps-geojson").forEach((item) => {
-        if (item.classList.contains("processed")) {
-          return;
-        }
-        item.classList.add("processed");
-
-        const mapWrapper = item.querySelector(".geolocation-geometry-widget-google-maps-geojson-map");
-        const inputWrapper = item.querySelector(".geolocation-geometry-widget-google-maps-geojson-input");
-        const geometryType = item.getAttribute("data-geometry-type");
-
-        Drupal.geolocation.maps.getMap(mapWrapper.getAttribute("id")).then(
-          /** @param {GoogleMaps} map */ (map) => {
-            let availableControls = [];
-            switch (geometryType) {
-              case "polygon":
-              case "multipolygon":
-                availableControls = ["Polygon"];
-                break;
-
-              case "polyline":
-              case "multipolyline":
-                availableControls = ["LineString"];
-                break;
-
-              case "point":
-              case "multipoint":
-                availableControls = ["Point"];
-                break;
-
-              default:
-                availableControls = ["Point", "LineString", "Polygon"];
-                break;
-            }
-
-            map.googleMap.data.setControls(availableControls);
-            map.googleMap.data.setControlPosition(google.maps.ControlPosition.TOP_CENTER);
-            map.googleMap.data.setStyle({
-              editable: true,
-              draggable: true,
-            });
-
-            if (inputWrapper.value) {
-              try {
-                const geometry = JSON.parse(inputWrapper.value);
-                map.googleMap.data.addGeoJson({
-                  type: "FeatureCollection",
-                  features: [
-                    {
-                      type: "Feature",
-                      id: "value",
-                      geometry,
-                    },
-                  ],
-                });
-              } catch (error) {
-                console.error(error.message);
-                return;
-              }
-
-              const bounds = new google.maps.LatLngBounds();
-              map.googleMap.data.forEach(function (feature) {
-                feature.getGeometry().forEachLatLng(function (latlng) {
-                  bounds.extend(latlng);
-                });
-              });
-              map.setBoundaries(map.normalizeBoundaries(bounds));
-            }
-
-            function refreshGeoJsonFromData() {
-              map.googleMap.data.toGeoJson(
-                /** @param {GoogleGeojsonData} geoJson */ (geoJson) => {
-                  if (typeof geoJson.features === "undefined") {
-                    inputWrapper.value = "";
-                  }
-
-                  switch (geoJson.features.length) {
-                    case 0:
-                      inputWrapper.value = "";
-                      break;
-
-                    case 1:
-                      inputWrapper.value = JSON.stringify(geoJson.features[0].geometry);
-                      break;
-
-                    default: {
-                      const types = {
-                        multi_polygon: "MultiPolygon",
-                        multi_polyline: "MultiPolyline",
-                        multi_point: "MultiPoint",
-                        default: "GeometryCollection",
-                      };
-
-                      const geometry = {
-                        type: types[geometryType] || types.default,
-                        geometries: [],
-                      };
-
-                      geoJson.features.forEach(function (feature) {
-                        geometry.geometries.push(feature.geometry);
-                      });
-                      inputWrapper.value = JSON.stringify(geometry);
-                      break;
-                    }
-                  }
-                }
-              );
-            }
-
-            function bindDataLayerListeners(dataLayer) {
-              dataLayer.addListener("addfeature", refreshGeoJsonFromData);
-              dataLayer.addListener("removefeature", refreshGeoJsonFromData);
-              dataLayer.addListener("setgeometry", refreshGeoJsonFromData);
-
-              map.googleMap.data.addListener("click", function (event) {
-                const newPolyPoints = [];
-
-                event.feature.getGeometry().forEachLatLng(function (latlng) {
-                  if (!(latlng.lat() === event.latLng.lat() && latlng.lng() === event.latLng.lng())) {
-                    newPolyPoints.push(latlng);
-                  }
-                });
-
-                if (newPolyPoints.length < 2) {
-                  dataLayer.remove(event.feature);
-                } else {
-                  event.feature.setGeometry(new google.maps.Data.Polygon([new google.maps.Data.LinearRing(newPolyPoints)]));
-                }
-              });
-            }
-
-            bindDataLayerListeners(map.googleMap.data);
-
-            inputWrapper.addEventListener("change", () => {
-              const newData = new google.maps.Data({
-                map: map.googleMap,
-                style: map.googleMap.data.getStyle(),
-                controls: availableControls,
-              });
-              try {
-                newData.addGeoJson(JSON.parse(inputWrapper.value));
-              } catch (error) {
-                newData.setMap(null);
-                return;
-              }
-              // No error means GeoJSON was valid!
-              map.googleMap.data.setMap(null);
-              map.googleMap.data = newData;
-              bindDataLayerListeners(newData);
-            });
-          }
-        );
-      });
-    },
-    detach: () => {},
-  };
-})(Drupal);
diff --git a/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php b/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php
index 3d8dedf0b396060d8cdc9a42d52e1df770355baa..584ad6f2ccaff01cc7ddceaa7ad4a13a097a6862 100644
--- a/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php
+++ b/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php
@@ -27,6 +27,7 @@ class GooglePlacesAPI extends GoogleGeocoderBase {
    * {@inheritdoc}
    */
   public function alterRenderArray(array &$render_array, string $identifier): ?array {
+    // @phpstan-ignore-next-line
     $render_array = parent::alterRenderArray($render_array, $identifier);
 
     $render_array['#attached'] = BubbleableMetadata::mergeAttachments(
diff --git a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php
deleted file mode 100644
index 729d5d45a4a62b8be8edd11c8e18a308792a9ba8..0000000000000000000000000000000000000000
--- a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace Drupal\geolocation_google_maps\Plugin\Field\FieldWidget;
-
-use Drupal\Core\Field\Attribute\FieldWidget;
-use Drupal\Core\Field\FieldItemListInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationGeometryWidgetBase;
-
-/**
- * Plugin implementation of 'geolocation_geometry_widget_google_maps' widget.
- */
-#[FieldWidget(
-  id: 'geolocation_geometry_widget_google_maps',
-  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Google Maps API - GeoJSON'),
-  field_types: [
-    'geolocation_geometry_point',
-    'geolocation_geometry_multi_point',
-    'geolocation_geometry_linestring',
-    'geolocation_geometry_multi_linestring',
-    'geolocation_geometry_polygon',
-    'geolocation_geometry_multi_polygon',
-    'geolocation_geometry_geometry',
-    'geolocation_geometry_multi_geometry',
-  ]
-)]
-class GeolocationGeometryWidgetGoogleMaps extends GeolocationGeometryWidgetBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected string $mapProviderId = 'google_maps';
-
-  /**
-   * {@inheritdoc}
-   */
-  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
-    $element = parent::formElement($items, $delta, $element, $form, $form_state);
-
-    $element['#attached'] = [
-      'library' => [
-        'geolocation_google_maps/widget.google_maps.geojson',
-      ],
-    ];
-
-    return $element;
-  }
-
-}
diff --git a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php
new file mode 100644
index 0000000000000000000000000000000000000000..8bdd9188a4b90f2190034a10412fe93a7d8fb362
--- /dev/null
+++ b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\geolocation_google_maps\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\Attribute\FieldWidget;
+use Drupal\geolocation_geometry\Plugin\Field\FieldWidget\GeolocationGeometryMapWidget;
+
+/**
+ * Plugin implementation of 'geolocation_geometry_widget_google' widget.
+ */
+#[FieldWidget(
+  id: 'geolocation_geometry_widget_google',
+  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Google - GeoJSON'),
+  field_types: [
+    'geolocation_geometry_point',
+    'geolocation_geometry_multi_point',
+    'geolocation_geometry_linestring',
+    'geolocation_geometry_polygon',
+  ]
+)]
+class GoogleGeolocationGeometry extends GeolocationGeometryMapWidget {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected ?string $mapProviderId = 'google_maps';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetFeatureId(): string {
+    return 'google_maps_geometry_widget_map_connector';
+  }
+
+}
diff --git a/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php b/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php
new file mode 100644
index 0000000000000000000000000000000000000000..02c8993ac896fb8dc596eddc198f2ec26426eecf
--- /dev/null
+++ b/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\geolocation_google_maps\Plugin\geolocation\MapFeature;
+
+use Drupal\geolocation\Attribute\MapFeature;
+use Drupal\geolocation\Plugin\geolocation\MapFeature\GeolocationFieldWidgetMapConnector;
+
+/**
+ * Provides Recenter control element.
+ */
+#[MapFeature(
+  id: 'google_maps_geometry_widget_map_connector',
+  name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Leaflet Geometry Widget Map Connector'),
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'),
+  type: 'all',
+  hidden: TRUE,
+)]
+class GoogleGeometryWidgetMapConnector extends GeolocationFieldWidgetMapConnector {}
diff --git a/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php b/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php
index 037954493adcd2c1e314136ce312463399e74fc2..e3220b2f5df9d9b2a08c1c991859e678acb01288 100644
--- a/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php
+++ b/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php
@@ -195,7 +195,7 @@ class GeolocationGpxFieldDataProvider extends DataProviderBase implements DataPr
       }
 
       $shapes[] = [
-        '#type' => 'geolocation_map_geometry',
+        '#type' => 'geolocation_map_shape',
         '#geometry' => $geometry,
         '#title' => $track->entity->name?->value ?? $gpx->name->value,
         '#stroke_color' => $settings['track_stroke_color_randomize'] ? sprintf('#%06X', mt_rand(0, 0xFFFFFF)) : $settings['track_stroke_color'],
@@ -217,7 +217,7 @@ class GeolocationGpxFieldDataProvider extends DataProviderBase implements DataPr
       }
 
       $shapes[] = [
-        '#type' => 'geolocation_map_geometry',
+        '#type' => 'geolocation_map_shape',
         '#geometry' => $geometry,
         '#title' => $route->entity->name->toString(),
         '#stroke_color' => $settings['track_stroke_color_randomize'] ? sprintf('#%06X', mt_rand(0, 0xFFFFFF)) : $settings['track_stroke_color'],
diff --git a/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml b/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml
deleted file mode 100644
index a7dabc064879b21531c32f49c74c54c9463043ce..0000000000000000000000000000000000000000
--- a/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-widget.leaflet.geojson:
-  version: 4.x
-  js:
-    js/geolocation-geometry-widget-leaflet.js: {}
-  dependencies:
-    - geolocation/geolocation.map
diff --git a/modules/geolocation_leaflet/js/LeafletShapeLine.js b/modules/geolocation_leaflet/js/LeafletShapeLine.js
index 11fee36e439aae4b449c4824dbb6039e128089e6..069f748ee141c2401d314fd550d5bf2e3029a800 100644
--- a/modules/geolocation_leaflet/js/LeafletShapeLine.js
+++ b/modules/geolocation_leaflet/js/LeafletShapeLine.js
@@ -1,4 +1,5 @@
 import { GeolocationShapeLine } from "../../../js/Base/GeolocationShapeLine.js";
+import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js";
 
 /**
  * @prop {Leaflet} map
@@ -9,20 +10,46 @@ export class LeafletShapeLine extends GeolocationShapeLine {
 
     this.leafletShapes = [];
 
-    const line = L.polyline(geometry.points, {
-      color: settings.strokeColor,
-      opacity: this.strokeOpacity,
-      weight: this.strokeWidth,
-    });
+    this.addShape();
+  }
+
+  addShape() {
+    const line = L.polyline(
+      [
+        this.geometry.coordinates.map((value) => {
+          return { lat: value[1], lng: value[0] };
+        }),
+      ],
+      {
+        color: this.strokeColor,
+        opacity: this.strokeOpacity,
+        weight: this.strokeWidth,
+      }
+    );
+
     if (this.title) {
       line.bindTooltip(this.title);
     }
 
+    line.on("click", (event) => {
+      this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng));
+    });
+
     line.addTo(this.map.leafletMap);
 
     this.leafletShapes.push(line);
   }
 
+  update(geometry, settings) {
+    super.update(geometry, settings);
+
+    this.leafletShapes.forEach((leafletShape) => {
+      leafletShape.remove();
+    });
+
+    this.addShape();
+  }
+
   remove() {
     this.leafletShapes.forEach((leafletShape) => {
       leafletShape.remove();
diff --git a/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js b/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js
index fc5fc978dde94cb3ebab6d18bf8bca15be9332a4..c4d23d01c52008f09211faecf58db259a8503bd9 100644
--- a/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js
+++ b/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js
@@ -1,4 +1,5 @@
 import { GeolocationShapeMultiLine } from "../../../js/Base/GeolocationShapeMultiLine.js";
+import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js";
 
 /**
  * @prop {Leaflet} map
@@ -8,21 +9,47 @@ export class LeafletShapeMultiLine extends GeolocationShapeMultiLine {
     super(geometry, settings, map);
 
     this.leafletShapes = [];
+  }
+
+  addShape() {
     this.geometry.lines.forEach((lineGeometry) => {
-      const line = L.polyline(lineGeometry.points, {
-        color: this.strokeColor,
-        opacity: this.strokeOpacity,
-        weight: this.strokeWidth,
-      });
+      const line = L.polyline(
+        [
+          lineGeometry.coordinates.map((value) => {
+            return { lat: value[1], lng: value[0] };
+          }),
+        ],
+        {
+          color: this.strokeColor,
+          opacity: this.strokeOpacity,
+          weight: this.strokeWidth,
+        }
+      );
+
       if (this.title) {
         line.bindTooltip(this.title);
       }
+
+      line.on("click", (event) => {
+        this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng));
+      });
+
       line.addTo(this.map.leafletMap);
 
       this.leafletShapes.push(line);
     });
   }
 
+  update(geometry, settings) {
+    super.update(geometry, settings);
+
+    this.leafletShapes.forEach((leafletShape) => {
+      leafletShape.remove();
+    });
+
+    this.addShape();
+  }
+
   remove() {
     this.leafletShapes.forEach((leafletShape) => {
       leafletShape.remove();
diff --git a/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js b/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js
index b14daced91323970258adf6cff6d7a89344a8774..f6ac531cf18b9f581e5504a068a5455b0b5a43e0 100644
--- a/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js
+++ b/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js
@@ -1,4 +1,5 @@
 import { GeolocationShapeMultiPolygon } from "../../../js/Base/GeolocationShapeMultiPolygon.js";
+import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js";
 
 /**
  * @prop {Leaflet} map
@@ -8,24 +9,53 @@ export class LeafletShapeMultiPolygon extends GeolocationShapeMultiPolygon {
     super(geometry, settings, map);
 
     this.leafletShapes = [];
-    this.geometry.polygons.forEach((polygonGeometry) => {
-      const polygon = L.polygon(polygonGeometry.points, {
-        color: this.strokeColor,
-        opacity: this.strokeOpacity,
-        weight: this.strokeWidth,
-        fillColor: this.fillColor,
-        fillOpacity: this.fillOpacity,
-        fill: this.fillOpacity > 0,
-      });
+
+    this.addShape();
+  }
+
+  addShape() {
+    this.geometry.forEach((polygonGeometry) => {
+      const polygon = L.polygon(
+        [
+          polygonGeometry.coordinates[0].map((value) => {
+            return { lat: value[1], lng: value[0] };
+          }),
+        ],
+        {
+          color: this.strokeColor,
+          opacity: this.strokeOpacity,
+          weight: this.strokeWidth,
+          fillColor: this.fillColor,
+          fillOpacity: this.fillOpacity,
+          fill: this.fillOpacity > 0,
+        }
+      );
+      polygon.parent = this;
+
       if (this.title) {
         polygon.bindTooltip(this.title);
       }
+
+      polygon.on("click", (event) => {
+        this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng));
+      });
+
       polygon.addTo(this.map.leafletMap);
 
       this.leafletShapes.push(polygon);
     });
   }
 
+  update(geometry, settings) {
+    super.update(geometry, settings);
+
+    this.leafletShapes.forEach((leafletShape) => {
+      leafletShape.remove();
+    });
+
+    this.addShape();
+  }
+
   remove() {
     this.leafletShapes.forEach((leafletShape) => {
       leafletShape.remove();
diff --git a/modules/geolocation_leaflet/js/LeafletShapePolygon.js b/modules/geolocation_leaflet/js/LeafletShapePolygon.js
index c272baa59792406899f61fa1eeaf81f841f36b5e..a2867b0d3ab4a0685f38aeb2967e9d25e9d381d8 100644
--- a/modules/geolocation_leaflet/js/LeafletShapePolygon.js
+++ b/modules/geolocation_leaflet/js/LeafletShapePolygon.js
@@ -1,4 +1,5 @@
 import { GeolocationShapePolygon } from "../../../js/Base/GeolocationShapePolygon.js";
+import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js";
 
 /**
  * @prop {Leaflet} map
@@ -8,23 +9,51 @@ export class LeafletShapePolygon extends GeolocationShapePolygon {
     super(geometry, settings, map);
 
     this.leafletShapes = [];
-    const polygon = L.polyline(geometry.points, {
-      color: this.strokeColor,
-      opacity: this.strokeOpacity,
-      weight: this.strokeWidth,
-      fillColor: this.fillColor,
-      fillOpacity: this.fillOpacity,
-      fill: this.fillOpacity > 0,
-    });
+
+    this.addShape();
+  }
+
+  addShape() {
+    const polygon = L.polygon(
+      [
+        this.geometry.coordinates[0].map((value) => {
+          return { lat: value[1], lng: value[0] };
+        }),
+      ],
+      {
+        color: this.strokeColor,
+        opacity: this.strokeOpacity,
+        weight: this.strokeWidth,
+        fillColor: this.fillColor,
+        fillOpacity: this.fillOpacity,
+        fill: this.fillOpacity > 0,
+        editable: this.settings.editable ?? false,
+      }
+    );
+
+    polygon.parent = this;
+
     if (this.title) {
       polygon.bindTooltip(this.title);
     }
 
-    polygon.addTo(this.map.leafletMap);
+    polygon.on("click", (event) => {
+      this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng));
+    });
 
     this.leafletShapes.push(polygon);
   }
 
+  update(geometry, settings) {
+    super.update(geometry, settings);
+
+    this.leafletShapes.forEach((leafletShape) => {
+      leafletShape.remove();
+    });
+
+    this.addShape();
+  }
+
   remove() {
     this.leafletShapes.forEach((leafletShape) => {
       leafletShape.remove();
diff --git a/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js b/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f71e8417dea811593912ae1ff18396bb88ddc62
--- /dev/null
+++ b/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js
@@ -0,0 +1,362 @@
+import { LeafletMapFeature } from "./LeafletMapFeature.js";
+import { GeolocationCoordinates } from "../../../../js/Base/GeolocationCoordinates.js";
+
+export default class LeafletGeometryWidgetMapConnector extends LeafletMapFeature {
+  constructor(settings, map) {
+    super(settings, map);
+
+    const drawTypes = {
+      polyline: false,
+      polygon: false,
+      rectangle: false,
+      circle: false,
+      circlemarker: false,
+      marker: false,
+    };
+
+    switch (this.settings.field_type.replace("geolocation_geometry_", "")) {
+      case "multiline":
+      case "line":
+        drawTypes.polyline = true;
+        break;
+      case "multipolygon":
+      case "polygon":
+        drawTypes.polygon = true;
+        break;
+      case "multipoint":
+      case "point":
+        drawTypes.marker = true;
+        break;
+    }
+
+    this.drawnItemsGroup = L.featureGroup().addTo(this.map.leafletMap);
+
+    const drawControl = new L.Control.Draw({
+      draw: drawTypes,
+      edit: {
+        featureGroup: this.drawnItemsGroup,
+      },
+    });
+    this.map.leafletMap.addControl(drawControl);
+  }
+
+  onMapReady() {
+    super.onMapReady();
+
+    this.map.dataLayers.get("default").shapes.forEach((shape) => {
+      shape.leafletShapes.forEach((leafletShape) => {
+        const index = this.getIndexByShape(shape);
+
+        let editableShape;
+        switch (shape.geometry.type) {
+          case "Point":
+            editableShape = L.marker(leafletShape.getLatLng());
+            break;
+          case "LineString":
+            editableShape = L.polyline(leafletShape.getLatLngs());
+            break;
+          case "Polygon":
+            editableShape = L.polygon(leafletShape.getLatLngs());
+            break;
+        }
+
+        editableShape.widgetIndex = index;
+        editableShape.bindTooltip((index + 1).toString());
+        this.drawnItemsGroup.addLayer(editableShape);
+      });
+    });
+
+    this.map.leafletMap.on(
+      L.Draw.Event.CREATED,
+      /** @param {DrawEvents.Created} event */ (event) => {
+        let currentElementCount = 0;
+        this.drawnItemsGroup.eachLayer(() => {
+          currentElementCount++;
+        });
+
+        let newIndex = 0;
+        if (this.settings.cardinality > currentElementCount || this.settings.cardinality === -1) {
+          this.drawnItemsGroup.eachLayer(
+            /**
+             * @param {int} element.widgetIndex
+             * */
+            (element) => {
+              if (typeof element.widgetIndex === "undefined") {
+                return;
+              }
+              if (element.widgetIndex >= newIndex) {
+                newIndex = element.widgetIndex + 1;
+              }
+            }
+          );
+        } else {
+          const warning = document.createElement("div");
+          warning.innerHTML = `<p>${Drupal.t("Maximum number of element reached.")}</p>`;
+          Drupal.dialog(warning, {
+            title: Drupal.t("Synchronization"),
+          }).showModal();
+          return;
+        }
+
+        switch (event.layerType) {
+          case "marker":
+            const geometry = event.layer.toGeoJSON().geometry;
+            const marker = this.createMarker(new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0]));
+            this.setIndexByMarker(marker, newIndex);
+            this.map.dataLayers.get("default").markerAdded(marker);
+            break;
+
+          case "polyline":
+            const polyline = this.map.createShapeLine(event.layer.toGeoJSON().geometry);
+            this.setIndexByShape(polyline, newIndex);
+            this.map.dataLayers.get("default").shapeAdded(polyline);
+            break;
+
+          case "rectangle":
+          case "polygon":
+            const polygon = this.map.createShapePolygon(event.layer.toGeoJSON().geometry);
+            this.setIndexByShape(polygon, newIndex);
+            this.map.dataLayers.get("default").shapeAdded(polygon);
+            break;
+        }
+
+        event.layer.widgetIndex = newIndex;
+        this.drawnItemsGroup.addLayer(event.layer);
+      }
+    );
+
+    this.map.leafletMap.on(
+      L.Draw.Event.EDITVERTEX,
+      /**
+       * @param {DrawEvents.EditVertex} event
+       */
+      (event) => {
+        if (typeof event.poly === "undefined") {
+          return console.error("Updated poly could not be identified.");
+        }
+
+        /**
+         * @type {Polyline | Polygon}
+         *
+         * @prop {int} widgetIndex
+         */
+        const poly = event.poly;
+
+        if (typeof poly.widgetIndex === "undefined") {
+          return console.error("Updated poly could not be associated to shape.");
+        }
+
+        const shape = this.getShapeByIndex(poly.widgetIndex);
+        shape.update(poly.toGeoJSON().geometry);
+        this.map.dataLayers.get("default").shapeUpdated(shape);
+      }
+    );
+
+    this.map.leafletMap.on(
+      L.Draw.Event.DELETED,
+      /**
+       * @param {DrawEvents.Deleted} event
+       */
+      (event) => {
+        event.layers.eachLayer(
+          /**
+           * @param {Layer} layer
+           * @param {int} layer.widgetIndex
+           */
+          (layer) => {
+            if (typeof layer.widgetIndex === "undefined") {
+              return console.error("Updated layer could not be associated to shape.");
+            }
+            const shape = this.getShapeByIndex(layer.widgetIndex);
+            shape.remove();
+            this.map.dataLayers.get("default").shapeRemoved(shape);
+          }
+        );
+      }
+    );
+  }
+
+  setWidgetSubscriber(subscriber) {
+    this.subscriber = subscriber;
+  }
+
+  /**
+   * @param {GeolocationShape} shape
+   *   Marker.
+   *
+   * @return {int|null}
+   *   Index.
+   */
+  getIndexByShape(shape) {
+    return Number(shape.wrapper.dataset.geolocationWidgetIndex ?? 0);
+  }
+
+  /**
+   * @param {int} index
+   *   Index.
+   *
+   * @return {GeolocationShape|null}
+   *   Shape.
+   */
+  getShapeByIndex(index) {
+    let returnValue = null;
+    this.map.dataLayers.get("default").shapes.forEach((shape) => {
+      if (index === this.getIndexByShape(shape)) {
+        returnValue = shape;
+      }
+    });
+
+    return returnValue;
+  }
+
+  /**
+   * @param {GeolocationShape} shape
+   *   Marker.
+   * @param {int|false} index
+   *   Index.
+   */
+  setIndexByShape(shape, index = false) {
+    if (index === false) {
+      delete shape.wrapper.dataset.geolocationWidgetIndex;
+    } else {
+      shape.wrapper.dataset.geolocationWidgetIndex = index.toString();
+    }
+  }
+
+  /**
+   * @param {Number} index
+   *   Index.
+   *
+   * @return {String}
+   *   Title.
+   */
+  getShapeTitle(index) {
+    return `${index + 1}`;
+  }
+
+  addShapeSilently(index, geometry) {
+    let shape;
+
+    switch (geometry.type) {
+      case "Point":
+        const coordinates = new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0]);
+        const marker = this.map.createMarker(coordinates, {
+          title: this.getMarkerTitle(index, coordinates),
+          label: index + 1,
+          draggable: true,
+        });
+
+        this.setIndexByMarker(marker, index);
+
+        marker.geolocationWidgetIgnore = true;
+        this.map.dataLayers.get("default").markerAdded(marker);
+        delete marker.geolocationWidgetIgnore;
+
+        this.map.fitMapToElements();
+
+        return marker;
+
+      case "LineString":
+        shape = this.map.createShapeLine(geometry, {
+          title: this.getShapeTitle(index),
+          label: index + 1,
+          draggable: true,
+        });
+        break;
+
+      case "Polygon":
+        shape = this.map.createShapePolygon(geometry, {
+          title: this.getShapeTitle(index),
+          label: index + 1,
+          draggable: true,
+        });
+        break;
+    }
+
+    if (!shape) return;
+
+    this.setIndexByShape(shape, index);
+
+    shape.geolocationWidgetIgnore = true;
+    this.map.dataLayers.get("default").shapeAdded(shape);
+    delete shape.geolocationWidgetIgnore;
+
+    this.map.fitMapToElements();
+
+    return shape;
+  }
+
+  reorderSilently(newOrder) {
+    this.map.dataLayers.get("default").markers.forEach((marker) => {
+      const oldIndex = this.getIndexByMarker(marker);
+      const newIndex = newOrder.indexOf(oldIndex);
+
+      marker.geolocationWidgetIgnore = true;
+      marker.update(null, {
+        title: this.getShapeTitle(newIndex),
+        label: newIndex + 1,
+      });
+      delete marker.geolocationWidgetIgnore;
+
+      this.setIndexByMarker(marker, newIndex);
+    });
+  }
+
+  updateShapeSilently(index, geometry, settings = null) {
+    const shape = this.getShapeByIndex(index);
+
+    if (!shape) return this.addShapeSilently(index, geometry);
+
+    if (shape.geometry === geometry && !settings) {
+      return;
+    }
+
+    shape.geolocationWidgetIgnore = true;
+    shape.update(geometry, settings ?? {});
+    delete shape.geolocationWidgetIgnore;
+
+    this.map.fitMapToElements();
+
+    return shape;
+  }
+
+  removeShapeSilently(index) {
+    const shape = this.getShapeByIndex(index);
+
+    shape.geolocationWidgetIgnore = true;
+    shape.remove();
+
+    this.map.fitMapToElements();
+  }
+
+  onShapeAdded(shape) {
+    super.onShapeAdded(shape);
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryAdded(shape.geometry, this.getIndexByShape(shape) ?? 0);
+  }
+
+  onShapeClicked(shape, coordinates) {
+    super.onShapeClicked(shape, coordinates);
+
+    // Will trigger onShapeRemove and notify broker.
+    shape.remove();
+  }
+
+  onShapeUpdated(shape) {
+    super.onShapeUpdated(shape);
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryAltered(shape.geometry, this.getIndexByShape(shape));
+  }
+
+  onShapeRemove(shape) {
+    super.onShapeRemove(shape);
+
+    if (shape.geolocationWidgetIgnore ?? false) return;
+
+    this.subscriber?.geometryRemoved(this.getIndexByShape(shape));
+  }
+}
diff --git a/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js b/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js
index e89ebb952195756f9c2be5eecdef510e2aeff344..a61cdcfe3f1899a68ccb2c346885e81eab952a80 100644
--- a/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js
+++ b/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js
@@ -1,5 +1,8 @@
 import { GeolocationMapFeature } from "../../../../js/MapFeature/GeolocationMapFeature.js";
 
+/**
+ * @prop {L.Map} map.leafletMap
+ */
 export class LeafletMapFeature extends GeolocationMapFeature {
   /**
    * @param {LeafletMapMarker} marker
diff --git a/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js b/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js
deleted file mode 100644
index eb7b1abb0f78a1898f7ae33277340f7d1c7da796..0000000000000000000000000000000000000000
--- a/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * @file
- * Javascript for the geolocation geometry Leaflet widget.
- */
-
-(function (Drupal) {
-  /**
-   * Leaflet GeoJSON widget.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Function} layerToGeoJson
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Widget.
-   */
-  Drupal.behaviors.geolocationGeometryWidgetLeaflet = {
-    /**
-     * @param {String} geometryType
-     */
-    getDrawSettingsByTyp: (geometryType) => {
-      switch (geometryType) {
-        case "polygon":
-        case "multipolygon":
-          return {
-            polyline: false,
-            marker: false,
-            circlemarker: false,
-          };
-
-        case "polyline":
-        case "multipolyline":
-          return {
-            polygon: false,
-            rectangle: false,
-            circle: false,
-            marker: false,
-            circlemarker: false,
-          };
-
-        case "point":
-        case "multipoint":
-          return {
-            polyline: false,
-            polygon: false,
-            rectangle: false,
-            circle: false,
-            circlemarker: false,
-          };
-
-        default:
-          return {
-            circlemarker: false,
-          };
-      }
-    },
-    /**
-     * @param {GeoJSON} layer
-     * @param {String} geometryType
-     */
-    layerToGeoJson: (layer, geometryType) => {
-      const featureCollection = layer.toGeoJSON();
-
-      switch (featureCollection.features.length) {
-        case 0:
-          return JSON.stringify("");
-
-        case 1:
-          return JSON.stringify(featureCollection.features[0].geometry);
-
-        default: {
-          const types = {
-            multipolygon: "MultiPolygon",
-            multipolyline: "MultiPolyline",
-            multipoint: "MultiPoint",
-            default: "GeometryCollection",
-          };
-
-          const geometryCollection = {
-            type: types[geometryType] || types.default,
-            geometries: [],
-          };
-
-          featureCollection.features.forEach((feature) => {
-            geometryCollection.geometries.push(feature.geometry);
-          });
-
-          return JSON.stringify(geometryCollection);
-        }
-      }
-    },
-    attach: (context) => {
-      context.querySelectorAll(".geolocation-geometry-widget-leaflet-geojson").forEach((item) => {
-        if (item.classList.contains("processed")) {
-          return;
-        }
-        item.classList.add("processed");
-
-        const mapWrapper = item.querySelector(".geolocation-geometry-widget-leaflet-geojson-map");
-        const inputWrapper = item.querySelector(".geolocation-geometry-widget-leaflet-geojson-input");
-        const geometryType = item.getAttribute("data-geometry-type");
-
-        Drupal.geolocation.maps.getMap(mapWrapper.getAttribute("id")).then(
-          /** @param {Leaflet} map */ (map) => {
-            Drupal.geolocation.addStylesheet("https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css");
-            Drupal.geolocation.addScript("https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js").then(() => {
-              const geoJsonLayer = L.geoJSON().addTo(map.leafletMap);
-              const drawControl = new L.Control.Draw({
-                draw: this.getDrawSettingsByTyp(geometryType),
-                edit: {
-                  featureGroup: geoJsonLayer,
-                },
-              });
-              map.leafletMap.addControl(drawControl);
-
-              map.leafletMap.on(
-                L.Draw.Event.CREATED,
-                /** @param {Created} event */ (event) => {
-                  geoJsonLayer.addLayer(event.layer);
-                  inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType);
-                }
-              );
-              map.leafletMap.on(L.Draw.Event.EDITED, () => {
-                inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType);
-              });
-              map.leafletMap.on(L.Draw.Event.DELETED, () => {
-                inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType);
-              });
-
-              if (inputWrapper.value) {
-                try {
-                  geoJsonLayer.addData(JSON.parse(inputWrapper.value));
-                } catch (error) {
-                  console.error(error.message);
-                  return;
-                }
-
-                map.setBoundaries(map.normalizeBoundaries(geoJsonLayer.getBounds()));
-              }
-            });
-          }
-        );
-      });
-    },
-    detach: () => {},
-  };
-})(Drupal);
diff --git a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php
deleted file mode 100644
index 4c1eb383473cc9e41e4077d768c0b95111011ccb..0000000000000000000000000000000000000000
--- a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace Drupal\geolocation_leaflet\Plugin\Field\FieldWidget;
-
-use Drupal\Core\Field\Attribute\FieldWidget;
-use Drupal\Core\Field\FieldItemListInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationGeometryWidgetBase;
-
-/**
- * Plugin implementation of 'geolocation_geometry_widget_leaflet' widget.
- */
-#[FieldWidget(
-  id: 'geolocation_geometry_widget_leaflet',
-  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Leaflet - GeoJSON'),
-  field_types: [
-    'geolocation_geometry_point',
-    'geolocation_geometry_multi_point',
-    'geolocation_geometry_linestring',
-    'geolocation_geometry_multi_linestring',
-    'geolocation_geometry_polygon',
-    'geolocation_geometry_multi_polygon',
-    'geolocation_geometry_geometry',
-    'geolocation_geometry_multi_geometry',
-  ]
-)]
-class GeolocationGeometryWidgetLeaflet extends GeolocationGeometryWidgetBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected string $mapProviderId = 'leaflet';
-
-  /**
-   * {@inheritdoc}
-   */
-  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
-    $element = parent::formElement($items, $delta, $element, $form, $form_state);
-
-    $element['#attached'] = [
-      'library' => [
-        'geolocation_leaflet/widget.leaflet.geojson',
-      ],
-    ];
-
-    return $element;
-  }
-
-}
diff --git a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php
new file mode 100644
index 0000000000000000000000000000000000000000..0408b7c12935ecacc53c024c196da248b5ad4d27
--- /dev/null
+++ b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\geolocation_leaflet\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\Attribute\FieldWidget;
+use Drupal\geolocation_geometry\Plugin\Field\FieldWidget\GeolocationGeometryMapWidget;
+
+/**
+ * Plugin implementation of 'geolocation_geometry_widget_leaflet' widget.
+ */
+#[FieldWidget(
+  id: 'geolocation_geometry_widget_leaflet',
+  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Leaflet - GeoJSON'),
+  field_types: [
+    'geolocation_geometry_point',
+    'geolocation_geometry_multi_point',
+    'geolocation_geometry_linestring',
+    'geolocation_geometry_polygon',
+  ]
+)]
+class LeafletGeolocationGeometry extends GeolocationGeometryMapWidget {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected ?string $mapProviderId = 'leaflet';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetFeatureId(): string {
+    return 'leaflet_geometry_widget_map_connector';
+  }
+
+}
diff --git a/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php b/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php
new file mode 100644
index 0000000000000000000000000000000000000000..f88eefd857722aa64c1cbee4ab2f708bc39bd1f0
--- /dev/null
+++ b/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\geolocation_leaflet\Plugin\geolocation\MapFeature;
+
+use Drupal\geolocation\Attribute\MapFeature;
+use Drupal\geolocation\Plugin\geolocation\MapFeature\GeolocationFieldWidgetMapConnector;
+
+/**
+ * Provides Recenter control element.
+ */
+#[MapFeature(
+  id: 'leaflet_geometry_widget_map_connector',
+  name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Leaflet Geometry Widget Map Connector'),
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'),
+  type: 'all',
+  hidden: TRUE,
+)]
+class LeafletGeometryWidgetMapConnector extends GeolocationFieldWidgetMapConnector {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected array $scripts = [
+    'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected array $stylesheets = [
+    'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css',
+  ];
+
+}
diff --git a/package.json b/package.json
index 6ec0832617b44c1a35356e4f0287db7908f1c486..899df71d3e497f415c8f9e9928fa61d593f1be4f 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
 {
   "devDependencies": {
     "@types/bmapgl": "^0.0.7",
-    "@types/chart.js": "0.0.15",
+    "@types/chart.js": "^0.0.15",
     "@types/google.maps": "^3.50",
     "@types/leaflet": "^1.9.0",
     "@types/leaflet.markercluster": "^1.5.4",
-    "@types/leaflet-draw": "1.0.11",
+    "@types/leaflet-draw": "^1.0.11",
     "@yandex/ymaps3-types": "^0.0.24",
     "prettier": "^2.8.1"
   },
diff --git a/phpstan.neon b/phpstan.neon
index 946ccc2740f1bbb23b2b910f06f04467bd1e3a13..ac59f3caaaf0858f9c3559973dc166916a32a5c0 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -29,6 +29,4 @@ parameters:
     - '#Unsafe usage of new static\(\)#'
     - '#\\Drupal calls should be avoided in classes, use dependency injection instead#'
     - '#no value type specified in iterable type#'
-    - "#with generic interface Drupal\\\\Core\\\\Field\\\\FieldItemListInterface but does not specify its types#"
-    - "#Call to an undefined method Drupal\\\\Tests\\\\WebAssert::waitForElement#"
-    - "#Call to an undefined method Drupal\\\\Tests\\\\WebAssert::assertWaitOnAjaxRequest#"
+    - "#but does not specify its types#"
diff --git a/src/Attribute/LayerFeature.php b/src/Attribute/LayerFeature.php
index 2be671986b6c5e6728a6f69f5ee435ff16e6b0c5..c5dc41ad111f98a1fdea5a4e81126b9553d2a636 100644
--- a/src/Attribute/LayerFeature.php
+++ b/src/Attribute/LayerFeature.php
@@ -21,6 +21,7 @@ class LayerFeature extends Plugin {
     public readonly ?TranslatableMarkup $name = NULL,
     public readonly ?TranslatableMarkup $description = NULL,
     public readonly ?string $type = NULL,
+    public readonly bool $hidden = FALSE,
     public readonly ?string $deriver = NULL,
   ) {}
 
diff --git a/src/Attribute/MapFeature.php b/src/Attribute/MapFeature.php
index e39b0a22fa5323a94a2e26d8ff4b82da6632ed8e..4907456cb142e6f39aaebdd991e5d70b0506388b 100644
--- a/src/Attribute/MapFeature.php
+++ b/src/Attribute/MapFeature.php
@@ -21,6 +21,7 @@ class MapFeature extends Plugin {
     public readonly ?TranslatableMarkup $name = NULL,
     public readonly ?TranslatableMarkup $description = NULL,
     public readonly ?string $type = NULL,
+    public readonly bool $hidden = FALSE,
     public readonly ?string $deriver = NULL,
   ) {}
 
diff --git a/src/DataProviderBase.php b/src/DataProviderBase.php
index 391661a26a1eef6cc544a19903ed7eacf153d163..0e217a36d1d282a44a713a40f339ce4ddf021395 100644
--- a/src/DataProviderBase.php
+++ b/src/DataProviderBase.php
@@ -123,10 +123,7 @@ abstract class DataProviderBase extends PluginBase implements DataProviderInterf
       $element['token_items'][] = $item;
     }
 
-    if (
-      $this->moduleHandler->moduleExists('token')
-      && method_exists($fieldDefinition, 'getTargetEntityTypeId')
-    ) {
+    if ($this->moduleHandler->moduleExists('token')) {
       // Add the token UI from the token module if present.
       $element['token_help'] = [
         '#theme' => 'token_tree_link',
diff --git a/src/Element/GeolocationMapGeometry.php b/src/Element/GeolocationMapShape.php
similarity index 84%
rename from src/Element/GeolocationMapGeometry.php
rename to src/Element/GeolocationMapShape.php
index 4573d6528dbddf524f5d17ce9a3feb2b8c8001d4..17aad62711ae0c39b18889a87730eda309710664 100644
--- a/src/Element/GeolocationMapGeometry.php
+++ b/src/Element/GeolocationMapShape.php
@@ -13,7 +13,7 @@ use Drupal\Core\Template\Attribute;
  *
  * @code
  * $form['map'] = [
- *   '#type' => 'geolocation_map_geometry',
+ *   '#type' => 'geolocation_map_shape',
  *   '#geometry' => [[[1,1],[2,2],[3,3]], [[4,4],[5,5],[6,6]]],
  *   '#id' => NULL,
  *   '#stroke_color' => NULL,
@@ -24,9 +24,9 @@ use Drupal\Core\Template\Attribute;
  * ];
  * @endcode
  *
- * @RenderElement("geolocation_map_geometry")
+ * @RenderElement("geolocation_map_shape")
  */
-class GeolocationMapGeometry extends RenderElementBase {
+class GeolocationMapShape extends RenderElementBase {
 
   /**
    * {@inheritdoc}
@@ -40,7 +40,7 @@ class GeolocationMapGeometry extends RenderElementBase {
       ],
       '#pre_render' => [
         [$class, 'preRenderGroup'],
-        [$this, 'preRenderGeolocationGeometry'],
+        [$this, 'preRenderGeolocationShape'],
       ],
       '#title' => NULL,
       '#geometry' => NULL,
@@ -63,14 +63,15 @@ class GeolocationMapGeometry extends RenderElementBase {
    * @return array
    *   Renderable map.
    */
-  public function preRenderGeolocationGeometry(array $render_array): array {
-    $render_array['#theme'] = 'geolocation_map_geometry';
+  public function preRenderGeolocationShape(array $render_array): array {
+    $render_array['#theme'] = 'geolocation_map_shape';
 
     $render_array['#attributes'] = new Attribute($render_array['#attributes'] ?? []);
     $render_array['#attributes']->addClass('geolocation-geometry');
     $render_array['#attributes']->addClass('js-hide');
 
     $render_array['#attributes']->setAttribute('id', $render_array['#id'] ?? uniqid('geometry-'));
+    $render_array['#attributes']->setAttribute('data-geometry-type', $render_array['#geometry_type'] ?? '');
     $render_array['#attributes']->setAttribute('data-stroke-color', $render_array['#stroke_color'] ?? '#0000FF');
     $render_array['#attributes']->setAttribute('data-stroke-width', $render_array['#stroke_width'] ?? 2);
     $render_array['#attributes']->setAttribute('data-stroke-opacity', $render_array['#stroke_opacity'] ?? 1);
diff --git a/src/LayerFeatureManager.php b/src/LayerFeatureManager.php
index c55cc4bcf1b104f11c8d6e059531ff6fea154572..d74d9c25cf4a92472dc999ac6b85dadfa936bef3 100644
--- a/src/LayerFeatureManager.php
+++ b/src/LayerFeatureManager.php
@@ -26,7 +26,7 @@ class LayerFeatureManager extends DefaultPluginManager {
   use DependencySerializationTrait;
 
   /**
-   * Constructs an LayerFeatureManager object.
+   * Constructs a LayerFeatureManager object.
    *
    * @param \Traversable $namespaces
    *   An object that implements \Traversable which contains the root paths
@@ -128,7 +128,7 @@ class LayerFeatureManager extends DefaultPluginManager {
     $layer_features_form = [
       '#type' => 'table',
       '#weight' => 100,
-      '#caption' => $this->t('<p>Select features to alter functionality of this layer.</p>'),
+      '#caption' => $this->t('<p>Select features to alter the functionality of this layer.</p>'),
       '#header' => [
         $this->t('Enable'),
         $this->t('Feature'),
@@ -153,6 +153,10 @@ class LayerFeatureManager extends DefaultPluginManager {
         continue;
       }
 
+      if ($feature->getPluginDefinition()['hidden'] ?? FALSE) {
+        continue;
+      }
+
       $feature_enable_id = Html::getUniqueId($feature_id . '_enabled');
       $weight = $settings[$feature_id]['weight'] ?? 0;
 
@@ -242,8 +246,7 @@ class LayerFeatureManager extends DefaultPluginManager {
         continue;
       }
 
-      $feature = $this->getLayerFeature($feature_id);
-      if ($feature && method_exists($feature, 'validateSettingsForm')) {
+      if ($feature = $this->getLayerFeature($feature_id)) {
         $feature_parents = $parents;
         array_push($feature_parents, $feature_id, 'settings');
         $feature->validateSettingsForm(empty($feature_settings['settings']) ? [] : $feature_settings['settings'], $form_state, $feature_parents);
diff --git a/src/MapFeatureManager.php b/src/MapFeatureManager.php
index 72c24591cadea3e78b848949bfb52f070526549f..9d02654a49009e5bc5e8e7608a3de610c406dfcf 100644
--- a/src/MapFeatureManager.php
+++ b/src/MapFeatureManager.php
@@ -26,7 +26,7 @@ class MapFeatureManager extends DefaultPluginManager {
   use DependencySerializationTrait;
 
   /**
-   * Constructs an MapFeatureManager object.
+   * Constructs a MapFeatureManager object.
    *
    * @param \Traversable $namespaces
    *   An object that implements \Traversable which contains the root paths
@@ -77,13 +77,9 @@ class MapFeatureManager extends DefaultPluginManager {
    *   Map feature list.
    */
   public function getMapFeaturesByMapType(string $type): array {
-    $definitions = $this->getDefinitions();
-    $list = [];
-    foreach ($definitions as $id => $definition) {
-      if ($definition['type'] == $type || $definition['type'] == 'all') {
-        $list[$id] = $definition;
-      }
-    }
+    $list = array_filter($this->getDefinitions(), function ($definition) use ($type) {
+      return $definition['type'] == $type || $definition['type'] == 'all';
+    });
 
     uasort($list, [self::class, 'sortByName']);
 
@@ -153,6 +149,10 @@ class MapFeatureManager extends DefaultPluginManager {
         continue;
       }
 
+      if ($feature->getPluginDefinition()['hidden'] ?? FALSE) {
+        continue;
+      }
+
       $feature_enable_id = Html::getUniqueId($feature_id . '_enabled');
       $weight = $settings[$feature_id]['weight'] ?? 0;
 
@@ -246,8 +246,7 @@ class MapFeatureManager extends DefaultPluginManager {
         continue;
       }
 
-      $feature = $this->getMapFeature($feature_id);
-      if ($feature && method_exists($feature, 'validateSettingsForm')) {
+      if ($feature = $this->getMapFeature($feature_id)) {
         $feature_parents = $parents;
         array_push($feature_parents, $feature_id, 'settings');
         $feature->validateSettingsForm($feature_settings['settings'] ?? [], $form_state, $feature_parents);
diff --git a/src/MapProviderInterface.php b/src/MapProviderInterface.php
index 0814308b746eeedd9d0a88428621aa5d39a9fea9..5427cdc606932d085bfc5886ceced672e7c8f387 100644
--- a/src/MapProviderInterface.php
+++ b/src/MapProviderInterface.php
@@ -40,7 +40,7 @@ interface MapProviderInterface extends PluginInspectionInterface {
   public function getSettingsSummary(array $settings): array;
 
   /**
-   * Provide a generic map settings form array.
+   * Provide the generic map settings form array.
    *
    * @param array $settings
    *   The current map settings.
diff --git a/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php b/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php
deleted file mode 100644
index 151b99c4dd22a3086a1445a7a8d7d09562c1f6b9..0000000000000000000000000000000000000000
--- a/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php
+++ /dev/null
@@ -1,150 +0,0 @@
-<?php
-
-namespace Drupal\geolocation\Plugin\Field\FieldWidget;
-
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\Core\Field\FieldItemListInterface;
-use Drupal\Core\Field\WidgetBase;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\geolocation\MapProviderInterface;
-use Drupal\geolocation\MapProviderManager;
-use Drupal\views\FieldAPIHandlerTrait;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Map geometry widget base.
- */
-abstract class GeolocationGeometryWidgetBase extends WidgetBase implements ContainerFactoryPluginInterface {
-
-  use FieldAPIHandlerTrait;
-
-  /**
-   * Map provider ID.
-   *
-   * @var string
-   */
-  protected string $mapProviderId;
-
-  /**
-   * Map provider.
-   *
-   * @var \Drupal\geolocation\MapProviderInterface
-   */
-  protected MapProviderInterface $mapProvider;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(
-    $plugin_id,
-    $plugin_definition,
-    FieldDefinitionInterface $field_definition,
-    array $settings,
-    array $third_party_settings,
-    protected MapProviderManager $mapProviderManager,
-  ) {
-    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
-
-    if ($this->mapProviderId) {
-      $this->mapProvider = $this->mapProviderManager->getMapProvider($this->mapProviderId);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): GeolocationGeometryWidgetBase {
-    return new static(
-      $plugin_id,
-      $plugin_definition,
-      $configuration['field_definition'],
-      $configuration['settings'],
-      $configuration['third_party_settings'],
-      $container->get('plugin.manager.geolocation.mapprovider'),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsForm(array $form, FormStateInterface $form_state): array {
-    $settings = $this->getSettings();
-    $element = parent::settingsForm($form, $form_state);
-
-    $parents = [
-      'fields',
-      $this->fieldDefinition->getName(),
-      'settings_edit_form',
-      'settings',
-    ];
-
-    $user_input = $form_state->getUserInput();
-    $map_provider_settings = NestedArray::getValue($user_input, array_merge($parents, ['map_provider_settings'])) ?? $settings['map_provider_settings'] ?? [];
-    $map_provider_settings = NestedArray::mergeDeep($this->mapProviderManager->getMapProviderDefaultSettings($this->mapProviderId) ?? [], $map_provider_settings);
-
-    $element['map_provider_settings'] = $this->mapProvider->getSettingsForm(
-      $map_provider_settings,
-      array_merge($parents, ['map_provider_settings'])
-    );
-
-    return $element;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsSummary(): array {
-    $summary = [];
-    $settings = $this->getSettings();
-
-    return array_replace_recursive($summary, $this->mapProvider->getSettingsSummary($settings['map_provider_settings'] ?? []));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
-
-    $settings = $this->getSettings();
-
-    $element['#type'] = 'container';
-    $element['#attributes'] = [
-      'data-geometry-type' => str_replace('geolocation_geometry_', '', $this->fieldDefinition->getType()),
-      'class' => [
-        str_replace('_', '-', $this->getPluginId()) . '-geojson',
-      ],
-    ];
-
-    $element['geojson'] = [
-      '#type' => 'textarea',
-      '#title' => $this->t('GeoJSON'),
-      '#default_value' => $items[$delta]->geojson ?? NULL,
-      '#empty_value' => '',
-      '#required' => $element['#required'],
-      '#attributes' => [
-        'class' => [
-          'geolocation-geometry-widget-geojson-input',
-          str_replace('_', '-', $this->getPluginId()) . '-geojson-input',
-        ],
-      ],
-    ];
-
-    $element['map'] = [
-      '#type' => 'geolocation_map',
-      '#maptype' => $this->mapProviderId,
-      '#weight' => -10,
-      '#settings' => $settings['map_provider_settings'],
-      '#context' => ['widget' => $this],
-      '#attributes' => [
-        'class' => [
-          str_replace('_', '-', $this->getPluginId()) . '-geojson-map',
-        ],
-      ],
-    ];
-
-    return $element;
-  }
-
-}
diff --git a/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php b/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php
index 3152e4ab873de9d9679b07821a5b02acd7dc7afe..09ce6efa07673246a6a1132deaa14faf8a0f7589 100644
--- a/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php
+++ b/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php
@@ -10,8 +10,11 @@ use Drupal\Core\Render\BubbleableMetadata;
 /**
  * Plugin implementation of the 'geolocation_map' widget.
  */
-#[FieldWidget(id: 'geolocation_map',
-  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Map'), field_types: ['geolocation'])]
+#[FieldWidget(
+  id: 'geolocation_map',
+  label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Map'),
+  field_types: ['geolocation']
+)]
 class GeolocationMapWidget extends GeolocationMapWidgetBase {
 
   /**
@@ -66,39 +69,36 @@ class GeolocationMapWidget extends GeolocationMapWidgetBase {
   public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL): array {
     $element = parent::form($items, $form, $form_state, $get_delta);
 
-    $element['#attached'] = BubbleableMetadata::mergeAttachments(
-      $element['#attached'] ?? [],
-      [
-        'drupalSettings' => [
-          'geolocation' => [
-            'widgetSettings' => [
-              $element['#attributes']['id'] => [
-                'widgetSubscribers' => [
-                  'geolocation_map' => [
-                    'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldMapWidget.js',
-                    'settings' => [
-                      'mapId' => $element['map']['#id'],
-                      'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
-                      'field_name' => $this->fieldDefinition->getName(),
-                      'featureSettings' => [
-                        'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/MapFeature/GeolocationFieldWidgetMapConnector.js',
-                      ],
-                    ],
+    $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], [
+      'drupalSettings' => [
+        'geolocation' => [
+          'widgetSettings' => [
+            $element['#attributes']['id'] => [
+              'widgetSubscribers' => [
+                'geolocation_field' => [
+                  'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldWidget.js',
+                  'settings' => [
+                    'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+                    'field_name' => $this->fieldDefinition->getName(),
+                    'field_type' => $this->fieldDefinition->getType(),
                   ],
-                  'geolocation_field' => [
-                    'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldWidget.js',
-                    'settings' => [
-                      'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
-                      'field_name' => $this->fieldDefinition->getName(),
-                    ],
+                ],
+                'geolocation_map' => [
+                  'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldMapWidget.js',
+                  'settings' => [
+                    'mapId' => $element['map']['#id'],
+                    'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+                    'field_name' => $this->fieldDefinition->getName(),
+                    'field_type' => $this->fieldDefinition->getType(),
+                    'feature_id' => $this->getWidgetFeatureId(),
                   ],
                 ],
               ],
             ],
           ],
         ],
-      ]
-    );
+      ],
+    ]);
 
     /**
      * @var Integer $index
@@ -123,16 +123,6 @@ class GeolocationMapWidget extends GeolocationMapWidgetBase {
       ];
     }
 
-    $context = [
-      'widget' => $this,
-      'form_state' => $form_state,
-      'field_definition' => $this->fieldDefinition,
-    ];
-
-    if (!$this->isDefaultValueWidget($form_state)) {
-      $this->moduleHandler->alter('geolocation_field_map_widget', $element, $context);
-    }
-
     return $element;
   }
 
diff --git a/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php b/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php
index 3c8116988e7e7401589465bce6833a4e0e822935..bca58d3fad85d0c44681571902753f829d048cea 100644
--- a/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php
+++ b/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php
@@ -18,7 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\ConstraintViolationListInterface;
 
 /**
- * Base class for map based field widgets.
+ * Base class for map-based field widgets.
  */
 abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerFactoryPluginInterface {
 
@@ -284,6 +284,19 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF
       $element['map_provider_settings']['#title'] .= ' - ' . $this->t('Override Map Default Preset');
     }
 
+    $element['map']['#settings'] = array_merge($element['map']['#settings'], [
+      'map_features' => [
+        $this->getWidgetFeatureId() => [
+          'enabled' => TRUE,
+          'settings' => [
+            'field_name' => $this->fieldDefinition->getName(),
+            'field_type' => $this->fieldDefinition->getType(),
+            'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+          ],
+        ],
+      ],
+    ]);
+
     $element['map'] = $this->mapCenterManager->alterMap($element['map'], $settings['centre']);
 
     if ($settings['hide_inputs'] ?? FALSE) {
@@ -314,6 +327,15 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF
         }
       }
     }
+    $context = [
+      'widget' => $this,
+      'form_state' => $form_state,
+      'field_definition' => $this->fieldDefinition,
+    ];
+
+    if (!$this->isDefaultValueWidget($form_state)) {
+      $this->moduleHandler->alter('geolocation_field_map_widget', $element, $context);
+    }
 
     return $element;
   }
@@ -375,4 +397,14 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF
     return NULL;
   }
 
+  /**
+   * Get ID of feature to connect map and widget.
+   *
+   * @return string
+   *   Feature ID.
+   */
+  protected function getWidgetFeatureId(): string {
+    return 'geolocation_field_widget_map_connector';
+  }
+
 }
diff --git a/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php b/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php
index 645d706a10e390fbc9427ede753b5fbcea9a00f6..f6a3ef9ca502a3ee4e7c4e7719a91c7eb7cfd42f 100644
--- a/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php
+++ b/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php
@@ -12,7 +12,9 @@ use Drupal\geolocation\MapProviderInterface;
  */
 #[LayerFeature(id: 'marker_zoom_by_anchor',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Marker Zoom By Anchor'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Set a URL anchor.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Set a URL anchor.'),
+  type: 'all',
+)]
 class MarkerZoomByAnchor extends LayerFeatureBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php b/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php
index 03568dc3da0857d93b41edb8be2e86722618c896..a09e6c05ec6864c54efdf47a0f3d27a9d2ddef83 100644
--- a/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php
+++ b/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php
@@ -15,9 +15,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 /**
  * Provides Geocoding control element.
  */
-#[MapFeature(id: 'control_geocoder',
+#[MapFeature(
+  id: 'control_geocoder',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Geocoder'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add address search with geocoding functionality map.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add address search with geocoding functionality map.'),
+  type: 'all',
+)]
 class ControlCustomGeocoder extends ControlCustomElementBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php b/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php
index 15941da30063fb916c8d06dce414db77d3161260..5b135503cc30e49655078ad6a594c3150fe800da 100644
--- a/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php
+++ b/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php
@@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface;
 /**
  * Provides Recenter control element.
  */
-#[MapFeature(id: 'control_recenter',
+#[MapFeature(
+  id: 'control_recenter',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Recenter'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add button to recenter map.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add button to recenter map.'),
+  type: 'all',
+)]
 class ControlCustomRecenter extends ControlCustomElementBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php b/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php
index 6dab8222f0d14485790e569eec207c2bd7943188..868f0440fd2d9a7ac712af900792cb6faeafb0ed 100644
--- a/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php
+++ b/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php
@@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface;
 /**
  * Provides Recenter control element.
  */
-#[MapFeature(id: 'control_loading_indicator',
+#[MapFeature(
+  id: 'control_loading_indicator',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Loading Indicator'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('When using an interactive map, shows a loading icon and label if there is currently data fetched from the backend via AJAX.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('When using an interactive map, shows a loading icon and label if there is currently data fetched from the backend via AJAX.'),
+  type: 'all',
+)]
 class ControlLoadingIndicator extends ControlCustomElementBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/ControlTileLayers.php b/src/Plugin/geolocation/MapFeature/ControlTileLayers.php
index e5b8ae7388c4b46785e4632b2ebd9ed2e4203fc8..d6f7dff730326accc306d6ce37f299255b160e8d 100644
--- a/src/Plugin/geolocation/MapFeature/ControlTileLayers.php
+++ b/src/Plugin/geolocation/MapFeature/ControlTileLayers.php
@@ -15,9 +15,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 /**
  * Provides Recenter control element.
  */
-#[MapFeature(id: 'control_tile_layers',
+#[MapFeature(
+  id: 'control_tile_layers',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Tile Layers'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Shows list of toggleable tile layers.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Shows list of toggleable tile layers.'),
+  type: 'all',
+)]
 class ControlTileLayers extends ControlCustomElementBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php b/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php
index 0f93a0a746d94bafc7673ba3bf5a3ce0d29a11cf..749fad9e8cf533acd0b68fe5cb4034ef50098ed8 100644
--- a/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php
+++ b/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php
@@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface;
 /**
  * Provides Recenter control element.
  */
-#[MapFeature(id: 'control_view_fullscreen',
+#[MapFeature(
+  id: 'control_view_fullscreen',
   name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - View Fullscreen'),
-  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Trigger Fullscreen on entire View container.'), type: 'all')]
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Trigger Fullscreen on entire View container.'),
+  type: 'all',
+)]
 class ControlViewFullscreen extends ControlCustomElementBase {
 
   /**
diff --git a/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php b/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d08837fa639a105e28716a74a474782080ca4f6
--- /dev/null
+++ b/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\geolocation\Plugin\geolocation\MapFeature;
+
+use Drupal\geolocation\Attribute\MapFeature;
+use Drupal\geolocation\MapFeatureBase;
+
+/**
+ * Provides Recenter control element.
+ */
+#[MapFeature(
+  id: 'geolocation_field_widget_map_connector',
+  name: new \Drupal\Core\StringTranslation\TranslatableMarkup('GeolocationFieldWidgetMapConnector'),
+  description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'),
+  type: 'all',
+  hidden: TRUE,
+)]
+class GeolocationFieldWidgetMapConnector extends MapFeatureBase {}
diff --git a/src/Plugin/views/argument/ProximityArgument.php b/src/Plugin/views/argument/ProximityArgument.php
index b56f23e560cb3e47c744a3818792a31f7d01ab34..a883e4cfba38e61516be0491336397412c22e8ae 100644
--- a/src/Plugin/views/argument/ProximityArgument.php
+++ b/src/Plugin/views/argument/ProximityArgument.php
@@ -88,6 +88,7 @@ class ProximityArgument extends Formula {
 
     // The addWhere function is only available for SQL queries.
     if ($this->query instanceof Sql) {
+      // @phpstan-ignore-next-line
       $this->query->addWhere(0, $formula, $placeholders, 'formula');
     }
   }
diff --git a/src/Plugin/views/field/ProximityField.php b/src/Plugin/views/field/ProximityField.php
index be2ba12f4f669a570ddb1f3eeed6bd8578a5c61f..d62f54494257f676dd19db8a2c19ea60a58bd16e 100644
--- a/src/Plugin/views/field/ProximityField.php
+++ b/src/Plugin/views/field/ProximityField.php
@@ -112,7 +112,8 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter
 
     // Get a placeholder for this query and save the field_alias for it.
     // Remove the initial ':' from the placeholder and avoid collision with
-    // original field name.
+    // the original field name.
+    // @phpstan-ignore-next-line
     $this->field_alias = $query->addField(NULL, $expression, substr($this->placeholder(), 1));
   }
 
diff --git a/src/Plugin/views/sort/ProximitySort.php b/src/Plugin/views/sort/ProximitySort.php
index d627769e98868b14f4a9fb014d101707c76f5ed6..a22fc069270af7197b9873dae396d3d4a01e2f1e 100644
--- a/src/Plugin/views/sort/ProximitySort.php
+++ b/src/Plugin/views/sort/ProximitySort.php
@@ -26,6 +26,7 @@ class ProximitySort extends SortPluginBase {
     $field = $this->displayHandler->getHandler('field', $this->field);
 
     if (!empty($field->field_alias) && $field->field_alias != 'unknown') {
+      // @phpstan-ignore-next-line
       $this->query->addOrderBy(NULL, NULL, $this->options['order'], $field->field_alias);
       if (!empty($field->tableAlias)) {
         $this->tableAlias = $field->tableAlias;
diff --git a/templates/geolocation-map-geometry.html.twig b/templates/geolocation-map-geometry.html.twig
deleted file mode 100644
index fa9fe81e9f4b4eea9c6d4c7bf110526d5c56394c..0000000000000000000000000000000000000000
--- a/templates/geolocation-map-geometry.html.twig
+++ /dev/null
@@ -1,62 +0,0 @@
-<div {{ attributes.addClass('geolocation-geometry') }} typeof="Place">
-
-  <span class="geometry" data-type="{{ geometry.type }}">
-    {% if geometry.type == 'point' %}
-      <span property="geo" typeof="GeoCoordinates">
-        <meta property="latitude" content="{{ geometry.lat }}" />
-        <meta property="longitude" content="{{ geometry.lng }}" />
-      </span>
-    {% elseif geometry.type == 'line' %}
-      <span property="geo" typeof="GeoShape">
-        <meta property="line" content="
-          {%- for point in geometry.points %}
-            {{- point.lat ~ ',' ~ point.lng ~ ' ' -}}
-          {% endfor -%}
-        ">
-      </span>
-    {% elseif geometry.type == 'polygon' %}
-      <span property="geo" typeof="GeoShape">
-        <meta property="polygon" content="
-          {%- for point in geometry.points %}
-            {{- point.lat ~ ',' ~ point.lng ~ ' ' -}}
-          {% endfor -%}
-        ">
-      </span>
-    {% elseif geometry.type == 'multipoint' %}
-      {% for point in geometry.points %}
-        <span property="geo" typeof="GeoCoordinates">
-          <meta property="latitude" content="{{ point.lat }}" />
-          <meta property="longitude" content="{{ point.lng }}" />
-        </span>
-      {% endfor %}
-    {% elseif geometry.type == 'multiline' %}
-      {% for line in geometry.lines %}
-        <span property="geo" typeof="GeoShape">
-          <meta property="line" content="
-            {%- for point in line.points %}
-              {{- point.lat ~ ',' ~ point.lng ~ ' ' -}}
-            {% endfor -%}
-          ">
-        </span>
-      {% endfor %}
-    {% elseif geometry.type == 'multipolygon' %}
-      {% for polygon in geometry.polygons %}
-        <span property="geo" typeof="GeoShape">
-          <meta property="polygon" content="
-            {%- for point in polygon.points %}
-              {{- point.lat ~ ',' ~ point.lng ~ ' ' -}}
-            {% endfor -%}
-          ">
-        </span>
-      {% endfor %}
-    {% endif %}
-  </span>
-
-  {% if title is not empty %}
-    <h2 class="title" property="name">{{ title }}</h2>
-  {% endif %}
-
-  {% if children is not empty %}
-    <div class="content">{{ children }}</div>
-  {% endif %}
-</div>
diff --git a/templates/geolocation-map-shape.html.twig b/templates/geolocation-map-shape.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..6234abbc50643d7a46b57737c8da9242031442e9
--- /dev/null
+++ b/templates/geolocation-map-shape.html.twig
@@ -0,0 +1,14 @@
+<div {{ attributes.addClass('geolocation-geometry') }} typeof="Place">
+
+  <script class="geometry" type="application/json">
+    {{ geometry|raw }}
+  </script>
+
+  {% if title is not empty %}
+    <h2 class="title" property="name">{{ title }}</h2>
+  {% endif %}
+
+  {% if children is not empty %}
+    <div class="content">{{ children }}</div>
+  {% endif %}
+</div>
diff --git a/tests/src/Kernel/GeolocationItemTest.php b/tests/src/Kernel/GeolocationItemTest.php
index e8789ed766f35372da82759b4fedcef319e023cb..222cfae70789fb83b2ab157fe6e8f6091b6e120d 100644
--- a/tests/src/Kernel/GeolocationItemTest.php
+++ b/tests/src/Kernel/GeolocationItemTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\Tests\geolocation\Kernel;
 
 use Drupal\Core\Field\FieldItemInterface;
-use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -65,7 +64,6 @@ class GeolocationItemTest extends FieldKernelTestBase {
 
     /** @var \Drupal\entity_test\Entity\EntityTest $entity */
     $entity = $entityTestStorage->load($id);
-    $this->assertInstanceOf(FieldItemListInterface::class, $entity->get('field_test'), 'Field implements interface.');
     $this->assertInstanceOf(FieldItemInterface::class, $entity->get('field_test')[0], 'Field item implements interface.');
 
     /** @var \Drupal\geolocation\GeolocationItemListInterface $field_item */