Skip to content
Snippets Groups Projects
Commit 459f350f authored by Miłosz Grocholewski's avatar Miłosz Grocholewski
Browse files

Add drawing shapes on a vector map

parent aa276cbb
No related branches found
No related tags found
1 merge request!249Add drawing shapes on a vector map
Showing
with 30 additions and 716 deletions
import drawerReducer from '@/redux/drawer/drawer.slice';
import type { DrawerState } from '@/redux/drawer/drawer.types';
import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer';
import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer';
import { render, screen } from '@testing-library/react';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { StoreType } from '@/redux/store';
import { NavBar } from './NavBar.component';
const renderComponent = (): { store: ToolkitStoreWithSingleSlice<DrawerState> } => {
const { Wrapper, store } = getReduxWrapperUsingSliceReducer('drawer', drawerReducer);
const renderComponent = (): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore();
return (
render(
<Wrapper>
......
......@@ -11,11 +11,16 @@ import { store } from '@/redux/store';
import Image from 'next/image';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { projectIdSelector } from '@/redux/project/project.selectors';
import { Switch } from '@/shared/Switch';
import { currentModelIdSelector, vectorRenderingSelector } from '@/redux/models/models.selectors';
import { setModelVectorRendering } from '@/redux/models/models.slice';
export const NavBar = (): JSX.Element => {
const dispatch = useAppDispatch();
const projectId = useAppSelector(projectIdSelector);
const vectorRendering = useAppSelector(vectorRenderingSelector);
const currentModelId = useAppSelector(currentModelIdSelector);
const toggleDrawerInfo = (): void => {
if (store.getState().drawer.isOpen && store.getState().drawer.drawerName === 'project-info') {
......@@ -77,7 +82,15 @@ export const NavBar = (): JSX.Element => {
<IconButton icon="legend" onClick={toggleDrawerLegend} title="Legend" />
</div>
</div>
<div className="flex flex-col items-center gap-[10px] text-center text-[12px]">
<span>Vector rendering</span>
<Switch
isChecked={vectorRendering}
onToggle={value =>
dispatch(setModelVectorRendering({ vectorRendering: value, mapId: currentModelId }))
}
/>
</div>
<div className="flex flex-col items-center gap-[20px]" data-testid="nav-logos-and-powered-by">
<Image
className="rounded rounded-e rounded-s bg-white-pearl pb-[7px]"
......
/* eslint-disable no-magic-numbers */
import { Drawer } from '@/components/Map/Drawer';
import { Legend } from '@/components/Map/Legend';
import { MapVectorViewer } from '@/components/Map/MapVectorViewer';
import { MapViewer } from '@/components/Map/MapViewer';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { vectorRenderingSelector } from '@/redux/models/models.selectors';
import { MapAdditionalActions } from './MapAdditionalActions';
import { MapAdditionalOptions } from './MapAdditionalOptions';
import { PluginsDrawer } from './PluginsDrawer';
export const Map = (): JSX.Element => {
const vectorRendering = useAppSelector(vectorRenderingSelector);
return (
<div
className="relative z-0 h-screen w-full overflow-hidden bg-black"
data-testid="map-container"
>
{!vectorRendering && <MapViewer />}
{vectorRendering && <MapVectorViewer />}
<MapViewer />
<MapAdditionalOptions />
<Drawer />
<PluginsDrawer />
......
import { render, screen } from '@testing-library/react';
import { StoreType } from '@/redux/store';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { MapVectorViewer } from './MapVectorViewer.component';
import { MAP_VECTOR_VIEWER_ROLE } from './MapVectorViewer.constants';
const renderComponent = (): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore({
map: initialMapStateFixture,
backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
});
return (
render(
<Wrapper>
<MapVectorViewer />
</Wrapper>,
),
{
store,
}
);
};
describe('MapVectorViewer - component', () => {
it('should render component container', () => {
renderComponent();
expect(screen.getByRole(MAP_VECTOR_VIEWER_ROLE)).toBeInTheDocument();
});
it('should render openlayers map inside the component', () => {
renderComponent();
const FIRST_NODE = 0;
expect(screen.getByRole(MAP_VECTOR_VIEWER_ROLE).childNodes[FIRST_NODE]).toHaveClass(
'ol-viewport',
);
});
});
import 'ol/ol.css';
import { useOlMap } from '@/components/Map/MapVectorViewer/utils/useOlMap';
import { MAP_VECTOR_VIEWER_ROLE } from '@/components/Map/MapVectorViewer/MapVectorViewer.constants';
export const MapVectorViewer = (): JSX.Element => {
const { mapRef } = useOlMap();
return (
<div
ref={mapRef}
role={MAP_VECTOR_VIEWER_ROLE}
className="absolute left-[88px] top-[104px] h-[calc(100%-104px)] w-[calc(100%-88px)] bg-[#e4e2de]"
/>
);
};
export const MAP_VECTOR_VIEWER_ROLE = 'map-vector-viewer';
import View from 'ol/View';
import BaseLayer from 'ol/layer/Base';
export type MapConfig = {
view: View;
layers: BaseLayer[];
};
export { MapVectorViewer } from './MapVectorViewer.component';
/* eslint-disable no-magic-numbers */
import { Feature } from 'ol';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { useMemo } from 'react';
import BasePolygon from '@/components/Map/MapVectorViewer/utils/shapes/BasePolygon';
import Polygon from 'ol/geom/Polygon';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<Polygon>>> => {
const pointToProjection = usePointToProjection();
const basePolygon = new BasePolygon({
points: [
pointToProjection({ x: 100, y: 100 }),
pointToProjection({ x: 188, y: 100 }),
pointToProjection({ x: 210, y: 200 }),
pointToProjection({ x: 100, y: 200 }),
],
});
const basePolygon2 = new BasePolygon({
points: [
pointToProjection({ x: 500, y: 200 }),
pointToProjection({ x: 560, y: 230 }),
pointToProjection({ x: 620, y: 200 }),
pointToProjection({ x: 620, y: 250 }),
pointToProjection({ x: 560, y: 280 }),
pointToProjection({ x: 500, y: 250 }),
],
fill: '#CCFFCC',
text: 'Test',
});
const vectorSource = useMemo(() => {
return new VectorSource({
features: [basePolygon.polygonFeature, basePolygon2.polygonFeature],
});
}, [basePolygon.polygonFeature, basePolygon2.polygonFeature]);
return useMemo(
() =>
new VectorLayer({
source: vectorSource,
}),
[vectorSource],
);
};
/* eslint-disable no-magic-numbers */
import {
BACKGROUNDS_MOCK,
BACKGROUND_INITIAL_STATE_MOCK,
} from '@/redux/backgrounds/background.mock';
import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/map.constants';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import { setMapPosition } from '@/redux/map/map.slice';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { act, renderHook, waitFor } from '@testing-library/react';
import { View } from 'ol';
import Map from 'ol/Map';
import React from 'react';
import { useOlMap } from '@/components/Map/MapVectorViewer/utils/useOlMap';
import { useOlMapView } from '@/components/Map/MapVectorViewer/utils/config/useOlMapView';
const useRefValue = {
current: null,
};
Object.defineProperty(useRefValue, 'current', {
get: jest.fn(() => ({
innerHTML: '',
appendChild: jest.fn(),
addEventListener: jest.fn(),
getRootNode: jest.fn(),
})),
set: jest.fn(() => ({
innerHTML: '',
appendChild: jest.fn(),
addEventListener: jest.fn(),
getRootNode: jest.fn(),
})),
});
jest.spyOn(React, 'useRef').mockReturnValue(useRefValue);
describe('useOlMapView - util', () => {
it('should modify view of the map instance on INITIAL position config change', async () => {
const { Wrapper, store } = getReduxWrapperWithStore({
map: initialMapStateFixture,
backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
});
const dummyElement = document.createElement('div');
const { result: hohResult } = renderHook(() => useOlMap({ target: dummyElement }), {
wrapper: Wrapper,
});
const setViewSpy = jest.spyOn(hohResult.current.mapInstance as Map, 'setView');
const CALLED_TWICE = 2;
await act(() => {
store.dispatch(
setMapPosition({
x: 0,
y: 0,
}),
);
});
renderHook(() => useOlMapView({ mapInstance: hohResult.current.mapInstance }), {
wrapper: Wrapper,
});
await waitFor(() => expect(setViewSpy).toBeCalledTimes(CALLED_TWICE));
});
it('should return valid View instance', async () => {
const { Wrapper } = getReduxWrapperWithStore({
map: {
data: {
...MAP_DATA_INITIAL_STATE,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
position: {
initial: {
x: 128,
y: 128,
},
last: {
x: 128,
y: 128,
},
},
},
loading: 'idle',
error: {
name: '',
message: '',
},
openedMaps: OPENED_MAPS_INITIAL_STATE,
},
backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
});
const dummyElement = document.createElement('div');
const { result: hohResult } = renderHook(() => useOlMap({ target: dummyElement }), {
wrapper: Wrapper,
});
const { result } = renderHook(
() => useOlMapView({ mapInstance: hohResult.current.mapInstance }),
{
wrapper: Wrapper,
},
);
expect(result.current).toBeInstanceOf(View);
expect(result.current.getCenter()).toStrictEqual([0, -0]);
});
});
/* eslint-disable no-magic-numbers */
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 '@/components/Map/MapVectorViewer/MapVectorViewer.types';
interface UseOlMapViewInput {
mapInstance: MapInstance;
}
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,
y: mapInitialPosition.y,
};
const [x, y] = pointToProjection(centerPoint);
return {
x,
y,
};
}, [mapInitialPosition, pointToProjection]);
const viewConfig = useMemo(
() => ({
center: [center.x, center.y],
zoom: mapInitialPosition.z,
showFullExtent: OPTIONS.showFullExtent,
maxZoom: mapSize.maxZoom,
minZoom: mapSize.minZoom,
extent,
}),
[mapInitialPosition.z, mapSize.maxZoom, mapSize.minZoom, center, extent],
);
const view = useMemo(() => new View(viewConfig), [viewConfig]);
useEffect(() => {
if (!mapInstance) {
return;
}
mapInstance.setView(view);
}, [view, mapInstance]);
return view;
};
/* eslint-disable no-magic-numbers */
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { mapDataSelector } from '@/redux/map/map.selectors';
import { MapSize } from '@/redux/map/map.types';
import { Point } from '@/types/map';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { renderHook } from '@testing-library/react';
import { ObjectEvent } from 'openlayers';
import { onMapPositionChange } from './onMapPositionChange';
const getEvent = (targetValues: ObjectEvent['target']['values_']): ObjectEvent =>
({
target: {
values_: targetValues,
},
}) as unknown as ObjectEvent;
describe('onMapPositionChange - util', () => {
const cases: [MapSize, ObjectEvent['target']['values_'], Point][] = [
[
{
width: 26779.25,
height: 13503,
tileSize: 256,
minZoom: 2,
maxZoom: 9,
},
{
center: [-18320768.57141088, 18421138.0064355],
zoom: 6,
},
{
x: 4589,
y: 4320,
z: 6,
},
],
[
{
width: 5170,
height: 1535.1097689075634,
tileSize: 256,
minZoom: 2,
maxZoom: 7,
},
{
center: [-17172011.827663105, 18910737.010646995],
zoom: 6.68620779943448,
},
{
x: 1479,
y: 581,
z: 6.68620779943448,
},
],
];
it.each(cases)(
'should set map data position to valid one',
(mapSize, targetValues, lastPosition) => {
const { Wrapper, store } = getReduxWrapperWithStore();
const { result } = renderHook(() => useAppDispatch(), { wrapper: Wrapper });
const dispatch = result.current;
const event = getEvent(targetValues);
onMapPositionChange(mapSize, dispatch)(event);
const { position } = mapDataSelector(store.getState());
expect(position.last).toMatchObject(lastPosition);
},
);
});
import { setMapPosition } from '@/redux/map/map.slice';
import { MapSize } from '@/redux/map/map.types';
import { AppDispatch } from '@/redux/store';
import { latLngToPoint } from '@/utils/map/latLngToPoint';
import { toLonLat } from 'ol/proj';
import { ObjectEvent } from 'openlayers';
/* prettier-ignore */
export const onMapPositionChange =
(mapSize: MapSize, dispatch: AppDispatch) =>
(e: ObjectEvent): void => {
// eslint-disable-next-line no-underscore-dangle
const { center, zoom } = e.target.values_;
const [lng, lat] = toLonLat(center);
const { x, y } = latLngToPoint([lat, lng], mapSize, { rounded: true });
dispatch(
setMapPosition({
x,
y,
z: zoom,
}),
);
};
import { renderHook } from '@testing-library/react';
import { View } from 'ol';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
import * as positionListener from './onMapPositionChange';
import { useOlMapListeners } from './useOlMapListeners';
jest.mock('./onMapPositionChange', () => ({
__esModule: true,
onMapPositionChange: jest.fn(),
}));
jest.mock('use-debounce', () => {
return {
useDebounce: (): void => {},
useDebouncedCallback: (): void => {},
};
});
describe('useOlMapListeners - util', () => {
const { Wrapper } = getReduxWrapperWithStore({
map: {
data: { ...initialMapDataFixture },
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
});
beforeEach(() => {
jest.clearAllMocks();
});
describe('on change:center view event', () => {
it('should run onMapPositionChange event', () => {
const CALLED_ONCE = 1;
const view = new View();
renderHook(() => useOlMapListeners({ view }), { wrapper: Wrapper });
view.dispatchEvent('change:center');
expect(positionListener.onMapPositionChange).toBeCalledTimes(CALLED_ONCE);
});
});
});
import { OPTIONS } from '@/constants/map';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { mapDataSizeSelector } from '@/redux/map/map.selectors';
import { View } from 'ol';
import { unByKey } from 'ol/Observable';
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useDebouncedCallback } from 'use-debounce';
import { onMapPositionChange } from './onMapPositionChange';
interface UseOlMapListenersInput {
view: View;
}
export const useOlMapListeners = ({ view }: UseOlMapListenersInput): void => {
const mapSize = useSelector(mapDataSizeSelector);
const dispatch = useAppDispatch();
const handleChangeCenter = useDebouncedCallback(
onMapPositionChange(mapSize, dispatch),
OPTIONS.queryPersistTime,
{ leading: false },
);
useEffect(() => {
const key = view.on('change:center', handleChangeCenter);
return () => unByKey(key);
}, [view, handleChangeCenter]);
};
/* eslint-disable no-magic-numbers */
import { Coordinate } from 'ol/coordinate';
import BasePolygon from '@/components/Map/MapVectorViewer/utils/shapes/BasePolygon';
import { usePointToProjection, UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import Style from 'ol/style/Style';
import {
GetReduxWrapperUsingSliceReducer,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { renderHook } from '@testing-library/react';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
const getPointToProjection = (
wrapper: ReturnType<GetReduxWrapperUsingSliceReducer>['Wrapper'],
): UsePointToProjectionResult => {
const { result: usePointToProjectionHook } = renderHook(() => usePointToProjection(), {
wrapper,
});
return usePointToProjectionHook.current;
};
describe('BasePolygon', () => {
const { Wrapper } = getReduxWrapperWithStore({
map: initialMapStateFixture,
});
const pointToProjection = getPointToProjection(Wrapper);
it('should create a polygon with default styles', () => {
const points: Array<Coordinate> = [
pointToProjection({ x: 0, y: 0 }),
pointToProjection({ x: 10, y: 0 }),
pointToProjection({ x: 10, y: 10 }),
pointToProjection({ x: 0, y: 10 }),
];
const polygon = new BasePolygon({ points });
expect(polygon).toBeInstanceOf(BasePolygon);
expect(polygon.polygonFeature.getGeometry()?.getCoordinates()).toEqual([points]);
const style = polygon.polygonFeature.getStyle();
if (style instanceof Style) {
expect(style.getFill()?.getColor()).toBe('#fff');
expect(style.getText()?.getText()).toBe('');
}
});
it('should create a polygon with custom fill and text', () => {
const points: Array<Coordinate> = [
pointToProjection({ x: 0, y: 0 }),
pointToProjection({ x: 10, y: 0 }),
pointToProjection({ x: 10, y: 10 }),
pointToProjection({ x: 0, y: 10 }),
];
const polygon = new BasePolygon({ points, fill: '#ff0000', text: 'Test' });
const style = polygon.polygonFeature.getStyle();
if (style instanceof Style) {
expect(style.getFill()?.getColor()).toBe('#ff0000');
expect(style.getText()?.getText()).toBe('Test');
}
});
});
/* eslint-disable no-magic-numbers */
import { Fill, Stroke, Style, Text } from 'ol/style';
import Feature from 'ol/Feature';
import { Polygon } from 'ol/geom';
import { Coordinate } from 'ol/coordinate';
function getPolygonStyle(text: string, fill: string): Style {
return new Style({
stroke: new Stroke({
color: '#000',
width: 1,
}),
fill: new Fill({
color: fill,
}),
text: new Text({
text,
font: '18px Calibri,sans-serif',
fill: new Fill({
color: '#000',
}),
placement: 'point',
}),
});
}
export default class BasePolygon {
private readonly polygonCoords: Array<Array<Array<number>>>;
private readonly polygonStyle: Style;
polygonFeature: Feature<Polygon>;
constructor({
points,
fill = '#fff',
text = '',
}: {
points: Array<Coordinate>;
fill?: string;
text?: string;
}) {
this.polygonCoords = [points];
this.polygonFeature = this.getPolygonFeature();
this.polygonStyle = getPolygonStyle(text, fill);
this.polygonFeature.setStyle(this.polygonStyle);
}
getPolygonFeature(): Feature<Polygon> {
return new Feature({
geometry: new Polygon(this.polygonCoords),
});
}
}
import { renderHook, waitFor } from '@testing-library/react';
import { Map } from 'ol';
import React from 'react';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock';
import { useOlMap } from './useOlMap';
const useRefValue = {
current: null,
};
Object.defineProperty(useRefValue, 'current', {
get: jest.fn(() => ({
innerHTML: '',
appendChild: jest.fn(),
addEventListener: jest.fn(),
getRootNode: jest.fn(),
})),
set: jest.fn(() => ({
innerHTML: '',
appendChild: jest.fn(),
addEventListener: jest.fn(),
getRootNode: jest.fn(),
})),
});
jest.spyOn(React, 'useRef').mockReturnValue(useRefValue);
describe('useOlMap - util', () => {
const { Wrapper } = getReduxWrapperWithStore({
map: initialMapStateFixture,
backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
});
describe('when initializing', () => {
it('should set map instance', async () => {
const dummyElement = document.createElement('div');
const { result } = renderHook(() => useOlMap({ target: dummyElement }), { wrapper: Wrapper });
await waitFor(() => expect(result.current.mapInstance).toBeInstanceOf(Map));
});
it('should render content inside the target element', async () => {
const FIRST_NODE = 0;
const dummyElement = document.createElement('div');
renderHook(() => useOlMap({ target: dummyElement }), { wrapper: Wrapper });
expect(dummyElement.childNodes[FIRST_NODE]).toHaveClass('ol-viewport');
});
});
});
import { MapInstance } from '@/types/map';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import Map from 'ol/Map';
import { Zoom } from 'ol/control';
import React, { MutableRefObject, useEffect } from 'react';
import { useOlMapLayers } from '@/components/Map/MapVectorViewer/utils/config/useOlMapLayers';
import { useOlMapView } from '@/components/Map/MapVectorViewer/utils/config/useOlMapView';
import { useOlMapListeners } from '@/components/Map/MapVectorViewer/utils/listeners/useOlMapListeners';
interface UseOlMapInput {
target?: HTMLElement;
}
interface UseOlMapOutput {
mapRef: MutableRefObject<null | HTMLDivElement>;
mapInstance: MapInstance;
}
type UseOlMap = (input?: UseOlMapInput) => UseOlMapOutput;
export const useOlMap: UseOlMap = ({ target } = {}) => {
const mapRef = React.useRef<null | HTMLDivElement>(null);
const { mapInstance, handleSetMapInstance } = useMapInstance();
const view = useOlMapView({ mapInstance });
useOlMapLayers({ mapInstance });
useOlMapListeners({ view });
useEffect(() => {
if (!mapRef.current || mapRef.current.innerHTML !== '') {
return;
}
const map = new Map({
target: target || mapRef.current,
});
map.getControls().forEach(mapControl => {
if (mapControl instanceof Zoom) {
map.removeControl(mapControl);
}
});
handleSetMapInstance(map);
}, [target, handleSetMapInstance]);
return {
mapRef,
mapInstance,
};
};
import { ColorObject } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
export const WHITE_COLOR: ColorObject = {
alpha: 255,
rgb: 16777215,
};
export const BLACK_COLOR: ColorObject = {
alpha: 255,
rgb: -16777216,
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment