diff --git a/package-lock.json b/package-lock.json
index 6f42bd0976df8e01c2764dd4c37253b89ed17558..b626982eea0e16bfe5bf9f8eef8a95f8a3b1200a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
         "molart": "1.15.0",
         "next": "13.4.19",
         "ol": "10.2.0",
+        "ol-ext": "4.0.24",
         "polished": "4.3.1",
         "postcss": "8.4.29",
         "query-string": "7.1.3",
@@ -56,6 +57,7 @@
         "@types/crypto-js": "4.2.2",
         "@types/is-uuid": "1.0.2",
         "@types/jest": "29.5.11",
+        "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.6.1",
         "@types/react-autosuggest": "^10.1.11",
         "@types/react-redux": "7.1.33",
         "@types/redux-mock-store": "1.0.6",
@@ -2562,6 +2564,16 @@
       "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
       "dev": true
     },
+    "node_modules/@types/ol-ext": {
+      "name": "@siedlerchr/types-ol-ext",
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@siedlerchr/types-ol-ext/-/types-ol-ext-3.6.1.tgz",
+      "integrity": "sha512-CSqgXdS1d028TvnVXf2/dgnU66lRGHM9+R5E8lL2ilmm6ktHVZtsIhRR8rWSXu4Ybt1rGdMbwbDxofIbilWIyQ==",
+      "dev": true,
+      "peerDependencies": {
+        "jspdf": "^2.5.2"
+      }
+    },
     "node_modules/@types/openlayers": {
       "version": "4.6.23",
       "resolved": "https://registry.npmjs.org/@types/openlayers/-/openlayers-4.6.23.tgz",
@@ -2577,6 +2589,14 @@
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
       "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
     },
+    "node_modules/@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/@types/rbush": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.3.tgz",
@@ -3317,6 +3337,19 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true,
+      "peer": true,
+      "bin": {
+        "atob": "bin/atob.js"
+      },
+      "engines": {
+        "node": ">= 4.5.0"
+      }
+    },
     "node_modules/attr-accept": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
@@ -3609,6 +3642,17 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -3729,6 +3773,19 @@
         "node-int64": "^0.4.0"
       }
     },
+    "node_modules/btoa": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+      "dev": true,
+      "peer": true,
+      "bin": {
+        "btoa": "bin/btoa.js"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
     "node_modules/buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -3892,6 +3949,35 @@
         }
       ]
     },
+    "node_modules/canvg": {
+      "version": "3.0.10",
+      "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
+      "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/canvg/node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -4519,6 +4605,19 @@
       "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
+    "node_modules/core-js": {
+      "version": "3.40.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
+      "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "peer": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
     "node_modules/core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -4610,6 +4709,17 @@
       "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
       "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
     },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/css.escape": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -5248,6 +5358,14 @@
         "node": ">=12"
       }
     },
+    "node_modules/dompurify": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/dot-prop": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -6684,6 +6802,13 @@
         "pend": "~1.2.0"
       }
     },
+    "node_modules/fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+      "dev": true,
+      "peer": true
+    },
     "node_modules/figures": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -7399,6 +7524,21 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/http-proxy-agent": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
@@ -9552,6 +9692,25 @@
         "node": "*"
       }
     },
+    "node_modules/jspdf": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@babel/runtime": "^7.23.2",
+        "atob": "^2.1.2",
+        "btoa": "^1.2.1",
+        "fflate": "^0.8.1"
+      },
+      "optionalDependencies": {
+        "canvg": "^3.0.6",
+        "core-js": "^3.6.0",
+        "dompurify": "^2.5.4",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "node_modules/jsprim": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
@@ -11001,6 +11160,14 @@
         "url": "https://opencollective.com/openlayers"
       }
     },
+    "node_modules/ol-ext": {
+      "version": "4.0.24",
+      "resolved": "https://registry.npmjs.org/ol-ext/-/ol-ext-4.0.24.tgz",
+      "integrity": "sha512-VEf1+mjvbe35mMsszVsugqcvWfeGcU8TwS+GgXm3nGYqiHR7CckX2DWmM9B94QCDnrJWKKXBicfInbkoe2xT7w==",
+      "peerDependencies": {
+        "ol": ">= 5.3.0"
+      }
+    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -11837,6 +12004,17 @@
       "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
       "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
     },
+    "node_modules/raf": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+      "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "performance-now": "^2.1.0"
+      }
+    },
     "node_modules/randexp": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz",
@@ -12408,6 +12586,17 @@
       "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
       "dev": true
     },
+    "node_modules/rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">= 0.8.15"
+      }
+    },
     "node_modules/rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -12848,6 +13037,17 @@
         "node": ">=8"
       }
     },
+    "node_modules/stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=0.1.14"
+      }
+    },
     "node_modules/stop-iteration-iterator": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -13224,6 +13424,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/symbol-tree": {
       "version": "3.2.4",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -13333,6 +13544,17 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -13823,6 +14045,17 @@
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/uuid": {
       "version": "9.0.1",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@@ -16276,6 +16509,13 @@
       "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
       "dev": true
     },
+    "@types/ol-ext": {
+      "version": "npm:@siedlerchr/types-ol-ext@3.6.1",
+      "resolved": "https://registry.npmjs.org/@siedlerchr/types-ol-ext/-/types-ol-ext-3.6.1.tgz",
+      "integrity": "sha512-CSqgXdS1d028TvnVXf2/dgnU66lRGHM9+R5E8lL2ilmm6ktHVZtsIhRR8rWSXu4Ybt1rGdMbwbDxofIbilWIyQ==",
+      "dev": true,
+      "requires": {}
+    },
     "@types/openlayers": {
       "version": "4.6.23",
       "resolved": "https://registry.npmjs.org/@types/openlayers/-/openlayers-4.6.23.tgz",
@@ -16291,6 +16531,14 @@
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
       "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
     },
+    "@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "@types/rbush": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.3.tgz",
@@ -16824,6 +17072,13 @@
       "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
       "dev": true
     },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true,
+      "peer": true
+    },
     "attr-accept": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
@@ -17037,6 +17292,14 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -17117,6 +17380,13 @@
         "node-int64": "^0.4.0"
       }
     },
+    "btoa": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+      "dev": true,
+      "peer": true
+    },
     "buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -17218,6 +17488,34 @@
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz",
       "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw=="
     },
