From 863891f7bacc186f269911666c5904214c1dbb57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Fri, 8 Mar 2024 02:07:43 +0100
Subject: [PATCH] feat: fix the map is not quite centered

---
 .../utils/useAdditionalActions.test.ts        |  6 ++--
 .../utils/config/useOlMapTileLayer.ts         | 21 +++++++++++++-
 .../utils/config/useOlMapView.test.ts         |  4 +--
 .../MapViewer/utils/config/useOlMapView.ts    | 28 +++++++++++++++++--
 src/constants/map.ts                          |  3 +-
 src/redux/map/map.thunks.test.ts              | 10 +++----
 6 files changed, 57 insertions(+), 15 deletions(-)

diff --git a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
index f711b392..9fd36d00 100644
--- a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
+++ b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
@@ -1,13 +1,13 @@
 /* eslint-disable no-magic-numbers */
 import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { modelsFixture } from '@/models/fixtures/modelsFixture';
 import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
+import { initialMapDataFixture } from '@/redux/map/map.fixtures';
 import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
 import Map from 'ol/Map';
-import { initialMapDataFixture } from '@/redux/map/map.fixtures';
-import { modelsFixture } from '@/models/fixtures/modelsFixture';
 import { useAddtionalActions } from './useAdditionalActions';
 import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
 
