From 7b44a3113c13856aad6c1bbe675430e79ef81b67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Wed, 23 Oct 2024 11:10:28 +0200
Subject: [PATCH] feat(vector-map): add drawer to control individual layers +
 add support for text layer

---
 .../NavBar/NavBar.component.tsx               |   9 +
 .../Map/Drawer/Drawer.component.tsx           |   2 +
 .../LayersDrawer.component.test.tsx           |  46 +++
 .../LayersDrawer/LayersDrawer.component.tsx   |  31 ++
 .../Map/Drawer/LayersDrawer/index.ts          |   1 +
 .../useOlMapAdditionalLayers.test.ts          |  25 ++
 .../useOlMapAdditionalLayers.ts               |  65 ++++
 .../reactionsLayer/useOlMapReactionsLayer.ts  |   9 +-
 .../utils/config/useOlMapVectorLayers.ts      |   4 +-
 .../{ => coords}/getBezierCurve.test.ts       |   0
 .../shapes/{ => coords}/getBezierCurve.ts     |   0
 .../shapes/{ => coords}/getCentroid.test.ts   |   0
 .../utils/shapes/{ => coords}/getCentroid.ts  |   0
 .../shapes/{ => coords}/getCoordsX.test.ts    |   0
 .../utils/shapes/{ => coords}/getCoordsX.ts   |   0
 .../shapes/{ => coords}/getCoordsY.test.ts    |   0
 .../utils/shapes/{ => coords}/getCoordsY.ts   |   0
 .../{ => coords}/getCurveCoords.test.ts       |   4 +-
 .../shapes/{ => coords}/getCurveCoords.ts     |   4 +-
 .../{ => coords}/getEllipseCoords.test.ts     |   4 +-
 .../shapes/{ => coords}/getEllipseCoords.ts   |  52 +--
 .../{ => coords}/getPolygonCoords.test.ts     |   8 +-
 .../shapes/{ => coords}/getPolygonCoords.ts   |   8 +-
 .../utils/shapes/coords/getRotation.test.ts   |  19 ++
 .../utils/shapes/coords/getRotation.ts        |   6 +
 .../shapes/{ => elements}/BaseMultiPolygon.ts |  23 +-
 .../shapes/{ => elements}/Compartment.ts      |   8 +-
 .../{ => elements}/CompartmentCircle.test.ts  |  34 +-
 .../{ => elements}/CompartmentCircle.ts       |   4 +-
 .../{ => elements}/CompartmentSquare.test.ts  |  34 +-
 .../{ => elements}/CompartmentSquare.ts       |   4 +-
 .../shapes/{ => elements}/MapElement.test.ts  |  30 +-
 .../utils/shapes/{ => elements}/MapElement.ts |  31 +-
 .../{ => elements}/getMultiPolygon.test.ts    |   8 +-
 .../shapes/{ => elements}/getMultiPolygon.ts  |   6 +-
 .../utils/shapes/getStroke.test.ts            |  21 --
 .../utils/shapes/getText.test.ts              |  41 ---
 .../MapViewerVector/utils/shapes/getText.ts   |  85 -----
 .../utils/shapes/layer/Layer.test.ts          | 145 ++++++++
 .../utils/shapes/layer/Layer.ts               | 320 ++++++++++++++++++
 .../utils/shapes/{ => style}/getFill.test.ts  |   0
 .../utils/shapes/{ => style}/getFill.ts       |   0
 .../utils/shapes/style/getStroke.test.ts      |  27 ++
 .../utils/shapes/{ => style}/getStroke.ts     |   3 +
 .../utils/shapes/style/getStyle.test.ts       |  52 +++
 .../utils/shapes/style/getStyle.ts            |  38 +++
 .../utils/shapes/{ => style}/rgbToHex.test.ts |   0
 .../utils/shapes/{ => style}/rgbToHex.ts      |   0
 .../utils/shapes/text/Text.test.ts            |  73 ++++
 .../MapViewerVector/utils/shapes/text/Text.ts | 115 +++++++
 .../utils/shapes/text/getTextCoords.test.ts   |  20 ++
 .../utils/shapes/text/getTextCoords.ts        |  48 +++
 .../utils/shapes/text/getTextStyle.test.ts    |  26 ++
 .../utils/shapes/text/getTextStyle.ts         |  32 ++
 src/models/arrowTypeSchema.ts                 |   7 +
 src/models/fixtures/arrowTypesFixture.ts      |  10 +
 src/models/fixtures/layerLinesFixture.ts      |  10 +
 src/models/fixtures/layerOvalsFixture.ts      |  10 +
 src/models/fixtures/layerRectsFixture.ts      |  10 +
 src/models/fixtures/layerTextsFixture.ts      |  10 +
 src/models/fixtures/layersFixture.ts          |  10 +
 src/models/fixtures/modelElementsFixture.ts   |   5 +-
 src/models/layerLineSchema.ts                 |  15 +
 src/models/layerOvalSchema.ts                 |  15 +
 src/models/layerRectSchema.ts                 |  16 +
 src/models/layerSchema.ts                     |  10 +
 src/models/layerTextSchema.ts                 |  22 ++
 src/models/modelElementsSchema.ts             |  11 -
 src/redux/apiPath.ts                          |  10 +
 src/redux/layers/layers.constants.ts          |   1 +
 src/redux/layers/layers.mock.ts               |  11 +
 src/redux/layers/layers.reducers.test.ts      | 128 +++++++
 src/redux/layers/layers.reducers.ts           |  30 ++
 src/redux/layers/layers.selectors.ts          |  12 +
 src/redux/layers/layers.slice.ts              |  18 +
 src/redux/layers/layers.thunks.test.ts        |  68 ++++
 src/redux/layers/layers.thunks.ts             |  65 ++++
 src/redux/layers/layers.types.ts              |  21 ++
 .../modelElements/modelElements.thunks.ts     |   8 +-
 src/redux/root/init.thunks.ts                 |   4 +-
 src/redux/root/root.fixtures.ts               |   2 +
 src/redux/shapes/shapes.constants.ts          |   2 +
 src/redux/shapes/shapes.mock.ts               |   5 +
 src/redux/shapes/shapes.reducers.test.ts      |  47 ++-
 src/redux/shapes/shapes.reducers.ts           |  15 +-
 src/redux/shapes/shapes.selectors.ts          |   5 +
 src/redux/shapes/shapes.slice.ts              |   7 +-
 src/redux/shapes/shapes.thunks.test.ts        |  21 +-
 src/redux/shapes/shapes.thunks.ts             |  18 +-
 src/redux/shapes/shapes.types.ts              |   5 +-
 src/redux/store.ts                            |   2 +
 src/shared/Icon/Icon.component.tsx            |   2 +
 src/shared/Icon/Icons/LayersIcon.tsx          |  39 +++
 src/types/drawerName.ts                       |   3 +-
 src/types/iconTypes.ts                        |   1 +
 src/types/models.ts                           |  21 +-
 96 files changed, 1958 insertions(+), 299 deletions(-)
 create mode 100644 src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.test.tsx
 create mode 100644 src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
 create mode 100644 src/components/Map/Drawer/LayersDrawer/index.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getBezierCurve.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getBezierCurve.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCentroid.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCentroid.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCoordsX.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCoordsX.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCoordsY.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCoordsY.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCurveCoords.test.ts (96%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getCurveCoords.ts (95%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getEllipseCoords.test.ts (95%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getEllipseCoords.ts (61%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getPolygonCoords.test.ts (95%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => coords}/getPolygonCoords.ts (90%)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.ts
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/BaseMultiPolygon.ts (88%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/Compartment.ts (95%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/CompartmentCircle.test.ts (79%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/CompartmentCircle.ts (96%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/CompartmentSquare.test.ts (79%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/CompartmentSquare.ts (96%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/MapElement.test.ts (80%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/MapElement.ts (92%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/getMultiPolygon.test.ts (96%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => elements}/getMultiPolygon.ts (91%)
 delete mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.test.ts
 delete mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.test.ts
 delete mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => style}/getFill.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => style}/getFill.ts (100%)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.test.ts
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => style}/getStroke.ts (79%)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => style}/rgbToHex.test.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/utils/shapes/{ => style}/rgbToHex.ts (100%)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
 create mode 100644 src/models/arrowTypeSchema.ts
 create mode 100644 src/models/fixtures/arrowTypesFixture.ts
 create mode 100644 src/models/fixtures/layerLinesFixture.ts
 create mode 100644 src/models/fixtures/layerOvalsFixture.ts
 create mode 100644 src/models/fixtures/layerRectsFixture.ts
 create mode 100644 src/models/fixtures/layerTextsFixture.ts
 create mode 100644 src/models/fixtures/layersFixture.ts
 create mode 100644 src/models/layerLineSchema.ts
 create mode 100644 src/models/layerOvalSchema.ts
 create mode 100644 src/models/layerRectSchema.ts
 create mode 100644 src/models/layerSchema.ts
 create mode 100644 src/models/layerTextSchema.ts
 delete mode 100644 src/models/modelElementsSchema.ts
 create mode 100644 src/redux/layers/layers.constants.ts
 create mode 100644 src/redux/layers/layers.mock.ts
 create mode 100644 src/redux/layers/layers.reducers.test.ts
 create mode 100644 src/redux/layers/layers.reducers.ts
 create mode 100644 src/redux/layers/layers.selectors.ts
 create mode 100644 src/redux/layers/layers.slice.ts
 create mode 100644 src/redux/layers/layers.thunks.test.ts
 create mode 100644 src/redux/layers/layers.thunks.ts
 create mode 100644 src/redux/layers/layers.types.ts
 create mode 100644 src/shared/Icon/Icons/LayersIcon.tsx

diff --git a/src/components/FunctionalArea/NavBar/NavBar.component.tsx b/src/components/FunctionalArea/NavBar/NavBar.component.tsx
index 7eb7b2b0..644b830c 100644
--- a/src/components/FunctionalArea/NavBar/NavBar.component.tsx
+++ b/src/components/FunctionalArea/NavBar/NavBar.component.tsx
@@ -50,6 +50,14 @@ export const NavBar = (): JSX.Element => {
     }
   };
 
+  const toggleDrawerLayers = (): void => {
+    if (store.getState().drawer.isOpen && store.getState().drawer.drawerName === 'layers') {
+      dispatch(closeDrawer());
+    } else {
+      dispatch(openDrawer('layers'));
+    }
+  };
+
   const toggleDrawerLegend = (): void => {
     if (store.getState().legend.isOpen) {
       dispatch(closeLegend());
@@ -77,6 +85,7 @@ export const NavBar = (): JSX.Element => {
           </a>
           <IconButton icon="plugin" onClick={toggleDrawerPlugins} title="Available plugins" />
           <IconButton icon="export" onClick={toggleDrawerExport} title="Export" />
+          <IconButton icon="layers" onClick={toggleDrawerLayers} title="Layers" />
         </div>
         <div className="flex flex-col gap-[10px]">
           <IconButton icon="legend" onClick={toggleDrawerLegend} title="Legend" />
diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx
index 71f10406..b18e949f 100644
--- a/src/components/Map/Drawer/Drawer.component.tsx
+++ b/src/components/Map/Drawer/Drawer.component.tsx
@@ -3,6 +3,7 @@ import { drawerSelector } from '@/redux/drawer/drawer.selectors';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { twMerge } from 'tailwind-merge';
 import { CommentDrawer } from '@/components/Map/Drawer/CommentDrawer';
+import { LayersDrawer } from '@/components/Map/Drawer/LayersDrawer/LayersDrawer.component';
 import { AvailablePluginsDrawer } from './AvailablePluginsDrawer';
 import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component';
 import { ExportDrawer } from './ExportDrawer';
@@ -32,6 +33,7 @@ export const Drawer = (): JSX.Element => {
       {isOpen && drawerName === 'export' && <ExportDrawer />}
       {isOpen && drawerName === 'available-plugins' && <AvailablePluginsDrawer />}
       {isOpen && drawerName === 'comment' && <CommentDrawer />}
+      {isOpen && drawerName === 'layers' && <LayersDrawer />}
     </div>
   );
 };
diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.test.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.test.tsx
new file mode 100644
index 00000000..e40ea751
--- /dev/null
+++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.test.tsx
@@ -0,0 +1,46 @@
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { StoreType } from '@/redux/store';
+import { render, screen } from '@testing-library/react';
+import { openedExportDrawerFixture } from '@/redux/drawer/drawerFixture';
+import { LayersDrawer } from '@/components/Map/Drawer/LayersDrawer/LayersDrawer.component';
+
+const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
+
+  return (
+    render(
+      <Wrapper>
+        <LayersDrawer />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('ExportDrawer - component', () => {
+  it('should display drawer heading', () => {
+    renderComponent();
+
+    expect(screen.getByText('Layers')).toBeInTheDocument();
+  });
+
+  it('should close drawer after clicking close button', () => {
+    const { store } = renderComponent({
+      drawer: openedExportDrawerFixture,
+    });
+    const closeButton = screen.getByRole('close-drawer-button');
+
+    closeButton.click();
+
+    const {
+      drawer: { isOpen },
+    } = store.getState();
+
+    expect(isOpen).toBe(false);
+  });
+});
diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
new file mode 100644
index 00000000..7e614298
--- /dev/null
+++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
@@ -0,0 +1,31 @@
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { DrawerHeading } from '@/shared/DrawerHeading';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { layersSelector, layersVisibilitySelector } from '@/redux/layers/layers.selectors';
+import { Switch } from '@/shared/Switch';
+import { setLayerVisibility } from '@/redux/layers/layers.slice';
+
+export const LayersDrawer = (): JSX.Element => {
+  const layers = useAppSelector(layersSelector);
+  const layersVisibility = useAppSelector(layersVisibilitySelector);
+  const dispatch = useAppDispatch();
+
+  return (
+    <div data-testid="layers-drawer" className="h-full max-h-full">
+      <DrawerHeading title="Layers" />
+      <div className="flex h-[calc(100%-93px)] max-h-[calc(100%-93px)] flex-col overflow-y-auto px-6">
+        {layers.map(layer => (
+          <div key={layer.details.id} className="flex items-center justify-between border-b p-4">
+            <h1>{layer.details.name}</h1>
+            <Switch
+              isChecked={layersVisibility[layer.details.layerId]}
+              onToggle={value =>
+                dispatch(setLayerVisibility({ visible: value, layerId: layer.details.layerId }))
+              }
+            />
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
diff --git a/src/components/Map/Drawer/LayersDrawer/index.ts b/src/components/Map/Drawer/LayersDrawer/index.ts
new file mode 100644
index 00000000..c608bace
--- /dev/null
+++ b/src/components/Map/Drawer/LayersDrawer/index.ts
@@ -0,0 +1 @@
+export { LayersDrawer } from './LayersDrawer.component';
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.test.ts
new file mode 100644
index 00000000..68151ba0
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.test.ts
@@ -0,0 +1,25 @@
+/* eslint-disable no-magic-numbers */
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { renderHook } from '@testing-library/react';
+import VectorLayer from 'ol/layer/Vector';
+import { initialMapStateFixture } from '@/redux/map/map.fixtures';
+import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock';
+import { Map } from 'ol';
+import { useOlMapAdditionalLayers } from '@/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers';
+
+describe('useOlMapAdditionalLayers - util', () => {
+  it('should return VectorLayer', () => {
+    const { Wrapper } = getReduxWrapperWithStore({
+      map: initialMapStateFixture,
+      backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
+    });
+
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({ target: dummyElement });
+    const { result } = renderHook(() => useOlMapAdditionalLayers(mapInstance), {
+      wrapper: Wrapper,
+    });
+
+    expect(result.current).toBeInstanceOf(Array<VectorLayer>);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
new file mode 100644
index 00000000..98a4276a
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
@@ -0,0 +1,65 @@
+/* eslint-disable no-magic-numbers */
+import { Feature } from 'ol';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import { useEffect, useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import { currentModelIdSelector } from '@/redux/models/models.selectors';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { getLayers } from '@/redux/layers/layers.thunks';
+import { layersSelector, layersVisibilitySelector } from '@/redux/layers/layers.selectors';
+import { usePointToProjection } from '@/utils/map/usePointToProjection';
+import { MapInstance } from '@/types/map';
+import { 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';
+
+export const useOlMapAdditionalLayers = (
+  mapInstance: MapInstance,
+): Array<
+  VectorLayer<
+    VectorSource<Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>
+  >
+> => {
+  const dispatch = useAppDispatch();
+  const currentModelId = useSelector(currentModelIdSelector);
+  const mapLayers = useSelector(layersSelector);
+  const layersVisibility = useSelector(layersVisibilitySelector);
+  const lineTypes = useSelector(lineTypesSelector);
+  const arrowTypes = useSelector(arrowTypesSelector);
+  const pointToProjection = usePointToProjection();
+
+  useEffect(() => {
+    dispatch(getLayers(currentModelId));
+  }, [currentModelId, dispatch]);
+
+  const vectorLayers = useMemo(() => {
+    return mapLayers.map(layer => {
+      const additionalLayer = new Layer({
+        texts: layer.texts,
+        rects: layer.rects,
+        ovals: layer.ovals,
+        lines: layer.lines,
+        visible: layer.details.visible,
+        layerId: layer.details.layerId,
+        lineTypes,
+        arrowTypes,
+        mapInstance,
+        pointToProjection,
+      });
+      return additionalLayer.vectorLayer;
+    });
+  }, [arrowTypes, lineTypes, mapInstance, mapLayers, pointToProjection]);
+
+  useEffect(() => {
+    vectorLayers.forEach(layer => {
+      const layerId = layer.get('id');
+      if (layerId && layersVisibility[layerId] !== undefined) {
+        layer.setVisible(layersVisibility[layerId]);
+      }
+    });
+  }, [layersVisibility, vectorLayers]);
+
+  return vectorLayers;
+};
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index d61918dc..9bc0d809 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -4,7 +4,7 @@ import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import { useEffect, useMemo } from 'react';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
-import MapElement from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement';
+import MapElement from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement';
 import { useSelector } from 'react-redux';
 import { bioShapesSelector, lineTypesSelector } from '@/redux/shapes/shapes.selectors';
 import { MapInstance } from '@/types/map';
@@ -16,8 +16,9 @@ import { modelElementsSelector } from '@/redux/modelElements/modelElements.selec
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare';
-import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle';
+import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
+import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
+import { ModelElement } from '@/types/models';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -39,7 +40,7 @@ export const useOlMapReactionsLayer = ({
     if (!modelElements || !shapes) return [];
 
     const validElements: Array<MapElement | CompartmentCircle | CompartmentSquare> = [];
-    modelElements.content.forEach(element => {
+    modelElements.content.forEach((element: ModelElement) => {
       const shape = shapes.find(bioShape => bioShape.sboTerm === element.sboTerm);
       if (shape) {
         validElements.push(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
index 52cf8ab0..7ebb6e70 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
@@ -1,6 +1,7 @@
 /* eslint-disable no-magic-numbers */
 import { MapInstance } from '@/types/map';
 import { useOlMapWhiteCardLayer } from '@/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer';
+import { useOlMapAdditionalLayers } from '@/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers';
 import { MapConfig } from '../../MapViewerVector.types';
 import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer';
 
@@ -11,6 +12,7 @@ interface UseOlMapLayersInput {
 export const useOlMapVectorLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig['layers'] => {
   const reactionsLayer = useOlMapReactionsLayer({ mapInstance });
   const whiteCardLayer = useOlMapWhiteCardLayer();
+  const additionalLayers = useOlMapAdditionalLayers(mapInstance);
 
-  return [whiteCardLayer, reactionsLayer];
+  return [whiteCardLayer, reactionsLayer, ...additionalLayers];
 };
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCentroid.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCentroid.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCentroid.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCentroid.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCentroid.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCentroid.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCentroid.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCentroid.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.test.ts
similarity index 96%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.test.ts
index 8ce8b569..d212e9ea 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.test.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import { ShapeCurvePoint } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import getCurveCoords from './getCurveCoords';
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.ts
similarity index 95%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.ts
index 2b695634..ebfed6b6 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords.ts
@@ -1,7 +1,7 @@
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import { Coordinate } from 'ol/coordinate';
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
 import { ShapeCurvePoint } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 
 export default function getCurveCoords({
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.test.ts
similarity index 95%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.test.ts
index cf6f4c49..4a0d514a 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.test.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import getEllipseCoords from './getEllipseCoords';
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts
similarity index 61%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts
index b3a307a0..45a7df69 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
 import { Coordinate } from 'ol/coordinate';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import {
@@ -20,31 +20,39 @@ export default function getEllipseCoords({
 }: {
   x: number;
   y: number;
-  center: EllipseCenter;
-  radius: EllipseRadius;
+  center?: EllipseCenter;
+  radius?: EllipseRadius;
   height: number;
   width: number;
   pointToProjection: UsePointToProjectionResult;
   points?: number;
 }): Array<Coordinate> {
-  const centerX = getCoordsX(
-    x,
-    center.absoluteX,
-    center.relativeX,
-    center.relativeHeightForX,
-    height,
-    width,
-  );
-  const centerY = getCoordsY(
-    y,
-    center.absoluteY,
-    center.relativeY,
-    center.relativeWidthForY,
-    height,
-    width,
-  );
-  const radiusX = radius.absoluteX + (radius.relativeX * width) / 100;
-  const radiusY = radius.absoluteY + (radius.relativeY * height) / 100;
+  let centerX = x;
+  let centerY = y;
+  let radiusX = width / 2;
+  let radiusY = height / 2;
+  if (center) {
+    centerX = getCoordsX(
+      x,
+      center.absoluteX,
+      center.relativeX,
+      center.relativeHeightForX,
+      height,
+      width,
+    );
+    centerY = getCoordsY(
+      y,
+      center.absoluteY,
+      center.relativeY,
+      center.relativeWidthForY,
+      height,
+      width,
+    );
+  }
+  if (radius) {
+    radiusX = radius.absoluteX + (radius.relativeX * width) / 100;
+    radiusY = radius.absoluteY + (radius.relativeY * height) / 100;
+  }
   let angle;
   let coordsX;
   let coordsY;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.test.ts
similarity index 95%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.test.ts
index e1911f7b..d88a2c04 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.test.ts
@@ -1,8 +1,8 @@
 /* eslint-disable no-magic-numbers */
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
-import getCurveCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords';
-import getBezierCurve from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
+import getCurveCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords';
+import getBezierCurve from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve';
 import {
   ShapePoint,
   ShapeCurvePoint,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts
similarity index 90%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts
index b60b0016..dac21118 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts
@@ -1,10 +1,10 @@
 /* eslint-disable no-magic-numbers */
 import { Coordinate } from 'ol/coordinate';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
-import getBezierCurve from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getBezierCurve';
-import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsX';
-import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCoordsY';
-import getCurveCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCurveCoords';
+import getBezierCurve from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve';
+import getCoordsX from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsX';
+import getCoordsY from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCoordsY';
+import getCurveCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCurveCoords';
 import {
   ShapeCurvePoint,
   ShapePoint,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.test.ts
new file mode 100644
index 00000000..363add1a
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.test.ts
@@ -0,0 +1,19 @@
+/* eslint-disable no-magic-numbers */
+import getRotation from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation';
+import { Coordinate } from 'ol/coordinate';
+
+const testCases: Array<[Coordinate, Coordinate, number]> = [
+  [[0, 0], [1, 1], -0.785],
+  [[1, 1], [1, 2], -1.57],
+  [[2, 2], [3, 0], 1.107],
+];
+
+describe('getRotation', () => {
+  it.each(testCases)(
+    'should return the correct rotation for start: %s and end: %s',
+    (start: Coordinate, end: Coordinate, expected: number) => {
+      const result = getRotation(start, end);
+      expect(result).toBeCloseTo(expected);
+    },
+  );
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.ts
new file mode 100644
index 00000000..5a647fce
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation.ts
@@ -0,0 +1,6 @@
+/* eslint-disable no-magic-numbers */
+import { Coordinate } from 'ol/coordinate';
+
+export default function getRotation(start: Coordinate, end: Coordinate): number {
+  return Math.atan2(-end[1] + start[1], end[0] - start[0]);
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
similarity index 88%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/BaseMultiPolygon.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 62ed6ea9..30ece3e9 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -10,8 +10,9 @@ import {
   VerticalAlign,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import { MapInstance } from '@/types/map';
-import getText from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getText';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 
 export interface BaseMapElementProps {
   x: number;
@@ -118,22 +119,28 @@ export default abstract class BaseMultiPolygon {
 
   protected drawText(): void {
     if (this.text) {
-      const { textCoords, textStyle } = getText({
-        text: this.text,
-        fontSize: this.fontSize,
+      const textCoords = getTextCoords({
         x: this.nameX,
         y: this.nameY,
         width: this.nameWidth,
         height: this.nameHeight,
-        color: rgbToHex(this.fontColor),
-        zIndex: this.zIndex,
+        fontSize: this.fontSize,
         verticalAlign: this.nameVerticalAlign,
         horizontalAlign: this.nameHorizontalAlign,
         pointToProjection: this.pointToProjection,
       });
+      const textPolygon = new Polygon([[textCoords, textCoords]]);
+      const textStyle = getTextStyle({
+        text: this.text,
+        fontSize: this.fontSize,
+        color: rgbToHex(this.fontColor),
+        zIndex: this.zIndex,
+        horizontalAlign: this.nameHorizontalAlign,
+      });
+      textStyle.setGeometry(textPolygon);
       this.styles.push(textStyle);
       this.polygonsTexts.push(this.text);
-      this.polygons.push(new Polygon([[textCoords, textCoords]]));
+      this.polygons.push(textPolygon);
     }
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
similarity index 95%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index deb5a5c5..786f8c1e 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -5,13 +5,13 @@ import {
   HorizontalAlign,
   VerticalAlign,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
-import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/BaseMultiPolygon';
+import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
 import { Coordinate } from 'ol/coordinate';
 import Polygon from 'ol/geom/Polygon';
 import { Style } from 'ol/style';
-import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
-import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
 import { MapInstance } from '@/types/map';
 
 export interface CompartmentProps {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
similarity index 79%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
index cfc57c56..47c47d38 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
@@ -2,11 +2,11 @@
 import { Feature, Map } from 'ol';
 import { Fill, Style, Text } from 'ol/style';
 import { Polygon, MultiPolygon } from 'ol/geom';
-import getText from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getText';
-import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon';
-import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke';
-import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import View from 'ol/View';
 import {
   WHITE_COLOR,
@@ -14,15 +14,17 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import CompartmentCircle, {
   CompartmentCircleProps,
-} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle';
-import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords';
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
+import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 
-jest.mock('./getText');
+jest.mock('../text/getTextStyle');
+jest.mock('../text/getTextCoords');
 jest.mock('./getMultiPolygon');
-jest.mock('./getStroke');
-jest.mock('./getEllipseCoords');
-jest.mock('./getFill');
-jest.mock('./rgbToHex');
+jest.mock('../style/getStroke');
+jest.mock('../coords/getEllipseCoords');
+jest.mock('../style/getFill');
+jest.mock('../style/rgbToHex');
 
 describe('MapElement', () => {
   let props: CompartmentCircleProps;
@@ -61,9 +63,8 @@ describe('MapElement', () => {
       mapInstance,
     };
 
-    (getText as jest.Mock).mockReturnValue({
-      textCoords: [0, 0],
-      textStyle: new Style({
+    (getTextStyle as jest.Mock).mockReturnValue(
+      new Style({
         text: new Text({
           text: props.text,
           font: `bold ${props.fontSize}px Arial`,
@@ -75,7 +76,8 @@ describe('MapElement', () => {
           textBaseline: 'middle',
         }),
       }),
-    });
+    );
+    (getTextCoords as jest.Mock).mockReturnValue([10, 10]);
     (getMultiPolygon as jest.Mock).mockReturnValue([
       new Polygon([
         [
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
similarity index 96%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
index f5f3c51a..5665fc29 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentCircle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
@@ -12,8 +12,8 @@ import {
   COMPARTMENT_CIRCLE_RADIUS,
   WHITE_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
-import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords';
-import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment';
+import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
+import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 
 export type CompartmentCircleProps = {
   x: number;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
similarity index 79%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
index d9913b38..5fd6ac55 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
@@ -2,11 +2,11 @@
 import { Feature, Map } from 'ol';
 import { Fill, Style, Text } from 'ol/style';
 import { Polygon, MultiPolygon } from 'ol/geom';
-import getText from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getText';
-import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon';
-import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke';
-import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import View from 'ol/View';
 import {
   WHITE_COLOR,
@@ -14,15 +14,17 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import CompartmentSquare, {
   CompartmentSquareProps,
-} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare';
-import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords';
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
+import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 
-jest.mock('./getText');
+jest.mock('../text/getTextStyle');
 jest.mock('./getMultiPolygon');
-jest.mock('./getStroke');
-jest.mock('./getPolygonCoords');
-jest.mock('./getFill');
-jest.mock('./rgbToHex');
+jest.mock('../text/getTextCoords');
+jest.mock('../style/getStroke');
+jest.mock('../coords/getPolygonCoords');
+jest.mock('../style/getFill');
+jest.mock('../style/rgbToHex');
 
 describe('MapElement', () => {
   let props: CompartmentSquareProps;
@@ -61,9 +63,8 @@ describe('MapElement', () => {
       mapInstance,
     };
 
-    (getText as jest.Mock).mockReturnValue({
-      textCoords: [0, 0],
-      textStyle: new Style({
+    (getTextStyle as jest.Mock).mockReturnValue(
+      new Style({
         text: new Text({
           text: props.text,
           font: `bold ${props.fontSize}px Arial`,
@@ -75,7 +76,8 @@ describe('MapElement', () => {
           textBaseline: 'middle',
         }),
       }),
-    });
+    );
+    (getTextCoords as jest.Mock).mockReturnValue([10, 10]);
     (getMultiPolygon as jest.Mock).mockReturnValue([
       new Polygon([
         [
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
similarity index 96%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
index 79962ee7..d868bc11 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/CompartmentSquare.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
@@ -11,8 +11,8 @@ import {
   COMPARTMENT_SQUARE_POINTS,
   WHITE_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
-import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords';
-import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment';
+import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
+import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 
 export type CompartmentSquareProps = {
   x: number;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
similarity index 80%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index 004f90cd..51aaeb65 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -2,25 +2,27 @@
 import { Feature, Map } from 'ol';
 import { Fill, Style, Text } from 'ol/style';
 import { Polygon, MultiPolygon } from 'ol/geom';
-import getText from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getText';
-import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon';
-import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke';
-import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import MapElement, {
   MapElementProps,
-} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement';
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement';
 import View from 'ol/View';
 import {
   WHITE_COLOR,
   BLACK_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 
-jest.mock('./getText');
+jest.mock('../text/getTextStyle');
+jest.mock('../text/getTextCoords');
 jest.mock('./getMultiPolygon');
-jest.mock('./getStroke');
-jest.mock('./getFill');
-jest.mock('./rgbToHex');
+jest.mock('../style/getStroke');
+jest.mock('../style/getFill');
+jest.mock('../style/rgbToHex');
 
 describe('MapElement', () => {
   let props: MapElementProps;
@@ -58,9 +60,8 @@ describe('MapElement', () => {
       mapInstance,
     };
 
-    (getText as jest.Mock).mockReturnValue({
-      textCoords: [0, 0],
-      textStyle: new Style({
+    (getTextStyle as jest.Mock).mockReturnValue(
+      new Style({
         text: new Text({
           text: props.text,
           font: `bold ${props.fontSize}px Arial`,
@@ -72,7 +73,8 @@ describe('MapElement', () => {
           textBaseline: 'middle',
         }),
       }),
-    });
+    );
+    (getTextCoords as jest.Mock).mockReturnValue([10, 10]);
     (getMultiPolygon as jest.Mock).mockReturnValue([
       new Polygon([
         [
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
similarity index 92%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 2e72e1a7..025ec2d8 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -1,11 +1,11 @@
 /* eslint-disable no-magic-numbers */
 import { Style, Text } from 'ol/style';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
-import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke';
-import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
 import Polygon from 'ol/geom/Polygon';
-import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon';
-import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex';
+import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import { BioShape, LineType, Modification, Shape } from '@/types/models';
 import { MapInstance } from '@/types/map';
 import {
@@ -17,7 +17,8 @@ import {
   BLACK_COLOR,
   WHITE_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
-import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/BaseMultiPolygon';
+import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
+import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
 
 export type MapElementProps = {
   shapes: Array<Shape>;
@@ -203,12 +204,10 @@ export default class MapElement extends BaseMultiPolygon {
     });
     activityBorderElement.forEach(polygon => {
       this.styles.push(
-        new Style({
+        getStyle({
           geometry: polygon,
-          fill: getFill({ color: 'rgba(0, 0, 0, 0)' }),
-          stroke: getStroke({
-            lineDash: [3, 5],
-          }),
+          fillColor: { rgb: 0, alpha: 0 },
+          lineDash: [3, 5],
           zIndex: this.zIndex,
         }),
       );
@@ -227,14 +226,12 @@ export default class MapElement extends BaseMultiPolygon {
     });
     elementPolygon.forEach(polygon => {
       this.styles.push(
-        new Style({
+        getStyle({
           geometry: polygon,
-          stroke: getStroke({
-            color: rgbToHex(this.borderColor),
-            width: this.lineWidth,
-            lineDash: this.lineDash,
-          }),
-          fill: getFill({ color: rgbToHex(this.fillColor) }),
+          borderColor: this.borderColor,
+          fillColor: this.fillColor,
+          lineWidth: this.lineWidth,
+          lineDash: this.lineDash,
           zIndex: this.zIndex,
         }),
       );
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.test.ts
similarity index 96%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.test.ts
index 76036f49..dbe051bd 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.test.ts
@@ -1,12 +1,12 @@
 /* eslint-disable no-magic-numbers */
-import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords';
-import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords';
+import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
+import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import Polygon from 'ol/geom/Polygon';
 import { Shape } from '@/types/models';
 import getMultiPolygon from './getMultiPolygon';
 
-jest.mock('./getPolygonCoords');
-jest.mock('./getEllipseCoords');
+jest.mock('../coords/getPolygonCoords');
+jest.mock('../coords/getEllipseCoords');
 
 describe('getMultiPolygon', () => {
   const mockPointToProjection = jest.fn(point => [point.x, point.y]);
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.ts
similarity index 91%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.ts
index e5effdb7..d91c741a 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon.ts
@@ -1,11 +1,11 @@
 /* eslint-disable no-magic-numbers */
-import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getPolygonCoords';
-import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getEllipseCoords';
+import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
+import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import Polygon from 'ol/geom/Polygon';
 import { Coordinate } from 'ol/coordinate';
 import { Shape } from '@/types/models';
-import getCentroid from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/getCentroid';
+import getCentroid from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getCentroid';
 
 export default function getMultiPolygon({
   x,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.test.ts
deleted file mode 100644
index 70a27921..00000000
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/* eslint-disable no-magic-numbers */
-import { Stroke } from 'ol/style';
-import getStroke from './getStroke';
-
-describe('getStroke', () => {
-  it('should return a Stoke object with the default color and width', () => {
-    const result = getStroke({});
-    expect(result).toBeInstanceOf(Stroke);
-    expect(result.getColor()).toBe('#000');
-    expect(result.getWidth()).toBe(1);
-  });
-
-  it('should return a Stoke object with the provided color and width', () => {
-    const color = '#ff0000';
-    const width = 2;
-    const result = getStroke({ color, width });
-    expect(result).toBeInstanceOf(Stroke);
-    expect(result.getColor()).toBe(color);
-    expect(result.getWidth()).toBe(width);
-  });
-});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.test.ts
deleted file mode 100644
index e25b4df7..00000000
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/* eslint-disable no-magic-numbers */
-import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
-import getText from './getText';
-
-describe('getText', () => {
-  const mockPointToProjection: UsePointToProjectionResult = jest.fn(point => [point.x, point.y]);
-  it('should return correct text coordinates and style when text is centered', () => {
-    const { textCoords, textStyle } = getText({
-      x: 0,
-      y: 0,
-      height: 100,
-      width: 100,
-      text: 'Text test',
-      fontSize: 12,
-      verticalAlign: 'MIDDLE',
-      horizontalAlign: 'CENTER',
-      pointToProjection: mockPointToProjection,
-    });
-
-    expect(textCoords).toEqual([50, 50]);
-    expect(textStyle.getText()?.getFont()).toEqual('12pt Arial');
-  });
-
-  it('should return correct text coordinates and style when text is aligned to bottom', () => {
-    const { textCoords, textStyle } = getText({
-      x: 20,
-      y: 30,
-      height: 100,
-      width: 100,
-      text: 'Text test',
-      fontSize: 18,
-      verticalAlign: 'BOTTOM',
-      horizontalAlign: 'CENTER',
-      pointToProjection: mockPointToProjection,
-    });
-
-    expect(textCoords).toEqual([70, 121]);
-
-    expect(textStyle.getText()?.getFont()).toEqual('18pt Arial');
-  });
-});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.ts
deleted file mode 100644
index 09dffbe2..00000000
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getText.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/* eslint-disable no-magic-numbers */
-import { Fill, Text } from 'ol/style';
-import Style from 'ol/style/Style';
-import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
-import { Coordinate } from 'ol/coordinate';
-import {
-  HorizontalAlign,
-  VerticalAlign,
-} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
-import { FeatureLike } from 'ol/Feature';
-import { Geometry, MultiPolygon } from 'ol/geom';
-import RenderFeature from 'ol/render/Feature';
-
-export default function getText({
-  x,
-  y,
-  height,
-  width,
-  text = '',
-  fontSize = 12,
-  color = '#000',
-  zIndex = 1,
-  verticalAlign = 'MIDDLE',
-  horizontalAlign = 'CENTER',
-  pointToProjection,
-}: {
-  x: number;
-  y: number;
-  height: number;
-  width: number;
-  text: string;
-  fontSize?: string | number;
-  color?: string;
-  zIndex?: number;
-  verticalAlign?: VerticalAlign;
-  horizontalAlign?: HorizontalAlign;
-  pointToProjection: UsePointToProjectionResult;
-}): { textCoords: Coordinate; textStyle: Style } {
-  const minX = x;
-  const maxX = x + width;
-  const minY = y;
-  const maxY = y + height;
-
-  let textY = (minY + maxY) / 2;
-  if (verticalAlign === 'TOP') {
-    textY = minY + +fontSize / 2;
-  } else if (verticalAlign === 'BOTTOM') {
-    textY = maxY - +fontSize / 2;
-  }
-
-  let textX = (minX + maxX) / 2;
-  if (['LEFT', 'START'].includes(horizontalAlign)) {
-    textX = minX;
-  } else if (['RIGHT', 'END'].includes(horizontalAlign)) {
-    textX = maxX;
-  }
-
-  const textCoords = pointToProjection({ x: textX, y: textY });
-
-  const textStyle = new Style({
-    geometry: (feature: FeatureLike): Geometry | RenderFeature | undefined => {
-      const geometry = feature.getGeometry();
-      if (geometry && geometry.getType() === 'MultiPolygon') {
-        return (geometry as MultiPolygon)
-          .getPolygon((geometry as MultiPolygon).getPolygons().length - 1)
-          .getInteriorPoint();
-      }
-      return undefined;
-    },
-    text: new Text({
-      text,
-      font: `${fontSize}pt Arial`,
-      fill: new Fill({
-        color,
-      }),
-      placement: 'point',
-      textAlign: horizontalAlign.toLowerCase() as CanvasTextAlign,
-      textBaseline: 'middle',
-      overflow: true,
-    }),
-    zIndex,
-  });
-
-  return { textCoords, textStyle };
-}
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
new file mode 100644
index 00000000..ab6e4eb0
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts
@@ -0,0 +1,145 @@
+/* eslint-disable no-magic-numbers */
+import { Map } from 'ol';
+import { Style, Text } from 'ol/style';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import View from 'ol/View';
+import {
+  WHITE_COLOR,
+  BLACK_COLOR,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import Layer, {
+  LayerProps,
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer';
+import VectorSource from 'ol/source/Vector';
+import VectorLayer from 'ol/layer/Vector';
+
+jest.mock('../text/getTextCoords');
+jest.mock('../text/getTextStyle');
+jest.mock('../style/getStroke');
+jest.mock('../style/rgbToHex');
+
+describe('Layer', () => {
+  let props: LayerProps;
+
+  beforeEach(() => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({
+      target: dummyElement,
+      view: new View({
+        zoom: 5,
+        minZoom: 3,
+        maxZoom: 7,
+      }),
+    });
+    props = {
+      texts: [
+        {
+          id: 1,
+          x: 10,
+          y: 10,
+          z: 3,
+          width: 100,
+          height: 100,
+          fontSize: 12,
+          size: 12312,
+          notes: 'XYZ',
+          glyph: null,
+          elementId: '34',
+          verticalAlign: 'MIDDLE',
+          horizontalAlign: 'CENTER',
+          backgroundColor: WHITE_COLOR,
+          borderColor: BLACK_COLOR,
+          color: BLACK_COLOR,
+        },
+      ],
+      rects: [
+        {
+          id: 1,
+          x: 10,
+          y: 10,
+          z: 3,
+          width: 100,
+          height: 100,
+          size: 12312,
+          elementId: '341',
+          lineWidth: 2,
+          borderColor: BLACK_COLOR,
+          fillColor: WHITE_COLOR,
+        },
+      ],
+      ovals: [
+        {
+          id: 1,
+          x: 10,
+          y: 10,
+          z: 3,
+          width: 100,
+          height: 100,
+          size: 12312,
+          elementId: '341',
+          lineWidth: 2,
+          borderColor: BLACK_COLOR,
+        },
+      ],
+      lines: [
+        {
+          id: 120899,
+          width: 5.0,
+          color: {
+            alpha: 255,
+            rgb: -16777216,
+          },
+          z: 0,
+          segments: [
+            {
+              x1: 36.0,
+              y1: 39.0,
+              x2: 213.0,
+              y2: 41.0,
+            },
+          ],
+          startArrow: {
+            arrowType: 'NONE',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15.0,
+          },
+          endArrow: {
+            arrowType: 'NONE',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15.0,
+          },
+          lineType: 'SOLID',
+        },
+      ],
+      visible: true,
+      layerId: '23',
+      pointToProjection: jest.fn(point => [point.x, point.y]),
+      mapInstance,
+      lineTypes: [],
+      arrowTypes: [],
+    };
+    (getTextStyle as jest.Mock).mockReturnValue(
+      new Style({
+        text: new Text({}),
+      }),
+    );
+    (getTextCoords as jest.Mock).mockReturnValue([10, 10]);
+    (getStroke as jest.Mock).mockReturnValue(new Style());
+    (rgbToHex as jest.Mock).mockReturnValue('#FFFFFF');
+  });
+
+  it('should initialize a Layer class', () => {
+    const layer = new Layer(props);
+
+    expect(layer.textFeatures.length).toBe(1);
+    expect(layer.rectFeatures.length).toBe(1);
+    expect(layer.ovalFeatures.length).toBe(1);
+    expect(layer.vectorSource).toBeInstanceOf(VectorSource);
+    expect(layer.vectorLayer).toBeInstanceOf(VectorLayer);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
new file mode 100644
index 00000000..259bff25
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -0,0 +1,320 @@
+/* eslint-disable no-magic-numbers */
+import {
+  Arrow,
+  ArrowType,
+  LayerLine,
+  LayerOval,
+  LayerRect,
+  LayerText,
+  LineType,
+} from '@/types/models';
+import { MapInstance } from '@/types/map';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import { Feature } from 'ol';
+import { LineString, MultiPolygon, Point } from 'ol/geom';
+import Text from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text';
+import Polygon from 'ol/geom/Polygon';
+import VectorSource from 'ol/source/Vector';
+import VectorLayer from 'ol/layer/Vector';
+import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
+import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
+import {
+  HorizontalAlign,
+  VerticalAlign,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon';
+import Style from 'ol/style/Style';
+import { BLACK_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import getRotation from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation';
+
+export interface LayerProps {
+  texts: Array<LayerText>;
+  rects: Array<LayerRect>;
+  ovals: Array<LayerOval>;
+  lines: Array<LayerLine>;
+  visible: boolean;
+  layerId: string;
+  lineTypes: Array<LineType>;
+  arrowTypes: Array<ArrowType>;
+  mapInstance: MapInstance;
+  pointToProjection: UsePointToProjectionResult;
+}
+
+export default class Layer {
+  texts: Array<LayerText>;
+
+  rects: Array<LayerRect>;
+
+  ovals: Array<LayerOval>;
+
+  lines: Array<LayerLine>;
+
+  lineTypes: Array<LineType>;
+
+  arrowTypes: Array<ArrowType>;
+
+  textFeatures: Array<Feature<Point>>;
+
+  rectFeatures: Array<Feature<Polygon>>;
+
+  ovalFeatures: Array<Feature<Polygon>>;
+
+  lineFeatures: Array<Feature<LineString>>;
+
+  arrowFeatures: Array<Feature<MultiPolygon>>;
+
+  vectorSource: VectorSource<
+    Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>
+  >;
+
+  vectorLayer: VectorLayer<
+    VectorSource<Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>
+  >;
+
+  constructor({
+    texts,
+    rects,
+    ovals,
+    lines,
+    visible,
+    layerId,
+    lineTypes,
+    arrowTypes,
+    mapInstance,
+    pointToProjection,
+  }: LayerProps) {
+    this.texts = texts;
+    this.rects = rects;
+    this.ovals = ovals;
+    this.lines = lines;
+    this.lineTypes = lineTypes;
+    this.arrowTypes = arrowTypes;
+    this.textFeatures = this.getTextsFeatures(mapInstance, pointToProjection);
+    this.rectFeatures = this.getRectsFeatures(pointToProjection);
+    this.ovalFeatures = this.getOvalsFeatures(pointToProjection);
+    const { linesFeatures, arrowsFeatures } = this.getLinesFeatures(pointToProjection);
+    this.lineFeatures = linesFeatures;
+    this.arrowFeatures = arrowsFeatures;
+    this.vectorSource = new VectorSource({
+      features: [
+        ...this.textFeatures,
+        ...this.rectFeatures,
+        ...this.ovalFeatures,
+        ...this.lineFeatures,
+        ...this.arrowFeatures,
+      ],
+    });
+    this.vectorLayer = new VectorLayer({
+      source: this.vectorSource,
+      visible,
+    });
+    this.vectorLayer.set('id', layerId);
+  }
+
+  private getTextsFeatures = (
+    mapInstance: MapInstance,
+    pointToProjection: UsePointToProjectionResult,
+  ): Array<Feature<Point>> => {
+    const textObjects = this.texts.map(text => {
+      return new Text({
+        x: text.x,
+        y: text.y,
+        zIndex: text.z,
+        width: text.width,
+        height: text.height,
+        fontColor: text.color,
+        fontSize: text.fontSize,
+        text: text.notes,
+        verticalAlign: text.verticalAlign as VerticalAlign,
+        horizontalAlign: text.horizontalAlign as HorizontalAlign,
+        pointToProjection,
+        mapInstance,
+      });
+    });
+    return textObjects.map(text => text.feature);
+  };
+
+  private getRectsFeatures = (
+    pointToProjection: UsePointToProjectionResult,
+  ): Array<Feature<Polygon>> => {
+    return this.rects.map(rect => {
+      const polygon = new Polygon([
+        [
+          pointToProjection({ x: rect.x, y: rect.y }),
+          pointToProjection({ x: rect.x + rect.width, y: rect.y }),
+          pointToProjection({ x: rect.x + rect.width, y: rect.y + rect.height }),
+          pointToProjection({ x: rect.x, y: rect.y + rect.height }),
+        ],
+      ]);
+      const polygonStyle = getStyle({
+        geometry: polygon,
+        borderColor: rect.borderColor,
+        fillColor: rect.fillColor,
+        lineWidth: rect.lineWidth,
+        zIndex: rect.z,
+      });
+      const rectFeature = new Feature<Polygon>({
+        geometry: polygon,
+      });
+      rectFeature.setStyle(polygonStyle);
+      return rectFeature;
+    });
+  };
+
+  private getOvalsFeatures = (
+    pointToProjection: UsePointToProjectionResult,
+  ): Array<Feature<Polygon>> => {
+    return this.ovals.map(oval => {
+      const coords = getEllipseCoords({
+        x: oval.x,
+        y: oval.y,
+        height: oval.height,
+        width: oval.width,
+        pointToProjection,
+        points: 36,
+      });
+      const polygon = new Polygon([coords]);
+      const polygonStyle = getStyle({
+        geometry: polygon,
+        borderColor: oval.borderColor,
+        fillColor: { rgb: 0, alpha: 0 },
+        lineWidth: oval.lineWidth,
+        zIndex: oval.z,
+      });
+      const ovalFeature = new Feature<Polygon>({
+        geometry: polygon,
+      });
+      ovalFeature.setStyle(polygonStyle);
+      return ovalFeature;
+    });
+  };
+
+  private getLinesFeatures = (
+    pointToProjection: UsePointToProjectionResult,
+  ): {
+    linesFeatures: Array<Feature<LineString>>;
+    arrowsFeatures: Array<Feature<MultiPolygon>>;
+  } => {
+    const linesFeatures: Array<Feature<LineString>> = [];
+    const arrowsFeatures: Array<Feature<MultiPolygon>> = [];
+
+    this.lines.forEach(line => {
+      const points = line.segments
+        .map((segment, index) => {
+          if (index === 0) {
+            return [
+              pointToProjection({ x: segment.x1, y: segment.y1 }),
+              pointToProjection({ x: segment.x2, y: segment.y2 }),
+            ];
+          }
+          return [pointToProjection({ x: segment.x2, y: segment.y2 })];
+        })
+        .flat();
+      const lineString = new LineString(points);
+
+      let lineDash;
+      const lineTypeFound = this.lineTypes.find(type => type.name === line.lineType);
+      if (lineTypeFound) {
+        lineDash = lineTypeFound.pattern;
+      }
+      const lineStyle = getStyle({
+        geometry: lineString,
+        borderColor: line.color,
+        lineWidth: line.width,
+        lineDash,
+        zIndex: line.z,
+      });
+      const lineFeature = new Feature<LineString>({
+        geometry: lineString,
+      });
+      lineFeature.setStyle(lineStyle);
+      linesFeatures.push(lineFeature);
+      arrowsFeatures.push(...this.getLineArrowsFeatures(line, pointToProjection));
+    });
+    return { linesFeatures, arrowsFeatures };
+  };
+
+  private getLineArrowsFeatures = (
+    line: LayerLine,
+    pointToProjection: UsePointToProjectionResult,
+  ): Array<Feature<MultiPolygon>> => {
+    const arrowsFeatures: Array<Feature<MultiPolygon>> = [];
+    const firstSegment = line.segments[0];
+    const startArrowRotation = getRotation(
+      [firstSegment.x1, firstSegment.y1],
+      [firstSegment.x2, firstSegment.y2],
+    );
+    const startArrowFeature = this.getLineArrowFeature(
+      line.startArrow,
+      firstSegment.x1,
+      firstSegment.y1,
+      line.z,
+      startArrowRotation,
+      line.width,
+      pointToProjection,
+    );
+    if (startArrowFeature) {
+      arrowsFeatures.push(startArrowFeature);
+    }
+
+    const lastSegment = line.segments[line.segments.length - 1];
+    const endArrowRotation = getRotation(
+      [lastSegment.x1, lastSegment.y1],
+      [lastSegment.x2, lastSegment.y2],
+    );
+    const endArrowFeature = this.getLineArrowFeature(
+      line.endArrow,
+      lastSegment.x2,
+      lastSegment.y2,
+      line.z,
+      endArrowRotation,
+      line.width,
+      pointToProjection,
+    );
+    if (endArrowFeature) {
+      arrowsFeatures.push(endArrowFeature);
+    }
+    return arrowsFeatures;
+  };
+
+  private getLineArrowFeature = (
+    arrow: Arrow,
+    x: number,
+    y: number,
+    zIndex: number,
+    rotation: number,
+    lineWidth: number,
+    pointToProjection: UsePointToProjectionResult,
+  ): undefined | Feature<MultiPolygon> => {
+    const arrowShapes = this.arrowTypes.find(arrowType => arrowType.arrowType === arrow.arrowType)
+      ?.shapes;
+    if (!arrowShapes) {
+      return undefined;
+    }
+    const arrowMultiPolygon = getMultiPolygon({
+      x,
+      y: y - arrow.length / 2,
+      width: arrow.length,
+      height: arrow.length,
+      shapes: arrowShapes,
+      pointToProjection,
+    });
+    const arrowStyles: Array<Style> = [];
+    arrowMultiPolygon.forEach(polygon => {
+      const style = getStyle({
+        geometry: polygon,
+        zIndex,
+        borderColor: BLACK_COLOR,
+        fillColor: BLACK_COLOR,
+        lineWidth,
+      });
+      arrowStyles.push(style);
+      polygon.rotate(rotation, pointToProjection({ x, y }));
+    });
+    const arrowFeature = new Feature({
+      geometry: new MultiPolygon(arrowMultiPolygon),
+    });
+    arrowFeature.setStyle(arrowStyles);
+    return arrowFeature;
+  };
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getFill.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.test.ts
new file mode 100644
index 00000000..9f485a73
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.test.ts
@@ -0,0 +1,27 @@
+/* eslint-disable no-magic-numbers */
+import { Stroke } from 'ol/style';
+import getStroke from './getStroke';
+
+describe('getStroke', () => {
+  it('should return a Stroke object with the default color and width', () => {
+    const result = getStroke({});
+    expect(result).toBeInstanceOf(Stroke);
+    expect(result.getColor()).toEqual('#000');
+    expect(result.getWidth()).toEqual(1);
+    expect(result.getLineDash()).toEqual([]);
+    expect(result.getLineCap()).toEqual('butt');
+  });
+
+  it('should return a Stroke object with the provided values', () => {
+    const color = '#ff0000';
+    const width = 2;
+    const lineDash = [10, 5];
+    const lineCap = 'round';
+    const result = getStroke({ color, width, lineDash, lineCap });
+    expect(result).toBeInstanceOf(Stroke);
+    expect(result.getColor()).toEqual(color);
+    expect(result.getWidth()).toEqual(width);
+    expect(result.getLineDash()).toEqual(lineDash);
+    expect(result.getLineCap()).toEqual(lineCap);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.ts
similarity index 79%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.ts
index 65328448..5625aedb 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/getStroke.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke.ts
@@ -5,14 +5,17 @@ export default function getStroke({
   color = '#000',
   width = 1,
   lineDash = [],
+  lineCap = 'butt',
 }: {
   color?: string;
   width?: number;
   lineDash?: Array<number>;
+  lineCap?: string;
 }): Stroke {
   return new Stroke({
     color,
     width,
     lineDash,
+    lineCap: lineCap as CanvasLineCap,
   });
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.test.ts
new file mode 100644
index 00000000..e46f9ef0
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.test.ts
@@ -0,0 +1,52 @@
+/* eslint-disable no-magic-numbers */
+import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
+import Style from 'ol/style/Style';
+import Polygon from 'ol/geom/Polygon';
+
+describe('getStyle', () => {
+  it('should return a Style object with the default values', () => {
+    const result = getStyle({});
+    expect(result).toBeInstanceOf(Style);
+    expect(result.getGeometry()).toEqual(null);
+    expect(result.getStroke()?.getWidth()).toEqual(1);
+    expect(result.getStroke()?.getColor()).toEqual('#000000FF');
+    expect(result.getStroke()?.getLineDash()).toEqual([]);
+    expect(result.getFill()?.getColor()).toEqual('#FFFFFFFF');
+    expect(result.getZIndex()).toEqual(1);
+  });
+
+  it('should return a Style object with the provided color and width', () => {
+    const geometry = new Polygon([
+      [
+        [10, 10],
+        [10, 10],
+      ],
+    ]);
+    const borderColor = {
+      alpha: 255,
+      rgb: -16777216,
+    };
+    const fillColor = {
+      alpha: 255,
+      rgb: -5646081,
+    };
+    const lineWidth = 3;
+    const lineDash = [10, 5];
+    const zIndex = 2;
+    const result = getStyle({
+      geometry,
+      borderColor,
+      fillColor,
+      lineWidth,
+      lineDash,
+      zIndex,
+    });
+    expect(result).toBeInstanceOf(Style);
+    expect(result.getGeometry()).toEqual(geometry);
+    expect(result.getStroke()?.getWidth()).toEqual(lineWidth);
+    expect(result.getStroke()?.getColor()).toEqual('#000000FF');
+    expect(result.getStroke()?.getLineDash()).toEqual(lineDash);
+    expect(result.getFill()?.getColor()).toEqual('#A9D8FFFF');
+    expect(result.getZIndex()).toEqual(zIndex);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
new file mode 100644
index 00000000..62f16157
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
@@ -0,0 +1,38 @@
+/* eslint-disable no-magic-numbers */
+import Style from 'ol/style/Style';
+import { Geometry } from 'ol/geom';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import {
+  BLACK_COLOR,
+  WHITE_COLOR,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import { ColorObject } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+
+export default function getStyle({
+  geometry,
+  borderColor = BLACK_COLOR,
+  fillColor = WHITE_COLOR,
+  lineWidth = 1,
+  lineDash = [],
+  zIndex = 1,
+}: {
+  geometry?: Geometry;
+  borderColor?: ColorObject;
+  fillColor?: ColorObject;
+  lineWidth?: number;
+  lineDash?: Array<number>;
+  zIndex?: number;
+}): Style {
+  return new Style({
+    geometry,
+    stroke: getStroke({
+      color: rgbToHex(borderColor),
+      width: lineWidth,
+      lineDash,
+    }),
+    fill: getFill({ color: rgbToHex(fillColor) }),
+    zIndex,
+  });
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex.test.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex.test.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/utils/shapes/rgbToHex.ts
rename to src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.test.ts
new file mode 100644
index 00000000..68b8d9f8
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.test.ts
@@ -0,0 +1,73 @@
+/* eslint-disable no-magic-numbers */
+import { Map } from 'ol';
+import { Style } from 'ol/style';
+import Text, { TextProps } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import View from 'ol/View';
+import { BLACK_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+
+jest.mock('./getTextCoords');
+jest.mock('./getTextStyle');
+jest.mock('../style/rgbToHex');
+
+describe('Text', () => {
+  let props: TextProps;
+
+  beforeEach(() => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({
+      target: dummyElement,
+      view: new View({
+        zoom: 5,
+        minZoom: 3,
+        maxZoom: 7,
+      }),
+    });
+    props = {
+      x: 0,
+      y: 0,
+      width: 100,
+      height: 100,
+      zIndex: 1,
+      text: 'Test',
+      fontSize: 12,
+      fontColor: BLACK_COLOR,
+      verticalAlign: 'MIDDLE',
+      horizontalAlign: 'CENTER',
+      pointToProjection: jest.fn(),
+      mapInstance,
+    };
+
+    (getTextStyle as jest.Mock).mockReturnValue(new Style());
+    (getTextCoords as jest.Mock).mockReturnValue([10, 10]);
+    (rgbToHex as jest.Mock).mockReturnValue('#FFFFFF');
+  });
+
+  it('should apply correct styles to the feature', () => {
+    const text = new Text(props);
+    const { feature } = text;
+
+    const style = feature.getStyleFunction()?.call(text, feature, 1);
+
+    if (Array.isArray(style)) {
+      expect(style.length).toBeGreaterThan(0);
+    } else {
+      expect(style).toBeInstanceOf(Style);
+    }
+  });
+
+  it('should hide text when the scaled font size is too small', () => {
+    const text = new Text(props);
+    const { feature } = text;
+
+    const style = feature.getStyleFunction()?.call(text, feature, 20);
+
+    if (Array.isArray(style)) {
+      expect(style[0].getText()?.getText()).toBeUndefined();
+    } else {
+      expect(style?.getText()?.getText()).toBeUndefined();
+    }
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts
new file mode 100644
index 00000000..687cf2fc
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts
@@ -0,0 +1,115 @@
+/* eslint-disable no-magic-numbers */
+import {
+  ColorObject,
+  HorizontalAlign,
+  VerticalAlign,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import Style from 'ol/style/Style';
+import { Point } from 'ol/geom';
+import { Feature } from 'ol';
+import { FeatureLike } from 'ol/Feature';
+import { MapInstance } from '@/types/map';
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+
+export interface TextProps {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+  zIndex: number;
+  text: string;
+  fontSize: number;
+  fontColor: ColorObject;
+  verticalAlign: VerticalAlign;
+  horizontalAlign: HorizontalAlign;
+  pointToProjection: UsePointToProjectionResult;
+  mapInstance: MapInstance;
+}
+
+export default class Text {
+  text: string;
+
+  fontSize: number;
+
+  style: Style;
+
+  point: Point;
+
+  feature: Feature<Point>;
+
+  constructor({
+    x,
+    y,
+    width,
+    height,
+    zIndex,
+    text,
+    fontSize,
+    fontColor,
+    verticalAlign,
+    horizontalAlign,
+    pointToProjection,
+    mapInstance,
+  }: TextProps) {
+    this.text = text;
+    this.fontSize = fontSize;
+
+    const textCoords = getTextCoords({
+      x,
+      y,
+      height,
+      width,
+      fontSize,
+      verticalAlign,
+      horizontalAlign,
+      pointToProjection,
+    });
+    const textStyle = getTextStyle({
+      text,
+      fontSize,
+      color: rgbToHex(fontColor),
+      zIndex,
+      horizontalAlign,
+    });
+    this.point = new Point(textCoords);
+    this.style = textStyle;
+    this.style.setGeometry(this.point);
+
+    this.feature = new Feature({
+      geometry: this.point,
+      getTextScale: (resolution: number): number => {
+        const maxZoom = mapInstance?.getView().getMaxZoom();
+        if (maxZoom) {
+          const minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom);
+          if (minResolution) {
+            return Math.round((minResolution / resolution) * 100) / 100;
+          }
+        }
+        return 1;
+      },
+    });
+
+    this.feature.setStyle(this.styleFunction.bind(this));
+  }
+
+  protected styleFunction(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const getTextScale = feature.get('getTextScale');
+    let textScale = 1;
+    if (getTextScale instanceof Function) {
+      textScale = getTextScale(resolution);
+    }
+
+    if (this.style.getText()) {
+      if (this.fontSize * textScale > 4) {
+        this.style.getText()?.setScale(textScale);
+        this.style.getText()?.setText(this.text);
+      } else {
+        this.style.getText()?.setText(undefined);
+      }
+    }
+    return this.style;
+  }
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.test.ts
new file mode 100644
index 00000000..6121a92d
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.test.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-magic-numbers */
+import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+
+describe('getTextCoords', () => {
+  it('should return a text coords', () => {
+    const mockPointToProjection: UsePointToProjectionResult = jest.fn(point => [point.x, point.y]);
+    const textCoords = getTextCoords({
+      x: 20,
+      y: 20,
+      height: 100,
+      width: 100,
+      fontSize: 12,
+      verticalAlign: 'MIDDLE',
+      horizontalAlign: 'CENTER',
+      pointToProjection: mockPointToProjection,
+    });
+    expect(textCoords).toEqual([70, 70]);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.ts
new file mode 100644
index 00000000..3dcbb8fa
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords.ts
@@ -0,0 +1,48 @@
+/* eslint-disable no-magic-numbers */
+import {
+  HorizontalAlign,
+  VerticalAlign,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import { Coordinate } from 'ol/coordinate';
+
+export default function getTextCoords({
+  x,
+  y,
+  height,
+  width,
+  fontSize,
+  verticalAlign,
+  horizontalAlign,
+  pointToProjection,
+}: {
+  x: number;
+  y: number;
+  height: number;
+  width: number;
+  fontSize: number;
+  verticalAlign: VerticalAlign;
+  horizontalAlign: HorizontalAlign;
+  pointToProjection: UsePointToProjectionResult;
+}): Coordinate {
+  const minX = x;
+  const maxX = x + width;
+  const minY = y;
+  const maxY = y + height;
+
+  let textY = (minY + maxY) / 2;
+  if (verticalAlign === 'TOP') {
+    textY = minY + (fontSize * 4) / 6;
+  } else if (verticalAlign === 'BOTTOM') {
+    textY = maxY - (fontSize * 4) / 6;
+  }
+
+  let textX = (minX + maxX) / 2;
+  if (['LEFT', 'START'].includes(horizontalAlign)) {
+    textX = minX;
+  } else if (['RIGHT', 'END'].includes(horizontalAlign)) {
+    textX = maxX;
+  }
+
+  return pointToProjection({ x: textX, y: textY });
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.test.ts
new file mode 100644
index 00000000..d16623b5
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.test.ts
@@ -0,0 +1,26 @@
+/* eslint-disable no-magic-numbers */
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import Style from 'ol/style/Style';
+
+describe('getTextStyle', () => {
+  it('should return a text style object', () => {
+    const text = 'Text styl test';
+    const fontSize = 12;
+    const color = '#CCFFCC';
+    const zIndex = 122;
+    const horizontalAlign = 'CENTER';
+    const textStyle = getTextStyle({
+      text,
+      fontSize,
+      color,
+      zIndex,
+      horizontalAlign,
+    });
+    expect(textStyle).toBeInstanceOf(Style);
+    expect(textStyle.getText()?.getText()).toBe(text);
+    expect(textStyle.getText()?.getFont()).toBe('12pt Arial');
+    expect(textStyle.getText()?.getFill()?.getColor()).toBe(color);
+    expect(textStyle.getZIndex()).toBe(zIndex);
+    expect(textStyle.getText()?.getTextAlign()).toBe(horizontalAlign.toLowerCase());
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
new file mode 100644
index 00000000..da9eed33
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
@@ -0,0 +1,32 @@
+import { Fill, Text } from 'ol/style';
+import Style from 'ol/style/Style';
+import { HorizontalAlign } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+
+export default function getTextStyle({
+  text,
+  fontSize,
+  color,
+  zIndex,
+  horizontalAlign,
+}: {
+  text: string;
+  fontSize: number;
+  color: string;
+  zIndex: number;
+  horizontalAlign: HorizontalAlign;
+}): Style {
+  return new Style({
+    text: new Text({
+      text,
+      font: `${fontSize}pt Arial`,
+      fill: new Fill({
+        color,
+      }),
+      placement: 'point',
+      textAlign: horizontalAlign.toLowerCase() as CanvasTextAlign,
+      textBaseline: 'middle',
+      overflow: true,
+    }),
+    zIndex,
+  });
+}
diff --git a/src/models/arrowTypeSchema.ts b/src/models/arrowTypeSchema.ts
new file mode 100644
index 00000000..929f6b5a
--- /dev/null
+++ b/src/models/arrowTypeSchema.ts
@@ -0,0 +1,7 @@
+import { z } from 'zod';
+import { shapeSchema } from '@/models/shapeSchema';
+
+export const arrowTypeSchema = z.object({
+  arrowType: z.string(),
+  shapes: z.array(shapeSchema),
+});
diff --git a/src/models/fixtures/arrowTypesFixture.ts b/src/models/fixtures/arrowTypesFixture.ts
new file mode 100644
index 00000000..2d452353
--- /dev/null
+++ b/src/models/fixtures/arrowTypesFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+import { z } from 'zod';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { arrowTypeSchema } from '@/models/arrowTypeSchema';
+
+export const arrowTypesFixture = createFixture(z.array(arrowTypeSchema), {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/fixtures/layerLinesFixture.ts b/src/models/fixtures/layerLinesFixture.ts
new file mode 100644
index 00000000..fc15d3c2
--- /dev/null
+++ b/src/models/fixtures/layerLinesFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { pageableSchema } from '@/models/pageableSchema';
+import { layerLineSchema } from '@/models/layerLineSchema';
+
+export const layerLinesFixture = createFixture(pageableSchema(layerLineSchema), {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/fixtures/layerOvalsFixture.ts b/src/models/fixtures/layerOvalsFixture.ts
new file mode 100644
index 00000000..50544273
--- /dev/null
+++ b/src/models/fixtures/layerOvalsFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { pageableSchema } from '@/models/pageableSchema';
+import { layerOvalSchema } from '@/models/layerOvalSchema';
+
+export const layerOvalsFixture = createFixture(pageableSchema(layerOvalSchema), {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/fixtures/layerRectsFixture.ts b/src/models/fixtures/layerRectsFixture.ts
new file mode 100644
index 00000000..98469f8b
--- /dev/null
+++ b/src/models/fixtures/layerRectsFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { pageableSchema } from '@/models/pageableSchema';
+import { layerRectSchema } from '@/models/layerRectSchema';
+
+export const layerRectsFixture = createFixture(pageableSchema(layerRectSchema), {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/fixtures/layerTextsFixture.ts b/src/models/fixtures/layerTextsFixture.ts
new file mode 100644
index 00000000..26e01467
--- /dev/null
+++ b/src/models/fixtures/layerTextsFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { layerTextSchema } from '@/models/layerTextSchema';
+import { pageableSchema } from '@/models/pageableSchema';
+
+export const layerTextsFixture = createFixture(pageableSchema(layerTextSchema), {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/fixtures/layersFixture.ts b/src/models/fixtures/layersFixture.ts
new file mode 100644
index 00000000..65a4841a
--- /dev/null
+++ b/src/models/fixtures/layersFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { layerSchema } from '@/models/layerSchema';
+import { pageableSchema } from '@/models/pageableSchema';
+
+export const layersFixture = createFixture(pageableSchema(layerSchema), {
+  seed: ZOD_SEED,
+  array: { min: 1, max: 1 },
+});
diff --git a/src/models/fixtures/modelElementsFixture.ts b/src/models/fixtures/modelElementsFixture.ts
index 87c8fb2e..0c3df37b 100644
--- a/src/models/fixtures/modelElementsFixture.ts
+++ b/src/models/fixtures/modelElementsFixture.ts
@@ -1,9 +1,10 @@
 import { ZOD_SEED } from '@/constants';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { createFixture } from 'zod-fixture';
-import { modelElementsSchema } from '@/models/modelElementsSchema';
+import { modelElementSchema } from '@/models/modelElementSchema';
+import { pageableSchema } from '@/models/pageableSchema';
 
-export const modelElementsFixture = createFixture(modelElementsSchema, {
+export const modelElementsFixture = createFixture(pageableSchema(modelElementSchema), {
   seed: ZOD_SEED,
   array: { min: 3, max: 3 },
 });
diff --git a/src/models/layerLineSchema.ts b/src/models/layerLineSchema.ts
new file mode 100644
index 00000000..e454f930
--- /dev/null
+++ b/src/models/layerLineSchema.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+import { colorSchema } from '@/models/colorSchema';
+import { segmentSchema } from '@/models/segmentSchema';
+import { arrowSchema } from '@/models/arrowSchema';
+
+export const layerLineSchema = z.object({
+  id: z.number().int().positive(),
+  width: z.number(),
+  color: colorSchema,
+  z: z.number(),
+  segments: z.array(segmentSchema),
+  startArrow: arrowSchema,
+  endArrow: arrowSchema,
+  lineType: z.string(),
+});
diff --git a/src/models/layerOvalSchema.ts b/src/models/layerOvalSchema.ts
new file mode 100644
index 00000000..abd708ef
--- /dev/null
+++ b/src/models/layerOvalSchema.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+import { colorSchema } from '@/models/colorSchema';
+
+export const layerOvalSchema = z.object({
+  id: z.number().int().positive(),
+  x: z.number(),
+  y: z.number(),
+  z: z.number(),
+  width: z.number(),
+  height: z.number(),
+  lineWidth: z.number(),
+  size: z.number(),
+  elementId: z.string(),
+  borderColor: colorSchema,
+});
diff --git a/src/models/layerRectSchema.ts b/src/models/layerRectSchema.ts
new file mode 100644
index 00000000..440c96d4
--- /dev/null
+++ b/src/models/layerRectSchema.ts
@@ -0,0 +1,16 @@
+import { z } from 'zod';
+import { colorSchema } from '@/models/colorSchema';
+
+export const layerRectSchema = z.object({
+  id: z.number().int().positive(),
+  x: z.number(),
+  y: z.number(),
+  z: z.number(),
+  width: z.number(),
+  height: z.number(),
+  lineWidth: z.number(),
+  size: z.number(),
+  fillColor: colorSchema,
+  borderColor: colorSchema,
+  elementId: z.string(),
+});
diff --git a/src/models/layerSchema.ts b/src/models/layerSchema.ts
new file mode 100644
index 00000000..be2ba10b
--- /dev/null
+++ b/src/models/layerSchema.ts
@@ -0,0 +1,10 @@
+import { z } from 'zod';
+
+export const layerSchema = z.object({
+  id: z.number(),
+  layerId: z.string(),
+  name: z.string(),
+  visible: z.boolean(),
+  locked: z.boolean(),
+  empty: z.boolean(),
+});
diff --git a/src/models/layerTextSchema.ts b/src/models/layerTextSchema.ts
new file mode 100644
index 00000000..3ad77ed0
--- /dev/null
+++ b/src/models/layerTextSchema.ts
@@ -0,0 +1,22 @@
+import { z } from 'zod';
+import { colorSchema } from '@/models/colorSchema';
+import { glyphSchema } from '@/models/glyphSchema';
+
+export const layerTextSchema = z.object({
+  id: z.number(),
+  x: z.number(),
+  y: z.number(),
+  z: z.number(),
+  width: z.number(),
+  height: z.number(),
+  fontSize: z.number(),
+  size: z.number(),
+  notes: z.string(),
+  glyph: glyphSchema.nullable(),
+  elementId: z.string(),
+  verticalAlign: z.string(),
+  horizontalAlign: z.string(),
+  backgroundColor: colorSchema,
+  borderColor: colorSchema,
+  color: colorSchema,
+});
diff --git a/src/models/modelElementsSchema.ts b/src/models/modelElementsSchema.ts
deleted file mode 100644
index 19969ba1..00000000
--- a/src/models/modelElementsSchema.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { z } from 'zod';
-import { modelElementSchema } from '@/models/modelElementSchema';
-
-export const modelElementsSchema = z.object({
-  content: z.array(modelElementSchema),
-  totalPages: z.number(),
-  totalElements: z.number(),
-  numberOfElements: z.number(),
-  size: z.number(),
-  number: z.number(),
-});
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index b830ff10..c193f101 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -52,6 +52,16 @@ export const apiPath = {
     `projects/${PROJECT_ID}/maps/${modelId}/bioEntities/elements/?size=10000`,
   getShapes: (): string => `projects/${PROJECT_ID}/shapes/`,
   getLineTypes: (): string => `projects/${PROJECT_ID}/lineTypes/`,
+  getArrowTypes: (): string => `projects/${PROJECT_ID}/arrowTypes/`,
+  getLayers: (modelId: number): string => `projects/${PROJECT_ID}/maps/${modelId}/layers/`,
+  getLayerTexts: (modelId: number, layerId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/texts/`,
+  getLayerRects: (modelId: number, layerId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/rects/`,
+  getLayerOvals: (modelId: number, layerId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/ovals/`,
+  getLayerLines: (modelId: number, layerId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/lines/`,
   getChemicalsStringWithQuery: (searchQuery: string): string =>
     `projects/${PROJECT_ID}/chemicals:search?query=${searchQuery}`,
   getAllOverlaysByProjectIdQuery: (
diff --git a/src/redux/layers/layers.constants.ts b/src/redux/layers/layers.constants.ts
new file mode 100644
index 00000000..56736b9f
--- /dev/null
+++ b/src/redux/layers/layers.constants.ts
@@ -0,0 +1 @@
+export const LAYERS_FETCHING_ERROR_PREFIX = 'Failed to fetch layers';
diff --git a/src/redux/layers/layers.mock.ts b/src/redux/layers/layers.mock.ts
new file mode 100644
index 00000000..9ec2ce4c
--- /dev/null
+++ b/src/redux/layers/layers.mock.ts
@@ -0,0 +1,11 @@
+import { LayersState } from '@/redux/layers/layers.types';
+import { DEFAULT_ERROR } from '@/constants/errors';
+
+export const LAYERS_STATE_INITIAL_MOCK: LayersState = {
+  data: {
+    layers: [],
+    layersVisibility: {},
+  },
+  loading: 'idle',
+  error: DEFAULT_ERROR,
+};
diff --git a/src/redux/layers/layers.reducers.test.ts b/src/redux/layers/layers.reducers.test.ts
new file mode 100644
index 00000000..827c1dff
--- /dev/null
+++ b/src/redux/layers/layers.reducers.test.ts
@@ -0,0 +1,128 @@
+/* eslint-disable no-magic-numbers */
+import { apiPath } from '@/redux/apiPath';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { unwrapResult } from '@reduxjs/toolkit';
+import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
+import { getLayers } from '@/redux/layers/layers.thunks';
+import { layersFixture } from '@/models/fixtures/layersFixture';
+import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture';
+import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture';
+import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture';
+import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture';
+import { LayersState } from './layers.types';
+import layersReducer from './layers.slice';
+
+const mockedAxiosClient = mockNetworkNewAPIResponse();
+
+const INITIAL_STATE: LayersState = LAYERS_STATE_INITIAL_MOCK;
+
+describe('layers reducer', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<LayersState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('layers', layersReducer);
+  });
+
+  it('should match initial state', () => {
+    const action = { type: 'unknown' };
+
+    expect(layersReducer(undefined, action)).toEqual(INITIAL_STATE);
+  });
+
+  it('should update store after successful getLayers query', async () => {
+    mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.Ok, layersFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerTexts(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerTextsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerRects(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerRectsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerOvals(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerOvalsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerLinesFixture);
+
+    const { type } = await store.dispatch(getLayers(1));
+    const { data, loading, error } = store.getState().layers;
+    expect(type).toBe('vectorMap/getLayers/fulfilled');
+    expect(loading).toEqual('succeeded');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual({
+      layers: [
+        {
+          details: layersFixture.content[0],
+          texts: layerTextsFixture.content,
+          rects: layerRectsFixture.content,
+          ovals: layerOvalsFixture.content,
+          lines: layerLinesFixture.content,
+        },
+      ],
+      layersVisibility: {
+        [layersFixture.content[0].layerId]: layersFixture.content[0].visible,
+      },
+    });
+  });
+
+  it('should update store after failed getLayers query', async () => {
+    mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.NotFound, []);
+
+    const action = await store.dispatch(getLayers(1));
+    const { data, loading, error } = store.getState().layers;
+
+    expect(action.type).toBe('vectorMap/getLayers/rejected');
+    expect(() => unwrapResult(action)).toThrow(
+      "Failed to fetch layers: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
+    expect(loading).toEqual('failed');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual({ layers: [], layersVisibility: {} });
+  });
+
+  it('should update store on loading getLayers query', async () => {
+    mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.Ok, layersFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerTexts(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerTextsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerRects(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerRectsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerOvals(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerOvalsFixture);
+    mockedAxiosClient
+      .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
+      .reply(HttpStatusCode.Ok, layerLinesFixture);
+
+    const layersPromise = store.dispatch(getLayers(1));
+
+    const { data, loading } = store.getState().layers;
+    expect(data).toEqual({ layers: [], layersVisibility: {} });
+    expect(loading).toEqual('pending');
+
+    layersPromise.then(() => {
+      const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().layers;
+
+      expect(dataPromiseFulfilled).toEqual({
+        layers: [
+          {
+            details: layersFixture.content[0],
+            texts: layerTextsFixture.content,
+            rects: layerRectsFixture.content,
+            ovals: layerOvalsFixture.content,
+            lines: layerLinesFixture.content,
+          },
+        ],
+        layersVisibility: {
+          [layersFixture.content[0].layerId]: layersFixture.content[0].visible,
+        },
+      });
+      expect(promiseFulfilled).toEqual('succeeded');
+    });
+  });
+});
diff --git a/src/redux/layers/layers.reducers.ts b/src/redux/layers/layers.reducers.ts
new file mode 100644
index 00000000..ed75a687
--- /dev/null
+++ b/src/redux/layers/layers.reducers.ts
@@ -0,0 +1,30 @@
+/* eslint-disable no-magic-numbers */
+import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
+import { getLayers } from '@/redux/layers/layers.thunks';
+import { LayersState } from '@/redux/layers/layers.types';
+
+export const getLayersReducer = (builder: ActionReducerMapBuilder<LayersState>): void => {
+  builder.addCase(getLayers.pending, state => {
+    state.loading = 'pending';
+  });
+  builder.addCase(getLayers.fulfilled, (state, action) => {
+    state.data = action.payload || {
+      layers: [],
+      layersVisibility: {},
+    };
+    state.loading = 'succeeded';
+  });
+  builder.addCase(getLayers.rejected, state => {
+    state.loading = 'failed';
+  });
+};
+
+export const setLayerVisibilityReducer = (
+  state: LayersState,
+  action: PayloadAction<{ visible: boolean; layerId: string }>,
+): void => {
+  const { payload } = action;
+  if (state.data && state.data.layersVisibility[payload.layerId] !== undefined) {
+    state.data.layersVisibility[payload.layerId] = payload.visible;
+  }
+};
diff --git a/src/redux/layers/layers.selectors.ts b/src/redux/layers/layers.selectors.ts
new file mode 100644
index 00000000..987ec4ac
--- /dev/null
+++ b/src/redux/layers/layers.selectors.ts
@@ -0,0 +1,12 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { rootSelector } from '@/redux/root/root.selectors';
+
+export const layersSelector = createSelector(
+  rootSelector,
+  state => state.layers?.data?.layers || [],
+);
+
+export const layersVisibilitySelector = createSelector(
+  rootSelector,
+  state => state.layers?.data?.layersVisibility || {},
+);
diff --git a/src/redux/layers/layers.slice.ts b/src/redux/layers/layers.slice.ts
new file mode 100644
index 00000000..47da06b0
--- /dev/null
+++ b/src/redux/layers/layers.slice.ts
@@ -0,0 +1,18 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
+import { getLayersReducer, setLayerVisibilityReducer } from '@/redux/layers/layers.reducers';
+
+export const layersSlice = createSlice({
+  name: 'layers',
+  initialState: LAYERS_STATE_INITIAL_MOCK,
+  reducers: {
+    setLayerVisibility: setLayerVisibilityReducer,
+  },
+  extraReducers: builder => {
+    getLayersReducer(builder);
+  },
+});
+
+export const { setLayerVisibility } = layersSlice.actions;
+
+export default layersSlice.reducer;
diff --git a/src/redux/layers/layers.thunks.test.ts b/src/redux/layers/layers.thunks.test.ts
new file mode 100644
index 00000000..a6c00186
--- /dev/null
+++ b/src/redux/layers/layers.thunks.test.ts
@@ -0,0 +1,68 @@
+/* eslint-disable no-magic-numbers */
+import { apiPath } from '@/redux/apiPath';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { LayersState } from '@/redux/layers/layers.types';
+import { getLayers } from '@/redux/layers/layers.thunks';
+import { layersFixture } from '@/models/fixtures/layersFixture';
+import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture';
+import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture';
+import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture';
+import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture';
+import layersReducer from './layers.slice';
+
+const mockedAxiosClient = mockNetworkNewAPIResponse();
+
+describe('layers thunks', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<LayersState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('layers', layersReducer);
+  });
+
+  describe('getLayers', () => {
+    it('should return data when data response from API is valid', async () => {
+      mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.Ok, layersFixture);
+      mockedAxiosClient
+        .onGet(apiPath.getLayerTexts(1, layersFixture.content[0].id))
+        .reply(HttpStatusCode.Ok, layerTextsFixture);
+      mockedAxiosClient
+        .onGet(apiPath.getLayerRects(1, layersFixture.content[0].id))
+        .reply(HttpStatusCode.Ok, layerRectsFixture);
+      mockedAxiosClient
+        .onGet(apiPath.getLayerOvals(1, layersFixture.content[0].id))
+        .reply(HttpStatusCode.Ok, layerOvalsFixture);
+      mockedAxiosClient
+        .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
+        .reply(HttpStatusCode.Ok, layerLinesFixture);
+
+      const { payload } = await store.dispatch(getLayers(1));
+      expect(payload).toEqual({
+        layers: [
+          {
+            details: layersFixture.content[0],
+            texts: layerTextsFixture.content,
+            rects: layerRectsFixture.content,
+            ovals: layerOvalsFixture.content,
+            lines: layerLinesFixture.content,
+          },
+        ],
+        layersVisibility: {
+          [layersFixture.content[0].layerId]: layersFixture.content[0].visible,
+        },
+      });
+    });
+
+    it('should return undefined when data response from API is not valid ', async () => {
+      mockedAxiosClient
+        .onGet(apiPath.getLayers(1))
+        .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
+
+      const { payload } = await store.dispatch(getLayers(1));
+      expect(payload).toEqual(undefined);
+    });
+  });
+});
diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts
new file mode 100644
index 00000000..9aa71e83
--- /dev/null
+++ b/src/redux/layers/layers.thunks.ts
@@ -0,0 +1,65 @@
+import { z } from 'zod';
+import { apiPath } from '@/redux/apiPath';
+import { Layer, Layers } from '@/types/models';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { ThunkConfig } from '@/types/store';
+import { getError } from '@/utils/error-report/getError';
+import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
+import { layerSchema } from '@/models/layerSchema';
+import { LAYERS_FETCHING_ERROR_PREFIX } from '@/redux/layers/layers.constants';
+import { LayersVisibilitiesState } from '@/redux/layers/layers.types';
+import { layerTextSchema } from '@/models/layerTextSchema';
+import { layerRectSchema } from '@/models/layerRectSchema';
+import { pageableSchema } from '@/models/pageableSchema';
+import { layerOvalSchema } from '@/models/layerOvalSchema';
+import { layerLineSchema } from '@/models/layerLineSchema';
+
+export const getLayers = createAsyncThunk<LayersVisibilitiesState | undefined, number, ThunkConfig>(
+  'vectorMap/getLayers',
+  async (modelId: number) => {
+    try {
+      const { data } = await axiosInstanceNewAPI.get<Layers>(apiPath.getLayers(modelId));
+      const isDataValid = validateDataUsingZodSchema(data, pageableSchema(layerSchema));
+      if (!isDataValid) {
+        return undefined;
+      }
+      let layers = await Promise.all(
+        data.content.map(async (layer: Layer) => {
+          const [textsResponse, rectsResponse, ovalsResponse, linesResponse] = await Promise.all([
+            axiosInstanceNewAPI.get(apiPath.getLayerTexts(modelId, layer.id)),
+            axiosInstanceNewAPI.get(apiPath.getLayerRects(modelId, layer.id)),
+            axiosInstanceNewAPI.get(apiPath.getLayerOvals(modelId, layer.id)),
+            axiosInstanceNewAPI.get(apiPath.getLayerLines(modelId, layer.id)),
+          ]);
+
+          return {
+            details: layer,
+            texts: textsResponse.data.content,
+            rects: rectsResponse.data.content,
+            ovals: ovalsResponse.data.content,
+            lines: linesResponse.data.content,
+          };
+        }),
+      );
+      layers = layers.filter(layer => {
+        return (
+          z.array(layerTextSchema).safeParse(layer.texts).success &&
+          z.array(layerRectSchema).safeParse(layer.rects).success &&
+          z.array(layerOvalSchema).safeParse(layer.ovals).success &&
+          z.array(layerLineSchema).safeParse(layer.lines).success
+        );
+      });
+      const layersVisibility = layers.reduce((acc: { [key: string]: boolean }, layer) => {
+        acc[layer.details.layerId] = layer.details.visible;
+        return acc;
+      }, {});
+      return {
+        layers,
+        layersVisibility,
+      };
+    } catch (error) {
+      return Promise.reject(getError({ error, prefix: LAYERS_FETCHING_ERROR_PREFIX }));
+    }
+  },
+);
diff --git a/src/redux/layers/layers.types.ts b/src/redux/layers/layers.types.ts
new file mode 100644
index 00000000..63637690
--- /dev/null
+++ b/src/redux/layers/layers.types.ts
@@ -0,0 +1,21 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { Layer, LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models';
+
+export type LayerState = {
+  details: Layer;
+  texts: LayerText[];
+  rects: LayerRect[];
+  ovals: LayerOval[];
+  lines: LayerLine[];
+};
+
+export type LayerVisibilityState = {
+  [key: string]: boolean;
+};
+
+export type LayersVisibilitiesState = {
+  layersVisibility: LayerVisibilityState;
+  layers: LayerState[];
+};
+
+export type LayersState = FetchDataState<LayersVisibilitiesState>;
diff --git a/src/redux/modelElements/modelElements.thunks.ts b/src/redux/modelElements/modelElements.thunks.ts
index 7898db9a..2c7e2f25 100644
--- a/src/redux/modelElements/modelElements.thunks.ts
+++ b/src/redux/modelElements/modelElements.thunks.ts
@@ -4,9 +4,10 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
 import { ThunkConfig } from '@/types/store';
 import { getError } from '@/utils/error-report/getError';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
-import { modelElementsSchema } from '@/models/modelElementsSchema';
 import { ModelElements } from '@/types/models';
 import { MODEL_ELEMENTS_FETCHING_ERROR_PREFIX } from '@/redux/modelElements/modelElements.constants';
+import { modelElementSchema } from '@/models/modelElementSchema';
+import { pageableSchema } from '@/models/pageableSchema';
 
 export const getModelElements = createAsyncThunk<ModelElements | undefined, number, ThunkConfig>(
   'vectorMap/getModelElements',
@@ -15,7 +16,10 @@ export const getModelElements = createAsyncThunk<ModelElements | undefined, numb
       const response = await axiosInstanceNewAPI.get<ModelElements>(
         apiPath.getModelElements(modelId),
       );
-      const isDataValid = validateDataUsingZodSchema(response.data, modelElementsSchema);
+      const isDataValid = validateDataUsingZodSchema(
+        response.data,
+        pageableSchema(modelElementSchema),
+      );
       return isDataValid ? response.data : undefined;
     } catch (error) {
       return Promise.reject(getError({ error, prefix: MODEL_ELEMENTS_FETCHING_ERROR_PREFIX }));
diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts
index 5f031272..34f7765e 100644
--- a/src/redux/root/init.thunks.ts
+++ b/src/redux/root/init.thunks.ts
@@ -15,7 +15,7 @@ import {
 import { openSelectProjectModal } from '@/redux/modal/modal.slice';
 import { getProjects } from '@/redux/projects/projects.thunks';
 import { getSubmapConnectionsBioEntity } from '@/redux/bioEntity/thunks/getSubmapConnectionsBioEntity';
-import { getLineTypes, getShapes } from '@/redux/shapes/shapes.thunks';
+import { getArrowTypes, getLineTypes, getShapes } from '@/redux/shapes/shapes.thunks';
 import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks';
 import { getConfiguration, getConfigurationOptions } from '../configuration/configuration.thunks';
 import {
@@ -61,8 +61,8 @@ export const fetchInitialAppData = createAsyncThunk<
     dispatch(getModels()),
     dispatch(getShapes()),
     dispatch(getLineTypes()),
+    dispatch(getArrowTypes()),
   ]);
-
   if (queryData.pluginsId) {
     await dispatch(
       getInitPlugins({
diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts
index 16bbee96..8b1cec49 100644
--- a/src/redux/root/root.fixtures.ts
+++ b/src/redux/root/root.fixtures.ts
@@ -5,6 +5,7 @@ import { COMMENT_INITIAL_STATE_MOCK } from '@/redux/comment/comment.mock';
 import { AUTOCOMPLETE_INITIAL_STATE } from '@/redux/autocomplete/autocomplete.constants';
 import { SHAPES_STATE_INITIAL_MOCK } from '@/redux/shapes/shapes.mock';
 import { MODEL_ELEMENTS_INITIAL_STATE_MOCK } from '@/redux/modelElements/modelElements.mock';
+import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
 import { BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock';
 import { BIOENTITY_INITIAL_STATE_MOCK } from '../bioEntity/bioEntity.mock';
 import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock';
@@ -44,6 +45,7 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
   chemicals: CHEMICALS_INITIAL_STATE_MOCK,
   models: MODELS_INITIAL_STATE_MOCK,
   modelElements: MODEL_ELEMENTS_INITIAL_STATE_MOCK,
+  layers: LAYERS_STATE_INITIAL_MOCK,
   bioEntity: BIOENTITY_INITIAL_STATE_MOCK,
   backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
   drawer: drawerInitialStateMock,
diff --git a/src/redux/shapes/shapes.constants.ts b/src/redux/shapes/shapes.constants.ts
index 1557cae9..18da4b1b 100644
--- a/src/redux/shapes/shapes.constants.ts
+++ b/src/redux/shapes/shapes.constants.ts
@@ -1,3 +1,5 @@
 export const SHAPES_FETCHING_ERROR_PREFIX = 'Failed to fetch shapes';
 
 export const LINE_TYPES_FETCHING_ERROR_PREFIX = 'Failed to fetch line types';
+
+export const ARROW_TYPES_FETCHING_ERROR_PREFIX = 'Failed to fetch arrow types';
diff --git a/src/redux/shapes/shapes.mock.ts b/src/redux/shapes/shapes.mock.ts
index 2ede21d1..b1ad3a5e 100644
--- a/src/redux/shapes/shapes.mock.ts
+++ b/src/redux/shapes/shapes.mock.ts
@@ -12,4 +12,9 @@ export const SHAPES_STATE_INITIAL_MOCK: ShapesState = {
     loading: 'idle',
     error: DEFAULT_ERROR,
   },
+  arrowTypesState: {
+    data: [],
+    loading: 'idle',
+    error: DEFAULT_ERROR,
+  },
 };
diff --git a/src/redux/shapes/shapes.reducers.test.ts b/src/redux/shapes/shapes.reducers.test.ts
index d9dcd3cf..9eab2b21 100644
--- a/src/redux/shapes/shapes.reducers.test.ts
+++ b/src/redux/shapes/shapes.reducers.test.ts
@@ -9,8 +9,9 @@ import { unwrapResult } from '@reduxjs/toolkit';
 import { bioShapesFixture } from '@/models/fixtures/bioShapesFixture';
 import { SHAPES_STATE_INITIAL_MOCK } from '@/redux/shapes/shapes.mock';
 import { lineTypesFixture } from '@/models/fixtures/lineTypesFixture';
+import { arrowTypesFixture } from '@/models/fixtures/arrowTypesFixture';
 import shapesReducer from './shapes.slice';
-import { getLineTypes, getShapes } from './shapes.thunks';
+import { getArrowTypes, getLineTypes, getShapes } from './shapes.thunks';
 import { ShapesState } from './shapes.types';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
@@ -51,6 +52,17 @@ describe('shapes reducer', () => {
     expect(data).toEqual(lineTypesFixture);
   });
 
+  it('should update store after successful getArrowTypes query', async () => {
+    mockedAxiosClient.onGet(apiPath.getArrowTypes()).reply(HttpStatusCode.Ok, arrowTypesFixture);
+
+    const { type } = await store.dispatch(getArrowTypes());
+    const { data, loading, error } = store.getState().shapes.arrowTypesState;
+    expect(type).toBe('vectorMap/getArrowTypes/fulfilled');
+    expect(loading).toEqual('succeeded');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual(arrowTypesFixture);
+  });
+
   it('should update store after failed getShapes query', async () => {
     mockedAxiosClient.onGet(apiPath.getShapes()).reply(HttpStatusCode.NotFound, []);
 
@@ -81,6 +93,21 @@ describe('shapes reducer', () => {
     expect(data).toEqual([]);
   });
 
+  it('should update store after failed getArrowTypes query', async () => {
+    mockedAxiosClient.onGet(apiPath.getArrowTypes()).reply(HttpStatusCode.NotFound, []);
+
+    const action = await store.dispatch(getArrowTypes());
+    const { data, loading, error } = store.getState().shapes.arrowTypesState;
+
+    expect(action.type).toBe('vectorMap/getArrowTypes/rejected');
+    expect(() => unwrapResult(action)).toThrow(
+      "Failed to fetch arrow types: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
+    expect(loading).toEqual('failed');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual([]);
+  });
+
   it('should update store on loading getShapes query', async () => {
     mockedAxiosClient.onGet(apiPath.getShapes()).reply(HttpStatusCode.Ok, bioShapesFixture);
 
@@ -116,4 +143,22 @@ describe('shapes reducer', () => {
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
+
+  it('should update store on loading getArrowTypes query', async () => {
+    mockedAxiosClient.onGet(apiPath.getArrowTypes()).reply(HttpStatusCode.Ok, arrowTypesFixture);
+
+    const arrowTypesPromise = store.dispatch(getArrowTypes());
+
+    const { data, loading } = store.getState().shapes.arrowTypesState;
+    expect(data).toEqual([]);
+    expect(loading).toEqual('pending');
+
+    arrowTypesPromise.then(() => {
+      const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
+        store.getState().shapes.arrowTypesState;
+
+      expect(dataPromiseFulfilled).toEqual(arrowTypesFixture);
+      expect(promiseFulfilled).toEqual('succeeded');
+    });
+  });
 });
diff --git a/src/redux/shapes/shapes.reducers.ts b/src/redux/shapes/shapes.reducers.ts
index 3e01282a..525cde54 100644
--- a/src/redux/shapes/shapes.reducers.ts
+++ b/src/redux/shapes/shapes.reducers.ts
@@ -1,6 +1,6 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
 import { ShapesState } from '@/redux/shapes/shapes.types';
-import { getLineTypes, getShapes } from '@/redux/shapes/shapes.thunks';
+import { getArrowTypes, getLineTypes, getShapes } from '@/redux/shapes/shapes.thunks';
 
 export const getShapesReducer = (builder: ActionReducerMapBuilder<ShapesState>): void => {
   builder.addCase(getShapes.pending, state => {
@@ -27,3 +27,16 @@ export const getLineTypesReducer = (builder: ActionReducerMapBuilder<ShapesState
     state.lineTypesState.loading = 'failed';
   });
 };
+
+export const getArrowTypesReducer = (builder: ActionReducerMapBuilder<ShapesState>): void => {
+  builder.addCase(getArrowTypes.pending, state => {
+    state.arrowTypesState.loading = 'pending';
+  });
+  builder.addCase(getArrowTypes.fulfilled, (state, action) => {
+    state.arrowTypesState.data = action.payload || [];
+    state.arrowTypesState.loading = 'succeeded';
+  });
+  builder.addCase(getArrowTypes.rejected, state => {
+    state.arrowTypesState.loading = 'failed';
+  });
+};
diff --git a/src/redux/shapes/shapes.selectors.ts b/src/redux/shapes/shapes.selectors.ts
index 0d042b2c..35569752 100644
--- a/src/redux/shapes/shapes.selectors.ts
+++ b/src/redux/shapes/shapes.selectors.ts
@@ -13,6 +13,11 @@ export const lineTypesSelector = createSelector(
   shapes => shapes.lineTypesState.data,
 );
 
+export const arrowTypesSelector = createSelector(
+  shapesSelector,
+  shapes => shapes.arrowTypesState.data,
+);
+
 export const shapeBySBOSelector = createSelector(
   [shapesSelector, (_state, shapeSBO: string): string => shapeSBO],
   (shapes, shapeSBO) =>
diff --git a/src/redux/shapes/shapes.slice.ts b/src/redux/shapes/shapes.slice.ts
index 5b3b81a0..7fcecfa0 100644
--- a/src/redux/shapes/shapes.slice.ts
+++ b/src/redux/shapes/shapes.slice.ts
@@ -1,5 +1,9 @@
 import { createSlice } from '@reduxjs/toolkit';
-import { getLineTypesReducer, getShapesReducer } from '@/redux/shapes/shapes.reducers';
+import {
+  getArrowTypesReducer,
+  getLineTypesReducer,
+  getShapesReducer,
+} from '@/redux/shapes/shapes.reducers';
 import { SHAPES_STATE_INITIAL_MOCK } from '@/redux/shapes/shapes.mock';
 
 export const shapesSlice = createSlice({
@@ -9,6 +13,7 @@ export const shapesSlice = createSlice({
   extraReducers: builder => {
     getShapesReducer(builder);
     getLineTypesReducer(builder);
+    getArrowTypesReducer(builder);
   },
 });
 
diff --git a/src/redux/shapes/shapes.thunks.test.ts b/src/redux/shapes/shapes.thunks.test.ts
index f2f2413c..7b9980bf 100644
--- a/src/redux/shapes/shapes.thunks.test.ts
+++ b/src/redux/shapes/shapes.thunks.test.ts
@@ -8,8 +8,9 @@ import { HttpStatusCode } from 'axios';
 import { bioShapesFixture } from '@/models/fixtures/bioShapesFixture';
 import { ShapesState } from '@/redux/shapes/shapes.types';
 import { lineTypesFixture } from '@/models/fixtures/lineTypesFixture';
+import { arrowTypesFixture } from '@/models/fixtures/arrowTypesFixture';
 import shapesReducer from './shapes.slice';
-import { getLineTypes, getShapes } from './shapes.thunks';
+import { getArrowTypes, getLineTypes, getShapes } from './shapes.thunks';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 
@@ -54,4 +55,22 @@ describe('shapes thunks', () => {
       expect(payload).toEqual(undefined);
     });
   });
+
+  describe('getArrowTypes', () => {
+    it('should return data when data response from API is valid', async () => {
+      mockedAxiosClient.onGet(apiPath.getArrowTypes()).reply(HttpStatusCode.Ok, arrowTypesFixture);
+
+      const { payload } = await store.dispatch(getArrowTypes());
+      expect(payload).toEqual(arrowTypesFixture);
+    });
+
+    it('should return undefined when data response from API is not valid ', async () => {
+      mockedAxiosClient
+        .onGet(apiPath.getArrowTypes())
+        .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
+
+      const { payload } = await store.dispatch(getArrowTypes());
+      expect(payload).toEqual(undefined);
+    });
+  });
 });
diff --git a/src/redux/shapes/shapes.thunks.ts b/src/redux/shapes/shapes.thunks.ts
index 90c1c41c..f882d2fb 100644
--- a/src/redux/shapes/shapes.thunks.ts
+++ b/src/redux/shapes/shapes.thunks.ts
@@ -1,17 +1,19 @@
 import { bioShapeSchema } from '@/models/bioShapeSchema';
 import { apiPath } from '@/redux/apiPath';
-import { BioShape, LineType } from '@/types/models';
+import { ArrowType, BioShape, LineType } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
 import { ThunkConfig } from '@/types/store';
 import { getError } from '@/utils/error-report/getError';
 import {
+  ARROW_TYPES_FETCHING_ERROR_PREFIX,
   LINE_TYPES_FETCHING_ERROR_PREFIX,
   SHAPES_FETCHING_ERROR_PREFIX,
 } from '@/redux/shapes/shapes.constants';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { lineTypeSchema } from '@/models/lineTypeSchema';
+import { arrowTypeSchema } from '@/models/arrowTypeSchema';
 
 export const getShapes = createAsyncThunk<BioShape[] | undefined, void, ThunkConfig>(
   'vectorMap/getShapes',
@@ -40,3 +42,17 @@ export const getLineTypes = createAsyncThunk<LineType[] | undefined, void, Thunk
     }
   },
 );
+
+export const getArrowTypes = createAsyncThunk<ArrowType[] | undefined, void, ThunkConfig>(
+  'vectorMap/getArrowTypes',
+  async () => {
+    try {
+      const { data } = await axiosInstanceNewAPI.get<ArrowType[]>(apiPath.getArrowTypes());
+      const isDataValid = validateDataUsingZodSchema(data, z.array(arrowTypeSchema));
+
+      return isDataValid ? data : undefined;
+    } catch (error) {
+      return Promise.reject(getError({ error, prefix: ARROW_TYPES_FETCHING_ERROR_PREFIX }));
+    }
+  },
+);
diff --git a/src/redux/shapes/shapes.types.ts b/src/redux/shapes/shapes.types.ts
index 254bc237..e597d194 100644
--- a/src/redux/shapes/shapes.types.ts
+++ b/src/redux/shapes/shapes.types.ts
@@ -1,11 +1,14 @@
 import { FetchDataState } from '@/types/fetchDataState';
-import { BioShape, LineType } from '@/types/models';
+import { ArrowType, BioShape, LineType } from '@/types/models';
 
 export type LineTypesState = FetchDataState<LineType[], []>;
 
+export type ArrowTypesState = FetchDataState<ArrowType[], []>;
+
 export type BioShapesState = FetchDataState<BioShape[], []>;
 
 export type ShapesState = {
   lineTypesState: LineTypesState;
+  arrowTypesState: ArrowTypesState;
   bioShapesState: BioShapesState;
 };
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 2df34675..f6b8ad65 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -12,6 +12,7 @@ import modalReducer from '@/redux/modal/modal.slice';
 import modelsReducer from '@/redux/models/models.slice';
 import shapesReducer from '@/redux/shapes/shapes.slice';
 import modelElementsReducer from '@/redux/modelElements/modelElements.slice';
+import layersReducer from '@/redux/layers/layers.slice';
 import oauthReducer from '@/redux/oauth/oauth.slice';
 import overlayBioEntityReducer from '@/redux/overlayBioEntity/overlayBioEntity.slice';
 import overlaysReducer from '@/redux/overlays/overlays.slice';
@@ -63,6 +64,7 @@ export const reducers = {
   models: modelsReducer,
   shapes: shapesReducer,
   modelElements: modelElementsReducer,
+  layers: layersReducer,
   reactions: reactionsReducer,
   contextMenu: contextMenuReducer,
   cookieBanner: cookieBannerReducer,
diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx
index b7434131..b05d1f57 100644
--- a/src/shared/Icon/Icon.component.tsx
+++ b/src/shared/Icon/Icon.component.tsx
@@ -7,6 +7,7 @@ import { ChevronUpIcon } from '@/shared/Icon/Icons/ChevronUpIcon';
 import { CloseIcon } from '@/shared/Icon/Icons/CloseIcon';
 import { DotsIcon } from '@/shared/Icon/Icons/DotsIcon';
 import { ExportIcon } from '@/shared/Icon/Icons/ExportIcon';
+import { LayersIcon } from '@/shared/Icon/Icons/LayersIcon';
 import { InfoIcon } from '@/shared/Icon/Icons/InfoIcon';
 import { LegendIcon } from '@/shared/Icon/Icons/LegendIcon';
 import { PageIcon } from '@/shared/Icon/Icons/PageIcon';
@@ -40,6 +41,7 @@ const icons: Record<IconTypes, IconComponentType> = {
   dots: DotsIcon,
   admin: AdminIcon,
   export: ExportIcon,
+  layers: LayersIcon,
   info: InfoIcon,
   legend: LegendIcon,
   page: PageIcon,
diff --git a/src/shared/Icon/Icons/LayersIcon.tsx b/src/shared/Icon/Icons/LayersIcon.tsx
new file mode 100644
index 00000000..bebcbea3
--- /dev/null
+++ b/src/shared/Icon/Icons/LayersIcon.tsx
@@ -0,0 +1,39 @@
+interface LayersIconProps {
+  className?: string;
+}
+
+export const LayersIcon = ({ className }: LayersIconProps): JSX.Element => (
+  <svg
+    width="20"
+    height="20"
+    viewBox="0 0 24 24"
+    fill="none"
+    className={className}
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M12 4L4 8.5L12 13L20 8.5L12 4Z"
+      stroke="black"
+      strokeWidth="1"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      fill="none"
+    />
+    <path
+      d="M4 12.5L12 17L20 12.5"
+      stroke="black"
+      strokeWidth="1"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      fill="none"
+    />
+    <path
+      d="M4 16.5L12 21L20 16.5"
+      stroke="black"
+      strokeWidth="1"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      fill="none"
+    />
+  </svg>
+);
diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts
index d6f79610..4a7f5114 100644
--- a/src/types/drawerName.ts
+++ b/src/types/drawerName.ts
@@ -10,4 +10,5 @@ export type DrawerName =
   | 'overlays'
   | 'bio-entity'
   | 'comment'
-  | 'available-plugins';
+  | 'available-plugins'
+  | 'layers';
diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts
index 07442161..6feeea09 100644
--- a/src/types/iconTypes.ts
+++ b/src/types/iconTypes.ts
@@ -8,6 +8,7 @@ export type IconTypes =
   | 'dots'
   | 'admin'
   | 'export'
+  | 'layers'
   | 'info'
   | 'legend'
   | 'page'
diff --git a/src/types/models.ts b/src/types/models.ts
index f9a2fe30..13dd6e28 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -67,9 +67,17 @@ import { javaStacktraceSchema } from '@/models/javaStacktraceSchema';
 import { oauthSchema } from '@/models/oauthSchema';
 import { bioShapeSchema } from '@/models/bioShapeSchema';
 import { shapeSchema } from '@/models/shapeSchema';
-import { modelElementsSchema } from '@/models/modelElementsSchema';
 import { modelElementModificationSchema } from '@/models/modelElementModificationSchema';
 import { lineTypeSchema } from '@/models/lineTypeSchema';
+import { layerSchema } from '@/models/layerSchema';
+import { layerTextSchema } from '@/models/layerTextSchema';
+import { layerRectSchema } from '@/models/layerRectSchema';
+import { pageableSchema } from '@/models/pageableSchema';
+import { modelElementSchema } from '@/models/modelElementSchema';
+import { layerOvalSchema } from '@/models/layerOvalSchema';
+import { layerLineSchema } from '@/models/layerLineSchema';
+import { arrowTypeSchema } from '@/models/arrowTypeSchema';
+import { arrowSchema } from '@/models/arrowSchema';
 
 export type Project = z.infer<typeof projectSchema>;
 export type OverviewImageView = z.infer<typeof overviewImageView>;
@@ -79,7 +87,18 @@ export type OverviewImageLinkModel = z.infer<typeof overviewImageLinkModel>;
 export type MapModel = z.infer<typeof mapModelSchema>;
 export type BioShape = z.infer<typeof bioShapeSchema>;
 export type LineType = z.infer<typeof lineTypeSchema>;
+export type ArrowType = z.infer<typeof arrowTypeSchema>;
+const layersSchema = pageableSchema(layerSchema);
+export type Layers = z.infer<typeof layersSchema>;
+export type Layer = z.infer<typeof layerSchema>;
+export type LayerText = z.infer<typeof layerTextSchema>;
+export type LayerRect = z.infer<typeof layerRectSchema>;
+export type LayerOval = z.infer<typeof layerOvalSchema>;
+export type LayerLine = z.infer<typeof layerLineSchema>;
+export type Arrow = z.infer<typeof arrowSchema>;
+const modelElementsSchema = pageableSchema(modelElementSchema);
 export type ModelElements = z.infer<typeof modelElementsSchema>;
+export type ModelElement = z.infer<typeof modelElementSchema>;
 export type Shape = z.infer<typeof shapeSchema>;
 export type Modification = z.infer<typeof modelElementModificationSchema>;
 export type MapOverlay = z.infer<typeof mapOverlay>;
-- 
GitLab