+    "canvg": {
+      "version": "3.0.10",
+      "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
+      "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.13.11",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+          "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+          "dev": true,
+          "optional": true,
+          "peer": true
+        }
+      }
+    },
     "caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -17692,6 +17990,14 @@
       "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
+    "core-js": {
+      "version": "3.40.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
+      "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -17752,6 +18058,17 @@
       "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
       "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
     },
+    "css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "css.escape": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -18254,6 +18571,14 @@
         "webidl-conversions": "^7.0.0"
       }
     },
+    "dompurify": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "dot-prop": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -19280,6 +19605,13 @@
         "pend": "~1.2.0"
       }
     },
+    "fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+      "dev": true,
+      "peer": true
+    },
     "figures": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -19792,6 +20124,18 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      }
+    },
     "http-proxy-agent": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
@@ -21332,6 +21676,23 @@
         "through": ">=2.2.7 <3"
       }
     },
+    "jspdf": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+      "dev": true,
+      "peer": true,
+      "requires": {
+        "@babel/runtime": "^7.23.2",
+        "atob": "^2.1.2",
+        "btoa": "^1.2.1",
+        "canvg": "^3.0.6",
+        "core-js": "^3.6.0",
+        "dompurify": "^2.5.4",
+        "fflate": "^0.8.1",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "jsprim": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
@@ -22385,6 +22746,12 @@
         "rbush": "^4.0.0"
       }
     },
+    "ol-ext": {
+      "version": "4.0.24",
+      "resolved": "https://registry.npmjs.org/ol-ext/-/ol-ext-4.0.24.tgz",
+      "integrity": "sha512-VEf1+mjvbe35mMsszVsugqcvWfeGcU8TwS+GgXm3nGYqiHR7CckX2DWmM9B94QCDnrJWKKXBicfInbkoe2xT7w==",
+      "requires": {}
+    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -22905,6 +23272,17 @@
       "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
       "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
     },
+    "raf": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+      "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "performance-now": "^2.1.0"
+      }
+    },
     "randexp": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz",
@@ -23329,6 +23707,14 @@
       "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
       "dev": true
     },
+    "rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -23658,6 +24044,14 @@
         }
       }
     },
+    "stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "stop-iteration-iterator": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -23923,6 +24317,14 @@
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
     },
+    "svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "symbol-tree": {
       "version": "3.2.4",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -24005,6 +24407,17 @@
       "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
       "dev": true
     },
+    "text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -24355,6 +24768,17 @@
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
+    "utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "requires": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "uuid": {
       "version": "9.0.1",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
diff --git a/package.json b/package.json
index 52f25ca960a70a3e6c7eb204f2519242adfd512c..1d5e4494e78b9aec83be16ff9ceb0dcdbc4c17ae 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
     "molart": "1.15.0",
     "next": "13.4.19",
     "ol": "10.2.0",
+    "ol-ext": "4.0.24",
     "polished": "4.3.1",
     "postcss": "8.4.29",
     "query-string": "7.1.3",
@@ -62,6 +63,7 @@
     "zod-to-json-schema": "3.22.4"
   },
   "devDependencies": {
+    "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.6.1",
     "@commitlint/cli": "17.8.1",
     "@commitlint/config-conventional": "17.8.1",
     "@testing-library/jest-dom": "6.1.6",
diff --git a/src/components/Map/MapDrawActions/MapDrawActions.component.tsx b/src/components/Map/MapDrawActions/MapDrawActions.component.tsx
index 50deda14baae2d6318e793162819e7c106472dc6..e7a0139374792c66da09cc71af07b038a0752be9 100644
--- a/src/components/Map/MapDrawActions/MapDrawActions.component.tsx
+++ b/src/components/Map/MapDrawActions/MapDrawActions.component.tsx
@@ -37,6 +37,12 @@ export const MapDrawActions = (): React.JSX.Element | null => {
         icon="image"
         title="Draw image"
       />
+      <MapDrawActionsButton
+        isActive={activeAction === MAP_EDIT_ACTIONS.TRANSFORM_IMAGE}
+        toggleMapEditAction={() => toggleMapEditAction(MAP_EDIT_ACTIONS.TRANSFORM_IMAGE)}
+        icon="resize-image"
+        title="Transform image"
+      />
     </div>
   );
 };
diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
index 63fddd60951fe9f46d1f628fbf562d7438516440..709336b14c2a6815994e21ebd1e24e138b5384ce 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
@@ -15,3 +15,10 @@ export type ScaleFunction = (resolution: number) => number;
 export type OverlayBioEntityGroupedElementsType = {
   [id: string]: Array<OverlayBioEntityRender & { amount: number }>;
 };
+
+export type BoundingBox = {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+};
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
index fb720a2799702c4a049aa1ca3ed4ba0c6db8d41b..11211b8bb13f926f939c3d76935376153149b2b7 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
@@ -7,10 +7,11 @@ import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/
 import Map from 'ol/Map';
 import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick';
 import { Comment } from '@/types/models';
-import { Layer } from 'ol/layer';
 import SimpleGeometry from 'ol/geom/SimpleGeometry';
 import { Feature } from 'ol';
 import { FEATURE_TYPE } from '@/constants/features';
+import VectorLayer from 'ol/layer/Vector';
+import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import * as leftClickHandleAlias from './leftClickHandleAlias';
 import * as clickHandleReaction from '../clickHandleReaction';
 
@@ -33,6 +34,8 @@ describe('onMapLeftClick', () => {
   const isResultDrawerOpen = true;
   const comments: Array<Comment> = [];
   let mapInstance: Map;
+  const vectorLayer = new VectorLayer({});
+  vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE);
   const event = { coordinate: [100, 50], pixel: [200, 100] };
   const mapSize = {
     width: 90,
@@ -51,11 +54,7 @@ describe('onMapLeftClick', () => {
   it('dispatches updateLastClick and resets data if no feature at pixel', async () => {
     const dispatch = jest.fn();
     jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
-      callback(
-        new Feature({ zIndex: 1 }),
-        null as unknown as Layer,
-        null as unknown as SimpleGeometry,
-      );
+      callback(new Feature({ zIndex: 1 }), vectorLayer, null as unknown as SimpleGeometry);
     });
     await onMapLeftClick(
       mapSize,
@@ -80,7 +79,7 @@ describe('onMapLeftClick', () => {
     }));
     const feature = new Feature({ id: 1, type: FEATURE_TYPE.ALIAS, zIndex: 1 });
     jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
-      callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry);
+      callback(feature, vectorLayer, null as unknown as SimpleGeometry);
     });
     (handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false });
 
