diff --git a/src/components/map/LayerStack.test.tsx b/src/components/map/LayerStack.test.tsx
new file mode 100644
index 0000000..4f6b477
--- /dev/null
+++ b/src/components/map/LayerStack.test.tsx
@@ -0,0 +1,264 @@
+import { renderHook, act, render, screen } from '@testing-library/react';
+import { useAtom } from 'jotai';
+import LayerStack from './LayerStack';
+
+describe('The LayerStack component', () => {
+ test('generates something', () => {
+ // const { result } = renderHook(() => useAtom(tiledLayersAtom));
+ render(
+
+ );
+ const svg = screen.getByTestId('layer-stack');
+ expect(svg).toMatchInlineSnapshot(`
+
+`);
+ });
+});
diff --git a/src/components/map/LayerStack.tsx b/src/components/map/LayerStack.tsx
new file mode 100644
index 0000000..037e72c
--- /dev/null
+++ b/src/components/map/LayerStack.tsx
@@ -0,0 +1,139 @@
+import react, { useCallback, useEffect, useRef, useState } from 'react';
+import { atom, useAtom } from 'jotai';
+
+import { TileFactory, TileKeyObject } from './types';
+
+import { coordinateSystemAtom } from './Map';
+import TiledLayer from './TiledLayer';
+import Tile from './Tile';
+import _, { floor, range } from 'lodash';
+import tileUri from './uris';
+
+export interface LayerStackProperties {
+ /**
+ * A key identifying the initial top left tile
+ */
+ keyObject: TileKeyObject;
+ /**
+ * Number of {@link components/map/TiledLayer!TiledLayer}.
+ */
+ numberOfTiledLayers?: number;
+}
+
+/**
+ *
+ * @param props
+ * @returns A stack of layers embedded in an SVG element
+ *
+ * This component does the conversion between the {@link components/map/Map!CoordinateSystem} stored
+ * in the {@link components/map/Map!coordinateSystemAtom} atom and the {@link components/map/TiledLayer!TiledLayer}
+ * components which units are in tiles.
+ *
+ */
+export const LayerStack: react.FC = (
+ props: LayerStackProperties
+) => {
+ const [coordinateSystem] = useAtom(coordinateSystemAtom);
+
+ const simpleTileFactory: TileFactory = useCallback(
+ (keyObject) => (
+
+ ),
+ []
+ );
+
+ const numberOfTiledLayers =
+ props.numberOfTiledLayers === undefined ? 1 : props.numberOfTiledLayers;
+
+ const [activeTiledLayer, setActiveTiledLayer] = useState(
+ Math.floor(numberOfTiledLayers / 2)
+ );
+
+ const viewPort = {
+ topLeft: {
+ x: Math.floor(-coordinateSystem.shift.x / coordinateSystem.zoom / 256),
+ y: Math.floor(-coordinateSystem.shift.y / coordinateSystem.zoom / 256),
+ },
+ bottomRight: {
+ x: Math.ceil(
+ (-coordinateSystem.shift.x + window.innerWidth) /
+ coordinateSystem.zoom /
+ 256
+ ),
+ y: Math.ceil(
+ (-coordinateSystem.shift.y + window.innerHeight) /
+ coordinateSystem.zoom /
+ 256
+ ),
+ },
+ };
+
+ const getTiledLayer = (i: number) => {
+ const relativeZoomLevel = i - activeTiledLayer;
+ const zoom = 2 ** relativeZoomLevel;
+ const origin = {
+ x: props.keyObject.x * zoom,
+ y: props.keyObject.y * zoom,
+ };
+ const keyObject = {
+ provider: props.keyObject.provider,
+ zoomLevel: props.keyObject.zoomLevel + relativeZoomLevel,
+ x: Math.floor(origin.x),
+ y: Math.floor(origin.y),
+ };
+ const shift = {
+ x: origin.x - floor(origin.x),
+ y: origin.y - floor(origin.y),
+ };
+ return (
+
+
+
+ );
+ };
+
+ const tileLayers = range(0, numberOfTiledLayers).map((i) => {});
+
+ // console.log(`tiledLayers: ${JSON.stringify(tiledLayers)}`);
+
+ return (
+
+ );
+};
+
+export default LayerStack;
diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx
index ab0f240..d885fcc 100644
--- a/src/components/map/Map.tsx
+++ b/src/components/map/Map.tsx
@@ -1,10 +1,9 @@
-import react, { useCallback } from 'react';
+import react from 'react';
import { atom, useAtom } from 'jotai';
import Handlers from './Handlers';
-import Tile from './Tile';
-import TiledLayer from './TiledLayer';
-import { Point, TileFactory } from './types';
+import { Point } from './types';
+import LayerStack from './LayerStack';
export interface MapProperties {}
@@ -75,54 +74,20 @@ export const relativeCoordinateSystemAtom = atom(
/**
*
* @returns A Map component
+ *
+ * TODO: Is this component really useful ?
+ * TODO: does the coordinate system belong to this component or to `` ?
*/
export const Map: react.FC = (props: MapProperties) => {
const [coordinateSystem, setCoordinateSystem] = useAtom(coordinateSystemAtom);
- const simpleTileFactory: TileFactory = useCallback(
- (keyObject) => (
-
- ),
- []
- );
-
- const viewPort = {
- topLeft: {
- x: Math.floor(-coordinateSystem.shift.x / coordinateSystem.zoom / 256),
- y: Math.floor(-coordinateSystem.shift.y / coordinateSystem.zoom / 256),
- },
- bottomRight: {
- x: Math.ceil(
- (-coordinateSystem.shift.x + window.innerWidth) /
- coordinateSystem.zoom /
- 256
- ),
- y: Math.ceil(
- (-coordinateSystem.shift.y + window.innerHeight) /
- coordinateSystem.zoom /
- 256
- ),
- },
- };
-
return (
<>
-
+
>
);
};
diff --git a/src/components/map/TiledLayer.tsx b/src/components/map/TiledLayer.tsx
index 57f4021..985638c 100644
--- a/src/components/map/TiledLayer.tsx
+++ b/src/components/map/TiledLayer.tsx
@@ -8,7 +8,7 @@ export interface TiledLayerProperties {
/** The key of the first (ie top/left) tile */
keyObject: TileKeyObject;
/** The current viewport expressed in tiles coordinates */
- viewPort: Rectangle;
+ viewPort?: Rectangle;
/** The factory to create tiles */
tileFactory: TileFactory;
}
@@ -19,12 +19,14 @@ export interface TiledLayerProperties {
* This component is rather dumb and is mainly a sparse array of tiles.
*
* New tiles are added to the array when the viewport is updated and they stay in the array until
- * the component is destroyed or its number of tiles is updated.
+ * the component is destroyed.
*
* This component has no need to know the number nor the size of its tiles: tiles can be added when needed and
* its unit is the tile size (the parent component needs to transform its enclosing SVG group to adapt its units)
*
* TODO: test tiles'X and Y boundaries.
+ * TODO: housekeeping
+ * TODO: remove tileFactory to facilitate memoisation.
*
*/
export const TiledLayer: react.FC = memo(
@@ -32,10 +34,13 @@ export const TiledLayer: react.FC = memo(
console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`);
const tiles = useRef(new Map());
- range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).map(
- (row) => {
- range(props.viewPort.topLeft.x, props.viewPort.bottomRight.x + 1).map(
- (col) => {
+ if (props.viewPort !== undefined) {
+ range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).forEach(
+ (row) => {
+ range(
+ props.viewPort!.topLeft.x,
+ props.viewPort!.bottomRight.x + 1
+ ).forEach((col) => {
const keyObject = {
provider: props.keyObject.provider,
zoomLevel: props.keyObject.zoomLevel,
@@ -51,14 +56,18 @@ export const TiledLayer: react.FC = memo(
);
}
- }
- );
- }
- );
+ });
+ }
+ );
+ }
return <>{Array.from(tiles.current.values())}>;
},
- isEqual
+ (prevProps, nextProps) =>
+ isEqual(
+ [prevProps.keyObject, prevProps.viewPort],
+ [nextProps.keyObject, nextProps.viewPort]
+ )
);
export default TiledLayer;
diff --git a/src/components/map/uris.test.ts b/src/components/map/uris.test.ts
index 1dab357..2137260 100644
--- a/src/components/map/uris.test.ts
+++ b/src/components/map/uris.test.ts
@@ -13,4 +13,13 @@ describe('Test that', () => {
y: 3,
});
});
+ test('x and y are optional', () => {
+ expect(tileUri({ provider: 'osm', zoomLevel: 16 })).toEqual('tile/osm/16//');
+ });
+ test('uri parsing works', () => {
+ expect(tileUri('tile/otm/5')).toEqual({
+ provider: 'otm',
+ zoomLevel: 5,
+ });
+ });
});
diff --git a/src/components/map/uris.ts b/src/components/map/uris.ts
index 940c119..9a094b5 100644
--- a/src/components/map/uris.ts
+++ b/src/components/map/uris.ts
@@ -2,18 +2,23 @@ import { route } from 'docuri';
/**
* A [docuri](https://github.com/jo/docuri) route for {@link components/map/types!TileKeyObject}
- *
+ *
* TODO: update docuri (or write a wrapper) to support datatyping (and formats).
*/
export const tileUri = (rte: any) => {
- const r = route('tile/:provider/:zoomLevel/:x/:y')(rte);
+ const r = route('tile/:provider/:zoomLevel(/:x/:y)')(rte);
if (typeof r === 'object') {
- return {
- provider: r.provider,
- zoomLevel: parseInt(r.zoomLevel),
- x: parseInt(r.x),
- y: parseInt(r.y),
- };
+ return r.x === undefined
+ ? {
+ provider: r.provider,
+ zoomLevel: parseInt(r.zoomLevel),
+ }
+ : {
+ provider: r.provider,
+ zoomLevel: parseInt(r.zoomLevel),
+ x: parseInt(r.x),
+ y: parseInt(r.y),
+ };
}
return r;
};