@@ -185,7 +185,7 @@ describe('useAddtionalActions - hook', () => {
         const position = store.getState().map?.data.position;
         expect(position?.last).toEqual(MAP_CONFIG.position);
         expect(actions[0]).toEqual({
-          payload: { x: 1750, y: 1000, z: 5 },
+          payload: { x: 1750, y: 1000, z: 4 },
           type: 'map/setMapPosition',
         });
       });
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
index b50d5c15..a636c57f 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
@@ -3,6 +3,9 @@ import { OPTIONS } from '@/constants/map';
 import { currentBackgroundImagePathSelector } from '@/redux/backgrounds/background.selectors';
 import { mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { projectDataSelector } from '@/redux/project/project.selectors';
+import { Point } from '@/types/map';
+import { usePointToProjection } from '@/utils/map/usePointToProjection';
+import { Extent, boundingExtent } from 'ol/extent';
 import BaseLayer from 'ol/layer/Base';
 import TileLayer from 'ol/layer/Tile';
 import { XYZ } from 'ol/source';
@@ -16,6 +19,21 @@ export const useOlMapTileLayer = (): BaseLayer => {
   const mapSize = useSelector(mapDataSizeSelector);
   const currentBackgroundImagePath = useSelector(currentBackgroundImagePathSelector);
   const project = useSelector(projectDataSelector);
+  const pointToProjection = usePointToProjection();
+
+  const tileExtent = useMemo((): Extent => {
+    const topLeftPoint: Point = {
+      x: mapSize.width,
+      y: mapSize.height,
+    };
+
+    const bottomRightPoint: Point = {
+      x: 0,
+      y: 0,
+    };
+
+    return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection));
+  }, [pointToProjection, mapSize]);
 
   const sourceUrl = useMemo(
     () => getMapTileUrl({ projectDirectory: project?.directory, currentBackgroundImagePath }),
@@ -39,8 +57,9 @@ export const useOlMapTileLayer = (): BaseLayer => {
       new TileLayer({
         visible: true,
         source,
+        extent: tileExtent,
       }),
-    [source],
+    [source, tileExtent],
   );
 
   return tileLayer;
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
index 48b643ce..1b52b84c 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
@@ -48,7 +48,7 @@ describe('useOlMapView - util', () => {
     });
 
     const setViewSpy = jest.spyOn(hohResult.current.mapInstance as Map, 'setView');
-    const CALLED_ONCE = 1;
+    const CALLED_TWICE = 2;
 
     await act(() => {
       store.dispatch(
@@ -63,7 +63,7 @@ describe('useOlMapView - util', () => {
       wrapper: Wrapper,
     });
 
-    await waitFor(() => expect(setViewSpy).toBeCalledTimes(CALLED_ONCE));
+    await waitFor(() => expect(setViewSpy).toBeCalledTimes(CALLED_TWICE));
   });
 
   it('should return valid View instance', async () => {
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
index 4a4d9dc1..174d0a13 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
@@ -1,9 +1,10 @@
 /* eslint-disable no-magic-numbers */
-import { OPTIONS } from '@/constants/map';
-import { mapDataInitialPositionSelector } from '@/redux/map/map.selectors';
+import { EXTENT_PADDING_MULTIPLICATOR, OPTIONS } from '@/constants/map';
+import { mapDataInitialPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { MapInstance, Point } from '@/types/map';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { View } from 'ol';
+import { Extent, boundingExtent } from 'ol/extent';
 import { useEffect, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 import { MapConfig } from '../../MapViewer.types';
@@ -14,8 +15,28 @@ interface UseOlMapViewInput {
 
 export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['view'] => {
   const mapInitialPosition = useSelector(mapDataInitialPositionSelector);
+  const mapSize = useSelector(mapDataSizeSelector);
   const pointToProjection = usePointToProjection();
 
+  const extent = useMemo((): Extent => {
+    const extentPadding = {
+      horizontal: mapSize.width * EXTENT_PADDING_MULTIPLICATOR,
+      vertical: mapSize.height * EXTENT_PADDING_MULTIPLICATOR,
+    };
+
+    const topLeftPoint: Point = {
+      x: mapSize.width + extentPadding.horizontal,
+      y: mapSize.height + extentPadding.vertical,
+    };
+
+    const bottomRightPoint: Point = {
+      x: -extentPadding.horizontal,
+      y: -extentPadding.vertical,
+    };
+
+    return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection));
+  }, [pointToProjection, mapSize]);
+
   const center = useMemo((): Point => {
     const centerPoint: Point = {
       x: mapInitialPosition.x,
@@ -35,8 +56,9 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
       center: [center.x, center.y],
       zoom: mapInitialPosition.z,
       showFullExtent: OPTIONS.showFullExtent,
+      extent,
     }),
-    [center.x, center.y, mapInitialPosition.z],
+    [mapInitialPosition.z, center, extent],
   );
 
   const view = useMemo(() => new View(viewConfig), [viewConfig]);
diff --git a/src/constants/map.ts b/src/constants/map.ts
index 47d6c6ce..ae669ac9 100644
--- a/src/constants/map.ts
+++ b/src/constants/map.ts
@@ -5,11 +5,12 @@ import { HALF_SECOND_MS, ONE_HUNDRED_MS } from './time';
 export const DEFAULT_TILE_SIZE = 256;
 export const DEFAULT_MIN_ZOOM = 2;
 export const DEFAULT_MAX_ZOOM = 9;
-export const DEFAULT_ZOOM = 5;
+export const DEFAULT_ZOOM = 4;
 export const DEFAULT_CENTER_X = 0;
 export const DEFAULT_CENTER_Y = 0;
 // eslint-disable-next-line no-magic-numbers
 export const LATLNG_FALLBACK: LatLng = [0, 0];
+export const EXTENT_PADDING_MULTIPLICATOR = 1;
 
 export const DEFAULT_CENTER_POINT: Point = {
   x: DEFAULT_CENTER_X,
diff --git a/src/redux/map/map.thunks.test.ts b/src/redux/map/map.thunks.test.ts
index 6d5e94a4..bdcf388d 100644
--- a/src/redux/map/map.thunks.test.ts
+++ b/src/redux/map/map.thunks.test.ts
@@ -2,16 +2,16 @@ import { MODELS_MOCK } from '@/models/mocks/modelsMock';
 /* eslint-disable no-magic-numbers */
 import { QueryData } from '@/types/query';
 import { BACKGROUNDS_MOCK, BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock';
-import { RootState } from '../store';
-import { INITIAL_STORE_STATE_MOCK } from '../root/root.fixtures';
 import { MODELS_INITIAL_STATE_MOCK } from '../models/models.mock';
+import { INITIAL_STORE_STATE_MOCK } from '../root/root.fixtures';
+import { RootState } from '../store';
+import { initialMapDataFixture, initialMapStateFixture } from './map.fixtures';
 import {
   getBackgroundId,
   getInitMapPosition,
   getInitMapSizeAndModelId,
   getOpenedMaps,
 } from './map.thunks';
-import { initialMapDataFixture, initialMapStateFixture } from './map.fixtures';
 
 const EMPTY_QUERY_DATA: QueryData = {
   modelId: undefined,
@@ -84,8 +84,8 @@ describe('map thunks - utils', () => {
     it('should return valid map position if query params do not include position', () => {
       const position = getInitMapPosition(STATE_WITH_MODELS, EMPTY_QUERY_DATA);
       expect(position).toEqual({
-        initial: { x: 13389.625, y: 6751.5, z: 5 },
-        last: { x: 13389.625, y: 6751.5, z: 5 },
+        initial: { x: 13389.625, y: 6751.5, z: 4 },
+        last: { x: 13389.625, y: 6751.5, z: 4 },
       });
     });
     it('should return default map position', () => {
-- 
GitLab