@@ -104,7 +103,7 @@ describe('onMapLeftClick', () => {
     }));
     const feature = new Feature({ id: 1, type: FEATURE_TYPE.REACTION, zIndex: 1 });
     jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
-      callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry);
+      callback(feature, vectorLayer, null as unknown as SimpleGeometry);
     });
     (handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false });
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
index 3a33837b1a82b2f8999bc503374096f15babaf38..d9a8031ca12b0d5a5abda289469363ff766be43b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
@@ -15,6 +15,7 @@ import { resetReactionsData } from '@/redux/reactions/reactions.slice';
 import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset';
 import { FEATURE_TYPE } from '@/constants/features';
 import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
+import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 function isFeatureFilledCompartment(feature: FeatureLike): boolean {
   return feature.get('type') === FEATURE_TYPE.COMPARTMENT && feature.get('filled');
@@ -50,9 +51,10 @@ export const onMapLeftClick =
       let featureAtPixel: FeatureLike | undefined;
       mapInstance.forEachFeatureAtPixel(
         pixel,
-        feature => {
+        (feature, layer) => {
           const featureZIndex = feature.get('zIndex');
           if (
+            layer && layer.get('type') === VECTOR_MAP_LAYER_TYPE &&
             (isFeatureFilledCompartment(feature) || isFeatureNotCompartment(feature)) &&
             (featureZIndex === undefined || featureZIndex >= 0)
           ) {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
index 6f60435b25a935e5b0e3a5adf4968a5b01a1a2fe..ada5b18ea5048e2a90b37f74aae6c2cdcf19375b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
@@ -1,5 +1,5 @@
 /* eslint-disable no-magic-numbers */
-import { Feature } from 'ol';
+import { Collection, Feature } from 'ol';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import { useEffect, useMemo, useState } from 'react';
@@ -15,7 +15,7 @@ import {
 } from '@/redux/layers/layers.selectors';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { MapInstance } from '@/types/map';
-import { LineString, MultiPolygon, Point } from 'ol/geom';
+import { Geometry, LineString, MultiPolygon, Point } from 'ol/geom';
 import Polygon from 'ol/geom/Polygon';
 import Layer from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer';
 import { arrowTypesSelector, lineTypesSelector } from '@/redux/shapes/shapes.selectors';
@@ -26,6 +26,7 @@ import { LayerState } from '@/redux/layers/layers.types';
 import { mapEditToolsActiveActionSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
 import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants';
 import { Extent } from 'ol/extent';
+import getTransformImageInteraction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction';
 
 export const useOlMapAdditionalLayers = (
   mapInstance: MapInstance,
@@ -92,11 +93,12 @@ export const useOlMapAdditionalLayers = (
         lineTypes,
         arrowTypes,
         mapInstance,
+        mapSize,
         pointToProjection,
       });
       return additionalLayer.vectorLayer;
     });
-  }, [layersState, lineTypes, arrowTypes, mapInstance, pointToProjection]);
+  }, [layersState, lineTypes, arrowTypes, mapInstance, mapSize, pointToProjection]);
 
   useEffect(() => {
     if (layersLoading === 'pending') {
@@ -107,6 +109,25 @@ export const useOlMapAdditionalLayers = (
     }
   }, [layersForCurrentModel, layersLoading, layersLoadingState]);
 
+  const transformInteraction = useMemo(() => {
+    if (!dispatch || !currentModelId || !activeLayer) {
+      return null;
+    }
+    let imagesFeatures: Collection<Feature<Geometry>> = new Collection();
+    const vectorLayer = vectorLayers.find(layer => layer.get('id') === activeLayer);
+    if (vectorLayer) {
+      imagesFeatures = new Collection(vectorLayer.get('imagesFeatures'));
+    }
+    return getTransformImageInteraction(
+      dispatch,
+      mapSize,
+      currentModelId,
+      activeLayer,
+      imagesFeatures,
+      restrictionExtent,
+    );
+  }, [dispatch, mapSize, currentModelId, activeLayer, vectorLayers, restrictionExtent]);
+
   useEffect(() => {
     vectorLayers.forEach(layer => {
       const layerId = layer.get('id');
@@ -116,6 +137,19 @@ export const useOlMapAdditionalLayers = (
     });
   }, [layersVisibilityForCurrentModel, vectorLayers]);
 
+  useEffect(() => {
+    if (!transformInteraction) {
+      return () => {};
+    }
+    if (!activeLayer || !vectorRendering || activeAction !== MAP_EDIT_ACTIONS.TRANSFORM_IMAGE) {
+      return () => {};
+    }
+    mapInstance?.addInteraction(transformInteraction);
+    return () => {
+      mapInstance?.removeInteraction(transformInteraction);
+    };
+  }, [activeAction, activeLayer, mapInstance, transformInteraction, vectorRendering]);
+
   useEffect(() => {
     if (!drawImageInteraction) {
       return;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 2f5b97ba2fd9449bff6952895cc0ec603b81bf01..60746292152eec5fc819559449a89c7c19d1a342 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -46,6 +46,7 @@ export default function processModelElements(
         zIndex: element.z,
         pointToProjection,
         mapInstance,
+        mapSize,
       });
       validElements.push(glyph);
       return;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..532c485dee1ef05dc9e188e3f600cc2c995d7fae
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.test.ts
@@ -0,0 +1,26 @@
+/* eslint-disable no-magic-numbers */
+import { Extent } from 'ol/extent';
+import getBoundingBoxFromExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent';
+
+describe('getBoundingBoxFromExtent', () => {
+  it('should return a bounding box for extent', () => {
+    const extent: Extent = [0, 195700, 195700, 0];
+    const mapSize = {
+      width: 512,
+      height: 512,
+      tileSize: 256,
+      minZoom: 2,
+      maxZoom: 4,
+    };
+
+    const result = getBoundingBoxFromExtent(extent, mapSize);
+
+    expect(result).toHaveProperty('x', 1024);
+    expect(result).toHaveProperty('y', 1024);
+    expect(result).toHaveProperty('width');
+    expect(result).toHaveProperty('height');
+
+    expect(result.width).toBeCloseTo(10);
+    expect(result.height).toBeCloseTo(10);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.ts
new file mode 100644
index 0000000000000000000000000000000000000000..efab1e2d7b3cd6d02e193a5a151981854b90cb15
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent.ts
@@ -0,0 +1,23 @@
+/* eslint-disable no-magic-numbers */
+import { MapSize } from '@/redux/map/map.types';
+import { toLonLat } from 'ol/proj';
+import { latLngToPoint } from '@/utils/map/latLngToPoint';
+import { Extent } from 'ol/extent';
+import { BoundingBox } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+
+export default function getBoundingBoxFromExtent(extent: Extent, mapSize: MapSize): BoundingBox {
+  const [startLng, startLat] = toLonLat([extent[0], extent[3]]);
+  const startPoint = latLngToPoint([startLat, startLng], mapSize);
+  const [endLng, endLat] = toLonLat([extent[2], extent[1]]);
+  const endPoint = latLngToPoint([endLat, endLng], mapSize);
+
+  const width = Math.abs(endPoint.x - startPoint.x);
+  const height = Math.abs(endPoint.y - startPoint.y);
+
+  return {
+    width,
+    height,
+    x: startPoint.x,
+    y: startPoint.y,
+  };
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.test.ts
index 6ac8c5d29e0e6aa78f4b7edb05e1a2f61a626b25..bef1e03750a22e80784dc3272d1d9b4d55310096 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.test.ts
@@ -13,6 +13,13 @@ describe('Glyph', () => {
   let glyph: Glyph;
   let mapInstance: MapInstance;
   let pointToProjectionMock: jest.MockedFunction<UsePointToProjectionResult>;
+  const mapSize = {
+    width: 90,
+    height: 90,
+    tileSize: 256,
+    minZoom: 2,
+    maxZoom: 9,
+  };
 
   beforeEach(() => {
     const dummyElement = document.createElement('div');
@@ -37,6 +44,7 @@ describe('Glyph', () => {
       zIndex: 1,
       pointToProjection: pointToProjectionMock,
       mapInstance,
+      mapSize,
     };
     glyph = new Glyph(props);
   });
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.ts
index c7844ae36e3acae71caa5accfab855a3e1d06b40..53f71dc9c250d3ff04e593ece5a65b1b9440f115 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph.ts
@@ -15,6 +15,9 @@ import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/st
 import { WHITE_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
 import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
+import getBoundingBoxFromExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent';
+import { MapSize } from '@/redux/map/map.types';
+import { LayerImage } from '@/types/models';
 
 export type GlyphProps = {
   elementId: number;
@@ -26,6 +29,7 @@ export type GlyphProps = {
   zIndex: number;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  mapSize: MapSize;
 };
 
 export default class Glyph {
@@ -39,6 +43,10 @@ export default class Glyph {
 
   polygonStyle: Style;
 
+  polygon: Polygon = new Polygon([]);
+
+  elementId: number;
+
   width: number;
 
   height: number;
@@ -47,6 +55,10 @@ export default class Glyph {
 
   y: number;
 
+  zIndex: number;
+
+  glyphId: number | null;
+
   widthOnMap: number;
 
   heightOnMap: number;
@@ -55,8 +67,20 @@ export default class Glyph {
 
   minResolution: number;
 
+  imageWidth: number = 1;
+
+  imageHeight: number = 1;
+
+  imageWidthOnMap: number = 1;
+
+  imageHeightOnMap: number = 1;
+
+  mapInstance: MapInstance;
+
   pointToProjection: UsePointToProjectionResult;
 
+  mapSize: MapSize;
+
   constructor({
     elementId,
     glyphId,
@@ -67,11 +91,17 @@ export default class Glyph {
     zIndex,
     pointToProjection,
     mapInstance,
+    mapSize,
   }: GlyphProps) {
+    this.elementId = elementId;
     this.width = width;
     this.height = height;
+    this.mapSize = mapSize;
+    this.glyphId = glyphId;
     this.x = x;
     this.y = y;
+    this.zIndex = zIndex;
+    this.mapInstance = mapInstance;
     this.pointToProjection = pointToProjection;
     const point1 = this.pointToProjection({ x: 0, y: 0 });
     const point2 = this.pointToProjection({ x: this.width, y: this.height });
@@ -81,26 +111,19 @@ export default class Glyph {
     const maxZoom = mapInstance?.getView().get('originalMaxZoom');
     this.minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom) || 1;
     this.pixelRatio = this.widthOnMap / this.minResolution / this.width;
-    const polygon = new Polygon([
-      [
-        pointToProjection({ x, y }),
-        pointToProjection({ x: x + width, y }),
-        pointToProjection({ x: x + width, y: y + height }),
-        pointToProjection({ x, y: y + height }),
-        pointToProjection({ x, y }),
-      ],
-    ]);
+
+    this.drawPolygon();
 
     this.polygonStyle = getStyle({
-      geometry: polygon,
-      zIndex,
+      geometry: this.polygon,
+      zIndex: this.zIndex,
       borderColor: { ...WHITE_COLOR, alpha: 0 },
       fillColor: { ...WHITE_COLOR, alpha: 0 },
     });
 
     this.noGlyphStyle = getStyle({
-      geometry: polygon,
-      zIndex,
+      geometry: this.polygon,
+      zIndex: this.zIndex,
       fillColor: '#E7E7E7',
     });
     this.noGlyphStyle.setText(
@@ -113,61 +136,127 @@ export default class Glyph {
     );
 
     this.feature = new Feature({
-      geometry: polygon,
-      id: elementId,
+      geometry: this.polygon,
+      id: this.elementId,
       type: FEATURE_TYPE.GLYPH,
-      zIndex,
-      getAnchorAndCoords: (): { anchor: Array<number>; coords: Coordinate } => {
-        const center = mapInstance?.getView().getCenter();
+      zIndex: this.zIndex,
+      getAnchorAndCoords: (coords: Coordinate): { anchor: Array<number>; coords: Coordinate } => {
+        const center = this.mapInstance?.getView().getCenter();
         let anchorX = 0;
         let anchorY = 0;
         if (center) {
-          anchorX =
-            (center[0] - this.pointToProjection({ x: this.x, y: this.y })[0]) / this.widthOnMap;
-          anchorY =
-            -(center[1] - this.pointToProjection({ x: this.x, y: this.y })[1]) / this.heightOnMap;
+          anchorX = (center[0] - coords[0]) / this.widthOnMap;
+          anchorY = -(center[1] - coords[1]) / this.heightOnMap;
         }
         return { anchor: [anchorX, anchorY], coords: center || [0, 0] };
       },
     });
 
+    this.feature.set('setCoordinates', this.setCoordinates.bind(this));
+    this.feature.set('getGlyphData', this.getGlyphData.bind(this));
+    this.feature.set('reset', this.reset.bind(this));
     this.feature.setStyle(this.getStyle.bind(this));
-    if (!glyphId) {
+
+    if (!this.glyphId) {
       return;
     }
     const img = new Image();
     img.onload = (): void => {
-      const imageWidth = img.naturalWidth;
-      const imageHeight = img.naturalHeight;
-      const heightScale = height / imageHeight;
-      const widthScale = width / imageWidth;
-      if (heightScale < widthScale) {
-        this.imageScale = heightScale;
-        this.widthOnMap = (this.heightOnMap * imageWidth) / imageHeight;
-      } else {
-        this.imageScale = widthScale;
-        this.heightOnMap = (this.widthOnMap * imageHeight) / imageWidth;
-      }
+      this.imageWidth = img.naturalWidth;
+      this.imageHeight = img.naturalHeight;
+      const imagePoint1 = this.pointToProjection({ x: 0, y: 0 });
+      const imagePoint2 = this.pointToProjection({ x: this.imageWidth, y: this.imageHeight });
+      this.imageWidthOnMap = Math.abs(imagePoint2[0] - imagePoint1[0]);
+      this.imageHeightOnMap = Math.abs(imagePoint2[1] - imagePoint1[1]);
+      this.setImageScaleAndDimensions(this.heightOnMap, this.widthOnMap);
       this.style = new Style({
         image: new Icon({
           anchor: [0, 0],
           img,
-          size: [imageWidth, imageHeight],
+          size: [this.imageWidth, this.imageHeight],
         }),
-        zIndex,
+        zIndex: this.zIndex,
       });
     };
-    img.src = `${BASE_NEW_API_URL}${apiPath.getGlyphImage(glyphId)}`;
+    img.src = `${BASE_NEW_API_URL}${apiPath.getGlyphImage(this.glyphId)}`;
+  }
+
+  private drawPolygon(): void {
+    this.polygon = new Polygon([
+      [
+        this.pointToProjection({ x: this.x, y: this.y }),
+        this.pointToProjection({ x: this.x + this.width, y: this.y }),
+        this.pointToProjection({ x: this.x + this.width, y: this.y + this.height }),
+        this.pointToProjection({ x: this.x, y: this.y + this.height }),
+        this.pointToProjection({ x: this.x, y: this.y }),
+      ],
+    ]);
+  }
+
+  private reset(): void {
+    this.drawPolygon();
+    this.polygonStyle.setGeometry(this.polygon);
+    this.feature.setGeometry(this.polygon);
+  }
+
+  protected setImageScaleAndDimensions(height: number, width: number): void {
+    this.widthOnMap = width;
+    this.heightOnMap = height;
+    const heightScale = height / this.imageHeightOnMap;
+    const widthScale = width / this.imageWidthOnMap;
+    if (heightScale < widthScale) {
+      this.imageScale = heightScale;
+      this.widthOnMap = (this.heightOnMap * this.imageWidth) / this.imageHeight;
+    } else {
+      this.imageScale = widthScale;
+      this.heightOnMap = (this.widthOnMap * this.imageHeight) / this.imageWidth;
+    }
+  }
+
+  private setCoordinates(coords: Coordinate[][]): void {
+    const geometry = this.polygonStyle.getGeometry();
+    if (geometry && geometry instanceof Polygon) {
+      geometry.setCoordinates(coords);
+      const boundingBox = getBoundingBoxFromExtent(geometry.getExtent(), this.mapSize);
+      this.x = boundingBox.x;
+      this.y = boundingBox.y;
+      this.width = boundingBox.width;
+      this.height = boundingBox.height;
+    }
+  }
+
+  private getGlyphData(): LayerImage {
+    return {
+      id: this.elementId,
+      x: this.x,
+      y: this.y,
+      width: this.width,
+      height: this.height,
+      glyph: this.glyphId,
+      z: this.zIndex,
+    };
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
     const scale = this.minResolution / resolution;
     const getAnchorAndCoords = feature.get('getAnchorAndCoords');
     let anchor = [0, 0];
-    let coords = this.pointToProjection({ x: this.x, y: this.y });
+    let coords = [this.x, this.y];
 
+    const geometry = feature.getGeometry();
+    if (geometry && geometry instanceof Polygon) {
+      const polygonExtent = geometry.getExtent();
+      if (polygonExtent) {
+        coords = [polygonExtent[0], polygonExtent[3]];
+        const width = Math.abs(polygonExtent[0] - polygonExtent[2]);
+        const height = Math.abs(polygonExtent[1] - polygonExtent[3]);
+        this.setImageScaleAndDimensions(height, width);
+      }
+    } else {
+      return [];
+    }
     if (getAnchorAndCoords instanceof Function) {
-      const anchorAndCoords = getAnchorAndCoords();
+      const anchorAndCoords = getAnchorAndCoords(coords);
       anchor = anchorAndCoords.anchor;
       coords = anchorAndCoords.coords;
     }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts
index 1cdd1aef0edf1bfae3d0b785eb3080420c2e3fe9..9da93414263578fa50ea5684ac1918f78dd1e5b5 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts
@@ -23,6 +23,13 @@ jest.mock('../style/rgbToHex');
 
 describe('Layer', () => {
   let props: LayerProps;
+  const mapSize = {
+    width: 90,
+    height: 90,
+    tileSize: 256,
+    minZoom: 2,
+    maxZoom: 9,
+  };
 
   beforeEach(() => {
     const dummyElement = document.createElement('div');
@@ -128,6 +135,7 @@ describe('Layer', () => {
       layerId: 23,
       pointToProjection: jest.fn(point => [point.x, point.y]),
       mapInstance,
+      mapSize,
       lineTypes: {},
       arrowTypes: {},
     };
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
index e48faf93c38db11b282b23a3a87e253979eee661..65c6f6132f43884169e4963ee1d3eb7d00cc873b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -27,6 +27,7 @@ import {
 import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
 import { Stroke } from 'ol/style';
 import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
+import { MapSize } from '@/redux/map/map.types';
 
 export interface LayerProps {
   texts: Array<LayerText>;
@@ -39,10 +40,13 @@ export interface LayerProps {
   lineTypes: LineTypeDict;
   arrowTypes: ArrowTypeDict;
   mapInstance: MapInstance;
+  mapSize: MapSize;
   pointToProjection: UsePointToProjectionResult;
 }
 
 export default class Layer {
+  layerId: number;
+
   texts: Array<LayerText>;
 
   rects: Array<LayerRect>;
@@ -61,6 +65,8 @@ export default class Layer {
 
   mapInstance: MapInstance;
 
+  mapSize: MapSize;
+
   vectorSource: VectorSource<
     Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>
   >;
@@ -80,6 +86,7 @@ export default class Layer {
     lineTypes,
     arrowTypes,
     mapInstance,
+    mapSize,
     pointToProjection,
   }: LayerProps) {
     this.vectorSource = new VectorSource({});
@@ -93,11 +100,14 @@ export default class Layer {
     this.arrowTypes = arrowTypes;
     this.pointToProjection = pointToProjection;
     this.mapInstance = mapInstance;
+    this.mapSize = mapSize;
+    this.layerId = layerId;
 
     this.vectorSource.addFeatures(this.getTextsFeatures());
     this.vectorSource.addFeatures(this.getRectsFeatures());
     this.vectorSource.addFeatures(this.getOvalsFeatures());
-    this.drawImages();
+    const imagesFeatures = this.getImagesFeatures();
+    this.vectorSource.addFeatures(imagesFeatures);
 
     const { linesFeatures, arrowsFeatures } = this.getLinesFeatures();
     this.vectorSource.addFeatures(linesFeatures);
@@ -111,6 +121,7 @@ export default class Layer {
     });
 
     this.vectorLayer.set('id', layerId);
+    this.vectorLayer.set('imagesFeatures', imagesFeatures);
     this.vectorLayer.set('drawImage', this.drawImage.bind(this));
   }
 
@@ -290,13 +301,22 @@ export default class Layer {
     return { linesFeatures, arrowsFeatures };
   };
 
-  private drawImages(): void {
-    Object.values(this.images).forEach(image => {
-      this.drawImage(image);
+  private getImagesFeatures(): Feature<Polygon>[] {
+    return Object.values(this.images).map(image => {
+      return this.getGlyphFeature(image);
     });
   }
 
   private drawImage(image: LayerImage): void {
+    const glyphFeature = this.getGlyphFeature(image);
+    const imagesFeatures = this.vectorLayer.get('imagesFeatures');
+    if (imagesFeatures && Array.isArray(imagesFeatures)) {
+      imagesFeatures.push(glyphFeature);
+    }
+    this.vectorSource.addFeature(glyphFeature);
+  }
+
+  private getGlyphFeature(image: LayerImage): Feature<Polygon> {
     const glyph = new Glyph({
       elementId: image.id,
       glyphId: image.glyph,
@@ -307,8 +327,9 @@ export default class Layer {
       zIndex: image.z,
       pointToProjection: this.pointToProjection,
       mapInstance: this.mapInstance,
+      mapSize: this.mapSize,
     });
-    this.vectorSource.addFeature(glyph.feature);
+    return glyph.feature;
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getDrawImageInteraction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getDrawImageInteraction.ts
index 55a459bcc74378a7a0e2cd289c27050308a7e4ea..35bd90a5543d3a96015ac202732164dd55e806fe 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getDrawImageInteraction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getDrawImageInteraction.ts
@@ -2,13 +2,12 @@
 import Draw from 'ol/interaction/Draw';
 import SimpleGeometry from 'ol/geom/SimpleGeometry';
 import Polygon from 'ol/geom/Polygon';
-import { toLonLat } from 'ol/proj';
-import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
 import { Coordinate } from 'ol/coordinate';
 import { openLayerImageObjectFactoryModal } from '@/redux/modal/modal.slice';
 import { Extent } from 'ol/extent';
+import getBoundingBoxFromExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent';
 
 export default function getDrawImageInteraction(
   mapSize: MapSize,
@@ -70,25 +69,13 @@ export default function getDrawImageInteraction(
     const geometry = event.feature.getGeometry() as Polygon;
     const extent = geometry.getExtent();
 
-    const [startLng, startLat] = toLonLat([extent[0], extent[3]]);
-    const startPoint = latLngToPoint([startLat, startLng], mapSize);
-    const [endLng, endLat] = toLonLat([extent[2], extent[1]]);
-    const endPoint = latLngToPoint([endLat, endLng], mapSize);
+    const boundingBox = getBoundingBoxFromExtent(extent, mapSize);
 
-    const width = Math.abs(endPoint.x - startPoint.x);
-    const height = Math.abs(endPoint.y - startPoint.y);
-
-    if (!width || !height) {
+    if (!boundingBox.width || !boundingBox.height) {
       return;
     }
-    dispatch(
-      openLayerImageObjectFactoryModal({
-        x: startPoint.x,
-        y: startPoint.y,
-        width,
-        height,
-      }),
-    );
+
+    dispatch(openLayerImageObjectFactoryModal(boundingBox));
   });
 
   return drawImageInteraction;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..92a891c86a15ff5806d96c2fc6792868f3899f12
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.test.ts
@@ -0,0 +1,54 @@
+/* eslint-disable no-magic-numbers */
+import modalReducer from '@/redux/modal/modal.slice';
+import { MapSize } from '@/redux/map/map.types';
+import {
+  createStoreInstanceUsingSliceReducer,
+  ToolkitStoreWithSingleSlice,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { ModalState } from '@/redux/modal/modal.types';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
+import { Collection, Feature } from 'ol';
+import getTransformImageInteraction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction';
+import Transform from 'ol-ext/interaction/Transform';
+import { Geometry } from 'ol/geom';
+
+jest.mock('../../../../../../../utils/map/latLngToPoint', () => ({
+  latLngToPoint: jest.fn(latLng => ({ x: latLng[0], y: latLng[1] })),
+}));
+
+describe('getTransformImageInteraction', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<ModalState>;
+  let modelIdMock: number;
+  let layerIdMock: number;
+  let featuresCollectionMock: Collection<Feature<Geometry>>;
+  const mockDispatch = jest.fn(() => {});
+
+  let mapSize: MapSize;
+
+  beforeEach(() => {
+    mapSize = {
+      width: 800,
+      height: 600,
+      minZoom: 1,
+      maxZoom: 9,
+      tileSize: DEFAULT_TILE_SIZE,
+    };
+    store = createStoreInstanceUsingSliceReducer('modal', modalReducer);
+    store.dispatch = mockDispatch;
+    modelIdMock = 1;
+    layerIdMock = 1;
+    featuresCollectionMock = new Collection<Feature<Geometry>>();
+  });
+
+  it('returns a Transform interaction', () => {
+    const transformInteraction = getTransformImageInteraction(
+      store.dispatch,
+      mapSize,
+      modelIdMock,
+      layerIdMock,
+      featuresCollectionMock,
+      [1000, 1000, 1000, 1000],
+    );
+    expect(transformInteraction).toBeInstanceOf(Transform);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20041fa337ce2d8d6fe2c27215081cafbdfe4bfe
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/getTransformImageInteraction.ts
@@ -0,0 +1,121 @@
+/* eslint-disable no-magic-numbers */
+import Polygon, { fromExtent } from 'ol/geom/Polygon';
+import { AppDispatch } from '@/redux/store';
+import Transform from 'ol-ext/interaction/Transform';
+import { Geometry } from 'ol/geom';
+import { Collection, Feature } from 'ol';
+import BaseEvent from 'ol/events/Event';
+import { updateLayerImageObject } from '@/redux/layers/layers.thunks';
+import { layerUpdateImage } from '@/redux/layers/layers.slice';
+import getBoundingBoxFromExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBoundingBoxFromExtent';
+import { MapSize } from '@/redux/map/map.types';
+import { Extent } from 'ol/extent';
+
+export default function getTransformImageInteraction(
+  dispatch: AppDispatch,
+  mapSize: MapSize,
+  modelId: number,
+  activeLayer: number,
+  featuresCollection: Collection<Feature<Geometry>>,
+  restrictionExtent: Extent,
+): Transform {
+  const transform = new Transform({
+    features: featuresCollection,
+    scale: true,
+    rotate: false,
+    stretch: false,
+    keepRectangle: true,
+    translate: true,
+    translateBBox: true,
+    noFlip: true,
+  });
+
+  transform.on('translating', (event: BaseEvent | Event) => {
+    const transformEvent = event as unknown as { feature: Feature };
+    const { feature } = transformEvent;
+    const geometry = feature.getGeometry();
+    if (geometry) {
+      const extent = geometry.getExtent();
+      const [minX, minY, maxX, maxY] = extent;
+
+      const width = maxX - minX;
+      const height = maxY - minY;
+
+      const correctedMinX = Math.min(
+        restrictionExtent[2] - width,
+        Math.max(restrictionExtent[0], minX),
+      );
+      const correctedMinY = Math.min(
+        restrictionExtent[3] - height,
+        Math.max(restrictionExtent[1], minY),
+      );
+
+      const dx = correctedMinX - minX;
+      const dy = correctedMinY - minY;
+
+      if (dx !== 0 || dy !== 0) {
+        geometry.translate(dx, dy);
+        geometry.changed();
+        transform.setSelection(new Collection([feature]));
+      }
+    }
+  });
+
+  transform.on('scaling', (event: BaseEvent | Event) => {
+    const transformEvent = event as unknown as { feature: Feature };
+    const { feature } = transformEvent;
+    const geometry = feature.getGeometry();
+
+    if (!geometry) {
+      return;
+    }
+
+    const extent = geometry.getExtent();
+    const [minX, minY, maxX, maxY] = extent;
+    const newMinX = Math.max(minX, restrictionExtent[0]);
+    const newMinY = Math.max(minY, restrictionExtent[1]);
+    const newMaxX = Math.min(maxX, restrictionExtent[2]);
+    const newMaxY = Math.min(maxY, restrictionExtent[3]);
+    const newExtent = [newMinX, newMinY, newMaxX, newMaxY];
+
+    const hasChanged = newExtent.some((value, index) => value !== extent[index]);
+
+    if (hasChanged) {
+      const newGeometry = fromExtent(newExtent);
+      newGeometry.scale(1, -1);
+      feature.setGeometry(newGeometry);
+      transform.setSelection(new Collection([feature]));
+    }
+  });
+
+  transform.on(['scaleend', 'translateend'], async (event: BaseEvent | Event): Promise<void> => {
+    const transformEvent = event as unknown as { feature: Feature };
+    const { feature } = transformEvent;
+    const setCoordinates = feature.get('setCoordinates');
+    const getGlyphData = feature.get('getGlyphData');
+    const reset = feature.get('reset');
+    const geometry = feature.getGeometry();
+    if (geometry && getGlyphData instanceof Function) {
+      const glyphData = getGlyphData();
+      try {
+        const boundingBox = getBoundingBoxFromExtent(geometry.getExtent(), mapSize);
+        const layerImage = await dispatch(
+          updateLayerImageObject({ modelId, layerId: activeLayer, ...glyphData, ...boundingBox }),
+        ).unwrap();
+        if (layerImage) {
+          dispatch(layerUpdateImage({ modelId, layerId: activeLayer, layerImage }));
+        }
+        if (geometry instanceof Polygon && setCoordinates instanceof Function) {
+          setCoordinates(geometry.getCoordinates());
+          geometry.changed();
+        }
+      } catch {
+        if (reset instanceof Function) {
+          reset();
+        }
+      }
+    }
+  });
+
+  return transform;
+}
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index af6950d54d6226d48cbba1b00dd400ba27195294..3f062d67a43b1aedd46367fbe908093c4416f90e 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -67,6 +67,8 @@ export const apiPath = {
     `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}`,
   addLayerImageObject: (modelId: number, layerId: number): string =>
     `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/images/`,
+  updateLayerImageObject: (modelId: number, layerId: number, imageId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/images/${imageId}`,
   getLayer: (modelId: number, layerId: number): string =>
     `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}`,
   getGlyphImage: (glyphId: number): string =>
diff --git a/src/redux/layers/layers.reducers.ts b/src/redux/layers/layers.reducers.ts
index 9e41f8ded97ed3a120bc25c17e06d5f55ae886f4..f5718ad4f23e8eb2898f418026564ab1c8c8c8fb 100644
--- a/src/redux/layers/layers.reducers.ts
+++ b/src/redux/layers/layers.reducers.ts
@@ -79,3 +79,19 @@ export const layerAddImageReducer = (
   }
   layer.images[layerImage.id] = layerImage;
 };
+
+export const layerUpdateImageReducer = (
+  state: LayersState,
+  action: PayloadAction<{ modelId: number; layerId: number; layerImage: LayerImage }>,
+): void => {
+  const { modelId, layerId, layerImage } = action.payload;
+  const { data } = state[modelId];
+  if (!data) {
+    return;
+  }
+  const layer = data.layers.find(layerState => layerState.details.id === layerId);
+  if (!layer) {
+    return;
+  }
+  layer.images[layerImage.id] = layerImage;
+};
diff --git a/src/redux/layers/layers.slice.ts b/src/redux/layers/layers.slice.ts
index 9f78f0dd112b9e817107f37b9958b738dfab0ff4..9e4ea3cd29ddfae970fc02f46f95d416dd33b3ae 100644
--- a/src/redux/layers/layers.slice.ts
+++ b/src/redux/layers/layers.slice.ts
@@ -3,6 +3,7 @@ import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
 import {
   getLayersForModelReducer,
   layerAddImageReducer,
+  layerUpdateImageReducer,
   setActiveLayerReducer,
   setLayerVisibilityReducer,
 } from '@/redux/layers/layers.reducers';
@@ -14,12 +15,14 @@ export const layersSlice = createSlice({
     setLayerVisibility: setLayerVisibilityReducer,
     setActiveLayer: setActiveLayerReducer,
     layerAddImage: layerAddImageReducer,
+    layerUpdateImage: layerUpdateImageReducer,
   },
   extraReducers: builder => {
     getLayersForModelReducer(builder);
   },
 });
 
-export const { setLayerVisibility, setActiveLayer, layerAddImage } = layersSlice.actions;
+export const { setLayerVisibility, setActiveLayer, layerAddImage, layerUpdateImage } =
+  layersSlice.actions;
 
 export default layersSlice.reducer;
diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts
index 354eb086e91b2bb6d3fa59a8c4c7093b9a5f4788..9c826c55cf02885614a01a75ee214caa91af9206 100644
--- a/src/redux/layers/layers.thunks.ts
+++ b/src/redux/layers/layers.thunks.ts
@@ -181,3 +181,43 @@ export const addLayerImageObject = createAsyncThunk<
     return Promise.reject(getError({ error }));
   }
 });
+
+export const updateLayerImageObject = createAsyncThunk<
+  LayerImage | null,
+  {
+    modelId: number;
+    layerId: number;
+    id: number;
+    x: number;
+    y: number;
+    z: number;
+    width: number;
+    height: number;
+    glyph: number | null;
+  },
+  ThunkConfig
+>(
+  'vectorMap/updateLayerImageObject',
+  async ({ modelId, layerId, id, x, y, z, width, height, glyph }) => {
+    try {
+      const { data } = await axiosInstanceNewAPI.put<LayerImage>(
+        apiPath.updateLayerImageObject(modelId, layerId, id),
+        {
+          x,
+          y,
+          z,
+          width,
+          height,
+          glyph,
+        },
+      );
+      const isDataValid = validateDataUsingZodSchema(data, layerImageSchema);
+      if (isDataValid) {
+        return data;
+      }
+      return null;
+    } catch (error) {
+      return Promise.reject(getError({ error }));
+    }
+  },
+);
diff --git a/src/redux/mapEditTools/mapEditTools.constants.ts b/src/redux/mapEditTools/mapEditTools.constants.ts
index 3f54d2b0e3720fe456fb0759663465d15b54c4b5..2524c4921fcde62474a6f01ab66be851fe22d909 100644
--- a/src/redux/mapEditTools/mapEditTools.constants.ts
+++ b/src/redux/mapEditTools/mapEditTools.constants.ts
@@ -1,3 +1,4 @@
 export const MAP_EDIT_ACTIONS = {
   DRAW_IMAGE: 'DRAW_IMAGE',
+  TRANSFORM_IMAGE: 'TRANSFORM_IMAGE',
 } as const;
diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx
index 784cfb0efe5c148cef4591a9f0753c809dab2a23..4d888fc27857a3659b614a2f777c9852b0e20901 100644
--- a/src/shared/Icon/Icon.component.tsx
+++ b/src/shared/Icon/Icon.component.tsx
@@ -19,6 +19,7 @@ import { PlusIcon } from '@/shared/Icon/Icons/PlusIcon';
 import type { IconComponentType, IconTypes } from '@/types/iconTypes';
 import { DownloadIcon } from '@/shared/Icon/Icons/DownloadIcon';
 import { ImageIcon } from '@/shared/Icon/Icons/ImageIcon';
+import { ResizeImageIcon } from '@/shared/Icon/Icons/ResizeImageIcon';
 import { LocationIcon } from './Icons/LocationIcon';
 import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn';
 import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut';
@@ -61,6 +62,7 @@ const icons: Record<IconTypes, IconComponentType> = {
   user: UserIcon,
   'manage-user': ManageUserIcon,
   image: ImageIcon,
+  'resize-image': ResizeImageIcon,
 } as const;
 
 export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => {
diff --git a/src/shared/Icon/Icons/ResizeImageIcon.tsx b/src/shared/Icon/Icons/ResizeImageIcon.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..71a0c3244cdd9b24e29ac8082cda7568df3f2a63
--- /dev/null
+++ b/src/shared/Icon/Icons/ResizeImageIcon.tsx
@@ -0,0 +1,19 @@
+interface ResizeImageIconProps {
+  className?: string;
+}
+
+export const ResizeImageIcon = ({ className }: ResizeImageIconProps): JSX.Element => (
+  <svg
+    width="24"
+    height="24"
+    viewBox="0 0 24 24"
+    fill="none"
+    className={className}
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <rect x="6" y="2" width="17" height="17" stroke="currentColor" strokeWidth="1.5" fill="none" />
+    <rect x="1" y="14" width="9" height="9" stroke="currentColor" strokeWidth="1.5" fill="white" />
+    <line x1="10" y1="14" x2="18" y2="7" stroke="currentColor" strokeWidth="1.5" />
+    <polygon points="12,5 20,5 20,13" fill="currentColor" strokeWidth="1.5" />
+  </svg>
+);
diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts
index 0ef11e99da00f9fde333a19968bd5fc7e46dd9b0..469b7699a5d3afc72da33a27ad6959ae8cbc7281 100644
--- a/src/types/iconTypes.ts
+++ b/src/types/iconTypes.ts
@@ -25,6 +25,7 @@ export type IconTypes =
   | 'manage-user'
   | 'download'
   | 'question'
-  | 'image';
+  | 'image'
+  | 'resize-image';
 
 export type IconComponentType = ({ className }: { className: string }) => JSX.Element;